- Changed all 50 org file :tangle targets from ../lisp/ to ~/.local/share/passepartout/lisp/ (XDG data dir) - Removed 49 generated .lisp files from project lisp/ directory - Removed tests/system-integration-tests.lisp (generated) - Removed lisp/*.fasl (compiled, stale) - Updated core-manifest.org to tangle .asd to XDG root - Remapped quicklisp symlink: local-projects/passepartout → XDG TUI fixes in channel-tui-main.org: - Removed with-raw-terminal (stty raw breaks fd 0 reads in this SBCL) - Use cat subprocess + pipe for keyboard input (via :input :interactive) - Blocking read-char on pipe with with-timeout 0.1s for daemon processing - Key events queued via drain-queue alongside daemon messages - Full dialog key routing (Escape, Up/Down, Enter, filters, Backspace) - SIGWINCH resize handling - Post-handshake backend-size re-query - Daemon version in status bar (was v0.5.0 hardcoded) - Handshake version stored in state, no add-msg - :daemon-version and :size-queried in state plist - view-status uses draw-rect for background - Test section gated with #+passepartout-tests
383 lines
14 KiB
Org Mode
383 lines
14 KiB
Org Mode
#+TITLE: SKILL: Config Manager (org-skill-config-manager.org)
|
|
#+AUTHOR: Agent
|
|
#+FILETAGS: :skill:setup:config:
|
|
#+PROPERTY: header-args:lisp :tangle /home/user/.local/share/passepartout/lisp/symbolic-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.
|
|
Returns nil if stdin is non-interactive."
|
|
(format t "~a" prompt-text)
|
|
(finish-output)
|
|
(ignore-errors (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")
|
|
("DeepSeek" . "DEEPSEEK_API_KEY")
|
|
("NVIDIA" . "NVIDIA_API_KEY")
|
|
("Local" . "LOCAL_BASE_URL")))
|
|
|
|
#+end_src
|
|
** 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
|
|
#+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 "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 "~%")))
|
|
|
|
|
|
|
|
|
|
#+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-symbolic-config
|
|
:priority 100
|
|
:trigger (lambda (ctx) (declare (ignore ctx)) nil))
|
|
#+end_src |