14 KiB
SKILL: Config Manager (org-skill-config-manager.org)
- Overview
- Implementation
- Configuration directory (config-directory)
- Config file path (config-file-path)
- Ensure config directory (config-directory-ensure)
- Config File Operations
- config-write
- config-get
- config-set
- Input Utilities
- prompt-yes-no
- prompt-choice
- LLM Provider Setup
- Provider descriptions (for setup wizard display)
- setup-llm-providers
- setup-add-provider
- Gateway Setup
- Skill Management
- Memory Settings
- Network Settings
- Main Setup Wizard
- Skill Registration
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
(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))))))
Config file path (config-file-path)
Returns the path to the .env file within the config directory.
;; REPL-VERIFIED: 2026-05-03T13:00:00
(defun config-file-path ()
"Returns the path to the .env configuration file."
(merge-pathnames ".env" (config-directory)))
Ensure config directory (config-directory-ensure)
Creates the config directory tree if it does not exist. ;; REPL-VERIFIED: 2026-05-03T13:00:00
(defun config-directory-ensure ()
"Creates the configuration directory if it does not exist."
(ensure-directories-exist (config-directory)))
Config File Operations
;; REPL-VERIFIED: 2026-05-03T13:00:00
(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)))))
config-write
;; REPL-VERIFIED: 2026-05-03T13:00:00
(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))))))
config-get
;; REPL-VERIFIED: 2026-05-03T13:00:00
(defun config-get (key)
"Gets a config value by key."
(let ((config (config-read)))
(cdr (assoc key config :test #'string=))))
config-set
;; REPL-VERIFIED: 2026-05-03T13:00:00
(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
Input Utilities
;; REPL-VERIFIED: 2026-05-03T13:00:00
(defun prompt (prompt-text)
"Simple prompt that returns user input as a string."
(format t "~a" prompt-text)
(finish-output)
(read-line))
prompt-yes-no
;; REPL-VERIFIED: 2026-05-03T13:00:00
(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"))))
prompt-choice
;; REPL-VERIFIED: 2026-05-03T13:00:00
(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
LLM Provider Setup
;; REPL-VERIFIED: 2026-05-03T13:00:00
(defparameter *available-providers*
'(("OpenAI" . "OPENAI_API_KEY")
("Anthropic" . "ANTHROPIC_API_KEY")
("OpenRouter" . "OPENROUTER_API_KEY")
("Groq" . "GROQ_API_KEY")
("Gemini" . "GEMINI_API_KEY")
("DeepSeek" . "DEEPSEEK_API_KEY")
("NVIDIA" . "NVIDIA_API_KEY")
("Local" . "LOCAL_BASE_URL")))
Provider descriptions (for setup wizard display)
These are shown inline when the user runs the setup wizard, so they know what they are choosing.
| Provider | Description | Where to sign up | Recommendation |
|---|---|---|---|
OpenRouter |
Free tier with 33+ models. No credit card required. Routes to best available free model. | openrouter.ai | ★ Recommended for new users |
OpenAI |
GPT-4o-mini and GPT-4o. Requires billing. | platform.openai.com | |
Anthropic |
Claude 3.5 Sonnet. Strong reasoning. | console.anthropic.com | |
Groq |
Very fast inference, free tier available. | console.groq.com | |
Gemini |
Google's Gemini models. Free tier via API. | aistudio.google.com | |
DeepSeek |
Competitive pricing, strong coding. | platform.deepseek.com | |
NVIDIA |
NVIDIA NIM. Hosted models, slower but capable. | build.nvidia.com | |
Local |
Any OpenAI-compatible local server (llama.cpp, vLLM, LM Studio, Ollama). No API key needed. | Run locally |
setup-llm-providers
;; REPL-VERIFIED: 2026-05-04
(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 "Currently configured: ~{~a~^, ~}~%~%" current-providers))
(format t "~%")
(format t "★ OpenRouter recommended for new users — free tier, no credit card required.~%")
(format t " Sign up at https://openrouter.ai and paste your API key below.~%")
(format t "~%")
(format t "Available providers:~%")
(format t " ~20@A ~25@A ~s~%" "Provider" "Key env var" "Notes")
(format t " ~20@A ~25@A ~s~%" "--------" "----------" "-----")
(dolist (p *available-providers*)
(let ((name (car p))
(env-key (cdr p))
(desc (case (car p)
("OpenRouter" "free tier, 33+ models")
("OpenAI" "paid, gpt-4o-mini")
("Anthropic" "paid, Claude 3.5 Sonnet")
("Groq" "fast inference, free tier")
("Gemini" "free via API")
("DeepSeek" "competitive pricing, coding")
("NVIDIA" "NVIDIA NIM hosted models")
("Local" "local server, no API key")
(t ""))))
(format t " ~20@A ~25@A ~a~%" name env-key desc)))
(format t "~%")
(loop
(when (not (prompt-yes-no "Configure a LLM provider?"))
(return))
(let ((chosen (prompt-choice "Select a provider:" (mapcar #'car *available-providers*))))
(unless chosen
(format t "Invalid choice.~%")
(return))
(let ((env-key (cdr (assoc chosen *available-providers* :test #'string=))))
(cond
((string= chosen "Local")
(format t "Enter the server URL (e.g., http://localhost:11434 for Ollama,~%")
(format t " or http://localhost:8080 for llama.cpp): ")
(let ((url (read-line)))
(if (> (length url) 0)
(progn (config-set env-key url)
(format t "✓ ~a configured at ~a~%" chosen url))
(format t "Skipping ~a — no URL entered.~%" chosen))))
(t
(format t "Enter API key for ~a~%" chosen)
(format t " (get one from the provider's website, paste it here): ")
(let ((key (read-line)))
(if (> (length key) 0)
(progn (config-set env-key key)
(format t "✓ ~a API key saved~%" chosen))
(format t "Skipping ~a — no key entered.~%" chosen))))))))
(format t "~%")))
setup-add-provider
;; REPL-VERIFIED: 2026-05-03T13:00:00
(defun setup-add-provider ()
"Entry point for adding a single provider (called from CLI)."
(setup-llm-providers))
#+end_src
Gateway Setup
;; REPL-VERIFIED: 2026-05-03T13:00:00
(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 "~%"))
Skill Management
;; REPL-VERIFIED: 2026-05-03T13:00:00
(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 "~%"))
Memory Settings
;; REPL-VERIFIED: 2026-05-03T13:00:00
(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 "~%"))
Network Settings
;; REPL-VERIFIED: 2026-05-03T13:00:00
(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 "~%"))
Main Setup Wizard
;; REPL-VERIFIED: 2026-05-03T13:00:00
(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 "~%"))
Skill Registration
(defskill :passepartout-system-config
:priority 100
:trigger (lambda (ctx) (declare (ignore ctx)) nil))