Files
passepartout/org/system-config.org
Amr Gharbeia 95d1ea3fed feat: add DeepSeek and NVIDIA NIM providers
- Add deepseek and nvidia entries to gateway-provider config

- Add DEEPSEEK_API_KEY and NVIDIA_API_KEY to .env.example

- Add deepseek and nvidia to doctor's LLM provider check

- Fix remaining harness-log → log-message reference
2026-05-02 22:25:24 -04:00

10 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.

(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.

(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.

(defun config-directory-ensure ()
  "Creates the configuration directory if it does not exist."
  (ensure-directories-exist (config-directory)))

Config File Operations

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

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

(defun config-get (key)
  "Gets a config value by key."
  (let ((config (config-read)))
    (cdr (assoc key config :test #'string=))))

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

Input Utilities

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

LLM Provider Setup

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

(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 "~%"))

(defun setup-add-provider ()
  "Entry point for adding a single provider (called from CLI)."
  (setup-llm-providers))

Gateway Setup

(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

(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

(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

(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

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