From dfd2a1b5b419a3715ff8a29e67fff47d39a5ba7b Mon Sep 17 00:00:00 2001 From: geromet <81250489+geromet@users.noreply.github.com> Date: Mon, 16 Mar 2026 01:58:00 +0100 Subject: [PATCH] fix: load tool API keys from auth.json at session startup (#563) Export TOOL_KEYS constant and add loadToolApiKeys() function to load API keys from ~/.gsd/agent/auth.json into environment variables. Called in session_start handler so tool-based extensions (Context7, Brave Search, Jina, Tavily, Groq) work immediately without requiring /gsd config. --- src/resources/extensions/gsd/commands.ts | 28 +++++++++++++++++++++++- src/resources/extensions/gsd/index.ts | 7 ++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/resources/extensions/gsd/commands.ts b/src/resources/extensions/gsd/commands.ts index 56df01e4a..e8894e212 100644 --- a/src/resources/extensions/gsd/commands.ts +++ b/src/resources/extensions/gsd/commands.ts @@ -628,7 +628,12 @@ function serializePreferencesToFrontmatter(prefs: Record): stri // ─── Tool Config Wizard ─────────────────────────────────────────────────────── -const TOOL_KEYS = [ +/** + * Tool API key configurations. + * This is the source of truth for tool credentials - used by both the config wizard + * and session startup to load keys from auth.json into environment variables. + */ +export const TOOL_KEYS = [ { id: "tavily", env: "TAVILY_API_KEY", label: "Tavily Search", hint: "tavily.com/app/api-keys" }, { id: "brave", env: "BRAVE_API_KEY", label: "Brave Search", hint: "brave.com/search/api" }, { id: "context7", env: "CONTEXT7_API_KEY", label: "Context7 Docs", hint: "context7.com/dashboard" }, @@ -636,6 +641,27 @@ const TOOL_KEYS = [ { id: "groq", env: "GROQ_API_KEY", label: "Groq Voice", hint: "console.groq.com" }, ] as const; +/** + * Load tool API keys from auth.json into environment variables. + * Called at session startup to ensure tools have access to their credentials. + */ +export function loadToolApiKeys(): void { + try { + const authPath = join(process.env.HOME ?? "", ".gsd", "agent", "auth.json"); + if (!existsSync(authPath)) return; + + const auth = AuthStorage.create(authPath); + for (const tool of TOOL_KEYS) { + const cred = auth.get(tool.id); + if (cred && "key" in cred && cred.key && !process.env[tool.env]) { + process.env[tool.env] = cred.key; + } + } + } catch { + // Failed to load tool keys — ignore, they can still be set via env vars + } +} + function getConfigAuthStorage(): InstanceType { const authPath = join(process.env.HOME ?? "", ".gsd", "agent", "auth.json"); mkdirSync(dirname(authPath), { recursive: true }); diff --git a/src/resources/extensions/gsd/index.ts b/src/resources/extensions/gsd/index.ts index cae32ff21..23f6b42b6 100644 --- a/src/resources/extensions/gsd/index.ts +++ b/src/resources/extensions/gsd/index.ts @@ -25,7 +25,7 @@ import type { } from "@gsd/pi-coding-agent"; import { createBashTool, createWriteTool, createReadTool, createEditTool, isToolCallEventType } from "@gsd/pi-coding-agent"; -import { registerGSDCommand } from "./commands.js"; +import { registerGSDCommand, loadToolApiKeys } from "./commands.js"; import { registerExitCommand } from "./exit-command.js"; import { registerWorktreeCommand, getWorktreeOriginalCwd, getActiveWorktreeName } from "./worktree-command.js"; import { saveFile, formatContinue, loadFile, parseContinue, parseSummary } from "./files.js"; @@ -188,7 +188,7 @@ export default function (pi: ExtensionAPI) { }; pi.registerTool(dynamicEdit as any); - // ── session_start: render branded GSD header + remote channel status ── + // ── session_start: render branded GSD header + load tool keys + remote status ── pi.on("session_start", async (_event, ctx) => { // Theme access throws in RPC mode (no TUI) — header is decorative, skip it try { @@ -204,6 +204,9 @@ export default function (pi: ExtensionAPI) { // RPC mode — no TUI, skip header rendering } + // Load tool API keys from auth.json into environment + loadToolApiKeys(); + // Notify remote questions status if configured try { const [{ getRemoteConfigStatus }, { getLatestPromptSummary }] = await Promise.all([