@node9/proxy 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +138 -18
  2. package/dist/cli.js +312 -110
  3. package/dist/cli.mjs +308 -106
  4. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -5033,12 +5033,12 @@ __export(tail_exports, {
5033
5033
  startTail: () => startTail
5034
5034
  });
5035
5035
  import http2 from "http";
5036
- import chalk13 from "chalk";
5036
+ import chalk14 from "chalk";
5037
5037
  import fs19 from "fs";
5038
5038
  import os17 from "os";
5039
5039
  import path20 from "path";
5040
- import readline2 from "readline";
5041
- import { spawn as spawn8, execSync as execSync3 } from "child_process";
5040
+ import readline3 from "readline";
5041
+ import { spawn as spawn9, execSync as execSync3 } from "child_process";
5042
5042
  function getIcon(tool) {
5043
5043
  const t = tool.toLowerCase();
5044
5044
  for (const [k, v] of Object.entries(ICONS)) {
@@ -5052,27 +5052,27 @@ function formatBase(activity) {
5052
5052
  const toolName = activity.tool.slice(0, 16).padEnd(16);
5053
5053
  const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ");
5054
5054
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
5055
- return `${chalk13.gray(time)} ${icon} ${chalk13.white.bold(toolName)} ${chalk13.dim(argsPreview)}`;
5055
+ return `${chalk14.gray(time)} ${icon} ${chalk14.white.bold(toolName)} ${chalk14.dim(argsPreview)}`;
5056
5056
  }
5057
5057
  function renderResult(activity, result) {
5058
5058
  const base = formatBase(activity);
5059
5059
  let status;
5060
5060
  if (result.status === "allow") {
5061
- status = chalk13.green("\u2713 ALLOW");
5061
+ status = chalk14.green("\u2713 ALLOW");
5062
5062
  } else if (result.status === "dlp") {
5063
- status = chalk13.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
5063
+ status = chalk14.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
5064
5064
  } else {
5065
- status = chalk13.red("\u2717 BLOCK");
5065
+ status = chalk14.red("\u2717 BLOCK");
5066
5066
  }
5067
5067
  if (process.stdout.isTTY) {
5068
- readline2.clearLine(process.stdout, 0);
5069
- readline2.cursorTo(process.stdout, 0);
5068
+ readline3.clearLine(process.stdout, 0);
5069
+ readline3.cursorTo(process.stdout, 0);
5070
5070
  }
5071
5071
  console.log(`${base} ${status}`);
5072
5072
  }
5073
5073
  function renderPending(activity) {
5074
5074
  if (!process.stdout.isTTY) return;
5075
- process.stdout.write(`${formatBase(activity)} ${chalk13.yellow("\u25CF \u2026")}\r`);
5075
+ process.stdout.write(`${formatBase(activity)} ${chalk14.yellow("\u25CF \u2026")}\r`);
5076
5076
  }
5077
5077
  async function ensureDaemon() {
5078
5078
  let pidPort = null;
@@ -5081,7 +5081,7 @@ async function ensureDaemon() {
5081
5081
  const { port } = JSON.parse(fs19.readFileSync(PID_FILE, "utf-8"));
5082
5082
  pidPort = port;
5083
5083
  } catch {
5084
- console.error(chalk13.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
5084
+ console.error(chalk14.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
5085
5085
  }
5086
5086
  }
5087
5087
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -5092,8 +5092,8 @@ async function ensureDaemon() {
5092
5092
  if (res.ok) return checkPort;
5093
5093
  } catch {
5094
5094
  }
5095
- console.log(chalk13.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
5096
- const child = spawn8(process.execPath, [process.argv[1], "daemon"], {
5095
+ console.log(chalk14.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
5096
+ const child = spawn9(process.execPath, [process.argv[1], "daemon"], {
5097
5097
  detached: true,
5098
5098
  stdio: "ignore",
5099
5099
  env: { ...process.env, NODE9_AUTO_STARTED: "1" }
@@ -5109,7 +5109,7 @@ async function ensureDaemon() {
5109
5109
  } catch {
5110
5110
  }
5111
5111
  }
5112
- console.error(chalk13.red("\u274C Daemon failed to start. Try: node9 daemon start"));
5112
+ console.error(chalk14.red("\u274C Daemon failed to start. Try: node9 daemon start"));
5113
5113
  process.exit(1);
5114
5114
  }
5115
5115
  function postDecisionHttp(id, decision, csrfToken, port) {
@@ -5180,7 +5180,7 @@ async function startTail(options = {}) {
5180
5180
  req2.end();
5181
5181
  });
5182
5182
  if (result.ok) {
5183
- console.log(chalk13.green("\u2713 Flight Recorder buffer cleared."));
5183
+ console.log(chalk14.green("\u2713 Flight Recorder buffer cleared."));
5184
5184
  } else if (result.code === "ECONNREFUSED") {
5185
5185
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
5186
5186
  } else if (result.code === "ETIMEDOUT") {
@@ -5198,7 +5198,7 @@ async function startTail(options = {}) {
5198
5198
  let cardLineCount = 0;
5199
5199
  let cancelActiveCard = null;
5200
5200
  const canApprove = process.stdout.isTTY && process.stdin.isTTY;
5201
- if (canApprove) readline2.emitKeypressEvents(process.stdin);
5201
+ if (canApprove) readline3.emitKeypressEvents(process.stdin);
5202
5202
  function clearCard() {
5203
5203
  if (cardLineCount > 0) {
5204
5204
  process.stdout.write(RESTORE_CURSOR + ERASE_DOWN);
@@ -5251,8 +5251,8 @@ async function startTail(options = {}) {
5251
5251
  } catch {
5252
5252
  }
5253
5253
  });
5254
- const decisionLabel = decision === "allow" ? chalk13.green("\u2713 ALLOWED (terminal)") : chalk13.red("\u2717 DENIED (terminal)");
5255
- console.log(`${chalk13.cyan("\u25C6")} ${chalk13.bold(req2.toolName.padEnd(16))} ${decisionLabel}`);
5254
+ const decisionLabel = decision === "allow" ? chalk14.green("\u2713 ALLOWED (terminal)") : chalk14.red("\u2717 DENIED (terminal)");
5255
+ console.log(`${chalk14.cyan("\u25C6")} ${chalk14.bold(req2.toolName.padEnd(16))} ${decisionLabel}`);
5256
5256
  approvalQueue.shift();
5257
5257
  cardActive = false;
5258
5258
  showNextCard();
@@ -5289,39 +5289,39 @@ async function startTail(options = {}) {
5289
5289
  }
5290
5290
  } catch {
5291
5291
  }
5292
- console.log(chalk13.cyan.bold(`
5293
- \u{1F6F0}\uFE0F Node9 tail `) + chalk13.dim(`\u2192 ${dashboardUrl}`));
5292
+ console.log(chalk14.cyan.bold(`
5293
+ \u{1F6F0}\uFE0F Node9 tail `) + chalk14.dim(`\u2192 ${dashboardUrl}`));
5294
5294
  if (canApprove) {
5295
- console.log(chalk13.dim("Interactive approvals enabled. [A] Allow [D] Deny"));
5295
+ console.log(chalk14.dim("Interactive approvals enabled. [A] Allow [D] Deny"));
5296
5296
  }
5297
5297
  if (options.history) {
5298
- console.log(chalk13.dim("Showing history + live events. Press Ctrl+C to exit.\n"));
5298
+ console.log(chalk14.dim("Showing history + live events. Press Ctrl+C to exit.\n"));
5299
5299
  } else {
5300
5300
  console.log(
5301
- chalk13.dim("Showing live events only. Use --history to include past. Press Ctrl+C to exit.\n")
5301
+ chalk14.dim("Showing live events only. Use --history to include past. Press Ctrl+C to exit.\n")
5302
5302
  );
5303
5303
  }
5304
5304
  process.on("SIGINT", () => {
5305
5305
  clearCard();
5306
5306
  process.stdout.write(SHOW_CURSOR);
5307
5307
  if (process.stdout.isTTY) {
5308
- readline2.clearLine(process.stdout, 0);
5309
- readline2.cursorTo(process.stdout, 0);
5308
+ readline3.clearLine(process.stdout, 0);
5309
+ readline3.cursorTo(process.stdout, 0);
5310
5310
  }
5311
- console.log(chalk13.dim("\n\u{1F6F0}\uFE0F Disconnected."));
5311
+ console.log(chalk14.dim("\n\u{1F6F0}\uFE0F Disconnected."));
5312
5312
  process.exit(0);
5313
5313
  });
5314
5314
  const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
5315
5315
  const req = http2.get(sseUrl, (res) => {
5316
5316
  if (res.statusCode !== 200) {
5317
- console.error(chalk13.red(`Failed to connect: HTTP ${res.statusCode}`));
5317
+ console.error(chalk14.red(`Failed to connect: HTTP ${res.statusCode}`));
5318
5318
  process.exit(1);
5319
5319
  }
5320
5320
  let currentEvent = "";
5321
5321
  let currentData = "";
5322
5322
  res.on("error", () => {
5323
5323
  });
5324
- const rl = readline2.createInterface({ input: res, crlfDelay: Infinity });
5324
+ const rl = readline3.createInterface({ input: res, crlfDelay: Infinity });
5325
5325
  rl.on("error", () => {
5326
5326
  });
5327
5327
  rl.on("line", (line) => {
@@ -5341,10 +5341,10 @@ async function startTail(options = {}) {
5341
5341
  clearCard();
5342
5342
  process.stdout.write(SHOW_CURSOR);
5343
5343
  if (process.stdout.isTTY) {
5344
- readline2.clearLine(process.stdout, 0);
5345
- readline2.cursorTo(process.stdout, 0);
5344
+ readline3.clearLine(process.stdout, 0);
5345
+ readline3.cursorTo(process.stdout, 0);
5346
5346
  }
5347
- console.log(chalk13.red("\n\u274C Daemon disconnected."));
5347
+ console.log(chalk14.red("\n\u274C Daemon disconnected."));
5348
5348
  process.exit(1);
5349
5349
  });
5350
5350
  });
@@ -5424,7 +5424,7 @@ async function startTail(options = {}) {
5424
5424
  }
5425
5425
  req.on("error", (err) => {
5426
5426
  const msg = err.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err.message;
5427
- console.error(chalk13.red(`
5427
+ console.error(chalk14.red(`
5428
5428
  \u274C ${msg}`));
5429
5429
  process.exit(1);
5430
5430
  });
@@ -5841,7 +5841,7 @@ async function setupCursor() {
5841
5841
 
5842
5842
  // src/cli.ts
5843
5843
  init_daemon2();
5844
- import chalk14 from "chalk";
5844
+ import chalk15 from "chalk";
5845
5845
  import fs20 from "fs";
5846
5846
  import path21 from "path";
5847
5847
  import os18 from "os";
@@ -7373,6 +7373,207 @@ function registerWatchCommand(program2) {
7373
7373
  });
7374
7374
  }
7375
7375
 
7376
+ // src/mcp-gateway/index.ts
7377
+ init_orchestrator();
7378
+ import readline2 from "readline";
7379
+ import chalk13 from "chalk";
7380
+ import { spawn as spawn8 } from "child_process";
7381
+ import { execa as execa2 } from "execa";
7382
+ function sanitize4(value) {
7383
+ return value.replace(/[\x00-\x1F\x7F]/g, "");
7384
+ }
7385
+ var RPC_INVALID_REQUEST = -32600;
7386
+ var RPC_SERVER_ERROR = -32e3;
7387
+ function isValidId(id) {
7388
+ return id === null || typeof id === "string" || typeof id === "number";
7389
+ }
7390
+ function extractMcpServer(toolName) {
7391
+ const match = toolName.match(/^mcp__([^_](?:[^_]|_(?!_))*?)__/i);
7392
+ return match?.[1];
7393
+ }
7394
+ function tokenize2(cmd) {
7395
+ const tokens = [];
7396
+ let current = "";
7397
+ let inDouble = false;
7398
+ let i = 0;
7399
+ while (i < cmd.length) {
7400
+ const ch = cmd[i];
7401
+ if (inDouble) {
7402
+ if (ch === '"') {
7403
+ inDouble = false;
7404
+ } else if (ch === "\\" && i + 1 < cmd.length) {
7405
+ current += cmd[++i];
7406
+ } else {
7407
+ current += ch;
7408
+ }
7409
+ } else {
7410
+ if (ch === '"') {
7411
+ inDouble = true;
7412
+ } else if (ch === " " || ch === " ") {
7413
+ if (current) {
7414
+ tokens.push(current);
7415
+ current = "";
7416
+ }
7417
+ } else if (ch === "\\" && i + 1 < cmd.length) {
7418
+ current += cmd[++i];
7419
+ } else {
7420
+ current += ch;
7421
+ }
7422
+ }
7423
+ i++;
7424
+ }
7425
+ if (current) tokens.push(current);
7426
+ return tokens;
7427
+ }
7428
+ async function runMcpGateway(upstreamCommand) {
7429
+ const commandParts = tokenize2(upstreamCommand);
7430
+ const cmd = commandParts[0];
7431
+ const cmdArgs = commandParts.slice(1);
7432
+ let executable = cmd;
7433
+ try {
7434
+ const { stdout } = await execa2("which", [cmd]);
7435
+ if (stdout) executable = stdout.trim();
7436
+ } catch {
7437
+ }
7438
+ console.error(chalk13.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
7439
+ const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
7440
+ "NODE_OPTIONS",
7441
+ "NODE_PATH",
7442
+ "LD_PRELOAD",
7443
+ "LD_LIBRARY_PATH",
7444
+ "DYLD_INSERT_LIBRARIES",
7445
+ "PYTHONPATH",
7446
+ "PYTHONSTARTUP",
7447
+ "PERL5LIB",
7448
+ "PERL5OPT",
7449
+ "RUBYLIB",
7450
+ "RUBYOPT",
7451
+ "JAVA_TOOL_OPTIONS",
7452
+ "JDK_JAVA_OPTIONS"
7453
+ ]);
7454
+ const safeEnv = Object.fromEntries(
7455
+ Object.entries(process.env).filter(([k]) => !UPSTREAM_INJECTOR_VARS.has(k))
7456
+ );
7457
+ const child = spawn8(executable, cmdArgs, {
7458
+ stdio: ["pipe", "pipe", "inherit"],
7459
+ // control stdin/stdout; inherit stderr
7460
+ shell: false,
7461
+ env: { ...safeEnv, FORCE_COLOR: "1" }
7462
+ });
7463
+ let authPending = false;
7464
+ let deferredExitCode = null;
7465
+ let deferredStdinEnd = false;
7466
+ const agentIn = readline2.createInterface({ input: process.stdin, terminal: false });
7467
+ agentIn.on("line", async (line) => {
7468
+ let message;
7469
+ try {
7470
+ const parsed = JSON.parse(line);
7471
+ if ("id" in parsed && !isValidId(parsed.id)) {
7472
+ const errorResponse = {
7473
+ jsonrpc: "2.0",
7474
+ id: null,
7475
+ error: {
7476
+ code: RPC_INVALID_REQUEST,
7477
+ message: "Invalid Request: id must be string, number, or null"
7478
+ }
7479
+ };
7480
+ process.stdout.write(JSON.stringify(errorResponse) + "\n");
7481
+ return;
7482
+ }
7483
+ message = { ...parsed, id: parsed.id };
7484
+ } catch {
7485
+ child.stdin.write(line + "\n");
7486
+ return;
7487
+ }
7488
+ if (message.method === "tools/call" || message.method === "call_tool" || message.method === "use_tool") {
7489
+ agentIn.pause();
7490
+ authPending = true;
7491
+ try {
7492
+ const toolName = sanitize4(
7493
+ String(message.params?.name ?? message.params?.tool_name ?? "unknown")
7494
+ );
7495
+ const toolArgs = message.params?.arguments ?? message.params?.tool_input ?? {};
7496
+ const mcpServer = extractMcpServer(toolName);
7497
+ const result = await authorizeHeadless(toolName, toolArgs, {
7498
+ agent: "MCP-Gateway",
7499
+ mcpServer
7500
+ });
7501
+ if (!result.approved) {
7502
+ console.error(chalk13.red(`
7503
+ \u{1F6D1} Node9 MCP Gateway: Action Blocked`));
7504
+ console.error(chalk13.gray(` Tool: ${toolName}`));
7505
+ console.error(chalk13.gray(` Reason: ${result.reason ?? "Security Policy"}
7506
+ `));
7507
+ const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
7508
+ const isHumanDecision = blockedByLabel.toLowerCase().includes("user") || blockedByLabel.toLowerCase().includes("daemon") || blockedByLabel.toLowerCase().includes("decision");
7509
+ const aiInstruction = buildNegotiationMessage(
7510
+ blockedByLabel,
7511
+ isHumanDecision,
7512
+ result.reason
7513
+ );
7514
+ const errorResponse = {
7515
+ jsonrpc: "2.0",
7516
+ id: message.id ?? null,
7517
+ error: {
7518
+ code: RPC_SERVER_ERROR,
7519
+ message: aiInstruction,
7520
+ data: { reason: result.reason, blockedBy: result.blockedByLabel }
7521
+ }
7522
+ };
7523
+ process.stdout.write(JSON.stringify(errorResponse) + "\n");
7524
+ return;
7525
+ }
7526
+ child.stdin.write(line + "\n");
7527
+ } catch {
7528
+ const errorResponse = {
7529
+ jsonrpc: "2.0",
7530
+ id: message.id ?? null,
7531
+ error: {
7532
+ code: -32e3,
7533
+ message: "Node9: Security engine encountered an error. Action blocked for safety."
7534
+ }
7535
+ };
7536
+ process.stdout.write(JSON.stringify(errorResponse) + "\n");
7537
+ return;
7538
+ } finally {
7539
+ authPending = false;
7540
+ agentIn.resume();
7541
+ if (deferredStdinEnd) child.stdin.end();
7542
+ if (deferredExitCode !== null) process.exit(deferredExitCode);
7543
+ }
7544
+ return;
7545
+ }
7546
+ child.stdin.write(line + "\n");
7547
+ });
7548
+ child.stdout.pipe(process.stdout);
7549
+ process.stdin.on("close", () => {
7550
+ if (authPending) {
7551
+ deferredStdinEnd = true;
7552
+ } else {
7553
+ child.stdin.end();
7554
+ }
7555
+ });
7556
+ child.on("exit", (code) => {
7557
+ if (authPending) {
7558
+ deferredExitCode = code ?? 0;
7559
+ } else {
7560
+ process.exit(code ?? 0);
7561
+ }
7562
+ });
7563
+ }
7564
+
7565
+ // src/cli/commands/mcp-gateway.ts
7566
+ function registerMcpGatewayCommand(program2) {
7567
+ program2.command("mcp-gateway").description(
7568
+ "Run Node9 as an MCP gateway \u2014 intercepts and authorizes tool calls before forwarding to the upstream MCP server"
7569
+ ).requiredOption(
7570
+ "--upstream <command>",
7571
+ 'The upstream MCP server command to wrap (e.g. "npx -y @modelcontextprotocol/server-filesystem /workspace")'
7572
+ ).action(async (options) => {
7573
+ await runMcpGateway(options.upstream);
7574
+ });
7575
+ }
7576
+
7376
7577
  // src/cli.ts
7377
7578
  var { version } = JSON.parse(
7378
7579
  fs20.readFileSync(path21.join(__dirname, "../package.json"), "utf-8")
@@ -7426,31 +7627,31 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
7426
7627
  fs20.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
7427
7628
  }
7428
7629
  if (options.profile && profileName !== "default") {
7429
- console.log(chalk14.green(`\u2705 Profile "${profileName}" saved`));
7430
- console.log(chalk14.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
7630
+ console.log(chalk15.green(`\u2705 Profile "${profileName}" saved`));
7631
+ console.log(chalk15.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
7431
7632
  } else if (options.local) {
7432
- console.log(chalk14.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
7433
- console.log(chalk14.gray(` All decisions stay on this machine.`));
7633
+ console.log(chalk15.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
7634
+ console.log(chalk15.gray(` All decisions stay on this machine.`));
7434
7635
  } else {
7435
- console.log(chalk14.green(`\u2705 Logged in \u2014 agent mode`));
7436
- console.log(chalk14.gray(` Team policy enforced for all calls via Node9 cloud.`));
7636
+ console.log(chalk15.green(`\u2705 Logged in \u2014 agent mode`));
7637
+ console.log(chalk15.gray(` Team policy enforced for all calls via Node9 cloud.`));
7437
7638
  }
7438
7639
  });
7439
7640
  program.command("addto").description("Integrate Node9 with an AI agent").addHelpText("after", "\n Supported targets: claude gemini cursor").argument("<target>", "The agent to protect: claude | gemini | cursor").action(async (target) => {
7440
7641
  if (target === "gemini") return await setupGemini();
7441
7642
  if (target === "claude") return await setupClaude();
7442
7643
  if (target === "cursor") return await setupCursor();
7443
- console.error(chalk14.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
7644
+ console.error(chalk15.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
7444
7645
  process.exit(1);
7445
7646
  });
7446
7647
  program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText("after", "\n Supported targets: claude gemini cursor").argument("[target]", "The agent to protect: claude | gemini | cursor").action(async (target) => {
7447
7648
  if (!target) {
7448
- console.log(chalk14.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
7449
- console.log(" Usage: " + chalk14.white("node9 setup <target>") + "\n");
7649
+ console.log(chalk15.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
7650
+ console.log(" Usage: " + chalk15.white("node9 setup <target>") + "\n");
7450
7651
  console.log(" Targets:");
7451
- console.log(" " + chalk14.green("claude") + " \u2014 Claude Code (hook mode)");
7452
- console.log(" " + chalk14.green("gemini") + " \u2014 Gemini CLI (hook mode)");
7453
- console.log(" " + chalk14.green("cursor") + " \u2014 Cursor (hook mode)");
7652
+ console.log(" " + chalk15.green("claude") + " \u2014 Claude Code (hook mode)");
7653
+ console.log(" " + chalk15.green("gemini") + " \u2014 Gemini CLI (hook mode)");
7654
+ console.log(" " + chalk15.green("cursor") + " \u2014 Cursor (hook mode)");
7454
7655
  console.log("");
7455
7656
  return;
7456
7657
  }
@@ -7458,7 +7659,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
7458
7659
  if (t === "gemini") return await setupGemini();
7459
7660
  if (t === "claude") return await setupClaude();
7460
7661
  if (t === "cursor") return await setupCursor();
7461
- console.error(chalk14.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
7662
+ console.error(chalk15.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
7462
7663
  process.exit(1);
7463
7664
  });
7464
7665
  program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText("after", "\n Supported targets: claude gemini cursor").argument("<target>", "The agent to remove from: claude | gemini | cursor").action((target) => {
@@ -7467,30 +7668,30 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
7467
7668
  else if (target === "gemini") fn = teardownGemini;
7468
7669
  else if (target === "cursor") fn = teardownCursor;
7469
7670
  else {
7470
- console.error(chalk14.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
7671
+ console.error(chalk15.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
7471
7672
  process.exit(1);
7472
7673
  }
7473
- console.log(chalk14.cyan(`
7674
+ console.log(chalk15.cyan(`
7474
7675
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
7475
7676
  `));
7476
7677
  try {
7477
7678
  fn();
7478
7679
  } catch (err) {
7479
- console.error(chalk14.red(` \u26A0\uFE0F Failed: ${err instanceof Error ? err.message : String(err)}`));
7680
+ console.error(chalk15.red(` \u26A0\uFE0F Failed: ${err instanceof Error ? err.message : String(err)}`));
7480
7681
  process.exit(1);
7481
7682
  }
7482
- console.log(chalk14.gray("\n Restart the agent for changes to take effect."));
7683
+ console.log(chalk15.gray("\n Restart the agent for changes to take effect."));
7483
7684
  });
7484
7685
  program.command("uninstall").description("Remove all Node9 hooks and optionally delete config files").option("--purge", "Also delete ~/.node9/ directory (config, audit log, credentials)").action(async (options) => {
7485
- console.log(chalk14.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
7486
- console.log(chalk14.bold("Stopping daemon..."));
7686
+ console.log(chalk15.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
7687
+ console.log(chalk15.bold("Stopping daemon..."));
7487
7688
  try {
7488
7689
  stopDaemon();
7489
- console.log(chalk14.green(" \u2705 Daemon stopped"));
7690
+ console.log(chalk15.green(" \u2705 Daemon stopped"));
7490
7691
  } catch {
7491
- console.log(chalk14.blue(" \u2139\uFE0F Daemon was not running"));
7692
+ console.log(chalk15.blue(" \u2139\uFE0F Daemon was not running"));
7492
7693
  }
7493
- console.log(chalk14.bold("\nRemoving hooks..."));
7694
+ console.log(chalk15.bold("\nRemoving hooks..."));
7494
7695
  let teardownFailed = false;
7495
7696
  for (const [label, fn] of [
7496
7697
  ["Claude", teardownClaude],
@@ -7502,7 +7703,7 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
7502
7703
  } catch (err) {
7503
7704
  teardownFailed = true;
7504
7705
  console.error(
7505
- chalk14.red(
7706
+ chalk15.red(
7506
7707
  ` \u26A0\uFE0F Failed to remove ${label} hooks: ${err instanceof Error ? err.message : String(err)}`
7507
7708
  )
7508
7709
  );
@@ -7519,28 +7720,28 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
7519
7720
  fs20.rmSync(node9Dir, { recursive: true });
7520
7721
  if (fs20.existsSync(node9Dir)) {
7521
7722
  console.error(
7522
- chalk14.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
7723
+ chalk15.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
7523
7724
  );
7524
7725
  } else {
7525
- console.log(chalk14.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
7726
+ console.log(chalk15.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
7526
7727
  }
7527
7728
  } else {
7528
- console.log(chalk14.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
7729
+ console.log(chalk15.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
7529
7730
  }
7530
7731
  } else {
7531
- console.log(chalk14.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
7732
+ console.log(chalk15.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
7532
7733
  }
7533
7734
  } else {
7534
7735
  console.log(
7535
- chalk14.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
7736
+ chalk15.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
7536
7737
  );
7537
7738
  }
7538
7739
  if (teardownFailed) {
7539
- console.error(chalk14.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
7740
+ console.error(chalk15.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
7540
7741
  process.exit(1);
7541
7742
  }
7542
- console.log(chalk14.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
7543
- console.log(chalk14.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
7743
+ console.log(chalk15.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
7744
+ console.log(chalk15.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
7544
7745
  });
7545
7746
  registerDoctorCommand(program, version);
7546
7747
  program.command("explain").description(
@@ -7553,7 +7754,7 @@ program.command("explain").description(
7553
7754
  try {
7554
7755
  args = JSON.parse(trimmed);
7555
7756
  } catch {
7556
- console.error(chalk14.red(`
7757
+ console.error(chalk15.red(`
7557
7758
  \u274C Invalid JSON: ${trimmed}
7558
7759
  `));
7559
7760
  process.exit(1);
@@ -7564,54 +7765,54 @@ program.command("explain").description(
7564
7765
  }
7565
7766
  const result = await explainPolicy(tool, args);
7566
7767
  console.log("");
7567
- console.log(chalk14.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
7768
+ console.log(chalk15.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
7568
7769
  console.log("");
7569
- console.log(` ${chalk14.bold("Tool:")} ${chalk14.white(result.tool)}`);
7770
+ console.log(` ${chalk15.bold("Tool:")} ${chalk15.white(result.tool)}`);
7570
7771
  if (argsRaw) {
7571
7772
  const preview = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
7572
- console.log(` ${chalk14.bold("Input:")} ${chalk14.gray(preview)}`);
7773
+ console.log(` ${chalk15.bold("Input:")} ${chalk15.gray(preview)}`);
7573
7774
  }
7574
7775
  console.log("");
7575
- console.log(chalk14.bold("Config Sources (Waterfall):"));
7776
+ console.log(chalk15.bold("Config Sources (Waterfall):"));
7576
7777
  for (const tier of result.waterfall) {
7577
- const num = chalk14.gray(` ${tier.tier}.`);
7778
+ const num = chalk15.gray(` ${tier.tier}.`);
7578
7779
  const label = tier.label.padEnd(16);
7579
7780
  let statusStr;
7580
7781
  if (tier.tier === 1) {
7581
- statusStr = chalk14.gray(tier.note ?? "");
7782
+ statusStr = chalk15.gray(tier.note ?? "");
7582
7783
  } else if (tier.status === "active") {
7583
- const loc = tier.path ? chalk14.gray(tier.path) : "";
7584
- const note = tier.note ? chalk14.gray(`(${tier.note})`) : "";
7585
- statusStr = chalk14.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
7784
+ const loc = tier.path ? chalk15.gray(tier.path) : "";
7785
+ const note = tier.note ? chalk15.gray(`(${tier.note})`) : "";
7786
+ statusStr = chalk15.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
7586
7787
  } else {
7587
- statusStr = chalk14.gray("\u25CB " + (tier.note ?? "not found"));
7788
+ statusStr = chalk15.gray("\u25CB " + (tier.note ?? "not found"));
7588
7789
  }
7589
- console.log(`${num} ${chalk14.white(label)} ${statusStr}`);
7790
+ console.log(`${num} ${chalk15.white(label)} ${statusStr}`);
7590
7791
  }
7591
7792
  console.log("");
7592
- console.log(chalk14.bold("Policy Evaluation:"));
7793
+ console.log(chalk15.bold("Policy Evaluation:"));
7593
7794
  for (const step of result.steps) {
7594
7795
  const isFinal = step.isFinal;
7595
7796
  let icon;
7596
- if (step.outcome === "allow") icon = chalk14.green(" \u2705");
7597
- else if (step.outcome === "review") icon = chalk14.red(" \u{1F534}");
7598
- else if (step.outcome === "skip") icon = chalk14.gray(" \u2500 ");
7599
- else icon = chalk14.gray(" \u25CB ");
7797
+ if (step.outcome === "allow") icon = chalk15.green(" \u2705");
7798
+ else if (step.outcome === "review") icon = chalk15.red(" \u{1F534}");
7799
+ else if (step.outcome === "skip") icon = chalk15.gray(" \u2500 ");
7800
+ else icon = chalk15.gray(" \u25CB ");
7600
7801
  const name = step.name.padEnd(18);
7601
- const nameStr = isFinal ? chalk14.white.bold(name) : chalk14.white(name);
7602
- const detail = isFinal ? chalk14.white(step.detail) : chalk14.gray(step.detail);
7603
- const arrow = isFinal ? chalk14.yellow(" \u2190 STOP") : "";
7802
+ const nameStr = isFinal ? chalk15.white.bold(name) : chalk15.white(name);
7803
+ const detail = isFinal ? chalk15.white(step.detail) : chalk15.gray(step.detail);
7804
+ const arrow = isFinal ? chalk15.yellow(" \u2190 STOP") : "";
7604
7805
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
7605
7806
  }
7606
7807
  console.log("");
7607
7808
  if (result.decision === "allow") {
7608
- console.log(chalk14.green.bold(" Decision: \u2705 ALLOW") + chalk14.gray(" \u2014 no approval needed"));
7809
+ console.log(chalk15.green.bold(" Decision: \u2705 ALLOW") + chalk15.gray(" \u2014 no approval needed"));
7609
7810
  } else {
7610
7811
  console.log(
7611
- chalk14.red.bold(" Decision: \u{1F534} REVIEW") + chalk14.gray(" \u2014 human approval required")
7812
+ chalk15.red.bold(" Decision: \u{1F534} REVIEW") + chalk15.gray(" \u2014 human approval required")
7612
7813
  );
7613
7814
  if (result.blockedByLabel) {
7614
- console.log(chalk14.gray(` Reason: ${result.blockedByLabel}`));
7815
+ console.log(chalk15.gray(` Reason: ${result.blockedByLabel}`));
7615
7816
  }
7616
7817
  }
7617
7818
  console.log("");
@@ -7619,8 +7820,8 @@ program.command("explain").description(
7619
7820
  program.command("init").description("Create ~/.node9/config.json with default policy (safe to run multiple times)").option("--force", "Overwrite existing config").option("-m, --mode <mode>", "Set initial security mode (standard, strict, audit)", "standard").action((options) => {
7620
7821
  const configPath = path21.join(os18.homedir(), ".node9", "config.json");
7621
7822
  if (fs20.existsSync(configPath) && !options.force) {
7622
- console.log(chalk14.yellow(`\u2139\uFE0F Global config already exists: ${configPath}`));
7623
- console.log(chalk14.gray(` Run with --force to overwrite.`));
7823
+ console.log(chalk15.yellow(`\u2139\uFE0F Global config already exists: ${configPath}`));
7824
+ console.log(chalk15.gray(` Run with --force to overwrite.`));
7624
7825
  return;
7625
7826
  }
7626
7827
  const requestedMode = options.mode.toLowerCase();
@@ -7635,10 +7836,10 @@ program.command("init").description("Create ~/.node9/config.json with default po
7635
7836
  const dir = path21.dirname(configPath);
7636
7837
  if (!fs20.existsSync(dir)) fs20.mkdirSync(dir, { recursive: true });
7637
7838
  fs20.writeFileSync(configPath, JSON.stringify(configToSave, null, 2));
7638
- console.log(chalk14.green(`\u2705 Global config created: ${configPath}`));
7639
- console.log(chalk14.cyan(` Mode set to: ${safeMode}`));
7839
+ console.log(chalk15.green(`\u2705 Global config created: ${configPath}`));
7840
+ console.log(chalk15.cyan(` Mode set to: ${safeMode}`));
7640
7841
  console.log(
7641
- chalk14.gray(` Undo Engine is ENABLED by default. Use 'node9 undo' to revert AI changes.`)
7842
+ chalk15.gray(` Undo Engine is ENABLED by default. Use 'node9 undo' to revert AI changes.`)
7642
7843
  );
7643
7844
  });
7644
7845
  registerAuditCommand(program);
@@ -7649,18 +7850,19 @@ program.command("tail").description("Stream live agent activity to the terminal"
7649
7850
  try {
7650
7851
  await startTail2(options);
7651
7852
  } catch (err) {
7652
- console.error(chalk14.red(`\u274C ${err instanceof Error ? err.message : String(err)}`));
7853
+ console.error(chalk15.red(`\u274C ${err instanceof Error ? err.message : String(err)}`));
7653
7854
  process.exit(1);
7654
7855
  }
7655
7856
  });
7656
7857
  registerWatchCommand(program);
7858
+ registerMcpGatewayCommand(program);
7657
7859
  registerCheckCommand(program);
7658
7860
  registerLogCommand(program);
7659
7861
  program.command("pause").description("Temporarily disable Node9 protection for a set duration").option("-d, --duration <duration>", "How long to pause (e.g. 15m, 1h, 30s)", "15m").action((options) => {
7660
7862
  const ms = parseDuration(options.duration);
7661
7863
  if (ms === null) {
7662
7864
  console.error(
7663
- chalk14.red(`
7865
+ chalk15.red(`
7664
7866
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
7665
7867
  `)
7666
7868
  );
@@ -7668,20 +7870,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
7668
7870
  }
7669
7871
  pauseNode9(ms, options.duration);
7670
7872
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
7671
- console.log(chalk14.yellow(`
7873
+ console.log(chalk15.yellow(`
7672
7874
  \u23F8 Node9 paused until ${expiresAt}`));
7673
- console.log(chalk14.gray(` All tool calls will be allowed without review.`));
7674
- console.log(chalk14.gray(` Run "node9 resume" to re-enable early.
7875
+ console.log(chalk15.gray(` All tool calls will be allowed without review.`));
7876
+ console.log(chalk15.gray(` Run "node9 resume" to re-enable early.
7675
7877
  `));
7676
7878
  });
7677
7879
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
7678
7880
  const { paused } = checkPause();
7679
7881
  if (!paused) {
7680
- console.log(chalk14.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
7882
+ console.log(chalk15.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
7681
7883
  return;
7682
7884
  }
7683
7885
  resumeNode9();
7684
- console.log(chalk14.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
7886
+ console.log(chalk15.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
7685
7887
  });
7686
7888
  var HOOK_BASED_AGENTS = {
7687
7889
  claude: "claude",
@@ -7694,15 +7896,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
7694
7896
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
7695
7897
  const target = HOOK_BASED_AGENTS[firstArg2];
7696
7898
  console.error(
7697
- chalk14.yellow(`
7899
+ chalk15.yellow(`
7698
7900
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
7699
7901
  );
7700
- console.error(chalk14.white(`
7902
+ console.error(chalk15.white(`
7701
7903
  "${target}" uses its own hook system. Use:`));
7702
7904
  console.error(
7703
- chalk14.green(` node9 addto ${target} `) + chalk14.gray("# one-time setup")
7905
+ chalk15.green(` node9 addto ${target} `) + chalk15.gray("# one-time setup")
7704
7906
  );
7705
- console.error(chalk14.green(` ${target} `) + chalk14.gray("# run normally"));
7907
+ console.error(chalk15.green(` ${target} `) + chalk15.gray("# run normally"));
7706
7908
  process.exit(1);
7707
7909
  }
7708
7910
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -7719,7 +7921,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
7719
7921
  }
7720
7922
  );
7721
7923
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
7722
- console.error(chalk14.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
7924
+ console.error(chalk15.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
7723
7925
  const daemonReady = await autoStartDaemonAndWait();
7724
7926
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
7725
7927
  }
@@ -7732,12 +7934,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
7732
7934
  }
7733
7935
  if (!result.approved) {
7734
7936
  console.error(
7735
- chalk14.red(`
7937
+ chalk15.red(`
7736
7938
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
7737
7939
  );
7738
7940
  process.exit(1);
7739
7941
  }
7740
- console.error(chalk14.green("\n\u2705 Approved \u2014 running command...\n"));
7942
+ console.error(chalk15.green("\n\u2705 Approved \u2014 running command...\n"));
7741
7943
  await runProxy(fullCommand);
7742
7944
  } else {
7743
7945
  program.help();