Commit 87b899149ac6

Vincent Demeester <vincent@sbr.pm>
2026-02-18 07:04:15
feat(pi): added declarative auth management with passage integration
Introduced declarative API key management for pi agent, separating declarative API keys from OAuth tokens managed by pi. The system uses passage for secure secret retrieval and merges keys with preserved OAuth credentials. Added auth-keys.json with passage-based API key references for google, openrouter, deepseek, groq, mistral, and zai providers. Created ensure-auth.sh script to atomically merge declarative keys with runtime OAuth tokens. Updated Makefile to include pi-agent-auth target. Expanded README with comprehensive auth management documentation.
1 parent 9e42972
dots/pi/agent/auth-keys.json
@@ -0,0 +1,26 @@
+{
+  "google": {
+    "type": "api_key",
+    "key": "!passage show ai/gemini/api_key"
+  },
+  "openrouter": {
+    "type": "api_key",
+    "key": "!passage show ai/openroute/api_key"
+  },
+  "deepseek": {
+    "type": "api_key",
+    "key": "!passage show ai/deepseek/api_key"
+  },
+  "groq": {
+    "type": "api_key",
+    "key": "!passage show ai/groq/wakasu"
+  },
+  "mistral": {
+    "type": "api_key",
+    "key": "!passage show ai/mistralai/api_key"
+  },
+  "zai": {
+    "type": "api_key",
+    "key": "!passage show ai/zai/api_key"
+  }
+}
dots/pi/agent/ensure-auth.sh
@@ -0,0 +1,68 @@
+#!/usr/bin/env bash
+# Ensure pi agent auth.json has API keys from passage without overwriting OAuth tokens
+# This script merges declarative API keys into ~/.pi/agent/auth.json while preserving
+# OAuth tokens that pi manages automatically (github-copilot, vertex-anthropic, etc.)
+
+set -euo pipefail
+
+RUNTIME_AUTH="$HOME/.pi/agent/auth.json"
+API_KEYS_FILE="$(dirname "$0")/auth-keys.json"
+
+# Required for merging JSON
+if ! command -v jq >/dev/null 2>&1; then
+	echo "⚠️  jq not found - cannot manage auth.json automatically"
+	echo "   Install jq or manually configure API keys in $RUNTIME_AUTH"
+	exit 1
+fi
+
+# Required for passage-based secret retrieval
+if ! command -v passage >/dev/null 2>&1; then
+	echo "⚠️  passage not found - API keys use passage for secret management"
+	echo "   Install passage (pass fork with age encryption) or update auth-keys.json"
+	exit 1
+fi
+
+# Create runtime auth directory if it doesn't exist
+mkdir -p "$(dirname "$RUNTIME_AUTH")"
+
+# Initialize with empty object if auth.json doesn't exist
+if [ ! -f "$RUNTIME_AUTH" ]; then
+	echo "�� Creating $RUNTIME_AUTH..."
+	echo '{}' > "$RUNTIME_AUTH"
+	chmod 600 "$RUNTIME_AUTH"
+fi
+
+# Extract OAuth tokens (preserve these - managed by pi)
+# These are set via /login in pi and should never be declaratively managed
+TEMP_OAUTH=$(mktemp)
+jq -r '
+  {
+    "github-copilot": .["github-copilot"],
+    "vertex-anthropic": .["vertex-anthropic"],
+    "claude-pro": .["claude-pro"],
+    "chatgpt": .["chatgpt"],
+    "gemini-cli": .["gemini-cli"],
+    "antigravity": .["antigravity"]
+  } | with_entries(select(.value != null))
+' "$RUNTIME_AUTH" > "$TEMP_OAUTH"
+
+# Merge OAuth tokens with declarative API keys
+# API keys override any existing keys, but OAuth tokens are preserved
+TEMP_MERGED=$(mktemp)
+jq -s '.[0] * .[1]' "$TEMP_OAUTH" "$API_KEYS_FILE" > "$TEMP_MERGED"
+
+# Atomic update
+mv "$TEMP_MERGED" "$RUNTIME_AUTH"
+chmod 600 "$RUNTIME_AUTH"
+rm -f "$TEMP_OAUTH"
+
+echo "✅ Pi agent auth.json updated with API keys:"
+jq -r 'to_entries | .[] | select(.value.type == "api_key") | "   - \(.key): \(.value.key)"' "$RUNTIME_AUTH"
+
+# Show preserved OAuth tokens
+OAUTH_COUNT=$(jq -r '[to_entries | .[] | select(.value.type == "oauth")] | length' "$RUNTIME_AUTH")
+if [ "$OAUTH_COUNT" -gt 0 ]; then
+	echo ""
+	echo "�� Preserved OAuth tokens:"
+	jq -r 'to_entries | .[] | select(.value.type == "oauth") | "   - \(.key)"' "$RUNTIME_AUTH"
+fi
dots/pi/agent/README.md
@@ -1,70 +1,167 @@
-# Pi Coding Agent Configuration
+# Pi Agent Configuration
 
-Configuration for [pi-coding-agent](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent),
-a minimal terminal coding agent.
+Declarative configuration for [pi coding agent](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent).
 
-## Structure
+## Files
 
-```
-~/.pi/agent/
-├── settings.json    # Global settings (provider, model, thinking)
-├── extensions/      # TypeScript extensions
-│   ├── ai-storage/  # Unified AI storage (sessions, research, plans, learnings)
-│   ├── org-todos/   # Org-mode TODO management via emacsclient
-│   ├── path-validator/ # Validate file paths and filenames
-│   ├── terminal-status.ts # Terminal title updates
-│   └── validate-git-push.ts # Block unsafe git push
-├── AGENTS.md        # Global agent instructions
-└── README.md
+### Declarative (Version Controlled)
+
+- **`auth-keys.json`** - API key providers with passage secret references
+- **`settings.json`** - Default settings template
+- **`AGENTS.md`** - Global agent instructions
+- **`keybindings.json`** - Custom keybindings
+- **`agents/`** - Project-agnostic subagents
+- **`extensions/`** - Custom pi extensions
+- **`prompts/`** - Custom prompt templates
+
+### Runtime (Not Version Controlled)
+
+- **`~/.pi/agent/auth.json`** - Generated, merges OAuth + API keys
+- **`~/.pi/agent/settings.json`** - Generated, merges template + user prefs
+- **`~/.pi/agent/sessions/`** - Symlinked to `~/.local/share/ai-sync/pi-sessions/`
+
+## Setup
+
+```bash
+# Install dotfiles (includes pi-agent target)
+cd ~/src/home
+make dots
+
+# Or manually:
+cd ~/src/home/dots
+make pi-agent pi-agent-settings pi-agent-auth
 ```
 
-## Extensions
+## Auth Management
 
-### ai-storage
-
-Unified AI storage extension. Saves sessions, research, plans, and learnings
-to `~/.local/share/ai/` with consistent `YYYY-MM-DD-description.md` naming.
-Shared across pi, claude, and opencode.
-
-### org-todos
-
-Org-mode TODO management via emacsclient. Provides the `org_todo` tool for
-listing, searching, creating, and managing TODOs without direct file edits.
-
-### path-validator
-
-Validates file paths and filenames against policies in `~/.config/ai/path-policies.json`.
-Blocks writes to old storage locations and enforces naming conventions.
-
-### validate-git-push.ts
-
-Blocks unsafe git commands: bare `git push` (no refspec), `git add .`, etc.
-
-### terminal-status.ts
-
-Updates terminal title with project and context info after tool calls.
-
-## Skills
-
-Pi loads Claude Code skills via `settings.json`:
+API keys are declared in `auth-keys.json` with passage references:
 
 ```json
 {
-  "skills": [
-    "~/.config/claude/skills"
+  "google": {
+    "type": "api_key",
+    "key": "!passage show gemini/api_key"
+  },
+  "openrouter": {
+    "type": "api_key",
+    "key": "!passage show openroute/api_key"
+  }
+}
+```
+
+OAuth tokens (GitHub Copilot, Vertex, etc.) are managed by pi via `/login` command and preserved automatically.
+
+The `ensure-auth.sh` script:
+1. Extracts existing OAuth tokens from `~/.pi/agent/auth.json`
+2. Merges with declarative API keys from `auth-keys.json`
+3. Writes atomically with secure permissions (600)
+
+### Adding New API Keys
+
+1. Store secret in passage:
+   ```bash
+   passage insert provider-name/api_key
+   ```
+
+2. Add to `auth-keys.json`:
+   ```json
+   {
+     "provider-name": {
+       "type": "api_key",
+       "key": "!passage show provider-name/api_key"
+     }
+   }
+   ```
+
+3. Re-run setup:
+   ```bash
+   make pi-agent-auth
+   ```
+
+### Available Providers
+
+Current passage-based API keys:
+- **`google`** - Gemini API (`ai/gemini/api_key`)
+- **`openrouter`** - OpenRouter API (`ai/openroute/api_key`)
+- **`deepseek`** - DeepSeek API (`ai/deepseek/api_key`)
+- **`groq`** - Groq API (`ai/groq/wakasu`)
+- **`mistral`** - Mistral API (`ai/mistralai/api_key`)
+- **`zai`** - Z.AI/GLM API (`ai/zai/api_key`) - *Optional, GLM models also available via OpenRouter*
+
+OAuth providers (managed by pi):
+- **`github-copilot`** - via `/login github-copilot`
+- **`vertex-anthropic`** - via `/login vertex-anthropic`
+
+**Note:** Z.AI (GLM-5, GLM-4.7) models are accessible via OpenRouter without a direct Z.AI API key.
+
+## Settings Management
+
+Required settings are declared in `settings.json` and merged into runtime config:
+
+```json
+{
+  "hideThinkingBlock": true,
+  "quietStartup": true,
+  "skills": ["~/.config/claude/skills"],
+  "subagentProviderPreference": [
+    "google-vertex-claude",
+    "vertex",
+    "google",
+    "llama-cpp"
   ]
 }
 ```
 
-## Usage
+The `ensure-settings.sh` script merges these with user preferences, preserving:
+- Model selections
+- Custom providers
+- Theme preferences
+- Other user-specific settings
 
-```bash
-# Start pi in current directory
-pi
+## Session Storage
 
-# Start with specific prompt
-pi "explain this codebase"
+Pi sessions are symlinked to syncthing-managed directory:
+- **Source:** `~/.local/share/ai-sync/pi-sessions/`
+- **Link:** `~/.pi/agent/sessions` → source
+- **Purpose:** Sync raw JSONL conversation logs across machines
 
-# Use a specific skill
-pi "/Git:commit"
-```
+Curated markdown session summaries go to `~/.local/share/ai/sessions/` (also synced).
+
+## Extensions
+
+Custom extensions in `extensions/`:
+- **`github/`** - Enhanced GitHub integration
+- **`jira/`** - Jira issue management
+- **`org-todos/`** - Org-mode TODO integration
+- **`vertex-claude/`** - Google Vertex AI Claude models
+- **`defaults/`** - Default extensions wrapper
+- **`lsp/`** - LSP integration
+- **`filter-output/`** - Output filtering
+- **`shell-completions/`** - Shell completion enhancements
+- **`threads/`** - Thread/session management
+
+Extensions are symlinked to `~/.pi/agent/extensions/` and npm dependencies are auto-installed.
+
+## Skills
+
+Skills are shared with Claude Code via `~/.config/claude/skills/`:
+- Pi loads via `settings.json`: `"skills": ["~/.config/claude/skills"]`
+- Enables unified skill library across AI coding tools
+
+## Keybindings
+
+Custom keybindings in `keybindings.json`:
+- Ctrl+R: Reapply last edit
+- Ctrl+D: Show diff
+- See `KEYBINDINGS.md` for full documentation
+
+## Agents
+
+Global agent instructions in `AGENTS.md`:
+- Core principles
+- Stack preferences  
+- Response patterns
+- Git safety rules
+- Skills integration
+
+Project-specific agents can be added to `.pi/agents/` (not in dots).
dots/Makefile
@@ -61,13 +61,15 @@ gh-news : ~/.config/gh-news/config.toml
 all += github-notif-manager
 github-notif-manager : ~/.config/github-notif-manager/config.yaml
 
-all += git-template copilot-hooks opencode-plugin pi-agent pi-agent-settings agent-skills agent-skill-manager-bin ai-config
+all += git-template copilot-hooks opencode-plugin pi-agent pi-agent-settings pi-agent-auth agent-skills agent-skill-manager-bin ai-config
 git-template : ~/.config/git/template
 copilot-hooks : ~/.config/copilot-hooks
 opencode-plugin : ~/.config/opencode/plugin
 pi-agent : ~/.pi/agent/extensions ~/.pi/agent/agents ~/.pi/agent/AGENTS.md ~/.pi/agent/README.md ~/.pi/agent/keybindings.json ~/.pi/agent/sessions
 pi-agent-settings : pi-agent
 	@$(dotfiles)/pi/agent/ensure-settings.sh
+pi-agent-auth : pi-agent
+	@$(dotfiles)/pi/agent/ensure-auth.sh
 agent-skills : ~/.config/agent-skills
 agent-skill-manager-bin : ~/bin/agent-skill-manager
 ai-config : ~/.config/ai/skills ~/.config/ai/path-policies.json