🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@getcordon/cli

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@getcordon/cli - npm Package Compare versions

Comparing version
0.4.0
to
0.4.1
+26
-15
dist/bin/cordon.js

@@ -127,14 +127,19 @@ #!/usr/bin/env node

if (!auth) return config;
const audit = config.audit;
let next = config;
const audit = next.audit;
const usesHosted = audit && (audit.output === "hosted" || Array.isArray(audit.output) && audit.output.includes("hosted"));
if (!usesHosted) return config;
if (audit.endpoint && audit.apiKey) return config;
return {
...config,
audit: {
...audit,
endpoint: audit.endpoint ?? auth.endpoint,
apiKey: audit.apiKey ?? auth.apiKey
}
};
if (usesHosted && !(audit.endpoint && audit.apiKey)) {
next = {
...next,
audit: { ...audit, endpoint: audit.endpoint ?? auth.endpoint, apiKey: audit.apiKey ?? auth.apiKey }
};
}
const approvals = next.approvals;
if (approvals?.channel === "slack" && !(approvals.endpoint && approvals.apiKey)) {
next = {
...next,
approvals: { ...approvals, endpoint: approvals.endpoint ?? auth.endpoint, apiKey: approvals.apiKey ?? auth.apiKey }
};
}
return next;
}

@@ -537,2 +542,11 @@ function applyHttpFlags(config, options) {

},`;
const approvalsBlock = auth ? `approvals: {
// Human-in-the-loop over Slack. Connect your workspace once via
// "Add to Slack" in the dashboard \u2014 endpoint + apiKey are auto-loaded.
channel: 'slack',
// timeoutMs: 60_000,
},` : `approvals: {
channel: 'terminal',
// timeoutMs: 60_000,
},`;
const content = `import { defineConfig } from '@getcordon/policy';

@@ -547,6 +561,3 @@

approvals: {
channel: 'terminal',
// timeoutMs: 60_000,
},
${approvalsBlock}
});

@@ -553,0 +564,0 @@ `;

@@ -1,1 +0,1 @@

{"version":3,"sources":["../../src/bin/cordon.ts","../../src/commands/start.ts","../../src/config-loader.ts","../../src/cli-state.ts","../../src/commands/init.ts","../../src/commands/login.ts","../../src/commands/logout.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { startCommand } from '../commands/start.js';\nimport { initCommand } from '../commands/init.js';\nimport { loginCommand } from '../commands/login.js';\nimport { logoutCommand } from '../commands/logout.js';\n\nconst program = new Command();\n\nprogram\n .name('cordon')\n .description('Cordon for MCP — security gateway for MCP tool calls')\n .version('0.1.0');\n\nprogram\n .command('start')\n .description('Start the Cordon gateway')\n .option('-c, --config <path>', 'Path to cordon.config.ts')\n .option('--http', 'Enable HTTP transport (for n8n and other HTTP-speaking MCP clients). Requires CORDON_GATEWAY_TOKEN env var or gateway.authToken in config.')\n .option('--port <port>', 'Port for HTTP transport (default: 7777)', (v) => Number.parseInt(v, 10))\n .action(startCommand);\n\nprogram\n .command('init')\n .description('Generate cordon.config.ts and patch Claude Desktop config')\n .action(initCommand);\n\nprogram\n .command('login')\n .description('Log in to Cordon (browser OAuth) and save an API key locally')\n .option('--endpoint <url>', 'Cordon server endpoint (defaults to the hosted instance)')\n .action((opts) => loginCommand(opts).catch((err) => {\n process.stderr.write(`\\x1b[31merror\\x1b[0m: login failed: ${String(err)}\\n`);\n process.exit(1);\n }));\n\nprogram\n .command('logout')\n .description('Remove the local Cordon credentials')\n .action(logoutCommand);\n\nprogram.parse();\n","import { CordonGateway } from '@getcordon/core';\nimport type { ResolvedConfig } from '@getcordon/policy';\nimport { emptyConfig, findConfigPath, loadConfig } from '../config-loader.js';\nimport { getState, setState, getAuth } from '../cli-state.js';\n\nconst DASHBOARD_URL = 'https://app.getcordon.com/dashboard/';\n\nfunction applyAuthDefaults(config: ResolvedConfig): ResolvedConfig {\n const auth = getAuth();\n if (!auth) return config;\n\n const audit = config.audit;\n const usesHosted = audit && (audit.output === 'hosted' || (Array.isArray(audit.output) && audit.output.includes('hosted')));\n if (!usesHosted) return config;\n if (audit.endpoint && audit.apiKey) return config;\n\n return {\n ...config,\n audit: {\n ...audit,\n endpoint: audit.endpoint ?? auth.endpoint,\n apiKey: audit.apiKey ?? auth.apiKey,\n },\n };\n}\n\ninterface StartOptions {\n config?: string;\n /** Enable HTTP/Streamable HTTP transport (overrides config.gateway.transport). */\n http?: boolean;\n /** Port for HTTP transport (overrides config.gateway.port). */\n port?: number;\n}\n\n/**\n * Apply CLI flags + env vars on top of the loaded config so the HTTP transport\n * can be enabled without editing the config file. Precedence:\n * - `--http` flag forces HTTP transport regardless of config.\n * - `--port` flag overrides config.gateway.port.\n * - authToken comes from config.gateway.authToken if present, otherwise\n * the `CORDON_GATEWAY_TOKEN` env var.\n * - host comes from config (defaults to localhost in core/transport/http.ts).\n */\nfunction applyHttpFlags(config: ResolvedConfig, options: StartOptions): ResolvedConfig {\n const wantHttp = options.http === true || config.gateway?.transport === 'http';\n if (!wantHttp) {\n if (options.port !== undefined) {\n process.stderr.write(\n '\\x1b[33m[cordon] --port has no effect without --http (or gateway config). Ignoring.\\x1b[0m\\n',\n );\n }\n return config;\n }\n\n const httpConfig = config.gateway?.transport === 'http' ? config.gateway : undefined;\n const authToken = httpConfig?.authToken ?? process.env.CORDON_GATEWAY_TOKEN;\n\n if (!authToken) {\n process.stderr.write(\n '\\x1b[31merror\\x1b[0m: HTTP transport requires an auth token.\\n' +\n ' Set `gateway: { transport: \"http\", authToken: process.env.CORDON_GATEWAY_TOKEN }` in\\n' +\n ' cordon.config.ts, or export `CORDON_GATEWAY_TOKEN` before running `cordon start --http`.\\n',\n );\n process.exit(1);\n }\n\n return {\n ...config,\n gateway: {\n transport: 'http',\n authToken,\n ...(options.port !== undefined ? { port: options.port } : httpConfig?.port !== undefined ? { port: httpConfig.port } : {}),\n ...(httpConfig?.host !== undefined ? { host: httpConfig.host } : {}),\n },\n };\n}\n\nexport async function startCommand(options: StartOptions): Promise<void> {\n if (!getState().welcomed && !getAuth()) {\n process.stderr.write(\n `\\n\\x1b[36m[cordon] Want centralized audit logs + Slack approvals?\\x1b[0m\\n` +\n `[cordon] Run \\`cordon login\\` or register at ${DASHBOARD_URL}?utm_source=cli_start\\n\\n`,\n );\n setState({ welcomed: true });\n }\n\n // Default to an empty server list so auto-install probes (Glama, MCP\n // registry, fresh `npx -y @getcordon/cli start`) succeed instead of crashing.\n // Real users get a loud stderr warning so they don't silently run a no-op.\n let config: ResolvedConfig = emptyConfig();\n let configPath: string | null = null;\n\n try {\n configPath = await findConfigPath(options.config);\n } catch (err) {\n if (options.config) {\n process.stderr.write(`\\x1b[31merror\\x1b[0m: ${String(err)}\\n`);\n process.exit(1);\n }\n process.stderr.write(\n '\\x1b[33m[cordon] no cordon.config.ts found — starting with zero upstream servers.\\x1b[0m\\n' +\n '[cordon] Run `cordon init` to generate a config and connect real MCP servers.\\n',\n );\n }\n\n if (configPath) {\n process.stderr.write(`[cordon] loading config from ${configPath}\\n`);\n try {\n config = await loadConfig(configPath);\n } catch (err) {\n process.stderr.write(`\\x1b[31merror\\x1b[0m: ${String(err)}\\n`);\n process.exit(1);\n }\n }\n\n config = applyAuthDefaults(config);\n config = applyHttpFlags(config, options);\n\n const gateway = new CordonGateway(config);\n\n const shutdown = async () => {\n process.stderr.write('\\n[cordon] shutting down...\\n');\n try {\n await gateway.stop();\n process.exit(0);\n } catch (err) {\n process.stderr.write(`\\x1b[31merror\\x1b[0m: shutdown failed: ${String(err)}\\n`);\n process.exit(1);\n }\n };\n\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n\n try {\n await gateway.start();\n } catch (err) {\n process.stderr.write(`\\x1b[31merror\\x1b[0m: gateway failed: ${String(err)}\\n`);\n process.exit(1);\n }\n}\n","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { createJiti } from 'jiti';\nimport type { CordonConfig, ResolvedConfig } from '@getcordon/policy';\n\nconst SEARCH_PATHS = [\n () => join(process.cwd(), 'cordon.config.ts'),\n () => join(process.cwd(), 'cordon.config.js'),\n () => join(homedir(), '.cordon', 'config.ts'),\n () => join(homedir(), '.cordon', 'config.js'),\n];\n\nexport async function findConfigPath(explicit?: string): Promise<string> {\n if (explicit) {\n if (!existsSync(explicit)) {\n throw new Error(`Config file not found: ${explicit}`);\n }\n return explicit;\n }\n\n for (const candidate of SEARCH_PATHS) {\n const p = candidate();\n if (existsSync(p)) return p;\n }\n\n throw new Error(\n 'No cordon config found. Run `cordon init` to create one, or pass --config <path>.',\n );\n}\n\nexport async function loadConfig(configPath: string): Promise<ResolvedConfig> {\n // jiti handles TypeScript config files without requiring a separate compile step\n const jiti = createJiti(import.meta.url);\n const mod = await jiti.import(configPath);\n\n const raw = (mod as { default?: CordonConfig }).default ?? (mod as CordonConfig);\n\n if (!raw || typeof raw !== 'object' || !Array.isArray(raw.servers)) {\n throw new Error(\n `Invalid config at ${configPath}: expected an object with a 'servers' array. ` +\n `Make sure you are using \\`export default defineConfig({...})\\`.`,\n );\n }\n\n return applyDefaults(raw as CordonConfig);\n}\n\nfunction applyDefaults(config: CordonConfig): ResolvedConfig {\n return {\n ...config,\n audit: config.audit ?? { enabled: true, output: 'stdout' },\n approvals: config.approvals ?? { channel: 'terminal' },\n };\n}\n\nexport function emptyConfig(): ResolvedConfig {\n return applyDefaults({ servers: [] });\n}\n","import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync, unlinkSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\n\ninterface CliState {\n welcomed?: boolean;\n}\n\nexport interface CliAuth {\n endpoint: string;\n apiKey: string;\n userLogin?: string;\n loggedInAt: string;\n}\n\nfunction cordonDir(): string {\n return join(homedir(), '.cordon');\n}\n\nfunction statePath(): string {\n return join(cordonDir(), 'state.json');\n}\n\nfunction authPath(): string {\n return join(cordonDir(), 'auth.json');\n}\n\nfunction ensureCordonDir(): void {\n const dir = cordonDir();\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n}\n\nexport function getState(): CliState {\n const path = statePath();\n if (!existsSync(path)) return {};\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as CliState;\n } catch {\n return {};\n }\n}\n\nexport function setState(patch: CliState): void {\n try {\n ensureCordonDir();\n const merged = { ...getState(), ...patch };\n writeFileSync(statePath(), JSON.stringify(merged, null, 2), 'utf8');\n } catch {\n // Non-fatal: missing state just means we re-show the banner next time.\n }\n}\n\nexport function getAuth(): CliAuth | null {\n const path = authPath();\n if (!existsSync(path)) return null;\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as CliAuth;\n } catch {\n return null;\n }\n}\n\nexport function setAuth(auth: CliAuth): void {\n ensureCordonDir();\n const path = authPath();\n writeFileSync(path, JSON.stringify(auth, null, 2), 'utf8');\n if (process.platform !== 'win32') {\n try { chmodSync(path, 0o600); } catch { /* best-effort */ }\n }\n}\n\nexport function clearAuth(): boolean {\n const path = authPath();\n if (!existsSync(path)) return false;\n try {\n unlinkSync(path);\n return true;\n } catch {\n return false;\n }\n}\n","import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { spawnSync } from 'node:child_process';\nimport { createInterface } from 'node:readline';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { setState, getAuth } from '../cli-state.js';\nimport { loginCommand } from './login.js';\n\nconst DASHBOARD_URL = 'https://app.getcordon.com/dashboard/';\n\nfunction ensureCordonSdkInstalled(cwd: string): void {\n // Config imports from '@getcordon/policy', and jiti resolves it from the config\n // file's directory. Without a local install, `cordon start` dies with\n // \"Cannot find module '@getcordon/policy'\" even when the CLI is global.\n if (existsSync(join(cwd, 'node_modules', '@getcordon/policy', 'package.json'))) {\n return;\n }\n\n if (!existsSync(join(cwd, 'package.json'))) {\n writeFileSync(\n join(cwd, 'package.json'),\n JSON.stringify({ name: 'cordon-config', version: '0.0.0', private: true }, null, 2) + '\\n',\n 'utf8',\n );\n process.stderr.write(`\\x1b[32m✓\\x1b[0m created package.json\\n`);\n }\n\n process.stderr.write(`[cordon] installing @getcordon/policy...\\n`);\n const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';\n const result = spawnSync(npmCmd, ['install', '@getcordon/policy'], {\n cwd,\n stdio: ['ignore', 'inherit', 'inherit'],\n });\n\n if (result.status === 0) {\n process.stderr.write(`\\x1b[32m✓\\x1b[0m installed @getcordon/policy\\n`);\n } else {\n process.stderr.write(\n `\\x1b[33mwarn\\x1b[0m: could not auto-install @getcordon/policy. ` +\n `Run 'npm install @getcordon/policy' in this directory before 'cordon start'.\\n`,\n );\n }\n}\n\nasync function promptYesNo(question: string): Promise<boolean> {\n // Non-interactive (CI, piped stdin): skip rather than surprise the operator\n // with a browser tab and a 5-minute callback timeout.\n if (!process.stdin.isTTY) return false;\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n rl.close();\n const trimmed = answer.trim().toLowerCase();\n // Empty = default Yes\n resolve(trimmed === '' || trimmed === 'y' || trimmed === 'yes');\n });\n });\n}\n\ninterface ClaudeDesktopConfig {\n mcpServers?: Record<string, { command: string; args?: string[]; env?: Record<string, string> }>;\n}\n\nfunction getClaudeConfigPath(): string | null {\n const candidates: string[] = [];\n\n if (process.platform === 'darwin') {\n candidates.push(\n join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'),\n );\n } else if (process.platform === 'win32') {\n const appData = process.env['APPDATA'] ?? join(homedir(), 'AppData', 'Roaming');\n candidates.push(join(appData, 'Claude', 'claude_desktop_config.json'));\n } else {\n candidates.push(join(homedir(), '.config', 'Claude', 'claude_desktop_config.json'));\n }\n\n return candidates.find((p) => existsSync(p)) ?? null;\n}\n\nexport async function initCommand(): Promise<void> {\n const outputPath = join(process.cwd(), 'cordon.config.ts');\n\n if (existsSync(outputPath)) {\n process.stderr.write(\n `\\x1b[33mwarn\\x1b[0m: cordon.config.ts already exists — not overwriting.\\n`,\n );\n process.exit(1);\n }\n\n // Try to discover existing MCP servers from Claude Desktop config\n const claudePath = getClaudeConfigPath();\n let claudeConfig: ClaudeDesktopConfig = {};\n\n if (claudePath) {\n process.stderr.write(`[cordon] found Claude Desktop config at ${claudePath}\\n`);\n try {\n claudeConfig = JSON.parse(readFileSync(claudePath, 'utf8')) as ClaudeDesktopConfig;\n } catch {\n process.stderr.write(`\\x1b[33mwarn\\x1b[0m: could not parse Claude Desktop config\\n`);\n }\n } else {\n process.stderr.write(\n `[cordon] no Claude Desktop config found — generating a blank config\\n`,\n );\n }\n\n const servers = Object.entries(claudeConfig.mcpServers ?? {});\n\n // Generate cordon.config.ts\n const serverBlocks = servers.length > 0\n ? servers\n .map(([name, cfg]) => {\n const argsStr = cfg.args?.length\n ? `, args: ${JSON.stringify(cfg.args)}`\n : '';\n const envStr =\n cfg.env && Object.keys(cfg.env).length\n ? `, env: ${JSON.stringify(cfg.env)}`\n : '';\n return ` {\n name: ${JSON.stringify(name)},\n transport: 'stdio',\n command: ${JSON.stringify(cfg.command)}${argsStr}${envStr},\n policy: 'allow',\n // tools: {\n // execute: 'approve',\n // delete: 'block',\n // },\n },`;\n })\n .join('\\n')\n : ` // {\n // name: 'my-server',\n // transport: 'stdio',\n // command: 'npx',\n // args: ['-y', '@my-org/my-mcp-server'],\n // policy: 'allow',\n // },`;\n\n let auth = getAuth();\n let promptShown = false;\n if (!auth && process.stdin.isTTY) {\n promptShown = true;\n process.stderr.write(\n `\\n\\x1b[36mCordon works locally out of the box.\\x1b[0m ` +\n `Sign in to enable hosted audit logs + Slack approvals (free).\\n`,\n );\n const wantsLogin = await promptYesNo('Sign in via browser now? [Y/n] ');\n if (wantsLogin) {\n try {\n await loginCommand();\n auth = getAuth();\n } catch (err) {\n const msg = err instanceof Error ? err.message : 'unknown error';\n process.stderr.write(\n `\\x1b[33mwarn\\x1b[0m: login didn't complete (${msg}). ` +\n `Continuing with local stdout audit. Run \\x1b[36mcordon login\\x1b[0m later.\\n`,\n );\n }\n }\n }\n\n const auditBlock = auth\n ? `audit: {\n enabled: true,\n output: 'hosted',\n // endpoint + apiKey are auto-loaded from ~/.cordon/auth.json (cordon login)\n },`\n : `audit: {\n enabled: true,\n // 'auto' streams to stdout until you run \\`cordon login\\`, then auto-switches to hosted.\n output: 'auto',\n },`;\n\n const content = `import { defineConfig } from '@getcordon/policy';\n\nexport default defineConfig({\n servers: [\n${serverBlocks}\n ],\n\n ${auditBlock}\n\n approvals: {\n channel: 'terminal',\n // timeoutMs: 60_000,\n },\n});\n`;\n\n writeFileSync(outputPath, content, 'utf8');\n process.stderr.write(`\\x1b[32m✓\\x1b[0m wrote cordon.config.ts\\n`);\n\n // The config imports from '@getcordon/policy'. Install it locally so jiti can\n // resolve it when `cordon start` runs.\n ensureCordonSdkInstalled(process.cwd());\n\n // Patch Claude Desktop config to route through cordon.\n //\n // We bind the spawn to the *exact* node + cordon.js paths that ran this\n // init. Earlier versions wrote `npx.cmd` (or `npx`), which relies on\n // PATH lookup at spawn time. Claude Desktop's spawned subprocesses\n // inherit a stripped PATH on Windows that often lacks the user's\n // nvm/Node bin dir, so `npx.cmd` fails with \"is not recognized as an\n // internal or external command\". Using full paths skips PATH and the\n // cmd.exe wrapper entirely.\n if (claudePath && servers.length > 0) {\n const cordonConfigPath = outputPath.replace(/\\\\/g, '/');\n const nodePath = process.execPath.replace(/\\\\/g, '/');\n const cordonScriptPath = (process.argv[1] ?? '').replace(/\\\\/g, '/');\n\n if (!cordonScriptPath) {\n process.stderr.write(\n `\\x1b[33mwarn\\x1b[0m: could not detect cordon CLI script path. ` +\n `Skipping Claude Desktop patch.\\n` +\n `Manually add this to your claude_desktop_config.json mcpServers:\\n` +\n ` \"cordon\": { \"command\": \"${nodePath}\", \"args\": [\"<path-to-cordon.js>\", \"start\", \"--config\", \"${cordonConfigPath}\"] }\\n`,\n );\n return;\n }\n\n const newClaudeConfig: ClaudeDesktopConfig = {\n ...claudeConfig,\n mcpServers: {\n cordon: {\n command: nodePath,\n args: [cordonScriptPath, 'start', '--config', cordonConfigPath],\n },\n },\n };\n\n // Backup the original — only if no backup exists yet, so re-running init\n // never overwrites the user's true pre-cordon config.\n const backupPath = `${claudePath}.cordon-backup`;\n if (existsSync(backupPath)) {\n process.stderr.write(\n `[cordon] existing backup at ${backupPath} preserved (won't overwrite)\\n`,\n );\n } else {\n writeFileSync(backupPath, readFileSync(claudePath, 'utf8'), 'utf8');\n process.stderr.write(`\\x1b[32m✓\\x1b[0m backed up Claude Desktop config to ${backupPath}\\n`);\n }\n\n writeFileSync(claudePath, JSON.stringify(newClaudeConfig, null, 2), 'utf8');\n process.stderr.write(`\\x1b[32m✓\\x1b[0m patched Claude Desktop config\\n`);\n process.stderr.write(\n `\\n\\x1b[36mRestart Claude Desktop to activate Cordon.\\x1b[0m\\n`,\n );\n } else if (!claudePath) {\n const nodePath = process.execPath.replace(/\\\\/g, '/');\n const cordonScriptPath = (process.argv[1] ?? '<path-to-cordon.js>').replace(/\\\\/g, '/');\n process.stderr.write(\n `\\n\\x1b[33mwarn\\x1b[0m: Claude Desktop config not found on this system.\\n` +\n `Edit cordon.config.ts, then manually add Cordon to your MCP client config:\\n\\n` +\n ` \"mcpServers\": {\\n` +\n ` \"cordon\": {\\n` +\n ` \"command\": \"${nodePath}\",\\n` +\n ` \"args\": [\"${cordonScriptPath}\", \"start\", \"--config\", \"${outputPath.replace(/\\\\/g, '/')}\"]\\n` +\n ` }\\n` +\n ` }\\n`,\n );\n } else {\n // claudePath found but no existing servers — config written, no patching needed\n process.stderr.write(\n `\\nEdit cordon.config.ts to add your MCP servers, then run \\x1b[36mnpx cordon start\\x1b[0m.\\n`,\n );\n }\n\n if (auth) {\n process.stderr.write(\n `\\n\\x1b[32m✓\\x1b[0m audit logs will stream to your Cordon account (${auth.endpoint})\\n`,\n );\n } else if (promptShown) {\n process.stderr.write(\n `\\n[cordon] running in local mode. Run \\x1b[36mcordon login\\x1b[0m later to enable hosted audit.\\n`,\n );\n } else {\n process.stderr.write(\n `\\n\\x1b[36mWant centralized audit logs + Slack approvals?\\x1b[0m\\n` +\n `Run \\x1b[36mcordon login\\x1b[0m to register a free account, ` +\n `or sign up at ${DASHBOARD_URL}?utm_source=cli_init\\n`,\n );\n }\n setState({ welcomed: true });\n}\n","import { createServer } from 'node:http';\nimport { spawn } from 'node:child_process';\nimport { randomBytes } from 'node:crypto';\nimport { setAuth } from '../cli-state.js';\n\nconst DEFAULT_ENDPOINT = 'https://app.getcordon.com';\nconst PORT_RANGE = [53247, 53249, 53251] as const;\nconst TIMEOUT_MS = 5 * 60 * 1000;\n\ninterface LoginOptions {\n endpoint?: string;\n}\n\nfunction openBrowser(url: string): void {\n const cmd = process.platform === 'darwin' ? 'open'\n : process.platform === 'win32' ? 'cmd'\n : 'xdg-open';\n // On Windows, `cmd /c start \"\" <url>` runs through cmd.exe, which treats\n // `&` and `^` as shell metacharacters. Without escaping them, query strings\n // like `?callback=…&state=…` get truncated at the first `&`, breaking the\n // OAuth state hand-off. Replace `^` first so we don't double-escape new ^&.\n const safeUrl = process.platform === 'win32'\n ? url.replace(/\\^/g, '^^').replace(/&/g, '^&')\n : url;\n const args = process.platform === 'win32' ? ['/c', 'start', '\"\"', safeUrl] : [safeUrl];\n try {\n spawn(cmd, args, { detached: true, stdio: 'ignore' }).unref();\n } catch { /* swallow — caller has already printed the URL for manual paste */ }\n}\n\ninterface CallbackResult {\n token: string;\n state: string;\n signup: string;\n}\n\nfunction renderCallbackPage(kind: 'success' | 'error', heading: string, body: string): string {\n const accent = kind === 'success' ? '#14b8a6' : '#f87171';\n const accentBg = kind === 'success' ? 'rgba(20, 184, 166, 0.15)' : 'rgba(248, 113, 113, 0.15)';\n const mark = kind === 'success' ? '✓' : '✗';\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${kind === 'success' ? 'Logged in' : 'Login failed'} — Cordon for MCP</title>\n <link rel=\"icon\" type=\"image/svg+xml\" href=\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90' fill='%2314b8a6'>◈</text></svg>\">\n <style>\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n background: #0d0d0d; color: #e8e8e8;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 1rem;\n }\n .card {\n background: #161616; border: 1px solid #2a2a2a; border-radius: 16px;\n padding: 48px; max-width: 420px; width: 100%; text-align: center;\n }\n .brand { display: flex; align-items: center; justify-content: center; gap: 10px; margin-bottom: 1.75rem; }\n .icon { font-size: 1.4rem; color: #14b8a6; }\n .name { font-weight: 600; font-size: 16px; letter-spacing: -0.3px; white-space: nowrap; }\n .mark {\n width: 56px; height: 56px; border-radius: 50%;\n background: ${accentBg}; color: ${accent};\n display: flex; align-items: center; justify-content: center;\n margin: 0 auto 1.25rem; font-size: 28px; font-weight: 700;\n }\n h1 { font-size: 1.4rem; font-weight: 700; letter-spacing: -0.02em; margin-bottom: 0.6rem; }\n p { color: #888; font-size: 0.95rem; line-height: 1.5; }\n p + p { margin-top: 0.5rem; }\n code {\n background: rgba(255, 255, 255, 0.06); color: #e8e8e8;\n padding: 1px 6px; border-radius: 3px; font-size: 0.85em;\n font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;\n }\n .btn {\n display: inline-block;\n background: #14b8a6; color: #000;\n font-weight: 600; padding: 0.65rem 1.25rem;\n border-radius: 6px; text-decoration: none;\n margin-top: 1.25rem; transition: opacity 0.15s;\n }\n .btn:hover { opacity: 0.85; }\n .hint {\n margin-top: 1.75rem; padding-top: 1.5rem;\n border-top: 1px solid #2a2a2a; font-size: 0.85rem; color: #666;\n }\n </style>\n</head>\n<body>\n <div class=\"card\">\n <div class=\"brand\">\n <span class=\"icon\">◈</span>\n <span class=\"name\">Cordon for MCP</span>\n </div>\n <div class=\"mark\">${mark}</div>\n <h1>${heading}</h1>\n ${body}\n </div>\n</body>\n</html>`;\n}\n\nasync function listenForCallback(port: number, expectedState: string, dashboardUrl: string): Promise<CallbackResult> {\n return new Promise((resolve, reject) => {\n const server = createServer((req, res) => {\n const url = new URL(req.url ?? '/', `http://localhost:${port}`);\n if (url.pathname !== '/cb') {\n res.writeHead(404).end('not found');\n return;\n }\n const token = url.searchParams.get('token') ?? '';\n const state = url.searchParams.get('state') ?? '';\n const signup = url.searchParams.get('signup') ?? 'false';\n\n if (state !== expectedState || !token) {\n const body =\n `<p>The login link didn't carry a valid token, or the security state didn't match.</p>` +\n `<p class=\"hint\">Run <code>cordon login</code> again from your terminal. You can close this tab.</p>`;\n res.writeHead(400, { 'Content-Type': 'text/html' })\n .end(renderCallbackPage('error', \"Login didn't complete\", body));\n server.close();\n reject(new Error('Callback state mismatch'));\n return;\n }\n\n const body =\n `<p>Your API key is saved. Return to your terminal to continue,` +\n ` or jump straight to your dashboard.</p>` +\n `<a href=\"${dashboardUrl}\" class=\"btn\">Open dashboard →</a>` +\n `<p class=\"hint\">You can close this tab.</p>`;\n res.writeHead(200, { 'Content-Type': 'text/html' })\n .end(renderCallbackPage('success', \"You're logged in\", body));\n server.close();\n resolve({ token, state, signup });\n });\n\n server.listen(port, '127.0.0.1');\n server.on('error', (err) => reject(err));\n\n setTimeout(() => {\n server.close();\n reject(new Error('Login timed out after 5 minutes'));\n }, TIMEOUT_MS).unref();\n });\n}\n\nexport async function loginCommand(options: LoginOptions = {}): Promise<void> {\n const endpoint = (options.endpoint ?? DEFAULT_ENDPOINT).replace(/\\/$/, '');\n const state = randomBytes(16).toString('hex');\n\n let port: number | null = null;\n let result: CallbackResult | null = null;\n let lastErr: unknown = null;\n\n for (const candidate of PORT_RANGE) {\n try {\n const callback = `http://localhost:${candidate}/cb`;\n const authUrl =\n `${endpoint}/auth/cli/start?callback=${encodeURIComponent(callback)}&state=${state}`;\n\n process.stderr.write(\n `\\n[cordon] opening browser to log in...\\n` +\n `[cordon] if it doesn't open, visit:\\n ${authUrl}\\n\\n`,\n );\n openBrowser(authUrl);\n\n const pending = listenForCallback(candidate, state, `${endpoint}/dashboard/`);\n port = candidate;\n result = await pending;\n break;\n } catch (err) {\n lastErr = err;\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'EADDRINUSE') continue;\n throw err;\n }\n }\n\n if (!result) {\n throw lastErr instanceof Error ? lastErr : new Error('Could not bind a local callback port');\n }\n\n setAuth({\n endpoint,\n apiKey: result.token,\n loggedInAt: new Date().toISOString(),\n });\n\n process.stderr.write(`\\x1b[32m✓\\x1b[0m logged in. API key saved to ~/.cordon/auth.json\\n`);\n if (result.signup === 'true') {\n process.stderr.write(\n `Welcome to Cordon for MCP.\\n` +\n `Dashboard: \\x1b[36m${endpoint}/dashboard/\\x1b[0m\\n` +\n `Run \\x1b[36mcordon init\\x1b[0m next to wire up your MCP servers.\\n`,\n );\n }\n // Suppress unused var warning when port is set but not otherwise used\n void port;\n}\n","import { clearAuth } from '../cli-state.js';\n\nexport function logoutCommand(): void {\n if (clearAuth()) {\n process.stderr.write(`\\x1b[32m✓\\x1b[0m logged out. ~/.cordon/auth.json removed\\n`);\n } else {\n process.stderr.write(`[cordon] not logged in\\n`);\n }\n}\n"],"mappings":"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","names":["existsSync","join","homedir","readFileSync","writeFileSync","existsSync","join","homedir","body","DASHBOARD_URL","existsSync","join","writeFileSync","homedir","readFileSync"]}
{"version":3,"sources":["../../src/bin/cordon.ts","../../src/commands/start.ts","../../src/config-loader.ts","../../src/cli-state.ts","../../src/commands/init.ts","../../src/commands/login.ts","../../src/commands/logout.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { startCommand } from '../commands/start.js';\nimport { initCommand } from '../commands/init.js';\nimport { loginCommand } from '../commands/login.js';\nimport { logoutCommand } from '../commands/logout.js';\n\nconst program = new Command();\n\nprogram\n .name('cordon')\n .description('Cordon for MCP — security gateway for MCP tool calls')\n .version('0.1.0');\n\nprogram\n .command('start')\n .description('Start the Cordon gateway')\n .option('-c, --config <path>', 'Path to cordon.config.ts')\n .option('--http', 'Enable HTTP transport (for n8n and other HTTP-speaking MCP clients). Requires CORDON_GATEWAY_TOKEN env var or gateway.authToken in config.')\n .option('--port <port>', 'Port for HTTP transport (default: 7777)', (v) => Number.parseInt(v, 10))\n .action(startCommand);\n\nprogram\n .command('init')\n .description('Generate cordon.config.ts and patch Claude Desktop config')\n .action(initCommand);\n\nprogram\n .command('login')\n .description('Log in to Cordon (browser OAuth) and save an API key locally')\n .option('--endpoint <url>', 'Cordon server endpoint (defaults to the hosted instance)')\n .action((opts) => loginCommand(opts).catch((err) => {\n process.stderr.write(`\\x1b[31merror\\x1b[0m: login failed: ${String(err)}\\n`);\n process.exit(1);\n }));\n\nprogram\n .command('logout')\n .description('Remove the local Cordon credentials')\n .action(logoutCommand);\n\nprogram.parse();\n","import { CordonGateway } from '@getcordon/core';\nimport type { ResolvedConfig } from '@getcordon/policy';\nimport { emptyConfig, findConfigPath, loadConfig } from '../config-loader.js';\nimport { getState, setState, getAuth } from '../cli-state.js';\n\nconst DASHBOARD_URL = 'https://app.getcordon.com/dashboard/';\n\nfunction applyAuthDefaults(config: ResolvedConfig): ResolvedConfig {\n const auth = getAuth();\n if (!auth) return config;\n\n let next = config;\n\n // Hosted audit: fill endpoint/apiKey from auth.json when not already set.\n const audit = next.audit;\n const usesHosted =\n audit && (audit.output === 'hosted' || (Array.isArray(audit.output) && audit.output.includes('hosted')));\n if (usesHosted && !(audit.endpoint && audit.apiKey)) {\n next = {\n ...next,\n audit: { ...audit, endpoint: audit.endpoint ?? auth.endpoint, apiKey: audit.apiKey ?? auth.apiKey },\n };\n }\n\n // Slack approvals are server-driven — fill endpoint/apiKey so the local side\n // can register + poll without the user hand-writing them into the config.\n const approvals = next.approvals;\n if (approvals?.channel === 'slack' && !(approvals.endpoint && approvals.apiKey)) {\n next = {\n ...next,\n approvals: { ...approvals, endpoint: approvals.endpoint ?? auth.endpoint, apiKey: approvals.apiKey ?? auth.apiKey },\n };\n }\n\n return next;\n}\n\ninterface StartOptions {\n config?: string;\n /** Enable HTTP/Streamable HTTP transport (overrides config.gateway.transport). */\n http?: boolean;\n /** Port for HTTP transport (overrides config.gateway.port). */\n port?: number;\n}\n\n/**\n * Apply CLI flags + env vars on top of the loaded config so the HTTP transport\n * can be enabled without editing the config file. Precedence:\n * - `--http` flag forces HTTP transport regardless of config.\n * - `--port` flag overrides config.gateway.port.\n * - authToken comes from config.gateway.authToken if present, otherwise\n * the `CORDON_GATEWAY_TOKEN` env var.\n * - host comes from config (defaults to localhost in core/transport/http.ts).\n */\nfunction applyHttpFlags(config: ResolvedConfig, options: StartOptions): ResolvedConfig {\n const wantHttp = options.http === true || config.gateway?.transport === 'http';\n if (!wantHttp) {\n if (options.port !== undefined) {\n process.stderr.write(\n '\\x1b[33m[cordon] --port has no effect without --http (or gateway config). Ignoring.\\x1b[0m\\n',\n );\n }\n return config;\n }\n\n const httpConfig = config.gateway?.transport === 'http' ? config.gateway : undefined;\n const authToken = httpConfig?.authToken ?? process.env.CORDON_GATEWAY_TOKEN;\n\n if (!authToken) {\n process.stderr.write(\n '\\x1b[31merror\\x1b[0m: HTTP transport requires an auth token.\\n' +\n ' Set `gateway: { transport: \"http\", authToken: process.env.CORDON_GATEWAY_TOKEN }` in\\n' +\n ' cordon.config.ts, or export `CORDON_GATEWAY_TOKEN` before running `cordon start --http`.\\n',\n );\n process.exit(1);\n }\n\n return {\n ...config,\n gateway: {\n transport: 'http',\n authToken,\n ...(options.port !== undefined ? { port: options.port } : httpConfig?.port !== undefined ? { port: httpConfig.port } : {}),\n ...(httpConfig?.host !== undefined ? { host: httpConfig.host } : {}),\n },\n };\n}\n\nexport async function startCommand(options: StartOptions): Promise<void> {\n if (!getState().welcomed && !getAuth()) {\n process.stderr.write(\n `\\n\\x1b[36m[cordon] Want centralized audit logs + Slack approvals?\\x1b[0m\\n` +\n `[cordon] Run \\`cordon login\\` or register at ${DASHBOARD_URL}?utm_source=cli_start\\n\\n`,\n );\n setState({ welcomed: true });\n }\n\n // Default to an empty server list so auto-install probes (Glama, MCP\n // registry, fresh `npx -y @getcordon/cli start`) succeed instead of crashing.\n // Real users get a loud stderr warning so they don't silently run a no-op.\n let config: ResolvedConfig = emptyConfig();\n let configPath: string | null = null;\n\n try {\n configPath = await findConfigPath(options.config);\n } catch (err) {\n if (options.config) {\n process.stderr.write(`\\x1b[31merror\\x1b[0m: ${String(err)}\\n`);\n process.exit(1);\n }\n process.stderr.write(\n '\\x1b[33m[cordon] no cordon.config.ts found — starting with zero upstream servers.\\x1b[0m\\n' +\n '[cordon] Run `cordon init` to generate a config and connect real MCP servers.\\n',\n );\n }\n\n if (configPath) {\n process.stderr.write(`[cordon] loading config from ${configPath}\\n`);\n try {\n config = await loadConfig(configPath);\n } catch (err) {\n process.stderr.write(`\\x1b[31merror\\x1b[0m: ${String(err)}\\n`);\n process.exit(1);\n }\n }\n\n config = applyAuthDefaults(config);\n config = applyHttpFlags(config, options);\n\n const gateway = new CordonGateway(config);\n\n const shutdown = async () => {\n process.stderr.write('\\n[cordon] shutting down...\\n');\n try {\n await gateway.stop();\n process.exit(0);\n } catch (err) {\n process.stderr.write(`\\x1b[31merror\\x1b[0m: shutdown failed: ${String(err)}\\n`);\n process.exit(1);\n }\n };\n\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n\n try {\n await gateway.start();\n } catch (err) {\n process.stderr.write(`\\x1b[31merror\\x1b[0m: gateway failed: ${String(err)}\\n`);\n process.exit(1);\n }\n}\n","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { createJiti } from 'jiti';\nimport type { CordonConfig, ResolvedConfig } from '@getcordon/policy';\n\nconst SEARCH_PATHS = [\n () => join(process.cwd(), 'cordon.config.ts'),\n () => join(process.cwd(), 'cordon.config.js'),\n () => join(homedir(), '.cordon', 'config.ts'),\n () => join(homedir(), '.cordon', 'config.js'),\n];\n\nexport async function findConfigPath(explicit?: string): Promise<string> {\n if (explicit) {\n if (!existsSync(explicit)) {\n throw new Error(`Config file not found: ${explicit}`);\n }\n return explicit;\n }\n\n for (const candidate of SEARCH_PATHS) {\n const p = candidate();\n if (existsSync(p)) return p;\n }\n\n throw new Error(\n 'No cordon config found. Run `cordon init` to create one, or pass --config <path>.',\n );\n}\n\nexport async function loadConfig(configPath: string): Promise<ResolvedConfig> {\n // jiti handles TypeScript config files without requiring a separate compile step\n const jiti = createJiti(import.meta.url);\n const mod = await jiti.import(configPath);\n\n const raw = (mod as { default?: CordonConfig }).default ?? (mod as CordonConfig);\n\n if (!raw || typeof raw !== 'object' || !Array.isArray(raw.servers)) {\n throw new Error(\n `Invalid config at ${configPath}: expected an object with a 'servers' array. ` +\n `Make sure you are using \\`export default defineConfig({...})\\`.`,\n );\n }\n\n return applyDefaults(raw as CordonConfig);\n}\n\nfunction applyDefaults(config: CordonConfig): ResolvedConfig {\n return {\n ...config,\n audit: config.audit ?? { enabled: true, output: 'stdout' },\n approvals: config.approvals ?? { channel: 'terminal' },\n };\n}\n\nexport function emptyConfig(): ResolvedConfig {\n return applyDefaults({ servers: [] });\n}\n","import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync, unlinkSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\n\ninterface CliState {\n welcomed?: boolean;\n}\n\nexport interface CliAuth {\n endpoint: string;\n apiKey: string;\n userLogin?: string;\n loggedInAt: string;\n}\n\nfunction cordonDir(): string {\n return join(homedir(), '.cordon');\n}\n\nfunction statePath(): string {\n return join(cordonDir(), 'state.json');\n}\n\nfunction authPath(): string {\n return join(cordonDir(), 'auth.json');\n}\n\nfunction ensureCordonDir(): void {\n const dir = cordonDir();\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n}\n\nexport function getState(): CliState {\n const path = statePath();\n if (!existsSync(path)) return {};\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as CliState;\n } catch {\n return {};\n }\n}\n\nexport function setState(patch: CliState): void {\n try {\n ensureCordonDir();\n const merged = { ...getState(), ...patch };\n writeFileSync(statePath(), JSON.stringify(merged, null, 2), 'utf8');\n } catch {\n // Non-fatal: missing state just means we re-show the banner next time.\n }\n}\n\nexport function getAuth(): CliAuth | null {\n const path = authPath();\n if (!existsSync(path)) return null;\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as CliAuth;\n } catch {\n return null;\n }\n}\n\nexport function setAuth(auth: CliAuth): void {\n ensureCordonDir();\n const path = authPath();\n writeFileSync(path, JSON.stringify(auth, null, 2), 'utf8');\n if (process.platform !== 'win32') {\n try { chmodSync(path, 0o600); } catch { /* best-effort */ }\n }\n}\n\nexport function clearAuth(): boolean {\n const path = authPath();\n if (!existsSync(path)) return false;\n try {\n unlinkSync(path);\n return true;\n } catch {\n return false;\n }\n}\n","import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { spawnSync } from 'node:child_process';\nimport { createInterface } from 'node:readline';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { setState, getAuth } from '../cli-state.js';\nimport { loginCommand } from './login.js';\n\nconst DASHBOARD_URL = 'https://app.getcordon.com/dashboard/';\n\nfunction ensureCordonSdkInstalled(cwd: string): void {\n // Config imports from '@getcordon/policy', and jiti resolves it from the config\n // file's directory. Without a local install, `cordon start` dies with\n // \"Cannot find module '@getcordon/policy'\" even when the CLI is global.\n if (existsSync(join(cwd, 'node_modules', '@getcordon/policy', 'package.json'))) {\n return;\n }\n\n if (!existsSync(join(cwd, 'package.json'))) {\n writeFileSync(\n join(cwd, 'package.json'),\n JSON.stringify({ name: 'cordon-config', version: '0.0.0', private: true }, null, 2) + '\\n',\n 'utf8',\n );\n process.stderr.write(`\\x1b[32m✓\\x1b[0m created package.json\\n`);\n }\n\n process.stderr.write(`[cordon] installing @getcordon/policy...\\n`);\n const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';\n const result = spawnSync(npmCmd, ['install', '@getcordon/policy'], {\n cwd,\n stdio: ['ignore', 'inherit', 'inherit'],\n });\n\n if (result.status === 0) {\n process.stderr.write(`\\x1b[32m✓\\x1b[0m installed @getcordon/policy\\n`);\n } else {\n process.stderr.write(\n `\\x1b[33mwarn\\x1b[0m: could not auto-install @getcordon/policy. ` +\n `Run 'npm install @getcordon/policy' in this directory before 'cordon start'.\\n`,\n );\n }\n}\n\nasync function promptYesNo(question: string): Promise<boolean> {\n // Non-interactive (CI, piped stdin): skip rather than surprise the operator\n // with a browser tab and a 5-minute callback timeout.\n if (!process.stdin.isTTY) return false;\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n rl.close();\n const trimmed = answer.trim().toLowerCase();\n // Empty = default Yes\n resolve(trimmed === '' || trimmed === 'y' || trimmed === 'yes');\n });\n });\n}\n\ninterface ClaudeDesktopConfig {\n mcpServers?: Record<string, { command: string; args?: string[]; env?: Record<string, string> }>;\n}\n\nfunction getClaudeConfigPath(): string | null {\n const candidates: string[] = [];\n\n if (process.platform === 'darwin') {\n candidates.push(\n join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'),\n );\n } else if (process.platform === 'win32') {\n const appData = process.env['APPDATA'] ?? join(homedir(), 'AppData', 'Roaming');\n candidates.push(join(appData, 'Claude', 'claude_desktop_config.json'));\n } else {\n candidates.push(join(homedir(), '.config', 'Claude', 'claude_desktop_config.json'));\n }\n\n return candidates.find((p) => existsSync(p)) ?? null;\n}\n\nexport async function initCommand(): Promise<void> {\n const outputPath = join(process.cwd(), 'cordon.config.ts');\n\n if (existsSync(outputPath)) {\n process.stderr.write(\n `\\x1b[33mwarn\\x1b[0m: cordon.config.ts already exists — not overwriting.\\n`,\n );\n process.exit(1);\n }\n\n // Try to discover existing MCP servers from Claude Desktop config\n const claudePath = getClaudeConfigPath();\n let claudeConfig: ClaudeDesktopConfig = {};\n\n if (claudePath) {\n process.stderr.write(`[cordon] found Claude Desktop config at ${claudePath}\\n`);\n try {\n claudeConfig = JSON.parse(readFileSync(claudePath, 'utf8')) as ClaudeDesktopConfig;\n } catch {\n process.stderr.write(`\\x1b[33mwarn\\x1b[0m: could not parse Claude Desktop config\\n`);\n }\n } else {\n process.stderr.write(\n `[cordon] no Claude Desktop config found — generating a blank config\\n`,\n );\n }\n\n const servers = Object.entries(claudeConfig.mcpServers ?? {});\n\n // Generate cordon.config.ts\n const serverBlocks = servers.length > 0\n ? servers\n .map(([name, cfg]) => {\n const argsStr = cfg.args?.length\n ? `, args: ${JSON.stringify(cfg.args)}`\n : '';\n const envStr =\n cfg.env && Object.keys(cfg.env).length\n ? `, env: ${JSON.stringify(cfg.env)}`\n : '';\n return ` {\n name: ${JSON.stringify(name)},\n transport: 'stdio',\n command: ${JSON.stringify(cfg.command)}${argsStr}${envStr},\n policy: 'allow',\n // tools: {\n // execute: 'approve',\n // delete: 'block',\n // },\n },`;\n })\n .join('\\n')\n : ` // {\n // name: 'my-server',\n // transport: 'stdio',\n // command: 'npx',\n // args: ['-y', '@my-org/my-mcp-server'],\n // policy: 'allow',\n // },`;\n\n let auth = getAuth();\n let promptShown = false;\n if (!auth && process.stdin.isTTY) {\n promptShown = true;\n process.stderr.write(\n `\\n\\x1b[36mCordon works locally out of the box.\\x1b[0m ` +\n `Sign in to enable hosted audit logs + Slack approvals (free).\\n`,\n );\n const wantsLogin = await promptYesNo('Sign in via browser now? [Y/n] ');\n if (wantsLogin) {\n try {\n await loginCommand();\n auth = getAuth();\n } catch (err) {\n const msg = err instanceof Error ? err.message : 'unknown error';\n process.stderr.write(\n `\\x1b[33mwarn\\x1b[0m: login didn't complete (${msg}). ` +\n `Continuing with local stdout audit. Run \\x1b[36mcordon login\\x1b[0m later.\\n`,\n );\n }\n }\n }\n\n const auditBlock = auth\n ? `audit: {\n enabled: true,\n output: 'hosted',\n // endpoint + apiKey are auto-loaded from ~/.cordon/auth.json (cordon login)\n },`\n : `audit: {\n enabled: true,\n // 'auto' streams to stdout until you run \\`cordon login\\`, then auto-switches to hosted.\n output: 'auto',\n },`;\n\n const approvalsBlock = auth\n ? `approvals: {\n // Human-in-the-loop over Slack. Connect your workspace once via\n // \"Add to Slack\" in the dashboard — endpoint + apiKey are auto-loaded.\n channel: 'slack',\n // timeoutMs: 60_000,\n },`\n : `approvals: {\n channel: 'terminal',\n // timeoutMs: 60_000,\n },`;\n\n const content = `import { defineConfig } from '@getcordon/policy';\n\nexport default defineConfig({\n servers: [\n${serverBlocks}\n ],\n\n ${auditBlock}\n\n ${approvalsBlock}\n});\n`;\n\n writeFileSync(outputPath, content, 'utf8');\n process.stderr.write(`\\x1b[32m✓\\x1b[0m wrote cordon.config.ts\\n`);\n\n // The config imports from '@getcordon/policy'. Install it locally so jiti can\n // resolve it when `cordon start` runs.\n ensureCordonSdkInstalled(process.cwd());\n\n // Patch Claude Desktop config to route through cordon.\n //\n // We bind the spawn to the *exact* node + cordon.js paths that ran this\n // init. Earlier versions wrote `npx.cmd` (or `npx`), which relies on\n // PATH lookup at spawn time. Claude Desktop's spawned subprocesses\n // inherit a stripped PATH on Windows that often lacks the user's\n // nvm/Node bin dir, so `npx.cmd` fails with \"is not recognized as an\n // internal or external command\". Using full paths skips PATH and the\n // cmd.exe wrapper entirely.\n if (claudePath && servers.length > 0) {\n const cordonConfigPath = outputPath.replace(/\\\\/g, '/');\n const nodePath = process.execPath.replace(/\\\\/g, '/');\n const cordonScriptPath = (process.argv[1] ?? '').replace(/\\\\/g, '/');\n\n if (!cordonScriptPath) {\n process.stderr.write(\n `\\x1b[33mwarn\\x1b[0m: could not detect cordon CLI script path. ` +\n `Skipping Claude Desktop patch.\\n` +\n `Manually add this to your claude_desktop_config.json mcpServers:\\n` +\n ` \"cordon\": { \"command\": \"${nodePath}\", \"args\": [\"<path-to-cordon.js>\", \"start\", \"--config\", \"${cordonConfigPath}\"] }\\n`,\n );\n return;\n }\n\n const newClaudeConfig: ClaudeDesktopConfig = {\n ...claudeConfig,\n mcpServers: {\n cordon: {\n command: nodePath,\n args: [cordonScriptPath, 'start', '--config', cordonConfigPath],\n },\n },\n };\n\n // Backup the original — only if no backup exists yet, so re-running init\n // never overwrites the user's true pre-cordon config.\n const backupPath = `${claudePath}.cordon-backup`;\n if (existsSync(backupPath)) {\n process.stderr.write(\n `[cordon] existing backup at ${backupPath} preserved (won't overwrite)\\n`,\n );\n } else {\n writeFileSync(backupPath, readFileSync(claudePath, 'utf8'), 'utf8');\n process.stderr.write(`\\x1b[32m✓\\x1b[0m backed up Claude Desktop config to ${backupPath}\\n`);\n }\n\n writeFileSync(claudePath, JSON.stringify(newClaudeConfig, null, 2), 'utf8');\n process.stderr.write(`\\x1b[32m✓\\x1b[0m patched Claude Desktop config\\n`);\n process.stderr.write(\n `\\n\\x1b[36mRestart Claude Desktop to activate Cordon.\\x1b[0m\\n`,\n );\n } else if (!claudePath) {\n const nodePath = process.execPath.replace(/\\\\/g, '/');\n const cordonScriptPath = (process.argv[1] ?? '<path-to-cordon.js>').replace(/\\\\/g, '/');\n process.stderr.write(\n `\\n\\x1b[33mwarn\\x1b[0m: Claude Desktop config not found on this system.\\n` +\n `Edit cordon.config.ts, then manually add Cordon to your MCP client config:\\n\\n` +\n ` \"mcpServers\": {\\n` +\n ` \"cordon\": {\\n` +\n ` \"command\": \"${nodePath}\",\\n` +\n ` \"args\": [\"${cordonScriptPath}\", \"start\", \"--config\", \"${outputPath.replace(/\\\\/g, '/')}\"]\\n` +\n ` }\\n` +\n ` }\\n`,\n );\n } else {\n // claudePath found but no existing servers — config written, no patching needed\n process.stderr.write(\n `\\nEdit cordon.config.ts to add your MCP servers, then run \\x1b[36mnpx cordon start\\x1b[0m.\\n`,\n );\n }\n\n if (auth) {\n process.stderr.write(\n `\\n\\x1b[32m✓\\x1b[0m audit logs will stream to your Cordon account (${auth.endpoint})\\n`,\n );\n } else if (promptShown) {\n process.stderr.write(\n `\\n[cordon] running in local mode. Run \\x1b[36mcordon login\\x1b[0m later to enable hosted audit.\\n`,\n );\n } else {\n process.stderr.write(\n `\\n\\x1b[36mWant centralized audit logs + Slack approvals?\\x1b[0m\\n` +\n `Run \\x1b[36mcordon login\\x1b[0m to register a free account, ` +\n `or sign up at ${DASHBOARD_URL}?utm_source=cli_init\\n`,\n );\n }\n setState({ welcomed: true });\n}\n","import { createServer } from 'node:http';\nimport { spawn } from 'node:child_process';\nimport { randomBytes } from 'node:crypto';\nimport { setAuth } from '../cli-state.js';\n\nconst DEFAULT_ENDPOINT = 'https://app.getcordon.com';\nconst PORT_RANGE = [53247, 53249, 53251] as const;\nconst TIMEOUT_MS = 5 * 60 * 1000;\n\ninterface LoginOptions {\n endpoint?: string;\n}\n\nfunction openBrowser(url: string): void {\n const cmd = process.platform === 'darwin' ? 'open'\n : process.platform === 'win32' ? 'cmd'\n : 'xdg-open';\n // On Windows, `cmd /c start \"\" <url>` runs through cmd.exe, which treats\n // `&` and `^` as shell metacharacters. Without escaping them, query strings\n // like `?callback=…&state=…` get truncated at the first `&`, breaking the\n // OAuth state hand-off. Replace `^` first so we don't double-escape new ^&.\n const safeUrl = process.platform === 'win32'\n ? url.replace(/\\^/g, '^^').replace(/&/g, '^&')\n : url;\n const args = process.platform === 'win32' ? ['/c', 'start', '\"\"', safeUrl] : [safeUrl];\n try {\n spawn(cmd, args, { detached: true, stdio: 'ignore' }).unref();\n } catch { /* swallow — caller has already printed the URL for manual paste */ }\n}\n\ninterface CallbackResult {\n token: string;\n state: string;\n signup: string;\n}\n\nfunction renderCallbackPage(kind: 'success' | 'error', heading: string, body: string): string {\n const accent = kind === 'success' ? '#14b8a6' : '#f87171';\n const accentBg = kind === 'success' ? 'rgba(20, 184, 166, 0.15)' : 'rgba(248, 113, 113, 0.15)';\n const mark = kind === 'success' ? '✓' : '✗';\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${kind === 'success' ? 'Logged in' : 'Login failed'} — Cordon for MCP</title>\n <link rel=\"icon\" type=\"image/svg+xml\" href=\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90' fill='%2314b8a6'>◈</text></svg>\">\n <style>\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n background: #0d0d0d; color: #e8e8e8;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 1rem;\n }\n .card {\n background: #161616; border: 1px solid #2a2a2a; border-radius: 16px;\n padding: 48px; max-width: 420px; width: 100%; text-align: center;\n }\n .brand { display: flex; align-items: center; justify-content: center; gap: 10px; margin-bottom: 1.75rem; }\n .icon { font-size: 1.4rem; color: #14b8a6; }\n .name { font-weight: 600; font-size: 16px; letter-spacing: -0.3px; white-space: nowrap; }\n .mark {\n width: 56px; height: 56px; border-radius: 50%;\n background: ${accentBg}; color: ${accent};\n display: flex; align-items: center; justify-content: center;\n margin: 0 auto 1.25rem; font-size: 28px; font-weight: 700;\n }\n h1 { font-size: 1.4rem; font-weight: 700; letter-spacing: -0.02em; margin-bottom: 0.6rem; }\n p { color: #888; font-size: 0.95rem; line-height: 1.5; }\n p + p { margin-top: 0.5rem; }\n code {\n background: rgba(255, 255, 255, 0.06); color: #e8e8e8;\n padding: 1px 6px; border-radius: 3px; font-size: 0.85em;\n font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;\n }\n .btn {\n display: inline-block;\n background: #14b8a6; color: #000;\n font-weight: 600; padding: 0.65rem 1.25rem;\n border-radius: 6px; text-decoration: none;\n margin-top: 1.25rem; transition: opacity 0.15s;\n }\n .btn:hover { opacity: 0.85; }\n .hint {\n margin-top: 1.75rem; padding-top: 1.5rem;\n border-top: 1px solid #2a2a2a; font-size: 0.85rem; color: #666;\n }\n </style>\n</head>\n<body>\n <div class=\"card\">\n <div class=\"brand\">\n <span class=\"icon\">◈</span>\n <span class=\"name\">Cordon for MCP</span>\n </div>\n <div class=\"mark\">${mark}</div>\n <h1>${heading}</h1>\n ${body}\n </div>\n</body>\n</html>`;\n}\n\nasync function listenForCallback(port: number, expectedState: string, dashboardUrl: string): Promise<CallbackResult> {\n return new Promise((resolve, reject) => {\n const server = createServer((req, res) => {\n const url = new URL(req.url ?? '/', `http://localhost:${port}`);\n if (url.pathname !== '/cb') {\n res.writeHead(404).end('not found');\n return;\n }\n const token = url.searchParams.get('token') ?? '';\n const state = url.searchParams.get('state') ?? '';\n const signup = url.searchParams.get('signup') ?? 'false';\n\n if (state !== expectedState || !token) {\n const body =\n `<p>The login link didn't carry a valid token, or the security state didn't match.</p>` +\n `<p class=\"hint\">Run <code>cordon login</code> again from your terminal. You can close this tab.</p>`;\n res.writeHead(400, { 'Content-Type': 'text/html' })\n .end(renderCallbackPage('error', \"Login didn't complete\", body));\n server.close();\n reject(new Error('Callback state mismatch'));\n return;\n }\n\n const body =\n `<p>Your API key is saved. Return to your terminal to continue,` +\n ` or jump straight to your dashboard.</p>` +\n `<a href=\"${dashboardUrl}\" class=\"btn\">Open dashboard →</a>` +\n `<p class=\"hint\">You can close this tab.</p>`;\n res.writeHead(200, { 'Content-Type': 'text/html' })\n .end(renderCallbackPage('success', \"You're logged in\", body));\n server.close();\n resolve({ token, state, signup });\n });\n\n server.listen(port, '127.0.0.1');\n server.on('error', (err) => reject(err));\n\n setTimeout(() => {\n server.close();\n reject(new Error('Login timed out after 5 minutes'));\n }, TIMEOUT_MS).unref();\n });\n}\n\nexport async function loginCommand(options: LoginOptions = {}): Promise<void> {\n const endpoint = (options.endpoint ?? DEFAULT_ENDPOINT).replace(/\\/$/, '');\n const state = randomBytes(16).toString('hex');\n\n let port: number | null = null;\n let result: CallbackResult | null = null;\n let lastErr: unknown = null;\n\n for (const candidate of PORT_RANGE) {\n try {\n const callback = `http://localhost:${candidate}/cb`;\n const authUrl =\n `${endpoint}/auth/cli/start?callback=${encodeURIComponent(callback)}&state=${state}`;\n\n process.stderr.write(\n `\\n[cordon] opening browser to log in...\\n` +\n `[cordon] if it doesn't open, visit:\\n ${authUrl}\\n\\n`,\n );\n openBrowser(authUrl);\n\n const pending = listenForCallback(candidate, state, `${endpoint}/dashboard/`);\n port = candidate;\n result = await pending;\n break;\n } catch (err) {\n lastErr = err;\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'EADDRINUSE') continue;\n throw err;\n }\n }\n\n if (!result) {\n throw lastErr instanceof Error ? lastErr : new Error('Could not bind a local callback port');\n }\n\n setAuth({\n endpoint,\n apiKey: result.token,\n loggedInAt: new Date().toISOString(),\n });\n\n process.stderr.write(`\\x1b[32m✓\\x1b[0m logged in. API key saved to ~/.cordon/auth.json\\n`);\n if (result.signup === 'true') {\n process.stderr.write(\n `Welcome to Cordon for MCP.\\n` +\n `Dashboard: \\x1b[36m${endpoint}/dashboard/\\x1b[0m\\n` +\n `Run \\x1b[36mcordon init\\x1b[0m next to wire up your MCP servers.\\n`,\n );\n }\n // Suppress unused var warning when port is set but not otherwise used\n void port;\n}\n","import { clearAuth } from '../cli-state.js';\n\nexport function logoutCommand(): void {\n if (clearAuth()) {\n process.stderr.write(`\\x1b[32m✓\\x1b[0m logged out. ~/.cordon/auth.json removed\\n`);\n } else {\n process.stderr.write(`[cordon] not logged in\\n`);\n }\n}\n"],"mappings":"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","names":["existsSync","join","homedir","readFileSync","writeFileSync","existsSync","join","homedir","body","DASHBOARD_URL","existsSync","join","writeFileSync","homedir","readFileSync"]}

@@ -112,14 +112,19 @@ // src/commands/start.ts

if (!auth) return config;
const audit = config.audit;
let next = config;
const audit = next.audit;
const usesHosted = audit && (audit.output === "hosted" || Array.isArray(audit.output) && audit.output.includes("hosted"));
if (!usesHosted) return config;
if (audit.endpoint && audit.apiKey) return config;
return {
...config,
audit: {
...audit,
endpoint: audit.endpoint ?? auth.endpoint,
apiKey: audit.apiKey ?? auth.apiKey
}
};
if (usesHosted && !(audit.endpoint && audit.apiKey)) {
next = {
...next,
audit: { ...audit, endpoint: audit.endpoint ?? auth.endpoint, apiKey: audit.apiKey ?? auth.apiKey }
};
}
const approvals = next.approvals;
if (approvals?.channel === "slack" && !(approvals.endpoint && approvals.apiKey)) {
next = {
...next,
approvals: { ...approvals, endpoint: approvals.endpoint ?? auth.endpoint, apiKey: approvals.apiKey ?? auth.apiKey }
};
}
return next;
}

@@ -522,2 +527,11 @@ function applyHttpFlags(config, options) {

},`;
const approvalsBlock = auth ? `approvals: {
// Human-in-the-loop over Slack. Connect your workspace once via
// "Add to Slack" in the dashboard \u2014 endpoint + apiKey are auto-loaded.
channel: 'slack',
// timeoutMs: 60_000,
},` : `approvals: {
channel: 'terminal',
// timeoutMs: 60_000,
},`;
const content = `import { defineConfig } from '@getcordon/policy';

@@ -532,6 +546,3 @@

approvals: {
channel: 'terminal',
// timeoutMs: 60_000,
},
${approvalsBlock}
});

@@ -538,0 +549,0 @@ `;

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/commands/start.ts","../src/config-loader.ts","../src/cli-state.ts","../src/commands/init.ts","../src/commands/login.ts"],"sourcesContent":["import { CordonGateway } from '@getcordon/core';\nimport type { ResolvedConfig } from '@getcordon/policy';\nimport { emptyConfig, findConfigPath, loadConfig } from '../config-loader.js';\nimport { getState, setState, getAuth } from '../cli-state.js';\n\nconst DASHBOARD_URL = 'https://app.getcordon.com/dashboard/';\n\nfunction applyAuthDefaults(config: ResolvedConfig): ResolvedConfig {\n const auth = getAuth();\n if (!auth) return config;\n\n const audit = config.audit;\n const usesHosted = audit && (audit.output === 'hosted' || (Array.isArray(audit.output) && audit.output.includes('hosted')));\n if (!usesHosted) return config;\n if (audit.endpoint && audit.apiKey) return config;\n\n return {\n ...config,\n audit: {\n ...audit,\n endpoint: audit.endpoint ?? auth.endpoint,\n apiKey: audit.apiKey ?? auth.apiKey,\n },\n };\n}\n\ninterface StartOptions {\n config?: string;\n /** Enable HTTP/Streamable HTTP transport (overrides config.gateway.transport). */\n http?: boolean;\n /** Port for HTTP transport (overrides config.gateway.port). */\n port?: number;\n}\n\n/**\n * Apply CLI flags + env vars on top of the loaded config so the HTTP transport\n * can be enabled without editing the config file. Precedence:\n * - `--http` flag forces HTTP transport regardless of config.\n * - `--port` flag overrides config.gateway.port.\n * - authToken comes from config.gateway.authToken if present, otherwise\n * the `CORDON_GATEWAY_TOKEN` env var.\n * - host comes from config (defaults to localhost in core/transport/http.ts).\n */\nfunction applyHttpFlags(config: ResolvedConfig, options: StartOptions): ResolvedConfig {\n const wantHttp = options.http === true || config.gateway?.transport === 'http';\n if (!wantHttp) {\n if (options.port !== undefined) {\n process.stderr.write(\n '\\x1b[33m[cordon] --port has no effect without --http (or gateway config). Ignoring.\\x1b[0m\\n',\n );\n }\n return config;\n }\n\n const httpConfig = config.gateway?.transport === 'http' ? config.gateway : undefined;\n const authToken = httpConfig?.authToken ?? process.env.CORDON_GATEWAY_TOKEN;\n\n if (!authToken) {\n process.stderr.write(\n '\\x1b[31merror\\x1b[0m: HTTP transport requires an auth token.\\n' +\n ' Set `gateway: { transport: \"http\", authToken: process.env.CORDON_GATEWAY_TOKEN }` in\\n' +\n ' cordon.config.ts, or export `CORDON_GATEWAY_TOKEN` before running `cordon start --http`.\\n',\n );\n process.exit(1);\n }\n\n return {\n ...config,\n gateway: {\n transport: 'http',\n authToken,\n ...(options.port !== undefined ? { port: options.port } : httpConfig?.port !== undefined ? { port: httpConfig.port } : {}),\n ...(httpConfig?.host !== undefined ? { host: httpConfig.host } : {}),\n },\n };\n}\n\nexport async function startCommand(options: StartOptions): Promise<void> {\n if (!getState().welcomed && !getAuth()) {\n process.stderr.write(\n `\\n\\x1b[36m[cordon] Want centralized audit logs + Slack approvals?\\x1b[0m\\n` +\n `[cordon] Run \\`cordon login\\` or register at ${DASHBOARD_URL}?utm_source=cli_start\\n\\n`,\n );\n setState({ welcomed: true });\n }\n\n // Default to an empty server list so auto-install probes (Glama, MCP\n // registry, fresh `npx -y @getcordon/cli start`) succeed instead of crashing.\n // Real users get a loud stderr warning so they don't silently run a no-op.\n let config: ResolvedConfig = emptyConfig();\n let configPath: string | null = null;\n\n try {\n configPath = await findConfigPath(options.config);\n } catch (err) {\n if (options.config) {\n process.stderr.write(`\\x1b[31merror\\x1b[0m: ${String(err)}\\n`);\n process.exit(1);\n }\n process.stderr.write(\n '\\x1b[33m[cordon] no cordon.config.ts found — starting with zero upstream servers.\\x1b[0m\\n' +\n '[cordon] Run `cordon init` to generate a config and connect real MCP servers.\\n',\n );\n }\n\n if (configPath) {\n process.stderr.write(`[cordon] loading config from ${configPath}\\n`);\n try {\n config = await loadConfig(configPath);\n } catch (err) {\n process.stderr.write(`\\x1b[31merror\\x1b[0m: ${String(err)}\\n`);\n process.exit(1);\n }\n }\n\n config = applyAuthDefaults(config);\n config = applyHttpFlags(config, options);\n\n const gateway = new CordonGateway(config);\n\n const shutdown = async () => {\n process.stderr.write('\\n[cordon] shutting down...\\n');\n try {\n await gateway.stop();\n process.exit(0);\n } catch (err) {\n process.stderr.write(`\\x1b[31merror\\x1b[0m: shutdown failed: ${String(err)}\\n`);\n process.exit(1);\n }\n };\n\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n\n try {\n await gateway.start();\n } catch (err) {\n process.stderr.write(`\\x1b[31merror\\x1b[0m: gateway failed: ${String(err)}\\n`);\n process.exit(1);\n }\n}\n","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { createJiti } from 'jiti';\nimport type { CordonConfig, ResolvedConfig } from '@getcordon/policy';\n\nconst SEARCH_PATHS = [\n () => join(process.cwd(), 'cordon.config.ts'),\n () => join(process.cwd(), 'cordon.config.js'),\n () => join(homedir(), '.cordon', 'config.ts'),\n () => join(homedir(), '.cordon', 'config.js'),\n];\n\nexport async function findConfigPath(explicit?: string): Promise<string> {\n if (explicit) {\n if (!existsSync(explicit)) {\n throw new Error(`Config file not found: ${explicit}`);\n }\n return explicit;\n }\n\n for (const candidate of SEARCH_PATHS) {\n const p = candidate();\n if (existsSync(p)) return p;\n }\n\n throw new Error(\n 'No cordon config found. Run `cordon init` to create one, or pass --config <path>.',\n );\n}\n\nexport async function loadConfig(configPath: string): Promise<ResolvedConfig> {\n // jiti handles TypeScript config files without requiring a separate compile step\n const jiti = createJiti(import.meta.url);\n const mod = await jiti.import(configPath);\n\n const raw = (mod as { default?: CordonConfig }).default ?? (mod as CordonConfig);\n\n if (!raw || typeof raw !== 'object' || !Array.isArray(raw.servers)) {\n throw new Error(\n `Invalid config at ${configPath}: expected an object with a 'servers' array. ` +\n `Make sure you are using \\`export default defineConfig({...})\\`.`,\n );\n }\n\n return applyDefaults(raw as CordonConfig);\n}\n\nfunction applyDefaults(config: CordonConfig): ResolvedConfig {\n return {\n ...config,\n audit: config.audit ?? { enabled: true, output: 'stdout' },\n approvals: config.approvals ?? { channel: 'terminal' },\n };\n}\n\nexport function emptyConfig(): ResolvedConfig {\n return applyDefaults({ servers: [] });\n}\n","import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync, unlinkSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\n\ninterface CliState {\n welcomed?: boolean;\n}\n\nexport interface CliAuth {\n endpoint: string;\n apiKey: string;\n userLogin?: string;\n loggedInAt: string;\n}\n\nfunction cordonDir(): string {\n return join(homedir(), '.cordon');\n}\n\nfunction statePath(): string {\n return join(cordonDir(), 'state.json');\n}\n\nfunction authPath(): string {\n return join(cordonDir(), 'auth.json');\n}\n\nfunction ensureCordonDir(): void {\n const dir = cordonDir();\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n}\n\nexport function getState(): CliState {\n const path = statePath();\n if (!existsSync(path)) return {};\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as CliState;\n } catch {\n return {};\n }\n}\n\nexport function setState(patch: CliState): void {\n try {\n ensureCordonDir();\n const merged = { ...getState(), ...patch };\n writeFileSync(statePath(), JSON.stringify(merged, null, 2), 'utf8');\n } catch {\n // Non-fatal: missing state just means we re-show the banner next time.\n }\n}\n\nexport function getAuth(): CliAuth | null {\n const path = authPath();\n if (!existsSync(path)) return null;\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as CliAuth;\n } catch {\n return null;\n }\n}\n\nexport function setAuth(auth: CliAuth): void {\n ensureCordonDir();\n const path = authPath();\n writeFileSync(path, JSON.stringify(auth, null, 2), 'utf8');\n if (process.platform !== 'win32') {\n try { chmodSync(path, 0o600); } catch { /* best-effort */ }\n }\n}\n\nexport function clearAuth(): boolean {\n const path = authPath();\n if (!existsSync(path)) return false;\n try {\n unlinkSync(path);\n return true;\n } catch {\n return false;\n }\n}\n","import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { spawnSync } from 'node:child_process';\nimport { createInterface } from 'node:readline';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { setState, getAuth } from '../cli-state.js';\nimport { loginCommand } from './login.js';\n\nconst DASHBOARD_URL = 'https://app.getcordon.com/dashboard/';\n\nfunction ensureCordonSdkInstalled(cwd: string): void {\n // Config imports from '@getcordon/policy', and jiti resolves it from the config\n // file's directory. Without a local install, `cordon start` dies with\n // \"Cannot find module '@getcordon/policy'\" even when the CLI is global.\n if (existsSync(join(cwd, 'node_modules', '@getcordon/policy', 'package.json'))) {\n return;\n }\n\n if (!existsSync(join(cwd, 'package.json'))) {\n writeFileSync(\n join(cwd, 'package.json'),\n JSON.stringify({ name: 'cordon-config', version: '0.0.0', private: true }, null, 2) + '\\n',\n 'utf8',\n );\n process.stderr.write(`\\x1b[32m✓\\x1b[0m created package.json\\n`);\n }\n\n process.stderr.write(`[cordon] installing @getcordon/policy...\\n`);\n const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';\n const result = spawnSync(npmCmd, ['install', '@getcordon/policy'], {\n cwd,\n stdio: ['ignore', 'inherit', 'inherit'],\n });\n\n if (result.status === 0) {\n process.stderr.write(`\\x1b[32m✓\\x1b[0m installed @getcordon/policy\\n`);\n } else {\n process.stderr.write(\n `\\x1b[33mwarn\\x1b[0m: could not auto-install @getcordon/policy. ` +\n `Run 'npm install @getcordon/policy' in this directory before 'cordon start'.\\n`,\n );\n }\n}\n\nasync function promptYesNo(question: string): Promise<boolean> {\n // Non-interactive (CI, piped stdin): skip rather than surprise the operator\n // with a browser tab and a 5-minute callback timeout.\n if (!process.stdin.isTTY) return false;\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n rl.close();\n const trimmed = answer.trim().toLowerCase();\n // Empty = default Yes\n resolve(trimmed === '' || trimmed === 'y' || trimmed === 'yes');\n });\n });\n}\n\ninterface ClaudeDesktopConfig {\n mcpServers?: Record<string, { command: string; args?: string[]; env?: Record<string, string> }>;\n}\n\nfunction getClaudeConfigPath(): string | null {\n const candidates: string[] = [];\n\n if (process.platform === 'darwin') {\n candidates.push(\n join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'),\n );\n } else if (process.platform === 'win32') {\n const appData = process.env['APPDATA'] ?? join(homedir(), 'AppData', 'Roaming');\n candidates.push(join(appData, 'Claude', 'claude_desktop_config.json'));\n } else {\n candidates.push(join(homedir(), '.config', 'Claude', 'claude_desktop_config.json'));\n }\n\n return candidates.find((p) => existsSync(p)) ?? null;\n}\n\nexport async function initCommand(): Promise<void> {\n const outputPath = join(process.cwd(), 'cordon.config.ts');\n\n if (existsSync(outputPath)) {\n process.stderr.write(\n `\\x1b[33mwarn\\x1b[0m: cordon.config.ts already exists — not overwriting.\\n`,\n );\n process.exit(1);\n }\n\n // Try to discover existing MCP servers from Claude Desktop config\n const claudePath = getClaudeConfigPath();\n let claudeConfig: ClaudeDesktopConfig = {};\n\n if (claudePath) {\n process.stderr.write(`[cordon] found Claude Desktop config at ${claudePath}\\n`);\n try {\n claudeConfig = JSON.parse(readFileSync(claudePath, 'utf8')) as ClaudeDesktopConfig;\n } catch {\n process.stderr.write(`\\x1b[33mwarn\\x1b[0m: could not parse Claude Desktop config\\n`);\n }\n } else {\n process.stderr.write(\n `[cordon] no Claude Desktop config found — generating a blank config\\n`,\n );\n }\n\n const servers = Object.entries(claudeConfig.mcpServers ?? {});\n\n // Generate cordon.config.ts\n const serverBlocks = servers.length > 0\n ? servers\n .map(([name, cfg]) => {\n const argsStr = cfg.args?.length\n ? `, args: ${JSON.stringify(cfg.args)}`\n : '';\n const envStr =\n cfg.env && Object.keys(cfg.env).length\n ? `, env: ${JSON.stringify(cfg.env)}`\n : '';\n return ` {\n name: ${JSON.stringify(name)},\n transport: 'stdio',\n command: ${JSON.stringify(cfg.command)}${argsStr}${envStr},\n policy: 'allow',\n // tools: {\n // execute: 'approve',\n // delete: 'block',\n // },\n },`;\n })\n .join('\\n')\n : ` // {\n // name: 'my-server',\n // transport: 'stdio',\n // command: 'npx',\n // args: ['-y', '@my-org/my-mcp-server'],\n // policy: 'allow',\n // },`;\n\n let auth = getAuth();\n let promptShown = false;\n if (!auth && process.stdin.isTTY) {\n promptShown = true;\n process.stderr.write(\n `\\n\\x1b[36mCordon works locally out of the box.\\x1b[0m ` +\n `Sign in to enable hosted audit logs + Slack approvals (free).\\n`,\n );\n const wantsLogin = await promptYesNo('Sign in via browser now? [Y/n] ');\n if (wantsLogin) {\n try {\n await loginCommand();\n auth = getAuth();\n } catch (err) {\n const msg = err instanceof Error ? err.message : 'unknown error';\n process.stderr.write(\n `\\x1b[33mwarn\\x1b[0m: login didn't complete (${msg}). ` +\n `Continuing with local stdout audit. Run \\x1b[36mcordon login\\x1b[0m later.\\n`,\n );\n }\n }\n }\n\n const auditBlock = auth\n ? `audit: {\n enabled: true,\n output: 'hosted',\n // endpoint + apiKey are auto-loaded from ~/.cordon/auth.json (cordon login)\n },`\n : `audit: {\n enabled: true,\n // 'auto' streams to stdout until you run \\`cordon login\\`, then auto-switches to hosted.\n output: 'auto',\n },`;\n\n const content = `import { defineConfig } from '@getcordon/policy';\n\nexport default defineConfig({\n servers: [\n${serverBlocks}\n ],\n\n ${auditBlock}\n\n approvals: {\n channel: 'terminal',\n // timeoutMs: 60_000,\n },\n});\n`;\n\n writeFileSync(outputPath, content, 'utf8');\n process.stderr.write(`\\x1b[32m✓\\x1b[0m wrote cordon.config.ts\\n`);\n\n // The config imports from '@getcordon/policy'. Install it locally so jiti can\n // resolve it when `cordon start` runs.\n ensureCordonSdkInstalled(process.cwd());\n\n // Patch Claude Desktop config to route through cordon.\n //\n // We bind the spawn to the *exact* node + cordon.js paths that ran this\n // init. Earlier versions wrote `npx.cmd` (or `npx`), which relies on\n // PATH lookup at spawn time. Claude Desktop's spawned subprocesses\n // inherit a stripped PATH on Windows that often lacks the user's\n // nvm/Node bin dir, so `npx.cmd` fails with \"is not recognized as an\n // internal or external command\". Using full paths skips PATH and the\n // cmd.exe wrapper entirely.\n if (claudePath && servers.length > 0) {\n const cordonConfigPath = outputPath.replace(/\\\\/g, '/');\n const nodePath = process.execPath.replace(/\\\\/g, '/');\n const cordonScriptPath = (process.argv[1] ?? '').replace(/\\\\/g, '/');\n\n if (!cordonScriptPath) {\n process.stderr.write(\n `\\x1b[33mwarn\\x1b[0m: could not detect cordon CLI script path. ` +\n `Skipping Claude Desktop patch.\\n` +\n `Manually add this to your claude_desktop_config.json mcpServers:\\n` +\n ` \"cordon\": { \"command\": \"${nodePath}\", \"args\": [\"<path-to-cordon.js>\", \"start\", \"--config\", \"${cordonConfigPath}\"] }\\n`,\n );\n return;\n }\n\n const newClaudeConfig: ClaudeDesktopConfig = {\n ...claudeConfig,\n mcpServers: {\n cordon: {\n command: nodePath,\n args: [cordonScriptPath, 'start', '--config', cordonConfigPath],\n },\n },\n };\n\n // Backup the original — only if no backup exists yet, so re-running init\n // never overwrites the user's true pre-cordon config.\n const backupPath = `${claudePath}.cordon-backup`;\n if (existsSync(backupPath)) {\n process.stderr.write(\n `[cordon] existing backup at ${backupPath} preserved (won't overwrite)\\n`,\n );\n } else {\n writeFileSync(backupPath, readFileSync(claudePath, 'utf8'), 'utf8');\n process.stderr.write(`\\x1b[32m✓\\x1b[0m backed up Claude Desktop config to ${backupPath}\\n`);\n }\n\n writeFileSync(claudePath, JSON.stringify(newClaudeConfig, null, 2), 'utf8');\n process.stderr.write(`\\x1b[32m✓\\x1b[0m patched Claude Desktop config\\n`);\n process.stderr.write(\n `\\n\\x1b[36mRestart Claude Desktop to activate Cordon.\\x1b[0m\\n`,\n );\n } else if (!claudePath) {\n const nodePath = process.execPath.replace(/\\\\/g, '/');\n const cordonScriptPath = (process.argv[1] ?? '<path-to-cordon.js>').replace(/\\\\/g, '/');\n process.stderr.write(\n `\\n\\x1b[33mwarn\\x1b[0m: Claude Desktop config not found on this system.\\n` +\n `Edit cordon.config.ts, then manually add Cordon to your MCP client config:\\n\\n` +\n ` \"mcpServers\": {\\n` +\n ` \"cordon\": {\\n` +\n ` \"command\": \"${nodePath}\",\\n` +\n ` \"args\": [\"${cordonScriptPath}\", \"start\", \"--config\", \"${outputPath.replace(/\\\\/g, '/')}\"]\\n` +\n ` }\\n` +\n ` }\\n`,\n );\n } else {\n // claudePath found but no existing servers — config written, no patching needed\n process.stderr.write(\n `\\nEdit cordon.config.ts to add your MCP servers, then run \\x1b[36mnpx cordon start\\x1b[0m.\\n`,\n );\n }\n\n if (auth) {\n process.stderr.write(\n `\\n\\x1b[32m✓\\x1b[0m audit logs will stream to your Cordon account (${auth.endpoint})\\n`,\n );\n } else if (promptShown) {\n process.stderr.write(\n `\\n[cordon] running in local mode. Run \\x1b[36mcordon login\\x1b[0m later to enable hosted audit.\\n`,\n );\n } else {\n process.stderr.write(\n `\\n\\x1b[36mWant centralized audit logs + Slack approvals?\\x1b[0m\\n` +\n `Run \\x1b[36mcordon login\\x1b[0m to register a free account, ` +\n `or sign up at ${DASHBOARD_URL}?utm_source=cli_init\\n`,\n );\n }\n setState({ welcomed: true });\n}\n","import { createServer } from 'node:http';\nimport { spawn } from 'node:child_process';\nimport { randomBytes } from 'node:crypto';\nimport { setAuth } from '../cli-state.js';\n\nconst DEFAULT_ENDPOINT = 'https://app.getcordon.com';\nconst PORT_RANGE = [53247, 53249, 53251] as const;\nconst TIMEOUT_MS = 5 * 60 * 1000;\n\ninterface LoginOptions {\n endpoint?: string;\n}\n\nfunction openBrowser(url: string): void {\n const cmd = process.platform === 'darwin' ? 'open'\n : process.platform === 'win32' ? 'cmd'\n : 'xdg-open';\n // On Windows, `cmd /c start \"\" <url>` runs through cmd.exe, which treats\n // `&` and `^` as shell metacharacters. Without escaping them, query strings\n // like `?callback=…&state=…` get truncated at the first `&`, breaking the\n // OAuth state hand-off. Replace `^` first so we don't double-escape new ^&.\n const safeUrl = process.platform === 'win32'\n ? url.replace(/\\^/g, '^^').replace(/&/g, '^&')\n : url;\n const args = process.platform === 'win32' ? ['/c', 'start', '\"\"', safeUrl] : [safeUrl];\n try {\n spawn(cmd, args, { detached: true, stdio: 'ignore' }).unref();\n } catch { /* swallow — caller has already printed the URL for manual paste */ }\n}\n\ninterface CallbackResult {\n token: string;\n state: string;\n signup: string;\n}\n\nfunction renderCallbackPage(kind: 'success' | 'error', heading: string, body: string): string {\n const accent = kind === 'success' ? '#14b8a6' : '#f87171';\n const accentBg = kind === 'success' ? 'rgba(20, 184, 166, 0.15)' : 'rgba(248, 113, 113, 0.15)';\n const mark = kind === 'success' ? '✓' : '✗';\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${kind === 'success' ? 'Logged in' : 'Login failed'} — Cordon for MCP</title>\n <link rel=\"icon\" type=\"image/svg+xml\" href=\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90' fill='%2314b8a6'>◈</text></svg>\">\n <style>\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n background: #0d0d0d; color: #e8e8e8;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 1rem;\n }\n .card {\n background: #161616; border: 1px solid #2a2a2a; border-radius: 16px;\n padding: 48px; max-width: 420px; width: 100%; text-align: center;\n }\n .brand { display: flex; align-items: center; justify-content: center; gap: 10px; margin-bottom: 1.75rem; }\n .icon { font-size: 1.4rem; color: #14b8a6; }\n .name { font-weight: 600; font-size: 16px; letter-spacing: -0.3px; white-space: nowrap; }\n .mark {\n width: 56px; height: 56px; border-radius: 50%;\n background: ${accentBg}; color: ${accent};\n display: flex; align-items: center; justify-content: center;\n margin: 0 auto 1.25rem; font-size: 28px; font-weight: 700;\n }\n h1 { font-size: 1.4rem; font-weight: 700; letter-spacing: -0.02em; margin-bottom: 0.6rem; }\n p { color: #888; font-size: 0.95rem; line-height: 1.5; }\n p + p { margin-top: 0.5rem; }\n code {\n background: rgba(255, 255, 255, 0.06); color: #e8e8e8;\n padding: 1px 6px; border-radius: 3px; font-size: 0.85em;\n font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;\n }\n .btn {\n display: inline-block;\n background: #14b8a6; color: #000;\n font-weight: 600; padding: 0.65rem 1.25rem;\n border-radius: 6px; text-decoration: none;\n margin-top: 1.25rem; transition: opacity 0.15s;\n }\n .btn:hover { opacity: 0.85; }\n .hint {\n margin-top: 1.75rem; padding-top: 1.5rem;\n border-top: 1px solid #2a2a2a; font-size: 0.85rem; color: #666;\n }\n </style>\n</head>\n<body>\n <div class=\"card\">\n <div class=\"brand\">\n <span class=\"icon\">◈</span>\n <span class=\"name\">Cordon for MCP</span>\n </div>\n <div class=\"mark\">${mark}</div>\n <h1>${heading}</h1>\n ${body}\n </div>\n</body>\n</html>`;\n}\n\nasync function listenForCallback(port: number, expectedState: string, dashboardUrl: string): Promise<CallbackResult> {\n return new Promise((resolve, reject) => {\n const server = createServer((req, res) => {\n const url = new URL(req.url ?? '/', `http://localhost:${port}`);\n if (url.pathname !== '/cb') {\n res.writeHead(404).end('not found');\n return;\n }\n const token = url.searchParams.get('token') ?? '';\n const state = url.searchParams.get('state') ?? '';\n const signup = url.searchParams.get('signup') ?? 'false';\n\n if (state !== expectedState || !token) {\n const body =\n `<p>The login link didn't carry a valid token, or the security state didn't match.</p>` +\n `<p class=\"hint\">Run <code>cordon login</code> again from your terminal. You can close this tab.</p>`;\n res.writeHead(400, { 'Content-Type': 'text/html' })\n .end(renderCallbackPage('error', \"Login didn't complete\", body));\n server.close();\n reject(new Error('Callback state mismatch'));\n return;\n }\n\n const body =\n `<p>Your API key is saved. Return to your terminal to continue,` +\n ` or jump straight to your dashboard.</p>` +\n `<a href=\"${dashboardUrl}\" class=\"btn\">Open dashboard →</a>` +\n `<p class=\"hint\">You can close this tab.</p>`;\n res.writeHead(200, { 'Content-Type': 'text/html' })\n .end(renderCallbackPage('success', \"You're logged in\", body));\n server.close();\n resolve({ token, state, signup });\n });\n\n server.listen(port, '127.0.0.1');\n server.on('error', (err) => reject(err));\n\n setTimeout(() => {\n server.close();\n reject(new Error('Login timed out after 5 minutes'));\n }, TIMEOUT_MS).unref();\n });\n}\n\nexport async function loginCommand(options: LoginOptions = {}): Promise<void> {\n const endpoint = (options.endpoint ?? DEFAULT_ENDPOINT).replace(/\\/$/, '');\n const state = randomBytes(16).toString('hex');\n\n let port: number | null = null;\n let result: CallbackResult | null = null;\n let lastErr: unknown = null;\n\n for (const candidate of PORT_RANGE) {\n try {\n const callback = `http://localhost:${candidate}/cb`;\n const authUrl =\n `${endpoint}/auth/cli/start?callback=${encodeURIComponent(callback)}&state=${state}`;\n\n process.stderr.write(\n `\\n[cordon] opening browser to log in...\\n` +\n `[cordon] if it doesn't open, visit:\\n ${authUrl}\\n\\n`,\n );\n openBrowser(authUrl);\n\n const pending = listenForCallback(candidate, state, `${endpoint}/dashboard/`);\n port = candidate;\n result = await pending;\n break;\n } catch (err) {\n lastErr = err;\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'EADDRINUSE') continue;\n throw err;\n }\n }\n\n if (!result) {\n throw lastErr instanceof Error ? lastErr : new Error('Could not bind a local callback port');\n }\n\n setAuth({\n endpoint,\n apiKey: result.token,\n loggedInAt: new Date().toISOString(),\n });\n\n process.stderr.write(`\\x1b[32m✓\\x1b[0m logged in. API key saved to ~/.cordon/auth.json\\n`);\n if (result.signup === 'true') {\n process.stderr.write(\n `Welcome to Cordon for MCP.\\n` +\n `Dashboard: \\x1b[36m${endpoint}/dashboard/\\x1b[0m\\n` +\n `Run \\x1b[36mcordon init\\x1b[0m next to wire up your MCP servers.\\n`,\n );\n }\n // Suppress unused var warning when port is set but not otherwise used\n void port;\n}\n"],"mappings":"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","names":["existsSync","join","homedir","readFileSync","writeFileSync","existsSync","join","homedir","body","DASHBOARD_URL","existsSync","join","writeFileSync","homedir","readFileSync"]}
{"version":3,"sources":["../src/commands/start.ts","../src/config-loader.ts","../src/cli-state.ts","../src/commands/init.ts","../src/commands/login.ts"],"sourcesContent":["import { CordonGateway } from '@getcordon/core';\nimport type { ResolvedConfig } from '@getcordon/policy';\nimport { emptyConfig, findConfigPath, loadConfig } from '../config-loader.js';\nimport { getState, setState, getAuth } from '../cli-state.js';\n\nconst DASHBOARD_URL = 'https://app.getcordon.com/dashboard/';\n\nfunction applyAuthDefaults(config: ResolvedConfig): ResolvedConfig {\n const auth = getAuth();\n if (!auth) return config;\n\n let next = config;\n\n // Hosted audit: fill endpoint/apiKey from auth.json when not already set.\n const audit = next.audit;\n const usesHosted =\n audit && (audit.output === 'hosted' || (Array.isArray(audit.output) && audit.output.includes('hosted')));\n if (usesHosted && !(audit.endpoint && audit.apiKey)) {\n next = {\n ...next,\n audit: { ...audit, endpoint: audit.endpoint ?? auth.endpoint, apiKey: audit.apiKey ?? auth.apiKey },\n };\n }\n\n // Slack approvals are server-driven — fill endpoint/apiKey so the local side\n // can register + poll without the user hand-writing them into the config.\n const approvals = next.approvals;\n if (approvals?.channel === 'slack' && !(approvals.endpoint && approvals.apiKey)) {\n next = {\n ...next,\n approvals: { ...approvals, endpoint: approvals.endpoint ?? auth.endpoint, apiKey: approvals.apiKey ?? auth.apiKey },\n };\n }\n\n return next;\n}\n\ninterface StartOptions {\n config?: string;\n /** Enable HTTP/Streamable HTTP transport (overrides config.gateway.transport). */\n http?: boolean;\n /** Port for HTTP transport (overrides config.gateway.port). */\n port?: number;\n}\n\n/**\n * Apply CLI flags + env vars on top of the loaded config so the HTTP transport\n * can be enabled without editing the config file. Precedence:\n * - `--http` flag forces HTTP transport regardless of config.\n * - `--port` flag overrides config.gateway.port.\n * - authToken comes from config.gateway.authToken if present, otherwise\n * the `CORDON_GATEWAY_TOKEN` env var.\n * - host comes from config (defaults to localhost in core/transport/http.ts).\n */\nfunction applyHttpFlags(config: ResolvedConfig, options: StartOptions): ResolvedConfig {\n const wantHttp = options.http === true || config.gateway?.transport === 'http';\n if (!wantHttp) {\n if (options.port !== undefined) {\n process.stderr.write(\n '\\x1b[33m[cordon] --port has no effect without --http (or gateway config). Ignoring.\\x1b[0m\\n',\n );\n }\n return config;\n }\n\n const httpConfig = config.gateway?.transport === 'http' ? config.gateway : undefined;\n const authToken = httpConfig?.authToken ?? process.env.CORDON_GATEWAY_TOKEN;\n\n if (!authToken) {\n process.stderr.write(\n '\\x1b[31merror\\x1b[0m: HTTP transport requires an auth token.\\n' +\n ' Set `gateway: { transport: \"http\", authToken: process.env.CORDON_GATEWAY_TOKEN }` in\\n' +\n ' cordon.config.ts, or export `CORDON_GATEWAY_TOKEN` before running `cordon start --http`.\\n',\n );\n process.exit(1);\n }\n\n return {\n ...config,\n gateway: {\n transport: 'http',\n authToken,\n ...(options.port !== undefined ? { port: options.port } : httpConfig?.port !== undefined ? { port: httpConfig.port } : {}),\n ...(httpConfig?.host !== undefined ? { host: httpConfig.host } : {}),\n },\n };\n}\n\nexport async function startCommand(options: StartOptions): Promise<void> {\n if (!getState().welcomed && !getAuth()) {\n process.stderr.write(\n `\\n\\x1b[36m[cordon] Want centralized audit logs + Slack approvals?\\x1b[0m\\n` +\n `[cordon] Run \\`cordon login\\` or register at ${DASHBOARD_URL}?utm_source=cli_start\\n\\n`,\n );\n setState({ welcomed: true });\n }\n\n // Default to an empty server list so auto-install probes (Glama, MCP\n // registry, fresh `npx -y @getcordon/cli start`) succeed instead of crashing.\n // Real users get a loud stderr warning so they don't silently run a no-op.\n let config: ResolvedConfig = emptyConfig();\n let configPath: string | null = null;\n\n try {\n configPath = await findConfigPath(options.config);\n } catch (err) {\n if (options.config) {\n process.stderr.write(`\\x1b[31merror\\x1b[0m: ${String(err)}\\n`);\n process.exit(1);\n }\n process.stderr.write(\n '\\x1b[33m[cordon] no cordon.config.ts found — starting with zero upstream servers.\\x1b[0m\\n' +\n '[cordon] Run `cordon init` to generate a config and connect real MCP servers.\\n',\n );\n }\n\n if (configPath) {\n process.stderr.write(`[cordon] loading config from ${configPath}\\n`);\n try {\n config = await loadConfig(configPath);\n } catch (err) {\n process.stderr.write(`\\x1b[31merror\\x1b[0m: ${String(err)}\\n`);\n process.exit(1);\n }\n }\n\n config = applyAuthDefaults(config);\n config = applyHttpFlags(config, options);\n\n const gateway = new CordonGateway(config);\n\n const shutdown = async () => {\n process.stderr.write('\\n[cordon] shutting down...\\n');\n try {\n await gateway.stop();\n process.exit(0);\n } catch (err) {\n process.stderr.write(`\\x1b[31merror\\x1b[0m: shutdown failed: ${String(err)}\\n`);\n process.exit(1);\n }\n };\n\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n\n try {\n await gateway.start();\n } catch (err) {\n process.stderr.write(`\\x1b[31merror\\x1b[0m: gateway failed: ${String(err)}\\n`);\n process.exit(1);\n }\n}\n","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { createJiti } from 'jiti';\nimport type { CordonConfig, ResolvedConfig } from '@getcordon/policy';\n\nconst SEARCH_PATHS = [\n () => join(process.cwd(), 'cordon.config.ts'),\n () => join(process.cwd(), 'cordon.config.js'),\n () => join(homedir(), '.cordon', 'config.ts'),\n () => join(homedir(), '.cordon', 'config.js'),\n];\n\nexport async function findConfigPath(explicit?: string): Promise<string> {\n if (explicit) {\n if (!existsSync(explicit)) {\n throw new Error(`Config file not found: ${explicit}`);\n }\n return explicit;\n }\n\n for (const candidate of SEARCH_PATHS) {\n const p = candidate();\n if (existsSync(p)) return p;\n }\n\n throw new Error(\n 'No cordon config found. Run `cordon init` to create one, or pass --config <path>.',\n );\n}\n\nexport async function loadConfig(configPath: string): Promise<ResolvedConfig> {\n // jiti handles TypeScript config files without requiring a separate compile step\n const jiti = createJiti(import.meta.url);\n const mod = await jiti.import(configPath);\n\n const raw = (mod as { default?: CordonConfig }).default ?? (mod as CordonConfig);\n\n if (!raw || typeof raw !== 'object' || !Array.isArray(raw.servers)) {\n throw new Error(\n `Invalid config at ${configPath}: expected an object with a 'servers' array. ` +\n `Make sure you are using \\`export default defineConfig({...})\\`.`,\n );\n }\n\n return applyDefaults(raw as CordonConfig);\n}\n\nfunction applyDefaults(config: CordonConfig): ResolvedConfig {\n return {\n ...config,\n audit: config.audit ?? { enabled: true, output: 'stdout' },\n approvals: config.approvals ?? { channel: 'terminal' },\n };\n}\n\nexport function emptyConfig(): ResolvedConfig {\n return applyDefaults({ servers: [] });\n}\n","import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync, unlinkSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\n\ninterface CliState {\n welcomed?: boolean;\n}\n\nexport interface CliAuth {\n endpoint: string;\n apiKey: string;\n userLogin?: string;\n loggedInAt: string;\n}\n\nfunction cordonDir(): string {\n return join(homedir(), '.cordon');\n}\n\nfunction statePath(): string {\n return join(cordonDir(), 'state.json');\n}\n\nfunction authPath(): string {\n return join(cordonDir(), 'auth.json');\n}\n\nfunction ensureCordonDir(): void {\n const dir = cordonDir();\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n}\n\nexport function getState(): CliState {\n const path = statePath();\n if (!existsSync(path)) return {};\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as CliState;\n } catch {\n return {};\n }\n}\n\nexport function setState(patch: CliState): void {\n try {\n ensureCordonDir();\n const merged = { ...getState(), ...patch };\n writeFileSync(statePath(), JSON.stringify(merged, null, 2), 'utf8');\n } catch {\n // Non-fatal: missing state just means we re-show the banner next time.\n }\n}\n\nexport function getAuth(): CliAuth | null {\n const path = authPath();\n if (!existsSync(path)) return null;\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as CliAuth;\n } catch {\n return null;\n }\n}\n\nexport function setAuth(auth: CliAuth): void {\n ensureCordonDir();\n const path = authPath();\n writeFileSync(path, JSON.stringify(auth, null, 2), 'utf8');\n if (process.platform !== 'win32') {\n try { chmodSync(path, 0o600); } catch { /* best-effort */ }\n }\n}\n\nexport function clearAuth(): boolean {\n const path = authPath();\n if (!existsSync(path)) return false;\n try {\n unlinkSync(path);\n return true;\n } catch {\n return false;\n }\n}\n","import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { spawnSync } from 'node:child_process';\nimport { createInterface } from 'node:readline';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { setState, getAuth } from '../cli-state.js';\nimport { loginCommand } from './login.js';\n\nconst DASHBOARD_URL = 'https://app.getcordon.com/dashboard/';\n\nfunction ensureCordonSdkInstalled(cwd: string): void {\n // Config imports from '@getcordon/policy', and jiti resolves it from the config\n // file's directory. Without a local install, `cordon start` dies with\n // \"Cannot find module '@getcordon/policy'\" even when the CLI is global.\n if (existsSync(join(cwd, 'node_modules', '@getcordon/policy', 'package.json'))) {\n return;\n }\n\n if (!existsSync(join(cwd, 'package.json'))) {\n writeFileSync(\n join(cwd, 'package.json'),\n JSON.stringify({ name: 'cordon-config', version: '0.0.0', private: true }, null, 2) + '\\n',\n 'utf8',\n );\n process.stderr.write(`\\x1b[32m✓\\x1b[0m created package.json\\n`);\n }\n\n process.stderr.write(`[cordon] installing @getcordon/policy...\\n`);\n const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';\n const result = spawnSync(npmCmd, ['install', '@getcordon/policy'], {\n cwd,\n stdio: ['ignore', 'inherit', 'inherit'],\n });\n\n if (result.status === 0) {\n process.stderr.write(`\\x1b[32m✓\\x1b[0m installed @getcordon/policy\\n`);\n } else {\n process.stderr.write(\n `\\x1b[33mwarn\\x1b[0m: could not auto-install @getcordon/policy. ` +\n `Run 'npm install @getcordon/policy' in this directory before 'cordon start'.\\n`,\n );\n }\n}\n\nasync function promptYesNo(question: string): Promise<boolean> {\n // Non-interactive (CI, piped stdin): skip rather than surprise the operator\n // with a browser tab and a 5-minute callback timeout.\n if (!process.stdin.isTTY) return false;\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n rl.close();\n const trimmed = answer.trim().toLowerCase();\n // Empty = default Yes\n resolve(trimmed === '' || trimmed === 'y' || trimmed === 'yes');\n });\n });\n}\n\ninterface ClaudeDesktopConfig {\n mcpServers?: Record<string, { command: string; args?: string[]; env?: Record<string, string> }>;\n}\n\nfunction getClaudeConfigPath(): string | null {\n const candidates: string[] = [];\n\n if (process.platform === 'darwin') {\n candidates.push(\n join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'),\n );\n } else if (process.platform === 'win32') {\n const appData = process.env['APPDATA'] ?? join(homedir(), 'AppData', 'Roaming');\n candidates.push(join(appData, 'Claude', 'claude_desktop_config.json'));\n } else {\n candidates.push(join(homedir(), '.config', 'Claude', 'claude_desktop_config.json'));\n }\n\n return candidates.find((p) => existsSync(p)) ?? null;\n}\n\nexport async function initCommand(): Promise<void> {\n const outputPath = join(process.cwd(), 'cordon.config.ts');\n\n if (existsSync(outputPath)) {\n process.stderr.write(\n `\\x1b[33mwarn\\x1b[0m: cordon.config.ts already exists — not overwriting.\\n`,\n );\n process.exit(1);\n }\n\n // Try to discover existing MCP servers from Claude Desktop config\n const claudePath = getClaudeConfigPath();\n let claudeConfig: ClaudeDesktopConfig = {};\n\n if (claudePath) {\n process.stderr.write(`[cordon] found Claude Desktop config at ${claudePath}\\n`);\n try {\n claudeConfig = JSON.parse(readFileSync(claudePath, 'utf8')) as ClaudeDesktopConfig;\n } catch {\n process.stderr.write(`\\x1b[33mwarn\\x1b[0m: could not parse Claude Desktop config\\n`);\n }\n } else {\n process.stderr.write(\n `[cordon] no Claude Desktop config found — generating a blank config\\n`,\n );\n }\n\n const servers = Object.entries(claudeConfig.mcpServers ?? {});\n\n // Generate cordon.config.ts\n const serverBlocks = servers.length > 0\n ? servers\n .map(([name, cfg]) => {\n const argsStr = cfg.args?.length\n ? `, args: ${JSON.stringify(cfg.args)}`\n : '';\n const envStr =\n cfg.env && Object.keys(cfg.env).length\n ? `, env: ${JSON.stringify(cfg.env)}`\n : '';\n return ` {\n name: ${JSON.stringify(name)},\n transport: 'stdio',\n command: ${JSON.stringify(cfg.command)}${argsStr}${envStr},\n policy: 'allow',\n // tools: {\n // execute: 'approve',\n // delete: 'block',\n // },\n },`;\n })\n .join('\\n')\n : ` // {\n // name: 'my-server',\n // transport: 'stdio',\n // command: 'npx',\n // args: ['-y', '@my-org/my-mcp-server'],\n // policy: 'allow',\n // },`;\n\n let auth = getAuth();\n let promptShown = false;\n if (!auth && process.stdin.isTTY) {\n promptShown = true;\n process.stderr.write(\n `\\n\\x1b[36mCordon works locally out of the box.\\x1b[0m ` +\n `Sign in to enable hosted audit logs + Slack approvals (free).\\n`,\n );\n const wantsLogin = await promptYesNo('Sign in via browser now? [Y/n] ');\n if (wantsLogin) {\n try {\n await loginCommand();\n auth = getAuth();\n } catch (err) {\n const msg = err instanceof Error ? err.message : 'unknown error';\n process.stderr.write(\n `\\x1b[33mwarn\\x1b[0m: login didn't complete (${msg}). ` +\n `Continuing with local stdout audit. Run \\x1b[36mcordon login\\x1b[0m later.\\n`,\n );\n }\n }\n }\n\n const auditBlock = auth\n ? `audit: {\n enabled: true,\n output: 'hosted',\n // endpoint + apiKey are auto-loaded from ~/.cordon/auth.json (cordon login)\n },`\n : `audit: {\n enabled: true,\n // 'auto' streams to stdout until you run \\`cordon login\\`, then auto-switches to hosted.\n output: 'auto',\n },`;\n\n const approvalsBlock = auth\n ? `approvals: {\n // Human-in-the-loop over Slack. Connect your workspace once via\n // \"Add to Slack\" in the dashboard — endpoint + apiKey are auto-loaded.\n channel: 'slack',\n // timeoutMs: 60_000,\n },`\n : `approvals: {\n channel: 'terminal',\n // timeoutMs: 60_000,\n },`;\n\n const content = `import { defineConfig } from '@getcordon/policy';\n\nexport default defineConfig({\n servers: [\n${serverBlocks}\n ],\n\n ${auditBlock}\n\n ${approvalsBlock}\n});\n`;\n\n writeFileSync(outputPath, content, 'utf8');\n process.stderr.write(`\\x1b[32m✓\\x1b[0m wrote cordon.config.ts\\n`);\n\n // The config imports from '@getcordon/policy'. Install it locally so jiti can\n // resolve it when `cordon start` runs.\n ensureCordonSdkInstalled(process.cwd());\n\n // Patch Claude Desktop config to route through cordon.\n //\n // We bind the spawn to the *exact* node + cordon.js paths that ran this\n // init. Earlier versions wrote `npx.cmd` (or `npx`), which relies on\n // PATH lookup at spawn time. Claude Desktop's spawned subprocesses\n // inherit a stripped PATH on Windows that often lacks the user's\n // nvm/Node bin dir, so `npx.cmd` fails with \"is not recognized as an\n // internal or external command\". Using full paths skips PATH and the\n // cmd.exe wrapper entirely.\n if (claudePath && servers.length > 0) {\n const cordonConfigPath = outputPath.replace(/\\\\/g, '/');\n const nodePath = process.execPath.replace(/\\\\/g, '/');\n const cordonScriptPath = (process.argv[1] ?? '').replace(/\\\\/g, '/');\n\n if (!cordonScriptPath) {\n process.stderr.write(\n `\\x1b[33mwarn\\x1b[0m: could not detect cordon CLI script path. ` +\n `Skipping Claude Desktop patch.\\n` +\n `Manually add this to your claude_desktop_config.json mcpServers:\\n` +\n ` \"cordon\": { \"command\": \"${nodePath}\", \"args\": [\"<path-to-cordon.js>\", \"start\", \"--config\", \"${cordonConfigPath}\"] }\\n`,\n );\n return;\n }\n\n const newClaudeConfig: ClaudeDesktopConfig = {\n ...claudeConfig,\n mcpServers: {\n cordon: {\n command: nodePath,\n args: [cordonScriptPath, 'start', '--config', cordonConfigPath],\n },\n },\n };\n\n // Backup the original — only if no backup exists yet, so re-running init\n // never overwrites the user's true pre-cordon config.\n const backupPath = `${claudePath}.cordon-backup`;\n if (existsSync(backupPath)) {\n process.stderr.write(\n `[cordon] existing backup at ${backupPath} preserved (won't overwrite)\\n`,\n );\n } else {\n writeFileSync(backupPath, readFileSync(claudePath, 'utf8'), 'utf8');\n process.stderr.write(`\\x1b[32m✓\\x1b[0m backed up Claude Desktop config to ${backupPath}\\n`);\n }\n\n writeFileSync(claudePath, JSON.stringify(newClaudeConfig, null, 2), 'utf8');\n process.stderr.write(`\\x1b[32m✓\\x1b[0m patched Claude Desktop config\\n`);\n process.stderr.write(\n `\\n\\x1b[36mRestart Claude Desktop to activate Cordon.\\x1b[0m\\n`,\n );\n } else if (!claudePath) {\n const nodePath = process.execPath.replace(/\\\\/g, '/');\n const cordonScriptPath = (process.argv[1] ?? '<path-to-cordon.js>').replace(/\\\\/g, '/');\n process.stderr.write(\n `\\n\\x1b[33mwarn\\x1b[0m: Claude Desktop config not found on this system.\\n` +\n `Edit cordon.config.ts, then manually add Cordon to your MCP client config:\\n\\n` +\n ` \"mcpServers\": {\\n` +\n ` \"cordon\": {\\n` +\n ` \"command\": \"${nodePath}\",\\n` +\n ` \"args\": [\"${cordonScriptPath}\", \"start\", \"--config\", \"${outputPath.replace(/\\\\/g, '/')}\"]\\n` +\n ` }\\n` +\n ` }\\n`,\n );\n } else {\n // claudePath found but no existing servers — config written, no patching needed\n process.stderr.write(\n `\\nEdit cordon.config.ts to add your MCP servers, then run \\x1b[36mnpx cordon start\\x1b[0m.\\n`,\n );\n }\n\n if (auth) {\n process.stderr.write(\n `\\n\\x1b[32m✓\\x1b[0m audit logs will stream to your Cordon account (${auth.endpoint})\\n`,\n );\n } else if (promptShown) {\n process.stderr.write(\n `\\n[cordon] running in local mode. Run \\x1b[36mcordon login\\x1b[0m later to enable hosted audit.\\n`,\n );\n } else {\n process.stderr.write(\n `\\n\\x1b[36mWant centralized audit logs + Slack approvals?\\x1b[0m\\n` +\n `Run \\x1b[36mcordon login\\x1b[0m to register a free account, ` +\n `or sign up at ${DASHBOARD_URL}?utm_source=cli_init\\n`,\n );\n }\n setState({ welcomed: true });\n}\n","import { createServer } from 'node:http';\nimport { spawn } from 'node:child_process';\nimport { randomBytes } from 'node:crypto';\nimport { setAuth } from '../cli-state.js';\n\nconst DEFAULT_ENDPOINT = 'https://app.getcordon.com';\nconst PORT_RANGE = [53247, 53249, 53251] as const;\nconst TIMEOUT_MS = 5 * 60 * 1000;\n\ninterface LoginOptions {\n endpoint?: string;\n}\n\nfunction openBrowser(url: string): void {\n const cmd = process.platform === 'darwin' ? 'open'\n : process.platform === 'win32' ? 'cmd'\n : 'xdg-open';\n // On Windows, `cmd /c start \"\" <url>` runs through cmd.exe, which treats\n // `&` and `^` as shell metacharacters. Without escaping them, query strings\n // like `?callback=…&state=…` get truncated at the first `&`, breaking the\n // OAuth state hand-off. Replace `^` first so we don't double-escape new ^&.\n const safeUrl = process.platform === 'win32'\n ? url.replace(/\\^/g, '^^').replace(/&/g, '^&')\n : url;\n const args = process.platform === 'win32' ? ['/c', 'start', '\"\"', safeUrl] : [safeUrl];\n try {\n spawn(cmd, args, { detached: true, stdio: 'ignore' }).unref();\n } catch { /* swallow — caller has already printed the URL for manual paste */ }\n}\n\ninterface CallbackResult {\n token: string;\n state: string;\n signup: string;\n}\n\nfunction renderCallbackPage(kind: 'success' | 'error', heading: string, body: string): string {\n const accent = kind === 'success' ? '#14b8a6' : '#f87171';\n const accentBg = kind === 'success' ? 'rgba(20, 184, 166, 0.15)' : 'rgba(248, 113, 113, 0.15)';\n const mark = kind === 'success' ? '✓' : '✗';\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${kind === 'success' ? 'Logged in' : 'Login failed'} — Cordon for MCP</title>\n <link rel=\"icon\" type=\"image/svg+xml\" href=\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90' fill='%2314b8a6'>◈</text></svg>\">\n <style>\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n background: #0d0d0d; color: #e8e8e8;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 1rem;\n }\n .card {\n background: #161616; border: 1px solid #2a2a2a; border-radius: 16px;\n padding: 48px; max-width: 420px; width: 100%; text-align: center;\n }\n .brand { display: flex; align-items: center; justify-content: center; gap: 10px; margin-bottom: 1.75rem; }\n .icon { font-size: 1.4rem; color: #14b8a6; }\n .name { font-weight: 600; font-size: 16px; letter-spacing: -0.3px; white-space: nowrap; }\n .mark {\n width: 56px; height: 56px; border-radius: 50%;\n background: ${accentBg}; color: ${accent};\n display: flex; align-items: center; justify-content: center;\n margin: 0 auto 1.25rem; font-size: 28px; font-weight: 700;\n }\n h1 { font-size: 1.4rem; font-weight: 700; letter-spacing: -0.02em; margin-bottom: 0.6rem; }\n p { color: #888; font-size: 0.95rem; line-height: 1.5; }\n p + p { margin-top: 0.5rem; }\n code {\n background: rgba(255, 255, 255, 0.06); color: #e8e8e8;\n padding: 1px 6px; border-radius: 3px; font-size: 0.85em;\n font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;\n }\n .btn {\n display: inline-block;\n background: #14b8a6; color: #000;\n font-weight: 600; padding: 0.65rem 1.25rem;\n border-radius: 6px; text-decoration: none;\n margin-top: 1.25rem; transition: opacity 0.15s;\n }\n .btn:hover { opacity: 0.85; }\n .hint {\n margin-top: 1.75rem; padding-top: 1.5rem;\n border-top: 1px solid #2a2a2a; font-size: 0.85rem; color: #666;\n }\n </style>\n</head>\n<body>\n <div class=\"card\">\n <div class=\"brand\">\n <span class=\"icon\">◈</span>\n <span class=\"name\">Cordon for MCP</span>\n </div>\n <div class=\"mark\">${mark}</div>\n <h1>${heading}</h1>\n ${body}\n </div>\n</body>\n</html>`;\n}\n\nasync function listenForCallback(port: number, expectedState: string, dashboardUrl: string): Promise<CallbackResult> {\n return new Promise((resolve, reject) => {\n const server = createServer((req, res) => {\n const url = new URL(req.url ?? '/', `http://localhost:${port}`);\n if (url.pathname !== '/cb') {\n res.writeHead(404).end('not found');\n return;\n }\n const token = url.searchParams.get('token') ?? '';\n const state = url.searchParams.get('state') ?? '';\n const signup = url.searchParams.get('signup') ?? 'false';\n\n if (state !== expectedState || !token) {\n const body =\n `<p>The login link didn't carry a valid token, or the security state didn't match.</p>` +\n `<p class=\"hint\">Run <code>cordon login</code> again from your terminal. You can close this tab.</p>`;\n res.writeHead(400, { 'Content-Type': 'text/html' })\n .end(renderCallbackPage('error', \"Login didn't complete\", body));\n server.close();\n reject(new Error('Callback state mismatch'));\n return;\n }\n\n const body =\n `<p>Your API key is saved. Return to your terminal to continue,` +\n ` or jump straight to your dashboard.</p>` +\n `<a href=\"${dashboardUrl}\" class=\"btn\">Open dashboard →</a>` +\n `<p class=\"hint\">You can close this tab.</p>`;\n res.writeHead(200, { 'Content-Type': 'text/html' })\n .end(renderCallbackPage('success', \"You're logged in\", body));\n server.close();\n resolve({ token, state, signup });\n });\n\n server.listen(port, '127.0.0.1');\n server.on('error', (err) => reject(err));\n\n setTimeout(() => {\n server.close();\n reject(new Error('Login timed out after 5 minutes'));\n }, TIMEOUT_MS).unref();\n });\n}\n\nexport async function loginCommand(options: LoginOptions = {}): Promise<void> {\n const endpoint = (options.endpoint ?? DEFAULT_ENDPOINT).replace(/\\/$/, '');\n const state = randomBytes(16).toString('hex');\n\n let port: number | null = null;\n let result: CallbackResult | null = null;\n let lastErr: unknown = null;\n\n for (const candidate of PORT_RANGE) {\n try {\n const callback = `http://localhost:${candidate}/cb`;\n const authUrl =\n `${endpoint}/auth/cli/start?callback=${encodeURIComponent(callback)}&state=${state}`;\n\n process.stderr.write(\n `\\n[cordon] opening browser to log in...\\n` +\n `[cordon] if it doesn't open, visit:\\n ${authUrl}\\n\\n`,\n );\n openBrowser(authUrl);\n\n const pending = listenForCallback(candidate, state, `${endpoint}/dashboard/`);\n port = candidate;\n result = await pending;\n break;\n } catch (err) {\n lastErr = err;\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'EADDRINUSE') continue;\n throw err;\n }\n }\n\n if (!result) {\n throw lastErr instanceof Error ? lastErr : new Error('Could not bind a local callback port');\n }\n\n setAuth({\n endpoint,\n apiKey: result.token,\n loggedInAt: new Date().toISOString(),\n });\n\n process.stderr.write(`\\x1b[32m✓\\x1b[0m logged in. API key saved to ~/.cordon/auth.json\\n`);\n if (result.signup === 'true') {\n process.stderr.write(\n `Welcome to Cordon for MCP.\\n` +\n `Dashboard: \\x1b[36m${endpoint}/dashboard/\\x1b[0m\\n` +\n `Run \\x1b[36mcordon init\\x1b[0m next to wire up your MCP servers.\\n`,\n );\n }\n // Suppress unused var warning when port is set but not otherwise used\n void port;\n}\n"],"mappings":";AAAA,SAAS,qBAAqB;;;ACA9B,SAAS,kBAAkB;AAC3B,SAAS,YAAY;AACrB,SAAS,eAAe;AACxB,SAAS,kBAAkB;AAG3B,IAAM,eAAe;AAAA,EACnB,MAAM,KAAK,QAAQ,IAAI,GAAG,kBAAkB;AAAA,EAC5C,MAAM,KAAK,QAAQ,IAAI,GAAG,kBAAkB;AAAA,EAC5C,MAAM,KAAK,QAAQ,GAAG,WAAW,WAAW;AAAA,EAC5C,MAAM,KAAK,QAAQ,GAAG,WAAW,WAAW;AAC9C;AAEA,eAAsB,eAAe,UAAoC;AACvE,MAAI,UAAU;AACZ,QAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,YAAM,IAAI,MAAM,0BAA0B,QAAQ,EAAE;AAAA,IACtD;AACA,WAAO;AAAA,EACT;AAEA,aAAW,aAAa,cAAc;AACpC,UAAM,IAAI,UAAU;AACpB,QAAI,WAAW,CAAC,EAAG,QAAO;AAAA,EAC5B;AAEA,QAAM,IAAI;AAAA,IACR;AAAA,EACF;AACF;AAEA,eAAsB,WAAW,YAA6C;AAE5E,QAAM,OAAO,WAAW,YAAY,GAAG;AACvC,QAAM,MAAM,MAAM,KAAK,OAAO,UAAU;AAExC,QAAM,MAAO,IAAmC,WAAY;AAE5D,MAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,CAAC,MAAM,QAAQ,IAAI,OAAO,GAAG;AAClE,UAAM,IAAI;AAAA,MACR,qBAAqB,UAAU;AAAA,IAEjC;AAAA,EACF;AAEA,SAAO,cAAc,GAAmB;AAC1C;AAEA,SAAS,cAAc,QAAsC;AAC3D,SAAO;AAAA,IACL,GAAG;AAAA,IACH,OAAO,OAAO,SAAS,EAAE,SAAS,MAAM,QAAQ,SAAS;AAAA,IACzD,WAAW,OAAO,aAAa,EAAE,SAAS,WAAW;AAAA,EACvD;AACF;AAEO,SAAS,cAA8B;AAC5C,SAAO,cAAc,EAAE,SAAS,CAAC,EAAE,CAAC;AACtC;;;AC1DA,SAAS,cAAc,eAAe,cAAAA,aAAY,WAAW,WAAW,kBAAkB;AAC1F,SAAS,QAAAC,aAAY;AACrB,SAAS,WAAAC,gBAAe;AAaxB,SAAS,YAAoB;AAC3B,SAAOD,MAAKC,SAAQ,GAAG,SAAS;AAClC;AAEA,SAAS,YAAoB;AAC3B,SAAOD,MAAK,UAAU,GAAG,YAAY;AACvC;AAEA,SAAS,WAAmB;AAC1B,SAAOA,MAAK,UAAU,GAAG,WAAW;AACtC;AAEA,SAAS,kBAAwB;AAC/B,QAAM,MAAM,UAAU;AACtB,MAAI,CAACD,YAAW,GAAG,EAAG,WAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAC1D;AAEO,SAAS,WAAqB;AACnC,QAAM,OAAO,UAAU;AACvB,MAAI,CAACA,YAAW,IAAI,EAAG,QAAO,CAAC;AAC/B,MAAI;AACF,WAAO,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC;AAAA,EAC9C,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEO,SAAS,SAAS,OAAuB;AAC9C,MAAI;AACF,oBAAgB;AAChB,UAAM,SAAS,EAAE,GAAG,SAAS,GAAG,GAAG,MAAM;AACzC,kBAAc,UAAU,GAAG,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,MAAM;AAAA,EACpE,QAAQ;AAAA,EAER;AACF;AAEO,SAAS,UAA0B;AACxC,QAAM,OAAO,SAAS;AACtB,MAAI,CAACA,YAAW,IAAI,EAAG,QAAO;AAC9B,MAAI;AACF,WAAO,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC;AAAA,EAC9C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,QAAQ,MAAqB;AAC3C,kBAAgB;AAChB,QAAM,OAAO,SAAS;AACtB,gBAAc,MAAM,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG,MAAM;AACzD,MAAI,QAAQ,aAAa,SAAS;AAChC,QAAI;AAAE,gBAAU,MAAM,GAAK;AAAA,IAAG,QAAQ;AAAA,IAAoB;AAAA,EAC5D;AACF;;;AFhEA,IAAM,gBAAgB;AAEtB,SAAS,kBAAkB,QAAwC;AACjE,QAAM,OAAO,QAAQ;AACrB,MAAI,CAAC,KAAM,QAAO;AAElB,MAAI,OAAO;AAGX,QAAM,QAAQ,KAAK;AACnB,QAAM,aACJ,UAAU,MAAM,WAAW,YAAa,MAAM,QAAQ,MAAM,MAAM,KAAK,MAAM,OAAO,SAAS,QAAQ;AACvG,MAAI,cAAc,EAAE,MAAM,YAAY,MAAM,SAAS;AACnD,WAAO;AAAA,MACL,GAAG;AAAA,MACH,OAAO,EAAE,GAAG,OAAO,UAAU,MAAM,YAAY,KAAK,UAAU,QAAQ,MAAM,UAAU,KAAK,OAAO;AAAA,IACpG;AAAA,EACF;AAIA,QAAM,YAAY,KAAK;AACvB,MAAI,WAAW,YAAY,WAAW,EAAE,UAAU,YAAY,UAAU,SAAS;AAC/E,WAAO;AAAA,MACL,GAAG;AAAA,MACH,WAAW,EAAE,GAAG,WAAW,UAAU,UAAU,YAAY,KAAK,UAAU,QAAQ,UAAU,UAAU,KAAK,OAAO;AAAA,IACpH;AAAA,EACF;AAEA,SAAO;AACT;AAmBA,SAAS,eAAe,QAAwB,SAAuC;AACrF,QAAM,WAAW,QAAQ,SAAS,QAAQ,OAAO,SAAS,cAAc;AACxE,MAAI,CAAC,UAAU;AACb,QAAI,QAAQ,SAAS,QAAW;AAC9B,cAAQ,OAAO;AAAA,QACb;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,OAAO,SAAS,cAAc,SAAS,OAAO,UAAU;AAC3E,QAAM,YAAY,YAAY,aAAa,QAAQ,IAAI;AAEvD,MAAI,CAAC,WAAW;AACd,YAAQ,OAAO;AAAA,MACb;AAAA,IAGF;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,SAAS;AAAA,MACP,WAAW;AAAA,MACX;AAAA,MACA,GAAI,QAAQ,SAAS,SAAY,EAAE,MAAM,QAAQ,KAAK,IAAI,YAAY,SAAS,SAAY,EAAE,MAAM,WAAW,KAAK,IAAI,CAAC;AAAA,MACxH,GAAI,YAAY,SAAS,SAAY,EAAE,MAAM,WAAW,KAAK,IAAI,CAAC;AAAA,IACpE;AAAA,EACF;AACF;AAEA,eAAsB,aAAa,SAAsC;AACvE,MAAI,CAAC,SAAS,EAAE,YAAY,CAAC,QAAQ,GAAG;AACtC,YAAQ,OAAO;AAAA,MACb;AAAA;AAAA,+CACgD,aAAa;AAAA;AAAA;AAAA,IAC/D;AACA,aAAS,EAAE,UAAU,KAAK,CAAC;AAAA,EAC7B;AAKA,MAAI,SAAyB,YAAY;AACzC,MAAI,aAA4B;AAEhC,MAAI;AACF,iBAAa,MAAM,eAAe,QAAQ,MAAM;AAAA,EAClD,SAAS,KAAK;AACZ,QAAI,QAAQ,QAAQ;AAClB,cAAQ,OAAO,MAAM,yBAAyB,OAAO,GAAG,CAAC;AAAA,CAAI;AAC7D,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,YAAQ,OAAO;AAAA,MACb;AAAA,IAEF;AAAA,EACF;AAEA,MAAI,YAAY;AACd,YAAQ,OAAO,MAAM,gCAAgC,UAAU;AAAA,CAAI;AACnE,QAAI;AACF,eAAS,MAAM,WAAW,UAAU;AAAA,IACtC,SAAS,KAAK;AACZ,cAAQ,OAAO,MAAM,yBAAyB,OAAO,GAAG,CAAC;AAAA,CAAI;AAC7D,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AAEA,WAAS,kBAAkB,MAAM;AACjC,WAAS,eAAe,QAAQ,OAAO;AAEvC,QAAM,UAAU,IAAI,cAAc,MAAM;AAExC,QAAM,WAAW,YAAY;AAC3B,YAAQ,OAAO,MAAM,+BAA+B;AACpD,QAAI;AACF,YAAM,QAAQ,KAAK;AACnB,cAAQ,KAAK,CAAC;AAAA,IAChB,SAAS,KAAK;AACZ,cAAQ,OAAO,MAAM,0CAA0C,OAAO,GAAG,CAAC;AAAA,CAAI;AAC9E,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AAEA,UAAQ,GAAG,UAAU,QAAQ;AAC7B,UAAQ,GAAG,WAAW,QAAQ;AAE9B,MAAI;AACF,UAAM,QAAQ,MAAM;AAAA,EACtB,SAAS,KAAK;AACZ,YAAQ,OAAO,MAAM,yCAAyC,OAAO,GAAG,CAAC;AAAA,CAAI;AAC7E,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;;;AGvJA,SAAS,gBAAAG,eAAc,iBAAAC,gBAAe,cAAAC,mBAAkB;AACxD,SAAS,iBAAiB;AAC1B,SAAS,uBAAuB;AAChC,SAAS,QAAAC,aAAY;AACrB,SAAS,WAAAC,gBAAe;;;ACJxB,SAAS,oBAAoB;AAC7B,SAAS,aAAa;AACtB,SAAS,mBAAmB;AAG5B,IAAM,mBAAmB;AACzB,IAAM,aAAa,CAAC,OAAO,OAAO,KAAK;AACvC,IAAM,aAAa,IAAI,KAAK;AAM5B,SAAS,YAAY,KAAmB;AACtC,QAAM,MAAM,QAAQ,aAAa,WAAW,SACxC,QAAQ,aAAa,UAAU,QAC/B;AAKJ,QAAM,UAAU,QAAQ,aAAa,UACjC,IAAI,QAAQ,OAAO,IAAI,EAAE,QAAQ,MAAM,IAAI,IAC3C;AACJ,QAAM,OAAO,QAAQ,aAAa,UAAU,CAAC,MAAM,SAAS,MAAM,OAAO,IAAI,CAAC,OAAO;AACrF,MAAI;AACF,UAAM,KAAK,MAAM,EAAE,UAAU,MAAM,OAAO,SAAS,CAAC,EAAE,MAAM;AAAA,EAC9D,QAAQ;AAAA,EAAsE;AAChF;AAQA,SAAS,mBAAmB,MAA2B,SAAiB,MAAsB;AAC5F,QAAM,SAAS,SAAS,YAAY,YAAY;AAChD,QAAM,WAAW,SAAS,YAAY,6BAA6B;AACnE,QAAM,OAAO,SAAS,YAAY,WAAM;AACxC,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,WAKE,SAAS,YAAY,cAAc,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAkBxC,QAAQ,YAAY,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAgCtB,IAAI;AAAA,UAClB,OAAO;AAAA,MACX,IAAI;AAAA;AAAA;AAAA;AAIV;AAEA,eAAe,kBAAkB,MAAc,eAAuB,cAA+C;AACnH,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,SAAS,aAAa,CAAC,KAAK,QAAQ;AACxC,YAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,oBAAoB,IAAI,EAAE;AAC9D,UAAI,IAAI,aAAa,OAAO;AAC1B,YAAI,UAAU,GAAG,EAAE,IAAI,WAAW;AAClC;AAAA,MACF;AACA,YAAM,QAAQ,IAAI,aAAa,IAAI,OAAO,KAAK;AAC/C,YAAM,QAAQ,IAAI,aAAa,IAAI,OAAO,KAAK;AAC/C,YAAM,SAAS,IAAI,aAAa,IAAI,QAAQ,KAAK;AAEjD,UAAI,UAAU,iBAAiB,CAAC,OAAO;AACrC,cAAMC,QACJ;AAEF,YAAI,UAAU,KAAK,EAAE,gBAAgB,YAAY,CAAC,EAC/C,IAAI,mBAAmB,SAAS,yBAAyBA,KAAI,CAAC;AACjE,eAAO,MAAM;AACb,eAAO,IAAI,MAAM,yBAAyB,CAAC;AAC3C;AAAA,MACF;AAEA,YAAM,OACJ,kHAEY,YAAY;AAE1B,UAAI,UAAU,KAAK,EAAE,gBAAgB,YAAY,CAAC,EAC/C,IAAI,mBAAmB,WAAW,oBAAoB,IAAI,CAAC;AAC9D,aAAO,MAAM;AACb,cAAQ,EAAE,OAAO,OAAO,OAAO,CAAC;AAAA,IAClC,CAAC;AAED,WAAO,OAAO,MAAM,WAAW;AAC/B,WAAO,GAAG,SAAS,CAAC,QAAQ,OAAO,GAAG,CAAC;AAEvC,eAAW,MAAM;AACf,aAAO,MAAM;AACb,aAAO,IAAI,MAAM,iCAAiC,CAAC;AAAA,IACrD,GAAG,UAAU,EAAE,MAAM;AAAA,EACvB,CAAC;AACH;AAEA,eAAsB,aAAa,UAAwB,CAAC,GAAkB;AAC5E,QAAM,YAAY,QAAQ,YAAY,kBAAkB,QAAQ,OAAO,EAAE;AACzE,QAAM,QAAQ,YAAY,EAAE,EAAE,SAAS,KAAK;AAE5C,MAAI,OAAsB;AAC1B,MAAI,SAAgC;AACpC,MAAI,UAAmB;AAEvB,aAAW,aAAa,YAAY;AAClC,QAAI;AACF,YAAM,WAAW,oBAAoB,SAAS;AAC9C,YAAM,UACJ,GAAG,QAAQ,4BAA4B,mBAAmB,QAAQ,CAAC,UAAU,KAAK;AAEpF,cAAQ,OAAO;AAAA,QACb;AAAA;AAAA;AAAA,IAC0C,OAAO;AAAA;AAAA;AAAA,MACnD;AACA,kBAAY,OAAO;AAEnB,YAAM,UAAU,kBAAkB,WAAW,OAAO,GAAG,QAAQ,aAAa;AAC5E,aAAO;AACP,eAAS,MAAM;AACf;AAAA,IACF,SAAS,KAAK;AACZ,gBAAU;AACV,YAAM,OAAQ,IAA8B;AAC5C,UAAI,SAAS,aAAc;AAC3B,YAAM;AAAA,IACR;AAAA,EACF;AAEA,MAAI,CAAC,QAAQ;AACX,UAAM,mBAAmB,QAAQ,UAAU,IAAI,MAAM,sCAAsC;AAAA,EAC7F;AAEA,UAAQ;AAAA,IACN;AAAA,IACA,QAAQ,OAAO;AAAA,IACf,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,EACrC,CAAC;AAED,UAAQ,OAAO,MAAM;AAAA,CAAoE;AACzF,MAAI,OAAO,WAAW,QAAQ;AAC5B,YAAQ,OAAO;AAAA,MACb;AAAA,qBACsB,QAAQ;AAAA;AAAA;AAAA,IAEhC;AAAA,EACF;AAEA,OAAK;AACP;;;AD/LA,IAAMC,iBAAgB;AAEtB,SAAS,yBAAyB,KAAmB;AAInD,MAAIC,YAAWC,MAAK,KAAK,gBAAgB,qBAAqB,cAAc,CAAC,GAAG;AAC9E;AAAA,EACF;AAEA,MAAI,CAACD,YAAWC,MAAK,KAAK,cAAc,CAAC,GAAG;AAC1C,IAAAC;AAAA,MACED,MAAK,KAAK,cAAc;AAAA,MACxB,KAAK,UAAU,EAAE,MAAM,iBAAiB,SAAS,SAAS,SAAS,KAAK,GAAG,MAAM,CAAC,IAAI;AAAA,MACtF;AAAA,IACF;AACA,YAAQ,OAAO,MAAM;AAAA,CAAyC;AAAA,EAChE;AAEA,UAAQ,OAAO,MAAM;AAAA,CAA4C;AACjE,QAAM,SAAS,QAAQ,aAAa,UAAU,YAAY;AAC1D,QAAM,SAAS,UAAU,QAAQ,CAAC,WAAW,mBAAmB,GAAG;AAAA,IACjE;AAAA,IACA,OAAO,CAAC,UAAU,WAAW,SAAS;AAAA,EACxC,CAAC;AAED,MAAI,OAAO,WAAW,GAAG;AACvB,YAAQ,OAAO,MAAM;AAAA,CAAgD;AAAA,EACvE,OAAO;AACL,YAAQ,OAAO;AAAA,MACb;AAAA;AAAA,IAEF;AAAA,EACF;AACF;AAEA,eAAe,YAAY,UAAoC;AAG7D,MAAI,CAAC,QAAQ,MAAM,MAAO,QAAO;AACjC,QAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,OAAG,SAAS,UAAU,CAAC,WAAW;AAChC,SAAG,MAAM;AACT,YAAM,UAAU,OAAO,KAAK,EAAE,YAAY;AAE1C,cAAQ,YAAY,MAAM,YAAY,OAAO,YAAY,KAAK;AAAA,IAChE,CAAC;AAAA,EACH,CAAC;AACH;AAMA,SAAS,sBAAqC;AAC5C,QAAM,aAAuB,CAAC;AAE9B,MAAI,QAAQ,aAAa,UAAU;AACjC,eAAW;AAAA,MACTA,MAAKE,SAAQ,GAAG,WAAW,uBAAuB,UAAU,4BAA4B;AAAA,IAC1F;AAAA,EACF,WAAW,QAAQ,aAAa,SAAS;AACvC,UAAM,UAAU,QAAQ,IAAI,SAAS,KAAKF,MAAKE,SAAQ,GAAG,WAAW,SAAS;AAC9E,eAAW,KAAKF,MAAK,SAAS,UAAU,4BAA4B,CAAC;AAAA,EACvE,OAAO;AACL,eAAW,KAAKA,MAAKE,SAAQ,GAAG,WAAW,UAAU,4BAA4B,CAAC;AAAA,EACpF;AAEA,SAAO,WAAW,KAAK,CAAC,MAAMH,YAAW,CAAC,CAAC,KAAK;AAClD;AAEA,eAAsB,cAA6B;AACjD,QAAM,aAAaC,MAAK,QAAQ,IAAI,GAAG,kBAAkB;AAEzD,MAAID,YAAW,UAAU,GAAG;AAC1B,YAAQ,OAAO;AAAA,MACb;AAAA;AAAA,IACF;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,QAAM,aAAa,oBAAoB;AACvC,MAAI,eAAoC,CAAC;AAEzC,MAAI,YAAY;AACd,YAAQ,OAAO,MAAM,2CAA2C,UAAU;AAAA,CAAI;AAC9E,QAAI;AACF,qBAAe,KAAK,MAAMI,cAAa,YAAY,MAAM,CAAC;AAAA,IAC5D,QAAQ;AACN,cAAQ,OAAO,MAAM;AAAA,CAA8D;AAAA,IACrF;AAAA,EACF,OAAO;AACL,YAAQ,OAAO;AAAA,MACb;AAAA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU,OAAO,QAAQ,aAAa,cAAc,CAAC,CAAC;AAG5D,QAAM,eAAe,QAAQ,SAAS,IAClC,QACG,IAAI,CAAC,CAAC,MAAM,GAAG,MAAM;AACpB,UAAM,UAAU,IAAI,MAAM,SACtB,WAAW,KAAK,UAAU,IAAI,IAAI,CAAC,KACnC;AACJ,UAAM,SACJ,IAAI,OAAO,OAAO,KAAK,IAAI,GAAG,EAAE,SAC5B,UAAU,KAAK,UAAU,IAAI,GAAG,CAAC,KACnC;AACJ,WAAO;AAAA,cACH,KAAK,UAAU,IAAI,CAAC;AAAA;AAAA,iBAEjB,KAAK,UAAU,IAAI,OAAO,CAAC,GAAG,OAAO,GAAG,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOvD,CAAC,EACA,KAAK,IAAI,IACZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQJ,MAAI,OAAO,QAAQ;AACnB,MAAI,cAAc;AAClB,MAAI,CAAC,QAAQ,QAAQ,MAAM,OAAO;AAChC,kBAAc;AACd,YAAQ,OAAO;AAAA,MACb;AAAA;AAAA;AAAA,IAEF;AACA,UAAM,aAAa,MAAM,YAAY,iCAAiC;AACtE,QAAI,YAAY;AACd,UAAI;AACF,cAAM,aAAa;AACnB,eAAO,QAAQ;AAAA,MACjB,SAAS,KAAK;AACZ,cAAM,MAAM,eAAe,QAAQ,IAAI,UAAU;AACjD,gBAAQ,OAAO;AAAA,UACb,+CAA+C,GAAG;AAAA;AAAA,QAEpD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,aAAa,OACf;AAAA;AAAA;AAAA;AAAA,QAKA;AAAA;AAAA;AAAA;AAAA;AAMJ,QAAM,iBAAiB,OACnB;AAAA;AAAA;AAAA;AAAA;AAAA,QAMA;AAAA;AAAA;AAAA;AAKJ,QAAM,UAAU;AAAA;AAAA;AAAA;AAAA,EAIhB,YAAY;AAAA;AAAA;AAAA,IAGV,UAAU;AAAA;AAAA,IAEV,cAAc;AAAA;AAAA;AAIhB,EAAAF,eAAc,YAAY,SAAS,MAAM;AACzC,UAAQ,OAAO,MAAM;AAAA,CAA2C;AAIhE,2BAAyB,QAAQ,IAAI,CAAC;AAWtC,MAAI,cAAc,QAAQ,SAAS,GAAG;AACpC,UAAM,mBAAmB,WAAW,QAAQ,OAAO,GAAG;AACtD,UAAM,WAAW,QAAQ,SAAS,QAAQ,OAAO,GAAG;AACpD,UAAM,oBAAoB,QAAQ,KAAK,CAAC,KAAK,IAAI,QAAQ,OAAO,GAAG;AAEnE,QAAI,CAAC,kBAAkB;AACrB,cAAQ,OAAO;AAAA,QACb;AAAA;AAAA,4BAG+B,QAAQ,4DAA4D,gBAAgB;AAAA;AAAA,MACrH;AACA;AAAA,IACF;AAEA,UAAM,kBAAuC;AAAA,MAC3C,GAAG;AAAA,MACH,YAAY;AAAA,QACV,QAAQ;AAAA,UACN,SAAS;AAAA,UACT,MAAM,CAAC,kBAAkB,SAAS,YAAY,gBAAgB;AAAA,QAChE;AAAA,MACF;AAAA,IACF;AAIA,UAAM,aAAa,GAAG,UAAU;AAChC,QAAIF,YAAW,UAAU,GAAG;AAC1B,cAAQ,OAAO;AAAA,QACb,+BAA+B,UAAU;AAAA;AAAA,MAC3C;AAAA,IACF,OAAO;AACL,MAAAE,eAAc,YAAYE,cAAa,YAAY,MAAM,GAAG,MAAM;AAClE,cAAQ,OAAO,MAAM,4DAAuD,UAAU;AAAA,CAAI;AAAA,IAC5F;AAEA,IAAAF,eAAc,YAAY,KAAK,UAAU,iBAAiB,MAAM,CAAC,GAAG,MAAM;AAC1E,YAAQ,OAAO,MAAM;AAAA,CAAkD;AACvE,YAAQ,OAAO;AAAA,MACb;AAAA;AAAA;AAAA,IACF;AAAA,EACF,WAAW,CAAC,YAAY;AACtB,UAAM,WAAW,QAAQ,SAAS,QAAQ,OAAO,GAAG;AACpD,UAAM,oBAAoB,QAAQ,KAAK,CAAC,KAAK,uBAAuB,QAAQ,OAAO,GAAG;AACtF,YAAQ,OAAO;AAAA,MACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAIqB,QAAQ;AAAA,kBACV,gBAAgB,4BAA4B,WAAW,QAAQ,OAAO,GAAG,CAAC;AAAA;AAAA;AAAA;AAAA,IAG/F;AAAA,EACF,OAAO;AAEL,YAAQ,OAAO;AAAA,MACb;AAAA;AAAA;AAAA,IACF;AAAA,EACF;AAEA,MAAI,MAAM;AACR,YAAQ,OAAO;AAAA,MACb;AAAA,uEAAqE,KAAK,QAAQ;AAAA;AAAA,IACpF;AAAA,EACF,WAAW,aAAa;AACtB,YAAQ,OAAO;AAAA,MACb;AAAA;AAAA;AAAA,IACF;AAAA,EACF,OAAO;AACL,YAAQ,OAAO;AAAA,MACb;AAAA;AAAA,4EAEiBH,cAAa;AAAA;AAAA,IAChC;AAAA,EACF;AACA,WAAS,EAAE,UAAU,KAAK,CAAC;AAC7B;","names":["existsSync","join","homedir","readFileSync","writeFileSync","existsSync","join","homedir","body","DASHBOARD_URL","existsSync","join","writeFileSync","homedir","readFileSync"]}
{
"name": "@getcordon/cli",
"version": "0.4.0",
"version": "0.4.1",
"mcpName": "io.github.marras0914/cordon",

@@ -5,0 +5,0 @@ "description": "Cordon for MCP — security gateway for MCP tool calls. Firewall, auditor, and human-in-the-loop approvals over any stdio MCP server.",