Files
passepartout/org/system-config.org

14 KiB

SKILL: Config Manager (org-skill-config-manager.org)

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))