cordon-cli
Advanced tools
| #!/usr/bin/env node | ||
| process.stderr.write([ | ||
| "", | ||
| " cordon-cli has been renamed to @getcordon/cli.", | ||
| "", | ||
| " Run:", | ||
| " npm install -g @getcordon/cli", | ||
| "", | ||
| " The new package has the activation flow, the Windows OAuth fix,", | ||
| " the dashboard integration, and call-graph policies.", | ||
| "", | ||
| " Repo: https://github.com/marras0914/cordon", | ||
| " Site: https://getcordon.com", | ||
| "", | ||
| "" | ||
| ].join("\n")); | ||
| process.exit(1); |
+21
| MIT License | ||
| Copyright (c) 2026 Cordon Contributors | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. |
+19
-53
| { | ||
| "name": "cordon-cli", | ||
| "version": "0.1.18", | ||
| "mcpName": "io.github.marras0914/cordon", | ||
| "description": "Cordon for MCP — security gateway for MCP tool calls. Firewall, auditor, and human-in-the-loop approvals over any stdio MCP server.", | ||
| "version": "0.1.19", | ||
| "description": "DEPRECATED — Cordon CLI has moved to @getcordon/cli. Run: npm install -g @getcordon/cli", | ||
| "bin": { | ||
| "cordon": "bin/cordon.js" | ||
| }, | ||
| "files": [ | ||
| "bin", | ||
| "README.md", | ||
| "LICENSE" | ||
| ], | ||
| "keywords": [ | ||
| "deprecated", | ||
| "cordon", | ||
| "mcp" | ||
| ], | ||
| "license": "MIT", | ||
| "author": "marras0914", | ||
| "license": "MIT", | ||
| "homepage": "https://getcordon.com", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/marras0914/cordon.git", | ||
| "directory": "packages/cli" | ||
| "url": "https://github.com/marras0914/cordon.git", | ||
| "directory": "packaging/cordon-cli" | ||
| }, | ||
| "bugs": { | ||
| "url": "https://github.com/marras0914/cordon/issues" | ||
| }, | ||
| "keywords": [ | ||
| "mcp", | ||
| "security", | ||
| "gateway", | ||
| "proxy", | ||
| "human-in-the-loop", | ||
| "audit-log", | ||
| "tool-call", | ||
| "ai-agent", | ||
| "cli", | ||
| "ai", | ||
| "llm", | ||
| "policy", | ||
| "cordon" | ||
| ], | ||
| "type": "module", | ||
| "bin": { | ||
| "cordon": "dist/bin/cordon.js" | ||
| }, | ||
| "exports": { | ||
| ".": { | ||
| "import": "./dist/index.js", | ||
| "types": "./dist/index.d.ts" | ||
| } | ||
| }, | ||
| "files": [ | ||
| "dist", | ||
| "README.md" | ||
| ], | ||
| "scripts": { | ||
| "build": "tsup", | ||
| "dev": "tsup --watch" | ||
| }, | ||
| "dependencies": { | ||
| "@getcordon/core": "*", | ||
| "cordon-sdk": "*", | ||
| "commander": "^12.1.0", | ||
| "jiti": "^2.4.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/node": "^22.0.0", | ||
| "typescript": "^5.6.0", | ||
| "tsup": "^8.3.0" | ||
| } | ||
| "deprecated": "cordon-cli has been renamed. Run: npm install -g @getcordon/cli" | ||
| } |
+6
-58
@@ -1,64 +0,12 @@ | ||
| # cordon-cli | ||
| # cordon-cli (deprecated) | ||
| The command-line interface for [Cordon for MCP](https://github.com/marras0914/cordon), the security gateway for MCP tool calls. | ||
| This package has been renamed. Install instead: | ||
| ## Install | ||
| ```bash | ||
| npm install -g cordon-cli | ||
| npm install -g @getcordon/cli | ||
| ``` | ||
| ## Quickstart | ||
| The new package has the activation flow, the Windows OAuth fix, the dashboard integration, and call-graph policies. | ||
| ```bash | ||
| cordon init # reads your Claude Desktop config, generates cordon.config.ts, | ||
| # patches Claude Desktop to route tool calls through Cordon | ||
| cordon start # launches the gateway | ||
| ``` | ||
| Restart Claude Desktop after `cordon init`. Every MCP tool call now flows through Cordon. | ||
| ## What it does | ||
| - Scans `claude_desktop_config.json` and generates a starter `cordon.config.ts` | ||
| - Patches your MCP client config to route through Cordon (opt-in, backed up) | ||
| - Runs the gateway as an MCP server that aggregates your existing upstream servers | ||
| - Enforces per-tool policies (allow, block, approve, read-only, log-only, hidden, sql-read-only, sql-approve-writes) | ||
| - Supports closed-world tool catalogs via `knownTools` so new upstream tools don't silently become callable | ||
| - SQL-aware policies parse the statement in tool-call args and decide based on type (SELECT vs DML vs DDL) | ||
| - Surfaces approval prompts in the terminal, or Slack via the hosted dashboard | ||
| ## Config example | ||
| ```typescript | ||
| import { defineConfig } from 'cordon-sdk'; | ||
| export default defineConfig({ | ||
| servers: [ | ||
| { | ||
| name: 'database', | ||
| transport: 'stdio', | ||
| command: 'npx', | ||
| args: ['-y', '@modelcontextprotocol/server-postgres', process.env.POSTGRES_URL!], | ||
| policy: 'read-only', | ||
| // Optional: declare the exact tool surface you expect. New tools in | ||
| // future upstream releases get blocked until you add them here. | ||
| knownTools: ['query', 'list_tables', 'describe_table'], | ||
| }, | ||
| ], | ||
| audit: { enabled: true, output: 'file' }, | ||
| approvals: { channel: 'terminal', timeoutMs: 60_000 }, | ||
| }); | ||
| ``` | ||
| ## Full documentation | ||
| Complete policy reference, approval channels, audit outputs, and architecture overview: | ||
| **https://github.com/marras0914/cordon** | ||
| Writeup with real-world examples: | ||
| **https://dev.to/marras0914/mcp-has-no-security-model-heres-how-to-fix-it-in-2-minutes-5f7o** | ||
| ## License | ||
| MIT | ||
| - Repo: https://github.com/marras0914/cordon | ||
| - Site: https://getcordon.com |
| #!/usr/bin/env node | ||
| // src/bin/cordon.ts | ||
| import { Command } from "commander"; | ||
| // src/commands/start.ts | ||
| import { CordonGateway } from "@getcordon/core"; | ||
| // src/config-loader.ts | ||
| import { existsSync } from "fs"; | ||
| import { join } from "path"; | ||
| import { homedir } from "os"; | ||
| import { createJiti } from "jiti"; | ||
| var SEARCH_PATHS = [ | ||
| () => join(process.cwd(), "cordon.config.ts"), | ||
| () => join(process.cwd(), "cordon.config.js"), | ||
| () => join(homedir(), ".cordon", "config.ts"), | ||
| () => join(homedir(), ".cordon", "config.js") | ||
| ]; | ||
| async function findConfigPath(explicit) { | ||
| if (explicit) { | ||
| if (!existsSync(explicit)) { | ||
| throw new Error(`Config file not found: ${explicit}`); | ||
| } | ||
| return explicit; | ||
| } | ||
| for (const candidate of SEARCH_PATHS) { | ||
| const p = candidate(); | ||
| if (existsSync(p)) return p; | ||
| } | ||
| throw new Error( | ||
| "No cordon config found. Run `cordon init` to create one, or pass --config <path>." | ||
| ); | ||
| } | ||
| async function loadConfig(configPath) { | ||
| const jiti = createJiti(import.meta.url); | ||
| const mod = await jiti.import(configPath); | ||
| const raw = mod.default ?? mod; | ||
| if (!raw || typeof raw !== "object" || !Array.isArray(raw.servers)) { | ||
| throw new Error( | ||
| `Invalid config at ${configPath}: expected an object with a 'servers' array. Make sure you are using \`export default defineConfig({...})\`.` | ||
| ); | ||
| } | ||
| return applyDefaults(raw); | ||
| } | ||
| function applyDefaults(config) { | ||
| return { | ||
| ...config, | ||
| audit: config.audit ?? { enabled: true, output: "stdout" }, | ||
| approvals: config.approvals ?? { channel: "terminal" } | ||
| }; | ||
| } | ||
| function emptyConfig() { | ||
| return applyDefaults({ servers: [] }); | ||
| } | ||
| // src/cli-state.ts | ||
| import { readFileSync, writeFileSync, existsSync as existsSync2, mkdirSync, chmodSync, unlinkSync } from "fs"; | ||
| import { join as join2 } from "path"; | ||
| import { homedir as homedir2 } from "os"; | ||
| function cordonDir() { | ||
| return join2(homedir2(), ".cordon"); | ||
| } | ||
| function statePath() { | ||
| return join2(cordonDir(), "state.json"); | ||
| } | ||
| function authPath() { | ||
| return join2(cordonDir(), "auth.json"); | ||
| } | ||
| function ensureCordonDir() { | ||
| const dir = cordonDir(); | ||
| if (!existsSync2(dir)) mkdirSync(dir, { recursive: true }); | ||
| } | ||
| function getState() { | ||
| const path = statePath(); | ||
| if (!existsSync2(path)) return {}; | ||
| try { | ||
| return JSON.parse(readFileSync(path, "utf8")); | ||
| } catch { | ||
| return {}; | ||
| } | ||
| } | ||
| function setState(patch) { | ||
| try { | ||
| ensureCordonDir(); | ||
| const merged = { ...getState(), ...patch }; | ||
| writeFileSync(statePath(), JSON.stringify(merged, null, 2), "utf8"); | ||
| } catch { | ||
| } | ||
| } | ||
| function getAuth() { | ||
| const path = authPath(); | ||
| if (!existsSync2(path)) return null; | ||
| try { | ||
| return JSON.parse(readFileSync(path, "utf8")); | ||
| } catch { | ||
| return null; | ||
| } | ||
| } | ||
| function setAuth(auth) { | ||
| ensureCordonDir(); | ||
| const path = authPath(); | ||
| writeFileSync(path, JSON.stringify(auth, null, 2), "utf8"); | ||
| if (process.platform !== "win32") { | ||
| try { | ||
| chmodSync(path, 384); | ||
| } catch { | ||
| } | ||
| } | ||
| } | ||
| function clearAuth() { | ||
| const path = authPath(); | ||
| if (!existsSync2(path)) return false; | ||
| try { | ||
| unlinkSync(path); | ||
| return true; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
| // src/commands/start.ts | ||
| var DASHBOARD_URL = "https://cordon-server-production.up.railway.app/dashboard/"; | ||
| function applyAuthDefaults(config) { | ||
| const auth = getAuth(); | ||
| if (!auth) return config; | ||
| const audit = config.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 | ||
| } | ||
| }; | ||
| } | ||
| async function startCommand(options) { | ||
| if (!getState().welcomed && !getAuth()) { | ||
| process.stderr.write( | ||
| ` | ||
| \x1B[36m[cordon] Want centralized audit logs + Slack approvals?\x1B[0m | ||
| [cordon] Run \`cordon login\` or register at ${DASHBOARD_URL}?utm_source=cli_start | ||
| ` | ||
| ); | ||
| setState({ welcomed: true }); | ||
| } | ||
| let config = emptyConfig(); | ||
| let configPath = null; | ||
| try { | ||
| configPath = await findConfigPath(options.config); | ||
| } catch (err) { | ||
| if (options.config) { | ||
| process.stderr.write(`\x1B[31merror\x1B[0m: ${String(err)} | ||
| `); | ||
| process.exit(1); | ||
| } | ||
| process.stderr.write( | ||
| "\x1B[33m[cordon] no cordon.config.ts found \u2014 starting with zero upstream servers.\x1B[0m\n[cordon] Run `cordon init` to generate a config and connect real MCP servers.\n" | ||
| ); | ||
| } | ||
| if (configPath) { | ||
| process.stderr.write(`[cordon] loading config from ${configPath} | ||
| `); | ||
| try { | ||
| config = await loadConfig(configPath); | ||
| } catch (err) { | ||
| process.stderr.write(`\x1B[31merror\x1B[0m: ${String(err)} | ||
| `); | ||
| process.exit(1); | ||
| } | ||
| } | ||
| config = applyAuthDefaults(config); | ||
| const gateway = new CordonGateway(config); | ||
| const shutdown = async () => { | ||
| process.stderr.write("\n[cordon] shutting down...\n"); | ||
| try { | ||
| await gateway.stop(); | ||
| process.exit(0); | ||
| } catch (err) { | ||
| process.stderr.write(`\x1B[31merror\x1B[0m: shutdown failed: ${String(err)} | ||
| `); | ||
| process.exit(1); | ||
| } | ||
| }; | ||
| process.on("SIGINT", shutdown); | ||
| process.on("SIGTERM", shutdown); | ||
| try { | ||
| await gateway.start(); | ||
| } catch (err) { | ||
| process.stderr.write(`\x1B[31merror\x1B[0m: gateway failed: ${String(err)} | ||
| `); | ||
| process.exit(1); | ||
| } | ||
| } | ||
| // src/commands/init.ts | ||
| import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3 } from "fs"; | ||
| import { spawnSync } from "child_process"; | ||
| import { join as join3 } from "path"; | ||
| import { homedir as homedir3 } from "os"; | ||
| var DASHBOARD_URL2 = "https://cordon-server-production.up.railway.app/dashboard/"; | ||
| function ensureCordonSdkInstalled(cwd) { | ||
| if (existsSync3(join3(cwd, "node_modules", "cordon-sdk", "package.json"))) { | ||
| return; | ||
| } | ||
| if (!existsSync3(join3(cwd, "package.json"))) { | ||
| writeFileSync2( | ||
| join3(cwd, "package.json"), | ||
| JSON.stringify({ name: "cordon-config", version: "0.0.0", private: true }, null, 2) + "\n", | ||
| "utf8" | ||
| ); | ||
| process.stderr.write(`\x1B[32m\u2713\x1B[0m created package.json | ||
| `); | ||
| } | ||
| process.stderr.write(`[cordon] installing cordon-sdk... | ||
| `); | ||
| const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm"; | ||
| const result = spawnSync(npmCmd, ["install", "cordon-sdk"], { | ||
| cwd, | ||
| stdio: ["ignore", "inherit", "inherit"] | ||
| }); | ||
| if (result.status === 0) { | ||
| process.stderr.write(`\x1B[32m\u2713\x1B[0m installed cordon-sdk | ||
| `); | ||
| } else { | ||
| process.stderr.write( | ||
| `\x1B[33mwarn\x1B[0m: could not auto-install cordon-sdk. Run 'npm install cordon-sdk' in this directory before 'cordon start'. | ||
| ` | ||
| ); | ||
| } | ||
| } | ||
| function getClaudeConfigPath() { | ||
| const candidates = []; | ||
| if (process.platform === "darwin") { | ||
| candidates.push( | ||
| join3(homedir3(), "Library", "Application Support", "Claude", "claude_desktop_config.json") | ||
| ); | ||
| } else if (process.platform === "win32") { | ||
| const appData = process.env["APPDATA"] ?? join3(homedir3(), "AppData", "Roaming"); | ||
| candidates.push(join3(appData, "Claude", "claude_desktop_config.json")); | ||
| } else { | ||
| candidates.push(join3(homedir3(), ".config", "Claude", "claude_desktop_config.json")); | ||
| } | ||
| return candidates.find((p) => existsSync3(p)) ?? null; | ||
| } | ||
| async function initCommand() { | ||
| const outputPath = join3(process.cwd(), "cordon.config.ts"); | ||
| if (existsSync3(outputPath)) { | ||
| process.stderr.write( | ||
| `\x1B[33mwarn\x1B[0m: cordon.config.ts already exists \u2014 not overwriting. | ||
| ` | ||
| ); | ||
| process.exit(1); | ||
| } | ||
| const claudePath = getClaudeConfigPath(); | ||
| let claudeConfig = {}; | ||
| if (claudePath) { | ||
| process.stderr.write(`[cordon] found Claude Desktop config at ${claudePath} | ||
| `); | ||
| try { | ||
| claudeConfig = JSON.parse(readFileSync2(claudePath, "utf8")); | ||
| } catch { | ||
| process.stderr.write(`\x1B[33mwarn\x1B[0m: could not parse Claude Desktop config | ||
| `); | ||
| } | ||
| } else { | ||
| process.stderr.write( | ||
| `[cordon] no Claude Desktop config found \u2014 generating a blank config | ||
| ` | ||
| ); | ||
| } | ||
| const servers = Object.entries(claudeConfig.mcpServers ?? {}); | ||
| const serverBlocks = servers.length > 0 ? servers.map(([name, cfg]) => { | ||
| const argsStr = cfg.args?.length ? `, args: ${JSON.stringify(cfg.args)}` : ""; | ||
| const envStr = cfg.env && Object.keys(cfg.env).length ? `, env: ${JSON.stringify(cfg.env)}` : ""; | ||
| return ` { | ||
| name: ${JSON.stringify(name)}, | ||
| transport: 'stdio', | ||
| command: ${JSON.stringify(cfg.command)}${argsStr}${envStr}, | ||
| policy: 'allow', | ||
| // tools: { | ||
| // execute: 'approve', | ||
| // delete: 'block', | ||
| // }, | ||
| },`; | ||
| }).join("\n") : ` // { | ||
| // name: 'my-server', | ||
| // transport: 'stdio', | ||
| // command: 'npx', | ||
| // args: ['-y', '@my-org/my-mcp-server'], | ||
| // policy: 'allow', | ||
| // },`; | ||
| const auth = getAuth(); | ||
| const auditBlock = auth ? `audit: { | ||
| enabled: true, | ||
| output: 'hosted', | ||
| // endpoint + apiKey are auto-loaded from ~/.cordon/auth.json (cordon login) | ||
| },` : `audit: { | ||
| enabled: true, | ||
| output: 'stdout', | ||
| },`; | ||
| const content = `import { defineConfig } from 'cordon-sdk'; | ||
| export default defineConfig({ | ||
| servers: [ | ||
| ${serverBlocks} | ||
| ], | ||
| ${auditBlock} | ||
| approvals: { | ||
| channel: 'terminal', | ||
| // timeoutMs: 60_000, | ||
| }, | ||
| }); | ||
| `; | ||
| writeFileSync2(outputPath, content, "utf8"); | ||
| process.stderr.write(`\x1B[32m\u2713\x1B[0m wrote cordon.config.ts | ||
| `); | ||
| ensureCordonSdkInstalled(process.cwd()); | ||
| if (claudePath && servers.length > 0) { | ||
| const cordonConfigPath = outputPath.replace(/\\/g, "/"); | ||
| const nodePath = process.execPath.replace(/\\/g, "/"); | ||
| const cordonScriptPath = (process.argv[1] ?? "").replace(/\\/g, "/"); | ||
| if (!cordonScriptPath) { | ||
| process.stderr.write( | ||
| `\x1B[33mwarn\x1B[0m: could not detect cordon CLI script path. Skipping Claude Desktop patch. | ||
| Manually add this to your claude_desktop_config.json mcpServers: | ||
| "cordon": { "command": "${nodePath}", "args": ["<path-to-cordon.js>", "start", "--config", "${cordonConfigPath}"] } | ||
| ` | ||
| ); | ||
| return; | ||
| } | ||
| const newClaudeConfig = { | ||
| ...claudeConfig, | ||
| mcpServers: { | ||
| cordon: { | ||
| command: nodePath, | ||
| args: [cordonScriptPath, "start", "--config", cordonConfigPath] | ||
| } | ||
| } | ||
| }; | ||
| const backupPath = `${claudePath}.cordon-backup`; | ||
| if (existsSync3(backupPath)) { | ||
| process.stderr.write( | ||
| `[cordon] existing backup at ${backupPath} preserved (won't overwrite) | ||
| ` | ||
| ); | ||
| } else { | ||
| writeFileSync2(backupPath, readFileSync2(claudePath, "utf8"), "utf8"); | ||
| process.stderr.write(`\x1B[32m\u2713\x1B[0m backed up Claude Desktop config to ${backupPath} | ||
| `); | ||
| } | ||
| writeFileSync2(claudePath, JSON.stringify(newClaudeConfig, null, 2), "utf8"); | ||
| process.stderr.write(`\x1B[32m\u2713\x1B[0m patched Claude Desktop config | ||
| `); | ||
| process.stderr.write( | ||
| ` | ||
| \x1B[36mRestart Claude Desktop to activate Cordon.\x1B[0m | ||
| ` | ||
| ); | ||
| } else if (!claudePath) { | ||
| const nodePath = process.execPath.replace(/\\/g, "/"); | ||
| const cordonScriptPath = (process.argv[1] ?? "<path-to-cordon.js>").replace(/\\/g, "/"); | ||
| process.stderr.write( | ||
| ` | ||
| \x1B[33mwarn\x1B[0m: Claude Desktop config not found on this system. | ||
| Edit cordon.config.ts, then manually add Cordon to your MCP client config: | ||
| "mcpServers": { | ||
| "cordon": { | ||
| "command": "${nodePath}", | ||
| "args": ["${cordonScriptPath}", "start", "--config", "${outputPath.replace(/\\/g, "/")}"] | ||
| } | ||
| } | ||
| ` | ||
| ); | ||
| } else { | ||
| process.stderr.write( | ||
| ` | ||
| Edit cordon.config.ts to add your MCP servers, then run \x1B[36mnpx cordon start\x1B[0m. | ||
| ` | ||
| ); | ||
| } | ||
| if (auth) { | ||
| process.stderr.write( | ||
| ` | ||
| \x1B[32m\u2713\x1B[0m audit logs will stream to your Cordon account (${auth.endpoint}) | ||
| ` | ||
| ); | ||
| } else { | ||
| process.stderr.write( | ||
| ` | ||
| \x1B[36mWant centralized audit logs + Slack approvals?\x1B[0m | ||
| Run \x1B[36mcordon login\x1B[0m to register a free account, or sign up at ${DASHBOARD_URL2}?utm_source=cli_init | ||
| ` | ||
| ); | ||
| } | ||
| setState({ welcomed: true }); | ||
| } | ||
| // src/commands/login.ts | ||
| import { createServer } from "http"; | ||
| import { spawn } from "child_process"; | ||
| import { randomBytes } from "crypto"; | ||
| var DEFAULT_ENDPOINT = "https://cordon-server-production.up.railway.app"; | ||
| var PORT_RANGE = [53247, 53249, 53251]; | ||
| var TIMEOUT_MS = 5 * 60 * 1e3; | ||
| function openBrowser(url) { | ||
| const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open"; | ||
| const args = process.platform === "win32" ? ["/c", "start", '""', url] : [url]; | ||
| try { | ||
| spawn(cmd, args, { detached: true, stdio: "ignore" }).unref(); | ||
| } catch { | ||
| } | ||
| } | ||
| async function listenForCallback(port, expectedState) { | ||
| return new Promise((resolve, reject) => { | ||
| const server = createServer((req, res) => { | ||
| const url = new URL(req.url ?? "/", `http://localhost:${port}`); | ||
| if (url.pathname !== "/cb") { | ||
| res.writeHead(404).end("not found"); | ||
| return; | ||
| } | ||
| const token = url.searchParams.get("token") ?? ""; | ||
| const state = url.searchParams.get("state") ?? ""; | ||
| const signup = url.searchParams.get("signup") ?? "false"; | ||
| if (state !== expectedState || !token) { | ||
| res.writeHead(400, { "Content-Type": "text/html" }).end("<html><body><h2>Login failed</h2><p>State mismatch or missing token. You can close this tab.</p></body></html>"); | ||
| server.close(); | ||
| reject(new Error("Callback state mismatch")); | ||
| return; | ||
| } | ||
| res.writeHead(200, { "Content-Type": "text/html" }).end('<html><body style="font-family:system-ui;padding:40px;"><h2>Logged in to Cordon for MCP</h2><p>You can close this tab and return to your terminal.</p></body></html>'); | ||
| server.close(); | ||
| resolve({ token, state, signup }); | ||
| }); | ||
| server.listen(port, "127.0.0.1"); | ||
| server.on("error", (err) => reject(err)); | ||
| setTimeout(() => { | ||
| server.close(); | ||
| reject(new Error("Login timed out after 5 minutes")); | ||
| }, TIMEOUT_MS).unref(); | ||
| }); | ||
| } | ||
| async function loginCommand(options = {}) { | ||
| const endpoint = (options.endpoint ?? DEFAULT_ENDPOINT).replace(/\/$/, ""); | ||
| const state = randomBytes(16).toString("hex"); | ||
| let port = null; | ||
| let result = null; | ||
| let lastErr = null; | ||
| for (const candidate of PORT_RANGE) { | ||
| try { | ||
| const callback = `http://localhost:${candidate}/cb`; | ||
| const authUrl = `${endpoint}/auth/cli/start?callback=${encodeURIComponent(callback)}&state=${state}`; | ||
| process.stderr.write( | ||
| ` | ||
| [cordon] opening browser to log in... | ||
| [cordon] if it doesn't open, visit: | ||
| ${authUrl} | ||
| ` | ||
| ); | ||
| openBrowser(authUrl); | ||
| const pending = listenForCallback(candidate, state); | ||
| port = candidate; | ||
| result = await pending; | ||
| break; | ||
| } catch (err) { | ||
| lastErr = err; | ||
| const code = err.code; | ||
| if (code === "EADDRINUSE") continue; | ||
| throw err; | ||
| } | ||
| } | ||
| if (!result) { | ||
| throw lastErr instanceof Error ? lastErr : new Error("Could not bind a local callback port"); | ||
| } | ||
| setAuth({ | ||
| endpoint, | ||
| apiKey: result.token, | ||
| loggedInAt: (/* @__PURE__ */ new Date()).toISOString() | ||
| }); | ||
| process.stderr.write(`\x1B[32m\u2713\x1B[0m logged in. API key saved to ~/.cordon/auth.json | ||
| `); | ||
| if (result.signup === "true") { | ||
| process.stderr.write(`Welcome to Cordon for MCP. Run \x1B[36mcordon init\x1B[0m next to wire up your MCP servers. | ||
| `); | ||
| } | ||
| void port; | ||
| } | ||
| // src/commands/logout.ts | ||
| function logoutCommand() { | ||
| if (clearAuth()) { | ||
| process.stderr.write(`\x1B[32m\u2713\x1B[0m logged out. ~/.cordon/auth.json removed | ||
| `); | ||
| } else { | ||
| process.stderr.write(`[cordon] not logged in | ||
| `); | ||
| } | ||
| } | ||
| // src/bin/cordon.ts | ||
| var program = new Command(); | ||
| program.name("cordon").description("Cordon for MCP \u2014 security gateway for MCP tool calls").version("0.1.0"); | ||
| program.command("start").description("Start the Cordon gateway").option("-c, --config <path>", "Path to cordon.config.ts").action(startCommand); | ||
| program.command("init").description("Generate cordon.config.ts and patch Claude Desktop config").action(initCommand); | ||
| program.command("login").description("Log in to Cordon (browser OAuth) and save an API key locally").option("--endpoint <url>", "Cordon server endpoint (defaults to the hosted instance)").action((opts) => loginCommand(opts).catch((err) => { | ||
| process.stderr.write(`\x1B[31merror\x1B[0m: login failed: ${String(err)} | ||
| `); | ||
| process.exit(1); | ||
| })); | ||
| program.command("logout").description("Remove the local Cordon credentials").action(logoutCommand); | ||
| program.parse(); | ||
| //# sourceMappingURL=cordon.js.map |
| {"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 .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 'cordon-sdk';\nimport { emptyConfig, findConfigPath, loadConfig } from '../config-loader.js';\nimport { getState, setState, getAuth } from '../cli-state.js';\n\nconst DASHBOARD_URL = 'https://cordon-server-production.up.railway.app/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}\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 cordon-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\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 'cordon-sdk';\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 { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { setState, getAuth } from '../cli-state.js';\n\nconst DASHBOARD_URL = 'https://cordon-server-production.up.railway.app/dashboard/';\n\nfunction ensureCordonSdkInstalled(cwd: string): void {\n // Config imports from 'cordon-sdk', and jiti resolves it from the config\n // file's directory. Without a local install, `cordon start` dies with\n // \"Cannot find module 'cordon-sdk'\" even when the CLI is global.\n if (existsSync(join(cwd, 'node_modules', 'cordon-sdk', '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 cordon-sdk...\\n`);\n const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';\n const result = spawnSync(npmCmd, ['install', 'cordon-sdk'], {\n cwd,\n stdio: ['ignore', 'inherit', 'inherit'],\n });\n\n if (result.status === 0) {\n process.stderr.write(`\\x1b[32m✓\\x1b[0m installed cordon-sdk\\n`);\n } else {\n process.stderr.write(\n `\\x1b[33mwarn\\x1b[0m: could not auto-install cordon-sdk. ` +\n `Run 'npm install cordon-sdk' in this directory before 'cordon start'.\\n`,\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 const auth = getAuth();\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 output: 'stdout',\n },`;\n\n const content = `import { defineConfig } from 'cordon-sdk';\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 'cordon-sdk'. 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 {\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://cordon-server-production.up.railway.app';\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 const args = process.platform === 'win32' ? ['/c', 'start', '\"\"', url] : [url];\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\nasync function listenForCallback(port: number, expectedState: 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 res.writeHead(400, { 'Content-Type': 'text/html' })\n .end('<html><body><h2>Login failed</h2><p>State mismatch or missing token. You can close this tab.</p></body></html>');\n server.close();\n reject(new Error('Callback state mismatch'));\n return;\n }\n\n res.writeHead(200, { 'Content-Type': 'text/html' })\n .end('<html><body style=\"font-family:system-ui;padding:40px;\"><h2>Logged in to Cordon for MCP</h2><p>You can close this tab and return to your terminal.</p></body></html>');\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);\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(`Welcome to Cordon for MCP. Run \\x1b[36mcordon init\\x1b[0m next to wire up your MCP servers.\\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","DASHBOARD_URL","existsSync","join","writeFileSync","homedir","readFileSync"]} |
| import { ResolvedConfig } from 'cordon-sdk'; | ||
| interface StartOptions { | ||
| config?: string; | ||
| } | ||
| declare function startCommand(options: StartOptions): Promise<void>; | ||
| declare function initCommand(): Promise<void>; | ||
| declare function findConfigPath(explicit?: string): Promise<string>; | ||
| declare function loadConfig(configPath: string): Promise<ResolvedConfig>; | ||
| export { findConfigPath, initCommand, loadConfig, startCommand }; |
-385
| // src/commands/start.ts | ||
| import { CordonGateway } from "@getcordon/core"; | ||
| // src/config-loader.ts | ||
| import { existsSync } from "fs"; | ||
| import { join } from "path"; | ||
| import { homedir } from "os"; | ||
| import { createJiti } from "jiti"; | ||
| var SEARCH_PATHS = [ | ||
| () => join(process.cwd(), "cordon.config.ts"), | ||
| () => join(process.cwd(), "cordon.config.js"), | ||
| () => join(homedir(), ".cordon", "config.ts"), | ||
| () => join(homedir(), ".cordon", "config.js") | ||
| ]; | ||
| async function findConfigPath(explicit) { | ||
| if (explicit) { | ||
| if (!existsSync(explicit)) { | ||
| throw new Error(`Config file not found: ${explicit}`); | ||
| } | ||
| return explicit; | ||
| } | ||
| for (const candidate of SEARCH_PATHS) { | ||
| const p = candidate(); | ||
| if (existsSync(p)) return p; | ||
| } | ||
| throw new Error( | ||
| "No cordon config found. Run `cordon init` to create one, or pass --config <path>." | ||
| ); | ||
| } | ||
| async function loadConfig(configPath) { | ||
| const jiti = createJiti(import.meta.url); | ||
| const mod = await jiti.import(configPath); | ||
| const raw = mod.default ?? mod; | ||
| if (!raw || typeof raw !== "object" || !Array.isArray(raw.servers)) { | ||
| throw new Error( | ||
| `Invalid config at ${configPath}: expected an object with a 'servers' array. Make sure you are using \`export default defineConfig({...})\`.` | ||
| ); | ||
| } | ||
| return applyDefaults(raw); | ||
| } | ||
| function applyDefaults(config) { | ||
| return { | ||
| ...config, | ||
| audit: config.audit ?? { enabled: true, output: "stdout" }, | ||
| approvals: config.approvals ?? { channel: "terminal" } | ||
| }; | ||
| } | ||
| function emptyConfig() { | ||
| return applyDefaults({ servers: [] }); | ||
| } | ||
| // src/cli-state.ts | ||
| import { readFileSync, writeFileSync, existsSync as existsSync2, mkdirSync, chmodSync, unlinkSync } from "fs"; | ||
| import { join as join2 } from "path"; | ||
| import { homedir as homedir2 } from "os"; | ||
| function cordonDir() { | ||
| return join2(homedir2(), ".cordon"); | ||
| } | ||
| function statePath() { | ||
| return join2(cordonDir(), "state.json"); | ||
| } | ||
| function authPath() { | ||
| return join2(cordonDir(), "auth.json"); | ||
| } | ||
| function ensureCordonDir() { | ||
| const dir = cordonDir(); | ||
| if (!existsSync2(dir)) mkdirSync(dir, { recursive: true }); | ||
| } | ||
| function getState() { | ||
| const path = statePath(); | ||
| if (!existsSync2(path)) return {}; | ||
| try { | ||
| return JSON.parse(readFileSync(path, "utf8")); | ||
| } catch { | ||
| return {}; | ||
| } | ||
| } | ||
| function setState(patch) { | ||
| try { | ||
| ensureCordonDir(); | ||
| const merged = { ...getState(), ...patch }; | ||
| writeFileSync(statePath(), JSON.stringify(merged, null, 2), "utf8"); | ||
| } catch { | ||
| } | ||
| } | ||
| function getAuth() { | ||
| const path = authPath(); | ||
| if (!existsSync2(path)) return null; | ||
| try { | ||
| return JSON.parse(readFileSync(path, "utf8")); | ||
| } catch { | ||
| return null; | ||
| } | ||
| } | ||
| // src/commands/start.ts | ||
| var DASHBOARD_URL = "https://cordon-server-production.up.railway.app/dashboard/"; | ||
| function applyAuthDefaults(config) { | ||
| const auth = getAuth(); | ||
| if (!auth) return config; | ||
| const audit = config.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 | ||
| } | ||
| }; | ||
| } | ||
| async function startCommand(options) { | ||
| if (!getState().welcomed && !getAuth()) { | ||
| process.stderr.write( | ||
| ` | ||
| \x1B[36m[cordon] Want centralized audit logs + Slack approvals?\x1B[0m | ||
| [cordon] Run \`cordon login\` or register at ${DASHBOARD_URL}?utm_source=cli_start | ||
| ` | ||
| ); | ||
| setState({ welcomed: true }); | ||
| } | ||
| let config = emptyConfig(); | ||
| let configPath = null; | ||
| try { | ||
| configPath = await findConfigPath(options.config); | ||
| } catch (err) { | ||
| if (options.config) { | ||
| process.stderr.write(`\x1B[31merror\x1B[0m: ${String(err)} | ||
| `); | ||
| process.exit(1); | ||
| } | ||
| process.stderr.write( | ||
| "\x1B[33m[cordon] no cordon.config.ts found \u2014 starting with zero upstream servers.\x1B[0m\n[cordon] Run `cordon init` to generate a config and connect real MCP servers.\n" | ||
| ); | ||
| } | ||
| if (configPath) { | ||
| process.stderr.write(`[cordon] loading config from ${configPath} | ||
| `); | ||
| try { | ||
| config = await loadConfig(configPath); | ||
| } catch (err) { | ||
| process.stderr.write(`\x1B[31merror\x1B[0m: ${String(err)} | ||
| `); | ||
| process.exit(1); | ||
| } | ||
| } | ||
| config = applyAuthDefaults(config); | ||
| const gateway = new CordonGateway(config); | ||
| const shutdown = async () => { | ||
| process.stderr.write("\n[cordon] shutting down...\n"); | ||
| try { | ||
| await gateway.stop(); | ||
| process.exit(0); | ||
| } catch (err) { | ||
| process.stderr.write(`\x1B[31merror\x1B[0m: shutdown failed: ${String(err)} | ||
| `); | ||
| process.exit(1); | ||
| } | ||
| }; | ||
| process.on("SIGINT", shutdown); | ||
| process.on("SIGTERM", shutdown); | ||
| try { | ||
| await gateway.start(); | ||
| } catch (err) { | ||
| process.stderr.write(`\x1B[31merror\x1B[0m: gateway failed: ${String(err)} | ||
| `); | ||
| process.exit(1); | ||
| } | ||
| } | ||
| // src/commands/init.ts | ||
| import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3 } from "fs"; | ||
| import { spawnSync } from "child_process"; | ||
| import { join as join3 } from "path"; | ||
| import { homedir as homedir3 } from "os"; | ||
| var DASHBOARD_URL2 = "https://cordon-server-production.up.railway.app/dashboard/"; | ||
| function ensureCordonSdkInstalled(cwd) { | ||
| if (existsSync3(join3(cwd, "node_modules", "cordon-sdk", "package.json"))) { | ||
| return; | ||
| } | ||
| if (!existsSync3(join3(cwd, "package.json"))) { | ||
| writeFileSync2( | ||
| join3(cwd, "package.json"), | ||
| JSON.stringify({ name: "cordon-config", version: "0.0.0", private: true }, null, 2) + "\n", | ||
| "utf8" | ||
| ); | ||
| process.stderr.write(`\x1B[32m\u2713\x1B[0m created package.json | ||
| `); | ||
| } | ||
| process.stderr.write(`[cordon] installing cordon-sdk... | ||
| `); | ||
| const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm"; | ||
| const result = spawnSync(npmCmd, ["install", "cordon-sdk"], { | ||
| cwd, | ||
| stdio: ["ignore", "inherit", "inherit"] | ||
| }); | ||
| if (result.status === 0) { | ||
| process.stderr.write(`\x1B[32m\u2713\x1B[0m installed cordon-sdk | ||
| `); | ||
| } else { | ||
| process.stderr.write( | ||
| `\x1B[33mwarn\x1B[0m: could not auto-install cordon-sdk. Run 'npm install cordon-sdk' in this directory before 'cordon start'. | ||
| ` | ||
| ); | ||
| } | ||
| } | ||
| function getClaudeConfigPath() { | ||
| const candidates = []; | ||
| if (process.platform === "darwin") { | ||
| candidates.push( | ||
| join3(homedir3(), "Library", "Application Support", "Claude", "claude_desktop_config.json") | ||
| ); | ||
| } else if (process.platform === "win32") { | ||
| const appData = process.env["APPDATA"] ?? join3(homedir3(), "AppData", "Roaming"); | ||
| candidates.push(join3(appData, "Claude", "claude_desktop_config.json")); | ||
| } else { | ||
| candidates.push(join3(homedir3(), ".config", "Claude", "claude_desktop_config.json")); | ||
| } | ||
| return candidates.find((p) => existsSync3(p)) ?? null; | ||
| } | ||
| async function initCommand() { | ||
| const outputPath = join3(process.cwd(), "cordon.config.ts"); | ||
| if (existsSync3(outputPath)) { | ||
| process.stderr.write( | ||
| `\x1B[33mwarn\x1B[0m: cordon.config.ts already exists \u2014 not overwriting. | ||
| ` | ||
| ); | ||
| process.exit(1); | ||
| } | ||
| const claudePath = getClaudeConfigPath(); | ||
| let claudeConfig = {}; | ||
| if (claudePath) { | ||
| process.stderr.write(`[cordon] found Claude Desktop config at ${claudePath} | ||
| `); | ||
| try { | ||
| claudeConfig = JSON.parse(readFileSync2(claudePath, "utf8")); | ||
| } catch { | ||
| process.stderr.write(`\x1B[33mwarn\x1B[0m: could not parse Claude Desktop config | ||
| `); | ||
| } | ||
| } else { | ||
| process.stderr.write( | ||
| `[cordon] no Claude Desktop config found \u2014 generating a blank config | ||
| ` | ||
| ); | ||
| } | ||
| const servers = Object.entries(claudeConfig.mcpServers ?? {}); | ||
| const serverBlocks = servers.length > 0 ? servers.map(([name, cfg]) => { | ||
| const argsStr = cfg.args?.length ? `, args: ${JSON.stringify(cfg.args)}` : ""; | ||
| const envStr = cfg.env && Object.keys(cfg.env).length ? `, env: ${JSON.stringify(cfg.env)}` : ""; | ||
| return ` { | ||
| name: ${JSON.stringify(name)}, | ||
| transport: 'stdio', | ||
| command: ${JSON.stringify(cfg.command)}${argsStr}${envStr}, | ||
| policy: 'allow', | ||
| // tools: { | ||
| // execute: 'approve', | ||
| // delete: 'block', | ||
| // }, | ||
| },`; | ||
| }).join("\n") : ` // { | ||
| // name: 'my-server', | ||
| // transport: 'stdio', | ||
| // command: 'npx', | ||
| // args: ['-y', '@my-org/my-mcp-server'], | ||
| // policy: 'allow', | ||
| // },`; | ||
| const auth = getAuth(); | ||
| const auditBlock = auth ? `audit: { | ||
| enabled: true, | ||
| output: 'hosted', | ||
| // endpoint + apiKey are auto-loaded from ~/.cordon/auth.json (cordon login) | ||
| },` : `audit: { | ||
| enabled: true, | ||
| output: 'stdout', | ||
| },`; | ||
| const content = `import { defineConfig } from 'cordon-sdk'; | ||
| export default defineConfig({ | ||
| servers: [ | ||
| ${serverBlocks} | ||
| ], | ||
| ${auditBlock} | ||
| approvals: { | ||
| channel: 'terminal', | ||
| // timeoutMs: 60_000, | ||
| }, | ||
| }); | ||
| `; | ||
| writeFileSync2(outputPath, content, "utf8"); | ||
| process.stderr.write(`\x1B[32m\u2713\x1B[0m wrote cordon.config.ts | ||
| `); | ||
| ensureCordonSdkInstalled(process.cwd()); | ||
| if (claudePath && servers.length > 0) { | ||
| const cordonConfigPath = outputPath.replace(/\\/g, "/"); | ||
| const nodePath = process.execPath.replace(/\\/g, "/"); | ||
| const cordonScriptPath = (process.argv[1] ?? "").replace(/\\/g, "/"); | ||
| if (!cordonScriptPath) { | ||
| process.stderr.write( | ||
| `\x1B[33mwarn\x1B[0m: could not detect cordon CLI script path. Skipping Claude Desktop patch. | ||
| Manually add this to your claude_desktop_config.json mcpServers: | ||
| "cordon": { "command": "${nodePath}", "args": ["<path-to-cordon.js>", "start", "--config", "${cordonConfigPath}"] } | ||
| ` | ||
| ); | ||
| return; | ||
| } | ||
| const newClaudeConfig = { | ||
| ...claudeConfig, | ||
| mcpServers: { | ||
| cordon: { | ||
| command: nodePath, | ||
| args: [cordonScriptPath, "start", "--config", cordonConfigPath] | ||
| } | ||
| } | ||
| }; | ||
| const backupPath = `${claudePath}.cordon-backup`; | ||
| if (existsSync3(backupPath)) { | ||
| process.stderr.write( | ||
| `[cordon] existing backup at ${backupPath} preserved (won't overwrite) | ||
| ` | ||
| ); | ||
| } else { | ||
| writeFileSync2(backupPath, readFileSync2(claudePath, "utf8"), "utf8"); | ||
| process.stderr.write(`\x1B[32m\u2713\x1B[0m backed up Claude Desktop config to ${backupPath} | ||
| `); | ||
| } | ||
| writeFileSync2(claudePath, JSON.stringify(newClaudeConfig, null, 2), "utf8"); | ||
| process.stderr.write(`\x1B[32m\u2713\x1B[0m patched Claude Desktop config | ||
| `); | ||
| process.stderr.write( | ||
| ` | ||
| \x1B[36mRestart Claude Desktop to activate Cordon.\x1B[0m | ||
| ` | ||
| ); | ||
| } else if (!claudePath) { | ||
| const nodePath = process.execPath.replace(/\\/g, "/"); | ||
| const cordonScriptPath = (process.argv[1] ?? "<path-to-cordon.js>").replace(/\\/g, "/"); | ||
| process.stderr.write( | ||
| ` | ||
| \x1B[33mwarn\x1B[0m: Claude Desktop config not found on this system. | ||
| Edit cordon.config.ts, then manually add Cordon to your MCP client config: | ||
| "mcpServers": { | ||
| "cordon": { | ||
| "command": "${nodePath}", | ||
| "args": ["${cordonScriptPath}", "start", "--config", "${outputPath.replace(/\\/g, "/")}"] | ||
| } | ||
| } | ||
| ` | ||
| ); | ||
| } else { | ||
| process.stderr.write( | ||
| ` | ||
| Edit cordon.config.ts to add your MCP servers, then run \x1B[36mnpx cordon start\x1B[0m. | ||
| ` | ||
| ); | ||
| } | ||
| if (auth) { | ||
| process.stderr.write( | ||
| ` | ||
| \x1B[32m\u2713\x1B[0m audit logs will stream to your Cordon account (${auth.endpoint}) | ||
| ` | ||
| ); | ||
| } else { | ||
| process.stderr.write( | ||
| ` | ||
| \x1B[36mWant centralized audit logs + Slack approvals?\x1B[0m | ||
| Run \x1B[36mcordon login\x1B[0m to register a free account, or sign up at ${DASHBOARD_URL2}?utm_source=cli_init | ||
| ` | ||
| ); | ||
| } | ||
| setState({ welcomed: true }); | ||
| } | ||
| export { | ||
| findConfigPath, | ||
| initCommand, | ||
| loadConfig, | ||
| startCommand | ||
| }; | ||
| //# sourceMappingURL=index.js.map |
| {"version":3,"sources":["../src/commands/start.ts","../src/config-loader.ts","../src/cli-state.ts","../src/commands/init.ts"],"sourcesContent":["import { CordonGateway } from '@getcordon/core';\nimport type { ResolvedConfig } from 'cordon-sdk';\nimport { emptyConfig, findConfigPath, loadConfig } from '../config-loader.js';\nimport { getState, setState, getAuth } from '../cli-state.js';\n\nconst DASHBOARD_URL = 'https://cordon-server-production.up.railway.app/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}\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 cordon-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\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 'cordon-sdk';\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 { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { setState, getAuth } from '../cli-state.js';\n\nconst DASHBOARD_URL = 'https://cordon-server-production.up.railway.app/dashboard/';\n\nfunction ensureCordonSdkInstalled(cwd: string): void {\n // Config imports from 'cordon-sdk', and jiti resolves it from the config\n // file's directory. Without a local install, `cordon start` dies with\n // \"Cannot find module 'cordon-sdk'\" even when the CLI is global.\n if (existsSync(join(cwd, 'node_modules', 'cordon-sdk', '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 cordon-sdk...\\n`);\n const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';\n const result = spawnSync(npmCmd, ['install', 'cordon-sdk'], {\n cwd,\n stdio: ['ignore', 'inherit', 'inherit'],\n });\n\n if (result.status === 0) {\n process.stderr.write(`\\x1b[32m✓\\x1b[0m installed cordon-sdk\\n`);\n } else {\n process.stderr.write(\n `\\x1b[33mwarn\\x1b[0m: could not auto-install cordon-sdk. ` +\n `Run 'npm install cordon-sdk' in this directory before 'cordon start'.\\n`,\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 const auth = getAuth();\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 output: 'stdout',\n },`;\n\n const content = `import { defineConfig } from 'cordon-sdk';\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 'cordon-sdk'. 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 {\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"],"mappings":"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","names":["existsSync","join","homedir","readFileSync","writeFileSync","existsSync","join","homedir","DASHBOARD_URL","existsSync","join","writeFileSync","homedir","readFileSync"]} |
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Wildcard dependency
QualityPackage has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version.
Found 2 instances
Network access
Supply chain riskThis module accesses the network.
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
0
-100%0
-100%0
-100%1
-90%1
-75%2451
-97.18%4
-42.86%17
-98.09%1
Infinity%13
-80%No
NaN- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed