- NEW: org-skill-utils-lisp (consolidated from org-skill-lisp-utils) * 3-phase validation: structural, syntactic, semantic * Sandboxed eval, AST extraction/injection/wrapping * Format, list-definitions utilities - NEW: org-skill-utils-org (consolidated from org-skill-emacs-edit) * Read/update/delete org headlines * Property management, TODO state handling * ID-link and internal link support - DELETE: org-skill-lisp-utils (merged into utils-lisp) - DELETE: org-skill-emacs-edit (merged into utils-org) - RENAME: run-all-tests.lisp -> run-tests.lisp - HARDEN: Skill loader with improved lisp keyword handling - FIX: Package jailing issues with def-cognitive-tool macro conflicts - ADD: Setup wizard (opencortex setup) and doctor (opencortex doctor) - ADD: TUI client with Croatoan for native terminal rendering - REMOVE: Dynamic loading from opencortex.asd (use :force t instead) - CLEANUP: Test file consolidation (removed duplicate test suites) Co-authored-by: Agent <agent@memex>
251 lines
9.4 KiB
Common Lisp
251 lines
9.4 KiB
Common Lisp
(in-package :opencortex)
|
|
|
|
(defun get-oc-config-dir ()
|
|
"Returns the absolute path to the opencortex config directory."
|
|
(let ((xdg (uiop:getenv "OC_CONFIG_DIR")))
|
|
(if (and xdg (string/= xdg ""))
|
|
(uiop:ensure-directory-pathname xdg)
|
|
(uiop:ensure-directory-pathname (merge-pathnames ".config/opencortex/" (user-homedir-pathname))))))
|
|
|
|
(defun get-config-file ()
|
|
"Returns the path to the .env config file."
|
|
(merge-pathnames ".env" (get-oc-config-dir)))
|
|
|
|
(defun ensure-config-dir ()
|
|
"Ensures the config directory exists."
|
|
(let ((dir (get-oc-config-dir)))
|
|
(unless (uiop:directory-exists-p dir)
|
|
(uiop:ensure-directory-pathname dir))
|
|
dir))
|
|
|
|
(defun read-config-file ()
|
|
"Reads the .env config file and returns an alist of KEY=VALUE pairs."
|
|
(let ((config-file (get-config-file)))
|
|
(when (uiop:file-exists-p config-file)
|
|
(let ((lines (uiop:read-file-lines config-file))
|
|
(result nil))
|
|
(dolist (line lines)
|
|
(when (and line (> (length line) 0)
|
|
(not (uiop:string-prefix-p "#" line)))
|
|
(let ((eq-pos (position #\= line)))
|
|
(when eq-pos
|
|
(let ((key (string-trim " " (subseq line 0 eq-pos)))
|
|
(value (string-trim " " (subseq line (1+ eq-pos)))))
|
|
(push (cons key value) result))))))
|
|
(nreverse result)))))
|
|
|
|
(defun write-config-file (config-alist)
|
|
"Writes the config alist to the .env file."
|
|
(ensure-config-dir)
|
|
(let ((config-file (get-config-file)))
|
|
(with-open-file (stream config-file :direction :output :if-exists :supersede :if-does-not-exist :create)
|
|
(format stream "# OpenCortex Configuration~%")
|
|
(format stream "# Generated by opencortex setup~%~%")
|
|
(dolist (pair config-alist)
|
|
(format stream "~a=~a~%" (car pair) (cdr pair))))))
|
|
|
|
(defun get-config-value (key)
|
|
"Gets a config value by key."
|
|
(let ((config (read-config-file)))
|
|
(cdr (assoc key config :test #'string=))))
|
|
|
|
(defun set-config-value (key value)
|
|
"Sets a config value and saves to file."
|
|
(let ((config (read-config-file))
|
|
(pair (cons key value)))
|
|
(let ((existing (assoc key config :test #'string=)))
|
|
(if existing
|
|
(setf (cdr existing) value)
|
|
(push pair config))
|
|
(write-config-file config)))
|
|
|
|
(defun prompt (prompt-text)
|
|
"Simple prompt that returns user input as a string."
|
|
(format t "~a" prompt-text)
|
|
(finish-output)
|
|
(read-line))
|
|
|
|
(defun prompt-yes-no (prompt-text)
|
|
"Prompts yes/no question. Returns T for yes, nil for no."
|
|
(let ((response (prompt (format nil "~a [Y/n]: " prompt-text))))
|
|
(or (string= response "")
|
|
(string-equal response "Y")
|
|
(string-equal response "y")
|
|
(string-equal response "yes"))))
|
|
|
|
(defun prompt-choice (prompt-text options)
|
|
"Prompts user to choose from a list of options. Returns the chosen option or nil."
|
|
(format t "~a~%" prompt-text)
|
|
(let ((i 1))
|
|
(dolist (opt options)
|
|
(format t " ~a) ~a~%" i opt)
|
|
(incf i)))
|
|
(let ((response (prompt "Choice")))
|
|
(let ((num (ignore-errors (parse-integer response))))
|
|
(when (and num (<= 1 num) (>= (length options) num))
|
|
(nth (1- num) options)))))
|
|
|
|
(defvar *available-providers*
|
|
'(("OpenAI" . "OPENAI_API_KEY")
|
|
("Anthropic" . "ANTHROPIC_API_KEY")
|
|
("OpenRouter" . "OPENROUTER_API_KEY")
|
|
("Groq" . "GROQ_API_KEY")
|
|
("Gemini" . "GEMINI_API_KEY")
|
|
("Ollama (local)" . "OLLAMA_URL")))
|
|
|
|
(defun setup-llm-providers ()
|
|
"Interactive wizard for configuring LLM providers."
|
|
(format t "~%~%")
|
|
(format t "==================================================~%")
|
|
(format t " LLM Provider Configuration~%")
|
|
(format t "==================================================~%~%")
|
|
|
|
(let ((current-providers (loop for (name . key) in *available-providers*
|
|
when (get-config-value key)
|
|
collect name)))
|
|
(when current-providers
|
|
(format t "Current providers: ~{~a~^, ~}~%~%" current-providers))
|
|
|
|
(format t "Available providers:~%")
|
|
(dolist (p *available-providers*)
|
|
(format t " - ~a~%" (car p)))
|
|
(format t "~%")
|
|
|
|
(when (prompt-yes-no "Configure a new provider?")
|
|
(let ((chosen (prompt-choice "Select provider:" (mapcar #'car *available-providers*))))
|
|
(when chosen
|
|
(let ((env-key (cdr (assoc chosen *available-providers* :test #'string= :key #'car))))
|
|
(if (string= chosen "Ollama (local)")
|
|
(progn
|
|
(format t "Enter Ollama URL (e.g., http://localhost:11434): ")
|
|
(let ((url (read-line)))
|
|
(set-config-value env-key url)
|
|
(format t "✓ Ollama configured at ~a~%" url)))
|
|
(progn
|
|
(format t "Enter API key for ~a: " chosen)
|
|
(let ((key (read-line)))
|
|
(set-config-value env-key key)
|
|
(format t "✓ ~a API key saved~%" chosen)))))))))
|
|
|
|
(format t "~%"))
|
|
|
|
(defun setup-add-provider ()
|
|
"Entry point for adding a single provider (called from CLI)."
|
|
(setup-llm-providers))
|
|
|
|
(defun setup-gateways ()
|
|
"Interactive wizard for configuring external gateways."
|
|
(format t "~%~%")
|
|
(format t "==================================================~%")
|
|
(format t " Gateway Configuration~%")
|
|
(format t "==================================================~%~%")
|
|
|
|
(format t "Available gateways:~%")
|
|
(format t " - Slack (https://api.slack.com/)~%")
|
|
(format t " - Discord (https://discord.com/developers/)~%")
|
|
(format t "~%")
|
|
|
|
(when (prompt-yes-no "Configure a gateway?")
|
|
(let ((chosen (prompt-choice "Select platform:" '("Slack" "Discord"))))
|
|
(when chosen
|
|
(let ((token (prompt (format nil "Enter ~a bot token: " chosen))))
|
|
(if (string= chosen "Slack")
|
|
(set-config-value "SLACK_TOKEN" token)
|
|
(set-config-value "DISCORD_TOKEN" token))
|
|
(format t "✓ ~a gateway configured~%" chosen))))))
|
|
|
|
(format t "~%"))
|
|
|
|
(defun setup-skills ()
|
|
"Interactive wizard for enabling/disabling skills."
|
|
(format t "~%~%")
|
|
(format t "==================================================~%")
|
|
(format t " Skill Management~%")
|
|
(format t "==================================================~%~%")
|
|
|
|
(format t "Note: Skill management is not yet implemented.~%")
|
|
(format t "Skills are automatically loaded from ~a~%" (or (uiop:getenv "SKILLS_DIR") "default location"))
|
|
(format t "~%"))
|
|
|
|
(defun setup-memory ()
|
|
"Interactive wizard for memory settings."
|
|
(format t "~%~%")
|
|
(format t "==================================================~%")
|
|
(format t " Memory Settings~%")
|
|
(format t "==================================================~%~%")
|
|
|
|
(let ((auto-save (prompt "Auto-save interval in seconds [300]:")))
|
|
(when (and auto-save (> (length auto-save) 0))
|
|
(set-config-value "MEMORY_AUTO_SAVE_INTERVAL" auto-save)))
|
|
|
|
(let ((history (prompt "History retention in lines [1000]:")))
|
|
(when (and history (> (length history) 0))
|
|
(set-config-value "MEMORY_HISTORY_RETENTION" history)))
|
|
|
|
(format t "✓ Memory settings saved~%")
|
|
(format t "~%"))
|
|
|
|
(defun setup-network ()
|
|
"Interactive wizard for network settings."
|
|
(format t "~%~%")
|
|
(format t "==================================================~%")
|
|
(format t " Network Settings~%")
|
|
(format t "==================================================~%~%")
|
|
|
|
(let ((timeout (prompt "Request timeout in seconds [30]:")))
|
|
(when (and timeout (> (length timeout) 0))
|
|
(set-config-value "REQUEST_TIMEOUT" timeout)))
|
|
|
|
(let ((proxy (prompt "Proxy URL (leave empty for none) []:")))
|
|
(when (and proxy (> (length proxy) 0))
|
|
(set-config-value "HTTP_PROXY" proxy)))
|
|
|
|
(format t "✓ Network settings saved~%")
|
|
(format t "~%"))
|
|
|
|
(defun run-setup-wizard ()
|
|
"Main entry point for the interactive setup wizard."
|
|
(format t "~%~%")
|
|
(format t "╔═══════════════════════════════════════════════════╗~%")
|
|
(format t "║ OpenCortex Setup Wizard ║~%")
|
|
(format t "╚═══════════════════════════════════════════════════╝~%")
|
|
(format t "~%")
|
|
(format t "This wizard will help you configure:~%")
|
|
(format t " 1. LLM Providers (OpenAI, Anthropic, etc.)~%")
|
|
(format t " 2. Gateway Links (Slack, Discord)~%")
|
|
(format t " 3. Memory Settings~%")
|
|
(format t " 4. Network Settings~%")
|
|
(format t "~%")
|
|
|
|
(ensure-config-dir)
|
|
|
|
;; Step 1: LLM Providers
|
|
(when (prompt-yes-no "Configure LLM providers?")
|
|
(setup-llm-providers))
|
|
|
|
;; Step 2: Gateways
|
|
(when (prompt-yes-no "Configure gateways?")
|
|
(setup-gateways))
|
|
|
|
;; Step 3: Memory
|
|
(when (prompt-yes-no "Configure memory settings?")
|
|
(setup-memory))
|
|
|
|
;; Step 4: Network
|
|
(when (prompt-yes-no "Configure network settings?")
|
|
(setup-network))
|
|
|
|
;; Summary
|
|
(format t "==================================================~%")
|
|
(format t " Setup Complete!~%")
|
|
(format t "==================================================~%")
|
|
(format t "~%")
|
|
(format t "Configuration saved to: ~a~%" (get-config-file))
|
|
(format t "~%")
|
|
(format t "To verify your setup, run: opencortex doctor~%")
|
|
(format t "~%"))
|
|
|
|
(defskill :skill-config-manager
|
|
:priority 100
|
|
:trigger (lambda (ctx) (declare (ignore ctx)) nil))
|