From 5875e7b081330ab2cb6875f3a79c17b2da804f81 Mon Sep 17 00:00:00 2001 From: Greg Methvin Date: Thu, 20 Nov 2025 12:27:39 -0800 Subject: [PATCH] Don't sync env vars when updating keys --- src/install.ts | 43 +------ src/keys-cli.ts | 117 +----------------- .../unit/env-permissions-enforcement.test.ts | 18 --- .../install-existing-key-env-sync.test.ts | 35 ------ 4 files changed, 6 insertions(+), 207 deletions(-) delete mode 100644 tests/unit/install-existing-key-env-sync.test.ts diff --git a/src/install.ts b/src/install.ts index f733281..8966609 100644 --- a/src/install.ts +++ b/src/install.ts @@ -126,34 +126,6 @@ export const buildMcpConfig = (options: { }; }; -/** - * Merge selected env flags with key metadata env (if provided), preferring the key's - * persisted values. This ensures the written MCP configs reflect the authoritative - * values saved to the key after any updates in the setup flow. - */ -export function resolveFinalMcpEnv( - selectedEnv: Record, - keyEnv?: Record -): Record { - const normalize = (v?: string) => (v === "true" ? "true" : "false"); - const result = { ...selectedEnv } as Record; - if (keyEnv) { - result.ITERABLE_USER_PII = normalize( - keyEnv.ITERABLE_USER_PII ?? result.ITERABLE_USER_PII - ); - result.ITERABLE_ENABLE_WRITES = normalize( - keyEnv.ITERABLE_ENABLE_WRITES ?? result.ITERABLE_ENABLE_WRITES - ); - result.ITERABLE_ENABLE_SENDS = normalize( - keyEnv.ITERABLE_ENABLE_SENDS ?? result.ITERABLE_ENABLE_SENDS - ); - } - result.ITERABLE_USER_PII = normalize(result.ITERABLE_USER_PII); - result.ITERABLE_ENABLE_WRITES = normalize(result.ITERABLE_ENABLE_WRITES); - result.ITERABLE_ENABLE_SENDS = normalize(result.ITERABLE_ENABLE_SENDS); - return result; -} - /** * Pick only permission-related env flags for persistence into key metadata. */ @@ -795,24 +767,13 @@ export const setupMcpServer = async (): Promise => { } console.log(); - // If we used an existing key, update its env overrides with chosen settings + // If we used an existing key, persist the chosen settings to it if (usedKeyName) { try { await keyManager.updateKeyEnv( usedKeyName, pickPersistablePermissionEnv(mcpEnv) ); - - // Re-read the key metadata and prefer its persisted env when writing - // tool configurations. This addresses cases where the key was activated - // during setup and ensures configs reflect the updated values. - const keys = await keyManager.listKeys(); - const updatedMeta = keys.find( - (k) => k.name === usedKeyName || k.id === usedKeyName - ); - if (updatedMeta?.env) { - mcpEnv = resolveFinalMcpEnv(mcpEnv, updatedMeta.env); - } } catch (err) { if (process.env.ITERABLE_DEBUG === "true") { console.warn( @@ -822,8 +783,6 @@ export const setupMcpServer = async (): Promise => { } } } - // Enforce again after merging persisted key env - mcpEnv = enforceSendsRequiresWrites(mcpEnv, (msg) => showWarning(msg)); // Step 5: Configure AI Tools console.log(); diff --git a/src/keys-cli.ts b/src/keys-cli.ts index 5280cc5..80833e0 100644 --- a/src/keys-cli.ts +++ b/src/keys-cli.ts @@ -3,13 +3,10 @@ * CLI commands for API key management with beautiful modern UI */ -import { execFile, spawn } from "child_process"; -import { promises as fs, readFileSync } from "fs"; +import { readFileSync } from "fs"; import inquirer from "inquirer"; -import os from "os"; import path from "path"; import { fileURLToPath } from "url"; -import { promisify } from "util"; const { dirname, join } = path; @@ -17,8 +14,6 @@ import { getSpinner, loadUi } from "./utils/cli-env.js"; import { getKeyStorageMessage } from "./utils/formatting.js"; import { promptForApiKey } from "./utils/password-prompt.js"; -const execFileAsync = promisify(execFile); - // Get package version const packageJson = JSON.parse( readFileSync( @@ -415,108 +410,7 @@ export async function handleKeysCommand(): Promise { console.log(formatKeyValue("User PII", pii)); console.log(formatKeyValue("Writes", writes)); console.log(formatKeyValue("Sends", sends)); - - // Sync configured AI tool JSON files to reflect the active key's flags - try { - const { - resolveFinalMcpEnv, - enforceSendsRequiresWrites, - buildMcpConfig, - } = await import("./install.js"); - let mcpEnv = resolveFinalMcpEnv( - { - ITERABLE_USER_PII: "false", - ITERABLE_ENABLE_WRITES: "false", - ITERABLE_ENABLE_SENDS: "false", - }, - meta.env as Record | undefined - ); - mcpEnv = enforceSendsRequiresWrites(mcpEnv); - - // Determine file-based tool config locations (Cursor, Claude Desktop) - const cursorPath = path.join(os.homedir(), ".cursor", "mcp.json"); - // macOS-only path (we already guard the command to run only on darwin) - const claudeDesktopPath = path.join( - os.homedir(), - "Library", - "Application Support", - "Claude", - "claude_desktop_config.json" - ); - - const targets = [ - { name: "Cursor", file: cursorPath }, - { name: "Claude Desktop", file: claudeDesktopPath }, - ]; - - const { updateToolConfig } = await import("./utils/tool-config.js"); - for (const t of targets) { - try { - const raw = await fs.readFile(t.file, "utf8").catch(() => ""); - if (!raw) continue; - const existing = JSON.parse(raw || "{}"); - if (!existing?.mcpServers?.iterable) continue; - - const iterableMcpConfig = buildMcpConfig({ - env: { - ...(existing.mcpServers.iterable.env || {}), - ...mcpEnv, - }, - }); - await updateToolConfig(t.file, iterableMcpConfig); - showSuccess( - `${t.name} configuration synced to active key permissions` - ); - } catch { - // Non-fatal: skip if cannot read/parse/write - } - } - - // Update Claude Code CLI registry if available - try { - await execFileAsync("claude", ["--version"]); - - // Build config using existing helper (keeps local/npx logic consistent) - const iterableMcpConfig = buildMcpConfig({ env: mcpEnv }); - const configJson = JSON.stringify(iterableMcpConfig); - - // Remove existing registration (ignore errors) - await execFileAsync("claude", [ - "mcp", - "remove", - "iterable", - ]).catch(() => {}); - - // Add new registration with inherited stdio to show Claude CLI output - await new Promise((resolve, reject) => { - const child = spawn( - "claude", - ["mcp", "add-json", "iterable", configJson], - { - stdio: "inherit", - } - ); - child.on("close", (code) => { - if (code === 0) resolve(); - else - reject( - new Error( - `claude mcp add-json exited with code ${code ?? "unknown"}` - ) - ); - }); - child.on("error", reject); - }); - - showSuccess( - "Claude Code configuration synced to active key permissions" - ); - } catch { - // If Claude CLI not installed or update fails, skip silently - } - } catch { - // Non-fatal: if syncing fails, continue - } + console.log(); } else { console.log(); showSuccess(`"${idOrName}" is now your active API key`); @@ -527,10 +421,9 @@ export async function handleKeysCommand(): Promise { [ chalk.yellow("Restart your AI tools to use this key"), "", - chalk.gray("The new key will be used after restarting:"), - chalk.white(" • Cursor"), - chalk.white(" • Claude Desktop"), - chalk.white(" • Claude Code"), + chalk.gray( + "The MCP server will automatically load the active key when it starts" + ), ], { icon: icons.zap, theme: "warning" } ); diff --git a/tests/unit/env-permissions-enforcement.test.ts b/tests/unit/env-permissions-enforcement.test.ts index 631e5cf..2ba8877 100644 --- a/tests/unit/env-permissions-enforcement.test.ts +++ b/tests/unit/env-permissions-enforcement.test.ts @@ -3,7 +3,6 @@ import { describe, expect, it, jest } from "@jest/globals"; import { enforceSendsRequiresWrites, pickPersistablePermissionEnv, - resolveFinalMcpEnv, } from "../../src/install.js"; describe("permission env enforcement and filtering", () => { @@ -46,21 +45,4 @@ describe("permission env enforcement and filtering", () => { expect(persisted.ITERABLE_USER_PII).toBe("false"); // normalized expect((persisted as any).ITERABLE_DEBUG).toBeUndefined(); }); - - it("resolveFinalMcpEnv normalizes and prefers key env values", () => { - const selected = { - ITERABLE_USER_PII: "false", - ITERABLE_ENABLE_WRITES: "false", - ITERABLE_ENABLE_SENDS: "false", - } as Record; - const keyEnv = { - ITERABLE_USER_PII: "true", - ITERABLE_ENABLE_WRITES: "true", - ITERABLE_ENABLE_SENDS: "true", - } as Record; - const finalEnv = resolveFinalMcpEnv(selected, keyEnv); - expect(finalEnv.ITERABLE_USER_PII).toBe("true"); - expect(finalEnv.ITERABLE_ENABLE_WRITES).toBe("true"); - expect(finalEnv.ITERABLE_ENABLE_SENDS).toBe("true"); - }); }); diff --git a/tests/unit/install-existing-key-env-sync.test.ts b/tests/unit/install-existing-key-env-sync.test.ts deleted file mode 100644 index 784b802..0000000 --- a/tests/unit/install-existing-key-env-sync.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { describe, expect, it } from "@jest/globals"; - -import { resolveFinalMcpEnv } from "../../src/install.js"; - -describe("install existing key env sync", () => { - it("prefers key metadata env values over selected env", () => { - const selectedEnv = { - ITERABLE_USER_PII: "false", - ITERABLE_ENABLE_WRITES: "false", - ITERABLE_ENABLE_SENDS: "false", - } as Record; - - const keyEnv = { - ITERABLE_USER_PII: "true", - ITERABLE_ENABLE_WRITES: "true", - ITERABLE_ENABLE_SENDS: "true", - } as Record; - - const finalEnv = resolveFinalMcpEnv(selectedEnv, keyEnv); - expect(finalEnv.ITERABLE_USER_PII).toBe("true"); - expect(finalEnv.ITERABLE_ENABLE_WRITES).toBe("true"); - expect(finalEnv.ITERABLE_ENABLE_SENDS).toBe("true"); - }); - - it("falls back to selected env when key env missing", () => { - const selectedEnv = { - ITERABLE_USER_PII: "false", - ITERABLE_ENABLE_WRITES: "true", - ITERABLE_ENABLE_SENDS: "false", - } as Record; - - const finalEnv = resolveFinalMcpEnv(selectedEnv); - expect(finalEnv).toEqual(selectedEnv); - }); -});