#+TITLE: Zero-to-One Setup (setup.org) #+AUTHOR: Amr #+FILETAGS: :harness:setup: #+STARTUP: content * Zero-to-One Setup (setup.org) The ~setup.org~ file defines the automated installation and initialization sequence for the OpenCortex. * Phase A: Demand (Thinking) ** The Agnostic LLM Provider Registry To fulfill the mandate of sovereignty and extensibility, the setup process must move away from a single hardcoded LLM provider (like OpenRouter). ** Design Goals: 1. **Modular Adapters:** Each provider (Ollama, Groq, OpenAI, etc.) is a data-driven structure defining its required fields (API_KEY, BASE_URL) and its "ping" validation logic. 2. **Interactive Selection:** The user should be presented with a multi-select list of providers. 3. **Local-First Default:** If no cloud keys are provided, the system must default to a local Ollama/llama.cpp configuration. 4. **State Persistence:** Configuration is saved to `providers.lisp` in the XDG Config directory. 5. **Secret Splitting:** Sensitive keys go to `.env`, while metadata (models, URLs) lives in `state/providers.lisp`. * Phase B: Protocol (Success Criteria) ** Test Suite Context #+begin_src lisp :tangle (expand-file-name "setup-wizard-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests")) (defpackage :opencortex-setup-tests (:use :cl :fiveam :opencortex) (:export #:setup-suite)) #+end_src #+begin_src lisp :tangle (expand-file-name "setup-wizard-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests")) (in-package :opencortex-setup-tests) #+end_src #+begin_src lisp :tangle (expand-file-name "setup-wizard-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests")) (def-suite setup-suite :description "Verification of the Lisp Setup Wizard") #+end_src #+begin_src lisp :tangle (expand-file-name "setup-wizard-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests")) (in-suite setup-suite) #+end_src ** Persistence Tests #+begin_src lisp :tangle (expand-file-name "setup-wizard-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests")) (test test-provider-registry-persistence "Verify that multiple providers can be registered and saved." (let ((opencortex::*providers* nil)) (opencortex:register-provider :ollama '(:url "http://localhost:11434" :model "llama3")) (opencortex:register-provider :groq '(:key "gsk_123" :model "mixtral-8x7b")) (is (equal "gsk_123" (getf (getf opencortex::*providers* :groq) :key))))) #+end_src ** Fallback Tests #+begin_src lisp :tangle (expand-file-name "setup-wizard-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests")) (test test-sovereign-fallback-logic "Verify that the system identifies as healthy with only local providers." (let ((opencortex::*providers* (list :ollama '(:url "http://localhost:11434")))) (is (opencortex:system-ready-p)))) #+end_src * Phase C: Implementation (Build) ** Package Context #+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness")) (in-package :opencortex) #+end_src ** Global Provider Registry #+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness")) (defvar *providers* nil "Global registry of configured LLM providers.") #+end_src ** Provider Templates #+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness")) (defvar *provider-templates* '((:ollama . (:name "Ollama (Local)" :fields ((:url :label "URL") (:model :label "Model")) :default-url "http://localhost:11434" :default-model "llama3")) (:openrouter . (:name "OpenRouter" :fields ((:key :label "API Key" :secret t) (:model :label "Model")) :default-model "anthropic/claude-3-opus-20240229")) (:openai . (:name "OpenAI" :fields ((:key :label "API Key" :secret t) (:model :label "Model")) :default-model "gpt-4-turbo")) (:groq . (:name "Groq" :fields ((:key :label "API Key" :secret t) (:model :label "Model")) :default-model "mixtral-8x7b-32768")) (:gemini . (:name "Google Gemini" :fields ((:key :label "API Key" :secret t) (:model :label "Model")) :default-model "gemini-1.5-pro")) (:anthropic . (:name "Anthropic" :fields ((:key :label "API Key" :secret t) (:model :label "Model")) :default-model "claude-3-5-sonnet-20240620"))) "Templates for supported LLM providers. Fields marked :secret go to .env.") #+end_src ** XDG Configuration Utilities #+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness")) (defun get-oc-config-dir () "Resolves the OpenCortex configuration directory following XDG standards." (let ((env (uiop:getenv "OC_CONFIG_DIR"))) (if (and env (> (length env) 0)) (uiop:ensure-directory-pathname env) (merge-pathnames ".config/opencortex/" (user-homedir-pathname))))) #+end_src ** Secret Persistence #+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness")) (defun save-secret (id key value) "Appends a secret to the XDG config .env file and updates the current environment." (let* ((env-key (format nil "~:@(~a_~a~)" id key)) (path (merge-pathnames ".env" (get-oc-config-dir)))) (ensure-directories-exist path) (with-open-file (s path :direction :output :if-exists :append :if-does-not-exist :create) (format s "~%~a=\"~a\"" env-key value)) (setf (uiop:getenv env-key) value))) #+end_src ** Provider Metadata Persistence #+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness")) (defun save-providers () "Persist provider configuration to XDG config directory." (let ((path (merge-pathnames "providers.lisp" (get-oc-config-dir)))) (ensure-directories-exist path) (with-open-file (s path :direction :output :if-exists :supersede) (format s ";;; OpenCortex Provider Metadata~%~s~%" *providers*)))) #+end_src #+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness")) (defun load-providers () "Load provider configuration from XDG config directory." (let ((path (merge-pathnames "providers.lisp" (get-oc-config-dir)))) (when (uiop:file-exists-p path) (with-open-file (s path) (setf *providers* (read s)))))) #+end_src ** Registry API #+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness")) (defun register-provider (id config) "Update the global provider registry." (setf (getf *providers* id) config)) #+end_src #+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness")) (defun system-ready-p () "Predicate verifying if at least one provider is configured." (and *providers* (> (length *providers*) 0))) #+end_src ** User Interface Primitives #+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness")) (defun prompt-for (label &optional default) "Interactively prompt the user for input with an optional default." (format t "~a~@[ [~a]~]: " label default) (finish-output) (let ((input (read-line))) (if (and (string= input "") default) default input))) #+end_src ** Provider Configuration Loop #+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness")) (defun configure-provider (id) "Guided configuration for a specific LLM provider template." (let* ((template (cdr (assoc id *provider-templates*))) (fields (getf template :fields)) (config nil)) (format t "~%--- Configuring ~a ---~%" (getf template :name)) (dolist (field-spec fields) (let* ((field (first field-spec)) (label (getf (rest field-spec) :label)) (is-secret (getf (rest field-spec) :secret)) (default-key (intern (format nil "DEFAULT-~a" field) :keyword)) (default (getf template default-key)) (val (prompt-for label default))) (if is-secret (save-secret id field val) (setf (getf config field) val)))) (register-provider id config) (format t "✓ ~a metadata registered.~%" (getf template :name)))) #+end_src ** Main Setup Orchestrator #+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness")) (defun run-setup-wizard () "Entry point for the interactive OpenCortex Lisp Setup Wizard." (format t "=== OpenCortex: Advanced Setup Wizard ===~%") ;; 1. Identity (let ((user (prompt-for "Your Name" "User")) (agent (prompt-for "Agent Name" "OpenCortex"))) (format t "Welcome, ~a. I am ~a.~%" user agent)) ;; 2. Providers (format t "~%Available Providers:~%") (loop for (id . data) in *provider-templates* do (format t " ~a: ~a~%" id (getf data :name))) (format t "~%Enter provider IDs to configure (comma separated, or 'all'): ") (finish-output) (let* ((input (read-line)) (ids (if (string= input "all") (mapcar #'car *provider-templates*) (mapcar (lambda (s) (intern (string-upcase (string-trim " " s)) :keyword)) (uiop:split-string input :separator ","))))) (dolist (id ids) (when (assoc id *provider-templates*) (configure-provider id)))) (save-providers) (format t "~%Setup complete. Running doctor check...~%") (doctor-run-all)) #+end_src