@node9/proxy 1.2.0 → 1.3.0

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 +125 -18
  2. package/dist/cli.js +312 -110
  3. package/dist/cli.mjs +308 -106
  4. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -5069,27 +5069,27 @@ function formatBase(activity) {
5069
5069
  const toolName = activity.tool.slice(0, 16).padEnd(16);
5070
5070
  const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ");
5071
5071
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
5072
- return `${import_chalk13.default.gray(time)} ${icon} ${import_chalk13.default.white.bold(toolName)} ${import_chalk13.default.dim(argsPreview)}`;
5072
+ return `${import_chalk14.default.gray(time)} ${icon} ${import_chalk14.default.white.bold(toolName)} ${import_chalk14.default.dim(argsPreview)}`;
5073
5073
  }
5074
5074
  function renderResult(activity, result) {
5075
5075
  const base = formatBase(activity);
5076
5076
  let status;
5077
5077
  if (result.status === "allow") {
5078
- status = import_chalk13.default.green("\u2713 ALLOW");
5078
+ status = import_chalk14.default.green("\u2713 ALLOW");
5079
5079
  } else if (result.status === "dlp") {
5080
- status = import_chalk13.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
5080
+ status = import_chalk14.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
5081
5081
  } else {
5082
- status = import_chalk13.default.red("\u2717 BLOCK");
5082
+ status = import_chalk14.default.red("\u2717 BLOCK");
5083
5083
  }
5084
5084
  if (process.stdout.isTTY) {
5085
- import_readline2.default.clearLine(process.stdout, 0);
5086
- import_readline2.default.cursorTo(process.stdout, 0);
5085
+ import_readline3.default.clearLine(process.stdout, 0);
5086
+ import_readline3.default.cursorTo(process.stdout, 0);
5087
5087
  }
5088
5088
  console.log(`${base} ${status}`);
5089
5089
  }
5090
5090
  function renderPending(activity) {
5091
5091
  if (!process.stdout.isTTY) return;
5092
- process.stdout.write(`${formatBase(activity)} ${import_chalk13.default.yellow("\u25CF \u2026")}\r`);
5092
+ process.stdout.write(`${formatBase(activity)} ${import_chalk14.default.yellow("\u25CF \u2026")}\r`);
5093
5093
  }
5094
5094
  async function ensureDaemon() {
5095
5095
  let pidPort = null;
@@ -5098,7 +5098,7 @@ async function ensureDaemon() {
5098
5098
  const { port } = JSON.parse(import_fs19.default.readFileSync(PID_FILE, "utf-8"));
5099
5099
  pidPort = port;
5100
5100
  } catch {
5101
- console.error(import_chalk13.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
5101
+ console.error(import_chalk14.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
5102
5102
  }
5103
5103
  }
5104
5104
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -5109,8 +5109,8 @@ async function ensureDaemon() {
5109
5109
  if (res.ok) return checkPort;
5110
5110
  } catch {
5111
5111
  }
5112
- console.log(import_chalk13.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
5113
- const child = (0, import_child_process12.spawn)(process.execPath, [process.argv[1], "daemon"], {
5112
+ console.log(import_chalk14.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
5113
+ const child = (0, import_child_process13.spawn)(process.execPath, [process.argv[1], "daemon"], {
5114
5114
  detached: true,
5115
5115
  stdio: "ignore",
5116
5116
  env: { ...process.env, NODE9_AUTO_STARTED: "1" }
@@ -5126,7 +5126,7 @@ async function ensureDaemon() {
5126
5126
  } catch {
5127
5127
  }
5128
5128
  }
5129
- console.error(import_chalk13.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
5129
+ console.error(import_chalk14.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
5130
5130
  process.exit(1);
5131
5131
  }
5132
5132
  function postDecisionHttp(id, decision, csrfToken, port) {
@@ -5197,7 +5197,7 @@ async function startTail(options = {}) {
5197
5197
  req2.end();
5198
5198
  });
5199
5199
  if (result.ok) {
5200
- console.log(import_chalk13.default.green("\u2713 Flight Recorder buffer cleared."));
5200
+ console.log(import_chalk14.default.green("\u2713 Flight Recorder buffer cleared."));
5201
5201
  } else if (result.code === "ECONNREFUSED") {
5202
5202
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
5203
5203
  } else if (result.code === "ETIMEDOUT") {
@@ -5215,7 +5215,7 @@ async function startTail(options = {}) {
5215
5215
  let cardLineCount = 0;
5216
5216
  let cancelActiveCard = null;
5217
5217
  const canApprove = process.stdout.isTTY && process.stdin.isTTY;
5218
- if (canApprove) import_readline2.default.emitKeypressEvents(process.stdin);
5218
+ if (canApprove) import_readline3.default.emitKeypressEvents(process.stdin);
5219
5219
  function clearCard() {
5220
5220
  if (cardLineCount > 0) {
5221
5221
  process.stdout.write(RESTORE_CURSOR + ERASE_DOWN);
@@ -5268,8 +5268,8 @@ async function startTail(options = {}) {
5268
5268
  } catch {
5269
5269
  }
5270
5270
  });
5271
- const decisionLabel = decision === "allow" ? import_chalk13.default.green("\u2713 ALLOWED (terminal)") : import_chalk13.default.red("\u2717 DENIED (terminal)");
5272
- console.log(`${import_chalk13.default.cyan("\u25C6")} ${import_chalk13.default.bold(req2.toolName.padEnd(16))} ${decisionLabel}`);
5271
+ const decisionLabel = decision === "allow" ? import_chalk14.default.green("\u2713 ALLOWED (terminal)") : import_chalk14.default.red("\u2717 DENIED (terminal)");
5272
+ console.log(`${import_chalk14.default.cyan("\u25C6")} ${import_chalk14.default.bold(req2.toolName.padEnd(16))} ${decisionLabel}`);
5273
5273
  approvalQueue.shift();
5274
5274
  cardActive = false;
5275
5275
  showNextCard();
@@ -5299,46 +5299,46 @@ async function startTail(options = {}) {
5299
5299
  try {
5300
5300
  const browserEnabled = getConfig().settings.approvers?.browser !== false;
5301
5301
  if (browserEnabled) {
5302
- if (process.platform === "darwin") (0, import_child_process12.execSync)(`open "${dashboardUrl}"`, { stdio: "ignore" });
5302
+ if (process.platform === "darwin") (0, import_child_process13.execSync)(`open "${dashboardUrl}"`, { stdio: "ignore" });
5303
5303
  else if (process.platform === "win32")
5304
- (0, import_child_process12.execSync)(`cmd /c start "" "${dashboardUrl}"`, { stdio: "ignore" });
5305
- else (0, import_child_process12.execSync)(`xdg-open "${dashboardUrl}"`, { stdio: "ignore" });
5304
+ (0, import_child_process13.execSync)(`cmd /c start "" "${dashboardUrl}"`, { stdio: "ignore" });
5305
+ else (0, import_child_process13.execSync)(`xdg-open "${dashboardUrl}"`, { stdio: "ignore" });
5306
5306
  }
5307
5307
  } catch {
5308
5308
  }
5309
- console.log(import_chalk13.default.cyan.bold(`
5310
- \u{1F6F0}\uFE0F Node9 tail `) + import_chalk13.default.dim(`\u2192 ${dashboardUrl}`));
5309
+ console.log(import_chalk14.default.cyan.bold(`
5310
+ \u{1F6F0}\uFE0F Node9 tail `) + import_chalk14.default.dim(`\u2192 ${dashboardUrl}`));
5311
5311
  if (canApprove) {
5312
- console.log(import_chalk13.default.dim("Interactive approvals enabled. [A] Allow [D] Deny"));
5312
+ console.log(import_chalk14.default.dim("Interactive approvals enabled. [A] Allow [D] Deny"));
5313
5313
  }
5314
5314
  if (options.history) {
5315
- console.log(import_chalk13.default.dim("Showing history + live events. Press Ctrl+C to exit.\n"));
5315
+ console.log(import_chalk14.default.dim("Showing history + live events. Press Ctrl+C to exit.\n"));
5316
5316
  } else {
5317
5317
  console.log(
5318
- import_chalk13.default.dim("Showing live events only. Use --history to include past. Press Ctrl+C to exit.\n")
5318
+ import_chalk14.default.dim("Showing live events only. Use --history to include past. Press Ctrl+C to exit.\n")
5319
5319
  );
5320
5320
  }
5321
5321
  process.on("SIGINT", () => {
5322
5322
  clearCard();
5323
5323
  process.stdout.write(SHOW_CURSOR);
5324
5324
  if (process.stdout.isTTY) {
5325
- import_readline2.default.clearLine(process.stdout, 0);
5326
- import_readline2.default.cursorTo(process.stdout, 0);
5325
+ import_readline3.default.clearLine(process.stdout, 0);
5326
+ import_readline3.default.cursorTo(process.stdout, 0);
5327
5327
  }
5328
- console.log(import_chalk13.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
5328
+ console.log(import_chalk14.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
5329
5329
  process.exit(0);
5330
5330
  });
5331
5331
  const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
5332
5332
  const req = import_http2.default.get(sseUrl, (res) => {
5333
5333
  if (res.statusCode !== 200) {
5334
- console.error(import_chalk13.default.red(`Failed to connect: HTTP ${res.statusCode}`));
5334
+ console.error(import_chalk14.default.red(`Failed to connect: HTTP ${res.statusCode}`));
5335
5335
  process.exit(1);
5336
5336
  }
5337
5337
  let currentEvent = "";
5338
5338
  let currentData = "";
5339
5339
  res.on("error", () => {
5340
5340
  });
5341
- const rl = import_readline2.default.createInterface({ input: res, crlfDelay: Infinity });
5341
+ const rl = import_readline3.default.createInterface({ input: res, crlfDelay: Infinity });
5342
5342
  rl.on("error", () => {
5343
5343
  });
5344
5344
  rl.on("line", (line) => {
@@ -5358,10 +5358,10 @@ async function startTail(options = {}) {
5358
5358
  clearCard();
5359
5359
  process.stdout.write(SHOW_CURSOR);
5360
5360
  if (process.stdout.isTTY) {
5361
- import_readline2.default.clearLine(process.stdout, 0);
5362
- import_readline2.default.cursorTo(process.stdout, 0);
5361
+ import_readline3.default.clearLine(process.stdout, 0);
5362
+ import_readline3.default.cursorTo(process.stdout, 0);
5363
5363
  }
5364
- console.log(import_chalk13.default.red("\n\u274C Daemon disconnected."));
5364
+ console.log(import_chalk14.default.red("\n\u274C Daemon disconnected."));
5365
5365
  process.exit(1);
5366
5366
  });
5367
5367
  });
@@ -5441,22 +5441,22 @@ async function startTail(options = {}) {
5441
5441
  }
5442
5442
  req.on("error", (err) => {
5443
5443
  const msg = err.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err.message;
5444
- console.error(import_chalk13.default.red(`
5444
+ console.error(import_chalk14.default.red(`
5445
5445
  \u274C ${msg}`));
5446
5446
  process.exit(1);
5447
5447
  });
5448
5448
  }
5449
- var import_http2, import_chalk13, import_fs19, import_os17, import_path20, import_readline2, import_child_process12, PID_FILE, ICONS, RESET, BOLD, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, SAVE_CURSOR, RESTORE_CURSOR;
5449
+ var import_http2, import_chalk14, import_fs19, import_os17, import_path20, import_readline3, import_child_process13, PID_FILE, ICONS, RESET, BOLD, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, SAVE_CURSOR, RESTORE_CURSOR;
5450
5450
  var init_tail = __esm({
5451
5451
  "src/tui/tail.ts"() {
5452
5452
  "use strict";
5453
5453
  import_http2 = __toESM(require("http"));
5454
- import_chalk13 = __toESM(require("chalk"));
5454
+ import_chalk14 = __toESM(require("chalk"));
5455
5455
  import_fs19 = __toESM(require("fs"));
5456
5456
  import_os17 = __toESM(require("os"));
5457
5457
  import_path20 = __toESM(require("path"));
5458
- import_readline2 = __toESM(require("readline"));
5459
- import_child_process12 = require("child_process");
5458
+ import_readline3 = __toESM(require("readline"));
5459
+ import_child_process13 = require("child_process");
5460
5460
  init_daemon2();
5461
5461
  init_core();
5462
5462
  PID_FILE = import_path20.default.join(import_os17.default.homedir(), ".node9", "daemon.pid");
@@ -5865,7 +5865,7 @@ async function setupCursor() {
5865
5865
 
5866
5866
  // src/cli.ts
5867
5867
  init_daemon2();
5868
- var import_chalk14 = __toESM(require("chalk"));
5868
+ var import_chalk15 = __toESM(require("chalk"));
5869
5869
  var import_fs20 = __toESM(require("fs"));
5870
5870
  var import_path21 = __toESM(require("path"));
5871
5871
  var import_os18 = __toESM(require("os"));
@@ -7397,6 +7397,207 @@ function registerWatchCommand(program2) {
7397
7397
  });
7398
7398
  }
7399
7399
 
7400
+ // src/mcp-gateway/index.ts
7401
+ var import_readline2 = __toESM(require("readline"));
7402
+ var import_chalk13 = __toESM(require("chalk"));
7403
+ var import_child_process12 = require("child_process");
7404
+ var import_execa3 = require("execa");
7405
+ init_orchestrator();
7406
+ function sanitize4(value) {
7407
+ return value.replace(/[\x00-\x1F\x7F]/g, "");
7408
+ }
7409
+ var RPC_INVALID_REQUEST = -32600;
7410
+ var RPC_SERVER_ERROR = -32e3;
7411
+ function isValidId(id) {
7412
+ return id === null || typeof id === "string" || typeof id === "number";
7413
+ }
7414
+ function extractMcpServer(toolName) {
7415
+ const match = toolName.match(/^mcp__([^_](?:[^_]|_(?!_))*?)__/i);
7416
+ return match?.[1];
7417
+ }
7418
+ function tokenize2(cmd) {
7419
+ const tokens = [];
7420
+ let current = "";
7421
+ let inDouble = false;
7422
+ let i = 0;
7423
+ while (i < cmd.length) {
7424
+ const ch = cmd[i];
7425
+ if (inDouble) {
7426
+ if (ch === '"') {
7427
+ inDouble = false;
7428
+ } else if (ch === "\\" && i + 1 < cmd.length) {
7429
+ current += cmd[++i];
7430
+ } else {
7431
+ current += ch;
7432
+ }
7433
+ } else {
7434
+ if (ch === '"') {
7435
+ inDouble = true;
7436
+ } else if (ch === " " || ch === " ") {
7437
+ if (current) {
7438
+ tokens.push(current);
7439
+ current = "";
7440
+ }
7441
+ } else if (ch === "\\" && i + 1 < cmd.length) {
7442
+ current += cmd[++i];
7443
+ } else {
7444
+ current += ch;
7445
+ }
7446
+ }
7447
+ i++;
7448
+ }
7449
+ if (current) tokens.push(current);
7450
+ return tokens;
7451
+ }
7452
+ async function runMcpGateway(upstreamCommand) {
7453
+ const commandParts = tokenize2(upstreamCommand);
7454
+ const cmd = commandParts[0];
7455
+ const cmdArgs = commandParts.slice(1);
7456
+ let executable = cmd;
7457
+ try {
7458
+ const { stdout } = await (0, import_execa3.execa)("which", [cmd]);
7459
+ if (stdout) executable = stdout.trim();
7460
+ } catch {
7461
+ }
7462
+ console.error(import_chalk13.default.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
7463
+ const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
7464
+ "NODE_OPTIONS",
7465
+ "NODE_PATH",
7466
+ "LD_PRELOAD",
7467
+ "LD_LIBRARY_PATH",
7468
+ "DYLD_INSERT_LIBRARIES",
7469
+ "PYTHONPATH",
7470
+ "PYTHONSTARTUP",
7471
+ "PERL5LIB",
7472
+ "PERL5OPT",
7473
+ "RUBYLIB",
7474
+ "RUBYOPT",
7475
+ "JAVA_TOOL_OPTIONS",
7476
+ "JDK_JAVA_OPTIONS"
7477
+ ]);
7478
+ const safeEnv = Object.fromEntries(
7479
+ Object.entries(process.env).filter(([k]) => !UPSTREAM_INJECTOR_VARS.has(k))
7480
+ );
7481
+ const child = (0, import_child_process12.spawn)(executable, cmdArgs, {
7482
+ stdio: ["pipe", "pipe", "inherit"],
7483
+ // control stdin/stdout; inherit stderr
7484
+ shell: false,
7485
+ env: { ...safeEnv, FORCE_COLOR: "1" }
7486
+ });
7487
+ let authPending = false;
7488
+ let deferredExitCode = null;
7489
+ let deferredStdinEnd = false;
7490
+ const agentIn = import_readline2.default.createInterface({ input: process.stdin, terminal: false });
7491
+ agentIn.on("line", async (line) => {
7492
+ let message;
7493
+ try {
7494
+ const parsed = JSON.parse(line);
7495
+ if ("id" in parsed && !isValidId(parsed.id)) {
7496
+ const errorResponse = {
7497
+ jsonrpc: "2.0",
7498
+ id: null,
7499
+ error: {
7500
+ code: RPC_INVALID_REQUEST,
7501
+ message: "Invalid Request: id must be string, number, or null"
7502
+ }
7503
+ };
7504
+ process.stdout.write(JSON.stringify(errorResponse) + "\n");
7505
+ return;
7506
+ }
7507
+ message = { ...parsed, id: parsed.id };
7508
+ } catch {
7509
+ child.stdin.write(line + "\n");
7510
+ return;
7511
+ }
7512
+ if (message.method === "tools/call" || message.method === "call_tool" || message.method === "use_tool") {
7513
+ agentIn.pause();
7514
+ authPending = true;
7515
+ try {
7516
+ const toolName = sanitize4(
7517
+ String(message.params?.name ?? message.params?.tool_name ?? "unknown")
7518
+ );
7519
+ const toolArgs = message.params?.arguments ?? message.params?.tool_input ?? {};
7520
+ const mcpServer = extractMcpServer(toolName);
7521
+ const result = await authorizeHeadless(toolName, toolArgs, {
7522
+ agent: "MCP-Gateway",
7523
+ mcpServer
7524
+ });
7525
+ if (!result.approved) {
7526
+ console.error(import_chalk13.default.red(`
7527
+ \u{1F6D1} Node9 MCP Gateway: Action Blocked`));
7528
+ console.error(import_chalk13.default.gray(` Tool: ${toolName}`));
7529
+ console.error(import_chalk13.default.gray(` Reason: ${result.reason ?? "Security Policy"}
7530
+ `));
7531
+ const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
7532
+ const isHumanDecision = blockedByLabel.toLowerCase().includes("user") || blockedByLabel.toLowerCase().includes("daemon") || blockedByLabel.toLowerCase().includes("decision");
7533
+ const aiInstruction = buildNegotiationMessage(
7534
+ blockedByLabel,
7535
+ isHumanDecision,
7536
+ result.reason
7537
+ );
7538
+ const errorResponse = {
7539
+ jsonrpc: "2.0",
7540
+ id: message.id ?? null,
7541
+ error: {
7542
+ code: RPC_SERVER_ERROR,
7543
+ message: aiInstruction,
7544
+ data: { reason: result.reason, blockedBy: result.blockedByLabel }
7545
+ }
7546
+ };
7547
+ process.stdout.write(JSON.stringify(errorResponse) + "\n");
7548
+ return;
7549
+ }
7550
+ child.stdin.write(line + "\n");
7551
+ } catch {
7552
+ const errorResponse = {
7553
+ jsonrpc: "2.0",
7554
+ id: message.id ?? null,
7555
+ error: {
7556
+ code: -32e3,
7557
+ message: "Node9: Security engine encountered an error. Action blocked for safety."
7558
+ }
7559
+ };
7560
+ process.stdout.write(JSON.stringify(errorResponse) + "\n");
7561
+ return;
7562
+ } finally {
7563
+ authPending = false;
7564
+ agentIn.resume();
7565
+ if (deferredStdinEnd) child.stdin.end();
7566
+ if (deferredExitCode !== null) process.exit(deferredExitCode);
7567
+ }
7568
+ return;
7569
+ }
7570
+ child.stdin.write(line + "\n");
7571
+ });
7572
+ child.stdout.pipe(process.stdout);
7573
+ process.stdin.on("close", () => {
7574
+ if (authPending) {
7575
+ deferredStdinEnd = true;
7576
+ } else {
7577
+ child.stdin.end();
7578
+ }
7579
+ });
7580
+ child.on("exit", (code) => {
7581
+ if (authPending) {
7582
+ deferredExitCode = code ?? 0;
7583
+ } else {
7584
+ process.exit(code ?? 0);
7585
+ }
7586
+ });
7587
+ }
7588
+
7589
+ // src/cli/commands/mcp-gateway.ts
7590
+ function registerMcpGatewayCommand(program2) {
7591
+ program2.command("mcp-gateway").description(
7592
+ "Run Node9 as an MCP gateway \u2014 intercepts and authorizes tool calls before forwarding to the upstream MCP server"
7593
+ ).requiredOption(
7594
+ "--upstream <command>",
7595
+ 'The upstream MCP server command to wrap (e.g. "npx -y @modelcontextprotocol/server-filesystem /workspace")'
7596
+ ).action(async (options) => {
7597
+ await runMcpGateway(options.upstream);
7598
+ });
7599
+ }
7600
+
7400
7601
  // src/cli.ts
7401
7602
  var { version } = JSON.parse(
7402
7603
  import_fs20.default.readFileSync(import_path21.default.join(__dirname, "../package.json"), "utf-8")
@@ -7450,31 +7651,31 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
7450
7651
  import_fs20.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
7451
7652
  }
7452
7653
  if (options.profile && profileName !== "default") {
7453
- console.log(import_chalk14.default.green(`\u2705 Profile "${profileName}" saved`));
7454
- console.log(import_chalk14.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
7654
+ console.log(import_chalk15.default.green(`\u2705 Profile "${profileName}" saved`));
7655
+ console.log(import_chalk15.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
7455
7656
  } else if (options.local) {
7456
- console.log(import_chalk14.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
7457
- console.log(import_chalk14.default.gray(` All decisions stay on this machine.`));
7657
+ console.log(import_chalk15.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
7658
+ console.log(import_chalk15.default.gray(` All decisions stay on this machine.`));
7458
7659
  } else {
7459
- console.log(import_chalk14.default.green(`\u2705 Logged in \u2014 agent mode`));
7460
- console.log(import_chalk14.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
7660
+ console.log(import_chalk15.default.green(`\u2705 Logged in \u2014 agent mode`));
7661
+ console.log(import_chalk15.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
7461
7662
  }
7462
7663
  });
7463
7664
  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) => {
7464
7665
  if (target === "gemini") return await setupGemini();
7465
7666
  if (target === "claude") return await setupClaude();
7466
7667
  if (target === "cursor") return await setupCursor();
7467
- console.error(import_chalk14.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
7668
+ console.error(import_chalk15.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
7468
7669
  process.exit(1);
7469
7670
  });
7470
7671
  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) => {
7471
7672
  if (!target) {
7472
- console.log(import_chalk14.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
7473
- console.log(" Usage: " + import_chalk14.default.white("node9 setup <target>") + "\n");
7673
+ console.log(import_chalk15.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
7674
+ console.log(" Usage: " + import_chalk15.default.white("node9 setup <target>") + "\n");
7474
7675
  console.log(" Targets:");
7475
- console.log(" " + import_chalk14.default.green("claude") + " \u2014 Claude Code (hook mode)");
7476
- console.log(" " + import_chalk14.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
7477
- console.log(" " + import_chalk14.default.green("cursor") + " \u2014 Cursor (hook mode)");
7676
+ console.log(" " + import_chalk15.default.green("claude") + " \u2014 Claude Code (hook mode)");
7677
+ console.log(" " + import_chalk15.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
7678
+ console.log(" " + import_chalk15.default.green("cursor") + " \u2014 Cursor (hook mode)");
7478
7679
  console.log("");
7479
7680
  return;
7480
7681
  }
@@ -7482,7 +7683,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
7482
7683
  if (t === "gemini") return await setupGemini();
7483
7684
  if (t === "claude") return await setupClaude();
7484
7685
  if (t === "cursor") return await setupCursor();
7485
- console.error(import_chalk14.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
7686
+ console.error(import_chalk15.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
7486
7687
  process.exit(1);
7487
7688
  });
7488
7689
  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) => {
@@ -7491,30 +7692,30 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
7491
7692
  else if (target === "gemini") fn = teardownGemini;
7492
7693
  else if (target === "cursor") fn = teardownCursor;
7493
7694
  else {
7494
- console.error(import_chalk14.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
7695
+ console.error(import_chalk15.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
7495
7696
  process.exit(1);
7496
7697
  }
7497
- console.log(import_chalk14.default.cyan(`
7698
+ console.log(import_chalk15.default.cyan(`
7498
7699
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
7499
7700
  `));
7500
7701
  try {
7501
7702
  fn();
7502
7703
  } catch (err) {
7503
- console.error(import_chalk14.default.red(` \u26A0\uFE0F Failed: ${err instanceof Error ? err.message : String(err)}`));
7704
+ console.error(import_chalk15.default.red(` \u26A0\uFE0F Failed: ${err instanceof Error ? err.message : String(err)}`));
7504
7705
  process.exit(1);
7505
7706
  }
7506
- console.log(import_chalk14.default.gray("\n Restart the agent for changes to take effect."));
7707
+ console.log(import_chalk15.default.gray("\n Restart the agent for changes to take effect."));
7507
7708
  });
7508
7709
  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) => {
7509
- console.log(import_chalk14.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
7510
- console.log(import_chalk14.default.bold("Stopping daemon..."));
7710
+ console.log(import_chalk15.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
7711
+ console.log(import_chalk15.default.bold("Stopping daemon..."));
7511
7712
  try {
7512
7713
  stopDaemon();
7513
- console.log(import_chalk14.default.green(" \u2705 Daemon stopped"));
7714
+ console.log(import_chalk15.default.green(" \u2705 Daemon stopped"));
7514
7715
  } catch {
7515
- console.log(import_chalk14.default.blue(" \u2139\uFE0F Daemon was not running"));
7716
+ console.log(import_chalk15.default.blue(" \u2139\uFE0F Daemon was not running"));
7516
7717
  }
7517
- console.log(import_chalk14.default.bold("\nRemoving hooks..."));
7718
+ console.log(import_chalk15.default.bold("\nRemoving hooks..."));
7518
7719
  let teardownFailed = false;
7519
7720
  for (const [label, fn] of [
7520
7721
  ["Claude", teardownClaude],
@@ -7526,7 +7727,7 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
7526
7727
  } catch (err) {
7527
7728
  teardownFailed = true;
7528
7729
  console.error(
7529
- import_chalk14.default.red(
7730
+ import_chalk15.default.red(
7530
7731
  ` \u26A0\uFE0F Failed to remove ${label} hooks: ${err instanceof Error ? err.message : String(err)}`
7531
7732
  )
7532
7733
  );
@@ -7543,28 +7744,28 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
7543
7744
  import_fs20.default.rmSync(node9Dir, { recursive: true });
7544
7745
  if (import_fs20.default.existsSync(node9Dir)) {
7545
7746
  console.error(
7546
- import_chalk14.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
7747
+ import_chalk15.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
7547
7748
  );
7548
7749
  } else {
7549
- console.log(import_chalk14.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
7750
+ console.log(import_chalk15.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
7550
7751
  }
7551
7752
  } else {
7552
- console.log(import_chalk14.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
7753
+ console.log(import_chalk15.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
7553
7754
  }
7554
7755
  } else {
7555
- console.log(import_chalk14.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
7756
+ console.log(import_chalk15.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
7556
7757
  }
7557
7758
  } else {
7558
7759
  console.log(
7559
- import_chalk14.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
7760
+ import_chalk15.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
7560
7761
  );
7561
7762
  }
7562
7763
  if (teardownFailed) {
7563
- console.error(import_chalk14.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
7764
+ console.error(import_chalk15.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
7564
7765
  process.exit(1);
7565
7766
  }
7566
- console.log(import_chalk14.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
7567
- console.log(import_chalk14.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
7767
+ console.log(import_chalk15.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
7768
+ console.log(import_chalk15.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
7568
7769
  });
7569
7770
  registerDoctorCommand(program, version);
7570
7771
  program.command("explain").description(
@@ -7577,7 +7778,7 @@ program.command("explain").description(
7577
7778
  try {
7578
7779
  args = JSON.parse(trimmed);
7579
7780
  } catch {
7580
- console.error(import_chalk14.default.red(`
7781
+ console.error(import_chalk15.default.red(`
7581
7782
  \u274C Invalid JSON: ${trimmed}
7582
7783
  `));
7583
7784
  process.exit(1);
@@ -7588,54 +7789,54 @@ program.command("explain").description(
7588
7789
  }
7589
7790
  const result = await explainPolicy(tool, args);
7590
7791
  console.log("");
7591
- console.log(import_chalk14.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
7792
+ console.log(import_chalk15.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
7592
7793
  console.log("");
7593
- console.log(` ${import_chalk14.default.bold("Tool:")} ${import_chalk14.default.white(result.tool)}`);
7794
+ console.log(` ${import_chalk15.default.bold("Tool:")} ${import_chalk15.default.white(result.tool)}`);
7594
7795
  if (argsRaw) {
7595
7796
  const preview = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
7596
- console.log(` ${import_chalk14.default.bold("Input:")} ${import_chalk14.default.gray(preview)}`);
7797
+ console.log(` ${import_chalk15.default.bold("Input:")} ${import_chalk15.default.gray(preview)}`);
7597
7798
  }
7598
7799
  console.log("");
7599
- console.log(import_chalk14.default.bold("Config Sources (Waterfall):"));
7800
+ console.log(import_chalk15.default.bold("Config Sources (Waterfall):"));
7600
7801
  for (const tier of result.waterfall) {
7601
- const num = import_chalk14.default.gray(` ${tier.tier}.`);
7802
+ const num = import_chalk15.default.gray(` ${tier.tier}.`);
7602
7803
  const label = tier.label.padEnd(16);
7603
7804
  let statusStr;
7604
7805
  if (tier.tier === 1) {
7605
- statusStr = import_chalk14.default.gray(tier.note ?? "");
7806
+ statusStr = import_chalk15.default.gray(tier.note ?? "");
7606
7807
  } else if (tier.status === "active") {
7607
- const loc = tier.path ? import_chalk14.default.gray(tier.path) : "";
7608
- const note = tier.note ? import_chalk14.default.gray(`(${tier.note})`) : "";
7609
- statusStr = import_chalk14.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
7808
+ const loc = tier.path ? import_chalk15.default.gray(tier.path) : "";
7809
+ const note = tier.note ? import_chalk15.default.gray(`(${tier.note})`) : "";
7810
+ statusStr = import_chalk15.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
7610
7811
  } else {
7611
- statusStr = import_chalk14.default.gray("\u25CB " + (tier.note ?? "not found"));
7812
+ statusStr = import_chalk15.default.gray("\u25CB " + (tier.note ?? "not found"));
7612
7813
  }
7613
- console.log(`${num} ${import_chalk14.default.white(label)} ${statusStr}`);
7814
+ console.log(`${num} ${import_chalk15.default.white(label)} ${statusStr}`);
7614
7815
  }
7615
7816
  console.log("");
7616
- console.log(import_chalk14.default.bold("Policy Evaluation:"));
7817
+ console.log(import_chalk15.default.bold("Policy Evaluation:"));
7617
7818
  for (const step of result.steps) {
7618
7819
  const isFinal = step.isFinal;
7619
7820
  let icon;
7620
- if (step.outcome === "allow") icon = import_chalk14.default.green(" \u2705");
7621
- else if (step.outcome === "review") icon = import_chalk14.default.red(" \u{1F534}");
7622
- else if (step.outcome === "skip") icon = import_chalk14.default.gray(" \u2500 ");
7623
- else icon = import_chalk14.default.gray(" \u25CB ");
7821
+ if (step.outcome === "allow") icon = import_chalk15.default.green(" \u2705");
7822
+ else if (step.outcome === "review") icon = import_chalk15.default.red(" \u{1F534}");
7823
+ else if (step.outcome === "skip") icon = import_chalk15.default.gray(" \u2500 ");
7824
+ else icon = import_chalk15.default.gray(" \u25CB ");
7624
7825
  const name = step.name.padEnd(18);
7625
- const nameStr = isFinal ? import_chalk14.default.white.bold(name) : import_chalk14.default.white(name);
7626
- const detail = isFinal ? import_chalk14.default.white(step.detail) : import_chalk14.default.gray(step.detail);
7627
- const arrow = isFinal ? import_chalk14.default.yellow(" \u2190 STOP") : "";
7826
+ const nameStr = isFinal ? import_chalk15.default.white.bold(name) : import_chalk15.default.white(name);
7827
+ const detail = isFinal ? import_chalk15.default.white(step.detail) : import_chalk15.default.gray(step.detail);
7828
+ const arrow = isFinal ? import_chalk15.default.yellow(" \u2190 STOP") : "";
7628
7829
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
7629
7830
  }
7630
7831
  console.log("");
7631
7832
  if (result.decision === "allow") {
7632
- console.log(import_chalk14.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk14.default.gray(" \u2014 no approval needed"));
7833
+ console.log(import_chalk15.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk15.default.gray(" \u2014 no approval needed"));
7633
7834
  } else {
7634
7835
  console.log(
7635
- import_chalk14.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk14.default.gray(" \u2014 human approval required")
7836
+ import_chalk15.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk15.default.gray(" \u2014 human approval required")
7636
7837
  );
7637
7838
  if (result.blockedByLabel) {
7638
- console.log(import_chalk14.default.gray(` Reason: ${result.blockedByLabel}`));
7839
+ console.log(import_chalk15.default.gray(` Reason: ${result.blockedByLabel}`));
7639
7840
  }
7640
7841
  }
7641
7842
  console.log("");
@@ -7643,8 +7844,8 @@ program.command("explain").description(
7643
7844
  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) => {
7644
7845
  const configPath = import_path21.default.join(import_os18.default.homedir(), ".node9", "config.json");
7645
7846
  if (import_fs20.default.existsSync(configPath) && !options.force) {
7646
- console.log(import_chalk14.default.yellow(`\u2139\uFE0F Global config already exists: ${configPath}`));
7647
- console.log(import_chalk14.default.gray(` Run with --force to overwrite.`));
7847
+ console.log(import_chalk15.default.yellow(`\u2139\uFE0F Global config already exists: ${configPath}`));
7848
+ console.log(import_chalk15.default.gray(` Run with --force to overwrite.`));
7648
7849
  return;
7649
7850
  }
7650
7851
  const requestedMode = options.mode.toLowerCase();
@@ -7659,10 +7860,10 @@ program.command("init").description("Create ~/.node9/config.json with default po
7659
7860
  const dir = import_path21.default.dirname(configPath);
7660
7861
  if (!import_fs20.default.existsSync(dir)) import_fs20.default.mkdirSync(dir, { recursive: true });
7661
7862
  import_fs20.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2));
7662
- console.log(import_chalk14.default.green(`\u2705 Global config created: ${configPath}`));
7663
- console.log(import_chalk14.default.cyan(` Mode set to: ${safeMode}`));
7863
+ console.log(import_chalk15.default.green(`\u2705 Global config created: ${configPath}`));
7864
+ console.log(import_chalk15.default.cyan(` Mode set to: ${safeMode}`));
7664
7865
  console.log(
7665
- import_chalk14.default.gray(` Undo Engine is ENABLED by default. Use 'node9 undo' to revert AI changes.`)
7866
+ import_chalk15.default.gray(` Undo Engine is ENABLED by default. Use 'node9 undo' to revert AI changes.`)
7666
7867
  );
7667
7868
  });
7668
7869
  registerAuditCommand(program);
@@ -7673,18 +7874,19 @@ program.command("tail").description("Stream live agent activity to the terminal"
7673
7874
  try {
7674
7875
  await startTail2(options);
7675
7876
  } catch (err) {
7676
- console.error(import_chalk14.default.red(`\u274C ${err instanceof Error ? err.message : String(err)}`));
7877
+ console.error(import_chalk15.default.red(`\u274C ${err instanceof Error ? err.message : String(err)}`));
7677
7878
  process.exit(1);
7678
7879
  }
7679
7880
  });
7680
7881
  registerWatchCommand(program);
7882
+ registerMcpGatewayCommand(program);
7681
7883
  registerCheckCommand(program);
7682
7884
  registerLogCommand(program);
7683
7885
  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) => {
7684
7886
  const ms = parseDuration(options.duration);
7685
7887
  if (ms === null) {
7686
7888
  console.error(
7687
- import_chalk14.default.red(`
7889
+ import_chalk15.default.red(`
7688
7890
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
7689
7891
  `)
7690
7892
  );
@@ -7692,20 +7894,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
7692
7894
  }
7693
7895
  pauseNode9(ms, options.duration);
7694
7896
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
7695
- console.log(import_chalk14.default.yellow(`
7897
+ console.log(import_chalk15.default.yellow(`
7696
7898
  \u23F8 Node9 paused until ${expiresAt}`));
7697
- console.log(import_chalk14.default.gray(` All tool calls will be allowed without review.`));
7698
- console.log(import_chalk14.default.gray(` Run "node9 resume" to re-enable early.
7899
+ console.log(import_chalk15.default.gray(` All tool calls will be allowed without review.`));
7900
+ console.log(import_chalk15.default.gray(` Run "node9 resume" to re-enable early.
7699
7901
  `));
7700
7902
  });
7701
7903
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
7702
7904
  const { paused } = checkPause();
7703
7905
  if (!paused) {
7704
- console.log(import_chalk14.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
7906
+ console.log(import_chalk15.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
7705
7907
  return;
7706
7908
  }
7707
7909
  resumeNode9();
7708
- console.log(import_chalk14.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
7910
+ console.log(import_chalk15.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
7709
7911
  });
7710
7912
  var HOOK_BASED_AGENTS = {
7711
7913
  claude: "claude",
@@ -7718,15 +7920,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
7718
7920
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
7719
7921
  const target = HOOK_BASED_AGENTS[firstArg2];
7720
7922
  console.error(
7721
- import_chalk14.default.yellow(`
7923
+ import_chalk15.default.yellow(`
7722
7924
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
7723
7925
  );
7724
- console.error(import_chalk14.default.white(`
7926
+ console.error(import_chalk15.default.white(`
7725
7927
  "${target}" uses its own hook system. Use:`));
7726
7928
  console.error(
7727
- import_chalk14.default.green(` node9 addto ${target} `) + import_chalk14.default.gray("# one-time setup")
7929
+ import_chalk15.default.green(` node9 addto ${target} `) + import_chalk15.default.gray("# one-time setup")
7728
7930
  );
7729
- console.error(import_chalk14.default.green(` ${target} `) + import_chalk14.default.gray("# run normally"));
7931
+ console.error(import_chalk15.default.green(` ${target} `) + import_chalk15.default.gray("# run normally"));
7730
7932
  process.exit(1);
7731
7933
  }
7732
7934
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -7743,7 +7945,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
7743
7945
  }
7744
7946
  );
7745
7947
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
7746
- console.error(import_chalk14.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
7948
+ console.error(import_chalk15.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
7747
7949
  const daemonReady = await autoStartDaemonAndWait();
7748
7950
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
7749
7951
  }
@@ -7756,12 +7958,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
7756
7958
  }
7757
7959
  if (!result.approved) {
7758
7960
  console.error(
7759
- import_chalk14.default.red(`
7961
+ import_chalk15.default.red(`
7760
7962
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
7761
7963
  );
7762
7964
  process.exit(1);
7763
7965
  }
7764
- console.error(import_chalk14.default.green("\n\u2705 Approved \u2014 running command...\n"));
7966
+ console.error(import_chalk15.default.green("\n\u2705 Approved \u2014 running command...\n"));
7765
7967
  await runProxy(fullCommand);
7766
7968
  } else {
7767
7969
  program.help();