#+TITLE: SKILL: Config Manager (org-skill-config-manager.org) #+AUTHOR: Agent #+FILETAGS: :skill:setup:config: #+PROPERTY: header-args:lisp :tangle ../lisp/system-config.lisp * Overview The *Config Manager* skill provides the Passepartout Agent with the capability to manage its own environment variables and provider configurations. It includes an interactive setup wizard for LLM providers, gateways, and system settings. * Implementation ** Configuration directory (config-directory) Resolves the XDG config directory for Passepartout. ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun config-directory () "Returns the absolute path to the opencortex config directory." (let ((xdg (uiop:getenv "OC_CONFIG_DIR"))) (if xdg xdg (namestring (merge-pathnames ".config/passepartout/" (user-homedir-pathname)))))) #+end_src ** Config file path (config-file-path) Returns the path to the ~.env~ file within the config directory. ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun config-file-path () "Returns the path to the .env configuration file." (merge-pathnames ".env" (config-directory))) #+end_src ** Ensure config directory (config-directory-ensure) Creates the config directory tree if it does not exist. ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun config-directory-ensure () "Creates the configuration directory if it does not exist." (ensure-directories-exist (config-directory))) #+end_src ** Config File Operations ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun config-read () "Reads the .env config file and returns an alist of KEY=VALUE pairs." (let ((config-file (config-file-path))) (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))))) #+end_src ** config-write ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun config-write (config-alist) "Writes the config alist to the .env file." (config-directory-ensure) (let ((config-file (config-file-path))) (with-open-file (stream config-file :direction :output :if-exists :supersede :if-does-not-exist :create) (format stream "# Passepartout Configuration~%") (format stream "# Generated by opencortex setup~%~%") (dolist (pair config-alist) (format stream "~a=~a~%" (car pair) (cdr pair)))))) #+end_src ** config-get ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun config-get (key) "Gets a config value by key." (let ((config (config-read))) (cdr (assoc key config :test #'string=)))) #+end_src ** config-set ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun config-set (key value) "Sets a config value and saves to file." (let ((config (config-read)) (pair (cons key value))) (let ((existing (assoc key config :test #'string=))) (if existing (setf (cdr existing) value) (push pair config)) (config-write config)))) #+end_src #+end_src ** Input Utilities ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun prompt (prompt-text) "Simple prompt that returns user input as a string." (format t "~a" prompt-text) (finish-output) (read-line)) #+end_src ** prompt-yes-no ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (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")))) #+end_src ** prompt-choice ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (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))))) #+end_src #+end_src ** LLM Provider Setup ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defparameter *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"))) #+end_src ** setup-llm-providers ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (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 (config-get 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=)))) (if (string= chosen "Ollama (local)") (progn (format t "Enter Ollama URL (e.g., http://localhost:11434): ") (let ((url (read-line))) (config-set env-key url) (format t "✓ Ollama configured at ~a~%" url))) (progn (format t "Enter API key for ~a: " chosen) (let ((key (read-line))) (config-set env-key key) (format t "✓ ~a API key saved~%" chosen))))))))) (format t "~%")) #+end_src ** setup-add-provider ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun setup-add-provider () "Entry point for adding a single provider (called from CLI)." (setup-llm-providers)) #+end_src #+end_src ** Gateway Setup ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (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") (config-set "SLACK_TOKEN" token) (config-set "DISCORD_TOKEN" token)) (format t "✓ ~a gateway configured~%" chosen))))) (format t "~%")) #+end_src ** Skill Management ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (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 "PASSEPARTOUT_DATA_DIR") "~/.local/share/passepartout")) (format t "~%")) #+end_src ** Memory Settings ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (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)) (config-set "MEMORY_AUTO_SAVE_INTERVAL" auto-save))) (let ((history (prompt "History retention in lines [1000]:"))) (when (and history (> (length history) 0)) (config-set "MEMORY_HISTORY_RETENTION" history))) (format t "✓ Memory settings saved~%") (format t "~%")) #+end_src ** Network Settings ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (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)) (config-set "REQUEST_TIMEOUT" timeout))) (let ((proxy (prompt "Proxy URL (leave empty for none) []:"))) (when (and proxy (> (length proxy) 0)) (config-set "HTTP_PROXY" proxy))) (format t "✓ Network settings saved~%") (format t "~%")) #+end_src ** Main Setup Wizard ;; REPL-VERIFIED: 2026-05-03T13:00:00 #+begin_src lisp (defun setup-wizard-run () "Main entry point for the interactive setup wizard." (format t "~%~%") (format t "╔═══════════════════════════════════════════════════╗~%") (format t "║ Passepartout 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 "~%") (config-directory-ensure) ;; 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~%" (config-file-path)) (format t "~%") (format t "To verify your setup, run: passepartout doctor~%") (format t "~%")) #+end_src ** Skill Registration #+begin_src lisp (defskill :passepartout-system-config :priority 100 :trigger (lambda (ctx) (declare (ignore ctx)) nil)) #+end_src