@node9/proxy 1.8.2 → 1.8.4

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.
package/dist/cli.mjs CHANGED
@@ -2852,9 +2852,7 @@ end run`;
2852
2852
  "--text",
2853
2853
  pangoMessage,
2854
2854
  "--ok-label",
2855
- locked ? "Waiting..." : "Allow \u21B5",
2856
- "--timeout",
2857
- "300"
2855
+ locked ? "Waiting..." : "Allow \u21B5"
2858
2856
  ];
2859
2857
  if (!locked) {
2860
2858
  argsList.push("--cancel-label", "Block \u238B");
@@ -5674,6 +5672,12 @@ function estimateToolCost(tool, args) {
5674
5672
  const newStr = a.new_string ?? "";
5675
5673
  return String(newStr).length / BYTES_PER_TOKEN / 1e6 * OUTPUT_PRICE_PER_1M;
5676
5674
  }
5675
+ if (t === "bash" || t === "shell" || t === "run_shell_command" || t === "terminal_execute") {
5676
+ const command = String(a.command ?? a.cmd ?? a.input ?? "");
5677
+ if (command.length > 0) {
5678
+ return command.length / BYTES_PER_TOKEN / 1e6 * OUTPUT_PRICE_PER_1M;
5679
+ }
5680
+ }
5677
5681
  return void 0;
5678
5682
  }
5679
5683
  function broadcast(event, data) {
@@ -6748,7 +6752,7 @@ import fs25 from "fs";
6748
6752
  import os21 from "os";
6749
6753
  import path28 from "path";
6750
6754
  import readline5 from "readline";
6751
- import { spawn as spawn9, execSync as execSync3 } from "child_process";
6755
+ import { spawn as spawn10, execSync as execSync3 } from "child_process";
6752
6756
  function getIcon(tool) {
6753
6757
  const t = tool.toLowerCase();
6754
6758
  for (const [k, v] of Object.entries(ICONS)) {
@@ -6805,7 +6809,7 @@ async function ensureDaemon() {
6805
6809
  } catch {
6806
6810
  }
6807
6811
  console.log(chalk17.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
6808
- const child = spawn9(process.execPath, [process.argv[1], "daemon"], {
6812
+ const child = spawn10(process.execPath, [process.argv[1], "daemon"], {
6809
6813
  detached: true,
6810
6814
  stdio: "ignore",
6811
6815
  env: { ...process.env, NODE9_AUTO_STARTED: "1" }
@@ -6979,6 +6983,7 @@ async function startTail(options = {}) {
6979
6983
  return;
6980
6984
  }
6981
6985
  const connectionTime = Date.now();
6986
+ let initialReplayDone = false;
6982
6987
  const activityPending = /* @__PURE__ */ new Map();
6983
6988
  const orphanedResults = /* @__PURE__ */ new Map();
6984
6989
  let csrfToken = "";
@@ -7310,11 +7315,17 @@ async function startTail(options = {}) {
7310
7315
  return;
7311
7316
  }
7312
7317
  if (event === "activity") {
7318
+ const isReplayEvent = data.status && data.status !== "pending";
7319
+ if (isReplayEvent && !initialReplayDone) {
7320
+ renderResult(data, data);
7321
+ return;
7322
+ }
7313
7323
  if (!options.history && data.ts > 0 && data.ts < connectionTime) return;
7314
- if (data.status && data.status !== "pending") {
7324
+ if (isReplayEvent) {
7315
7325
  renderResult(data, data);
7316
7326
  return;
7317
7327
  }
7328
+ if (!initialReplayDone) initialReplayDone = true;
7318
7329
  const orphaned = orphanedResults.get(data.id);
7319
7330
  if (orphaned) {
7320
7331
  orphanedResults.delete(data.id);
@@ -7695,6 +7706,24 @@ function renderContextLine(stdin) {
7695
7706
  async function main() {
7696
7707
  try {
7697
7708
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
7709
+ if (fs26.existsSync(path29.join(os22.homedir(), ".node9", "hud-debug"))) {
7710
+ try {
7711
+ const logPath = path29.join(os22.homedir(), ".node9", "hud-debug.log");
7712
+ const MAX_LOG_SIZE = 10 * 1024 * 1024;
7713
+ let size = 0;
7714
+ try {
7715
+ size = fs26.statSync(logPath).size;
7716
+ } catch {
7717
+ }
7718
+ if (size < MAX_LOG_SIZE) {
7719
+ fs26.appendFileSync(
7720
+ logPath,
7721
+ JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
7722
+ );
7723
+ }
7724
+ } catch {
7725
+ }
7726
+ }
7698
7727
  if (!daemonStatus2) {
7699
7728
  renderOffline();
7700
7729
  return;
@@ -7807,7 +7836,7 @@ function isNode9Hook(cmd) {
7807
7836
  function teardownClaude() {
7808
7837
  const homeDir2 = os10.homedir();
7809
7838
  const hooksPath = path14.join(homeDir2, ".claude", "settings.json");
7810
- const mcpPath = path14.join(homeDir2, ".claude.json");
7839
+ const mcpPath = path14.join(homeDir2, ".claude", ".mcp.json");
7811
7840
  let changed = false;
7812
7841
  const settings = readJson(hooksPath);
7813
7842
  if (settings?.hooks) {
@@ -7833,11 +7862,12 @@ function teardownClaude() {
7833
7862
  let mcpChanged = false;
7834
7863
  if (removeNode9McpServer(claudeConfig.mcpServers)) {
7835
7864
  mcpChanged = true;
7836
- console.log(chalk.green(" \u2705 Removed node9 MCP server entry from ~/.claude.json"));
7865
+ console.log(chalk.green(" \u2705 Removed node9 MCP server entry from ~/.claude/.mcp.json"));
7837
7866
  }
7838
7867
  for (const [name, server] of Object.entries(claudeConfig.mcpServers)) {
7839
- if (server.command === "node9" && Array.isArray(server.args) && server.args.length > 0) {
7840
- const [originalCmd, ...originalArgs] = server.args;
7868
+ const args = server.args;
7869
+ if (server.command === "node9" && Array.isArray(args) && args[0] === "mcp" && args[1] === "--upstream" && typeof args[2] === "string") {
7870
+ const [originalCmd, ...originalArgs] = args[2].split(" ");
7841
7871
  claudeConfig.mcpServers[name] = {
7842
7872
  ...server,
7843
7873
  command: originalCmd,
@@ -7845,16 +7875,11 @@ function teardownClaude() {
7845
7875
  };
7846
7876
  mcpChanged = true;
7847
7877
  } else if (server.command === "node9") {
7848
- console.warn(
7849
- chalk.yellow(
7850
- ` \u26A0\uFE0F Cannot unwrap MCP server "${name}" in ~/.claude.json \u2014 args is empty. Remove it manually.`
7851
- )
7852
- );
7853
7878
  }
7854
7879
  }
7855
7880
  if (mcpChanged) {
7856
7881
  writeJson(mcpPath, claudeConfig);
7857
- console.log(chalk.green(" \u2705 Unwrapped MCP servers in ~/.claude.json"));
7882
+ console.log(chalk.green(" \u2705 Unwrapped MCP servers in ~/.claude/.mcp.json"));
7858
7883
  }
7859
7884
  }
7860
7885
  }
@@ -7883,8 +7908,9 @@ function teardownGemini() {
7883
7908
  console.log(chalk.green(" \u2705 Removed node9 MCP server entry from ~/.gemini/settings.json"));
7884
7909
  }
7885
7910
  for (const [name, server] of Object.entries(settings.mcpServers)) {
7886
- if (server.command === "node9" && Array.isArray(server.args) && server.args.length > 0) {
7887
- const [originalCmd, ...originalArgs] = server.args;
7911
+ const args = server.args;
7912
+ if (server.command === "node9" && Array.isArray(args) && args[0] === "mcp" && args[1] === "--upstream" && typeof args[2] === "string") {
7913
+ const [originalCmd, ...originalArgs] = args[2].split(" ");
7888
7914
  settings.mcpServers[name] = {
7889
7915
  ...server,
7890
7916
  command: originalCmd,
@@ -7915,8 +7941,9 @@ function teardownCursor() {
7915
7941
  console.log(chalk.green(" \u2705 Removed node9 MCP server entry from ~/.cursor/mcp.json"));
7916
7942
  }
7917
7943
  for (const [name, server] of Object.entries(mcpConfig.mcpServers)) {
7918
- if (server.command === "node9" && Array.isArray(server.args) && server.args.length > 0) {
7919
- const [originalCmd, ...originalArgs] = server.args;
7944
+ const args = server.args;
7945
+ if (server.command === "node9" && Array.isArray(args) && args[0] === "mcp" && args[1] === "--upstream" && typeof args[2] === "string") {
7946
+ const [originalCmd, ...originalArgs] = args[2].split(" ");
7920
7947
  mcpConfig.mcpServers[name] = {
7921
7948
  ...server,
7922
7949
  command: originalCmd,
@@ -7934,7 +7961,7 @@ function teardownCursor() {
7934
7961
  }
7935
7962
  async function setupClaude() {
7936
7963
  const homeDir2 = os10.homedir();
7937
- const mcpPath = path14.join(homeDir2, ".claude.json");
7964
+ const mcpPath = path14.join(homeDir2, ".claude", ".mcp.json");
7938
7965
  const hooksPath = path14.join(homeDir2, ".claude", "settings.json");
7939
7966
  const claudeConfig = readJson(mcpPath) ?? {};
7940
7967
  const settings = readJson(hooksPath) ?? {};
@@ -7949,7 +7976,7 @@ async function setupClaude() {
7949
7976
  if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
7950
7977
  settings.hooks.PreToolUse.push({
7951
7978
  matcher: ".*",
7952
- hooks: [{ type: "command", command: fullPathCommand("check"), timeout: 60 }]
7979
+ hooks: [{ type: "command", command: fullPathCommand("check"), timeout: 600 }]
7953
7980
  });
7954
7981
  console.log(chalk.green(" \u2705 PreToolUse hook added \u2192 node9 check"));
7955
7982
  hooksChanged = true;
@@ -7975,6 +8002,15 @@ async function setupClaude() {
7975
8002
  console.log(chalk.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
7976
8003
  anythingChanged = true;
7977
8004
  }
8005
+ const hudCommand = fullPathCommand("hud");
8006
+ const statusLineObj = { type: "command", command: hudCommand };
8007
+ const existingStatusLine = settings.statusLine;
8008
+ const existingStatusCommand = typeof existingStatusLine === "object" ? existingStatusLine?.command : existingStatusLine;
8009
+ if (existingStatusCommand !== hudCommand) {
8010
+ settings.statusLine = statusLineObj;
8011
+ hooksChanged = true;
8012
+ anythingChanged = true;
8013
+ }
7978
8014
  if (hooksChanged) {
7979
8015
  writeJson(hooksPath, settings);
7980
8016
  console.log("");
@@ -7982,20 +8018,24 @@ async function setupClaude() {
7982
8018
  const serversToWrap = [];
7983
8019
  for (const [name, server] of Object.entries(servers)) {
7984
8020
  if (!server.command || server.command === "node9") continue;
7985
- const parts = [server.command, ...server.args ?? []];
7986
- serversToWrap.push({ name, originalCmd: parts.join(" "), parts });
8021
+ const upstream = [server.command, ...server.args ?? []].join(" ");
8022
+ serversToWrap.push({ name, upstream });
7987
8023
  }
7988
8024
  if (serversToWrap.length > 0) {
7989
8025
  console.log(chalk.bold("The following existing entries will be modified:\n"));
7990
8026
  console.log(chalk.white(` ${mcpPath}`));
7991
- for (const { name, originalCmd } of serversToWrap) {
7992
- console.log(chalk.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
8027
+ for (const { name, upstream } of serversToWrap) {
8028
+ console.log(chalk.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
7993
8029
  }
7994
8030
  console.log("");
7995
8031
  const proceed = await confirm({ message: "Wrap these MCP servers?", default: true });
7996
8032
  if (proceed) {
7997
- for (const { name, parts } of serversToWrap) {
7998
- servers[name] = { ...servers[name], command: "node9", args: parts };
8033
+ for (const { name, upstream } of serversToWrap) {
8034
+ servers[name] = {
8035
+ ...servers[name],
8036
+ command: "node9",
8037
+ args: ["mcp", "--upstream", upstream]
8038
+ };
7999
8039
  }
8000
8040
  claudeConfig.mcpServers = servers;
8001
8041
  writeJson(mcpPath, claudeConfig);
@@ -8075,20 +8115,24 @@ async function setupGemini() {
8075
8115
  const serversToWrap = [];
8076
8116
  for (const [name, server] of Object.entries(servers)) {
8077
8117
  if (!server.command || server.command === "node9") continue;
8078
- const parts = [server.command, ...server.args ?? []];
8079
- serversToWrap.push({ name, originalCmd: parts.join(" "), parts });
8118
+ const upstream = [server.command, ...server.args ?? []].join(" ");
8119
+ serversToWrap.push({ name, upstream });
8080
8120
  }
8081
8121
  if (serversToWrap.length > 0) {
8082
8122
  console.log(chalk.bold("The following existing entries will be modified:\n"));
8083
8123
  console.log(chalk.white(` ${settingsPath} (mcpServers)`));
8084
- for (const { name, originalCmd } of serversToWrap) {
8085
- console.log(chalk.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
8124
+ for (const { name, upstream } of serversToWrap) {
8125
+ console.log(chalk.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
8086
8126
  }
8087
8127
  console.log("");
8088
8128
  const proceed = await confirm({ message: "Wrap these MCP servers?", default: true });
8089
8129
  if (proceed) {
8090
- for (const { name, parts } of serversToWrap) {
8091
- servers[name] = { ...servers[name], command: "node9", args: parts };
8130
+ for (const { name, upstream } of serversToWrap) {
8131
+ servers[name] = {
8132
+ ...servers[name],
8133
+ command: "node9",
8134
+ args: ["mcp", "--upstream", upstream]
8135
+ };
8092
8136
  }
8093
8137
  settings.mcpServers = servers;
8094
8138
  writeJson(settingsPath, settings);
@@ -8147,20 +8191,24 @@ async function setupCursor() {
8147
8191
  const serversToWrap = [];
8148
8192
  for (const [name, server] of Object.entries(servers)) {
8149
8193
  if (!server.command || server.command === "node9") continue;
8150
- const parts = [server.command, ...server.args ?? []];
8151
- serversToWrap.push({ name, originalCmd: parts.join(" "), parts });
8194
+ const upstream = [server.command, ...server.args ?? []].join(" ");
8195
+ serversToWrap.push({ name, upstream });
8152
8196
  }
8153
8197
  if (serversToWrap.length > 0) {
8154
8198
  console.log(chalk.bold("The following existing entries will be modified:\n"));
8155
8199
  console.log(chalk.white(` ${mcpPath}`));
8156
- for (const { name, originalCmd } of serversToWrap) {
8157
- console.log(chalk.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
8200
+ for (const { name, upstream } of serversToWrap) {
8201
+ console.log(chalk.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
8158
8202
  }
8159
8203
  console.log("");
8160
8204
  const proceed = await confirm({ message: "Wrap these MCP servers?", default: true });
8161
8205
  if (proceed) {
8162
- for (const { name, parts } of serversToWrap) {
8163
- servers[name] = { ...servers[name], command: "node9", args: parts };
8206
+ for (const { name, upstream } of serversToWrap) {
8207
+ servers[name] = {
8208
+ ...servers[name],
8209
+ command: "node9",
8210
+ args: ["mcp", "--upstream", upstream]
8211
+ };
8164
8212
  }
8165
8213
  mcpConfig.mcpServers = servers;
8166
8214
  writeJson(mcpPath, mcpConfig);
@@ -8223,20 +8271,24 @@ async function setupCodex() {
8223
8271
  const serversToWrap = [];
8224
8272
  for (const [name, server] of Object.entries(servers)) {
8225
8273
  if (!server.command || server.command === "node9") continue;
8226
- const parts = [server.command, ...server.args ?? []];
8227
- serversToWrap.push({ name, originalCmd: parts.join(" "), parts });
8274
+ const upstream = [server.command, ...server.args ?? []].join(" ");
8275
+ serversToWrap.push({ name, upstream });
8228
8276
  }
8229
8277
  if (serversToWrap.length > 0) {
8230
8278
  console.log(chalk.bold("The following existing entries will be modified:\n"));
8231
8279
  console.log(chalk.white(` ${configPath}`));
8232
- for (const { name, originalCmd } of serversToWrap) {
8233
- console.log(chalk.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
8280
+ for (const { name, upstream } of serversToWrap) {
8281
+ console.log(chalk.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
8234
8282
  }
8235
8283
  console.log("");
8236
8284
  const proceed = await confirm({ message: "Wrap these MCP servers?", default: true });
8237
8285
  if (proceed) {
8238
- for (const { name, parts } of serversToWrap) {
8239
- servers[name] = { ...servers[name], command: "node9", args: parts };
8286
+ for (const { name, upstream } of serversToWrap) {
8287
+ servers[name] = {
8288
+ ...servers[name],
8289
+ command: "node9",
8290
+ args: ["mcp", "--upstream", upstream]
8291
+ };
8240
8292
  }
8241
8293
  config.mcp_servers = servers;
8242
8294
  writeToml(configPath, config);
@@ -8425,18 +8477,20 @@ async function runProxy(targetCommand) {
8425
8477
  const cmd = commandParts[0];
8426
8478
  const args = commandParts.slice(1);
8427
8479
  let executable = cmd;
8480
+ let useShell = false;
8428
8481
  try {
8429
8482
  const { stdout } = await execa("which", [cmd]);
8430
8483
  if (stdout) executable = stdout.trim();
8431
8484
  } catch {
8485
+ useShell = true;
8432
8486
  }
8433
8487
  console.error(chalk4.green(`\u{1F680} Node9 Proxy Active: Monitoring [${targetCommand}]`));
8434
- const child = spawn3(executable, args, {
8488
+ const spawnEnv = { ...process.env, FORCE_COLOR: "1" };
8489
+ const child = useShell ? spawn3("/bin/bash", ["-c", targetCommand], {
8435
8490
  stdio: ["pipe", "pipe", "inherit"],
8436
- // We control STDIN and STDOUT
8437
8491
  shell: false,
8438
- env: { ...process.env, FORCE_COLOR: "1" }
8439
- });
8492
+ env: spawnEnv
8493
+ }) : spawn3(executable, args, { stdio: ["pipe", "pipe", "inherit"], shell: false, env: spawnEnv });
8440
8494
  const agentIn = readline.createInterface({ input: process.stdin, terminal: false });
8441
8495
  agentIn.on("line", async (line) => {
8442
8496
  let message;
@@ -8549,6 +8603,7 @@ init_config();
8549
8603
  init_policy();
8550
8604
  import chalk5 from "chalk";
8551
8605
  import fs18 from "fs";
8606
+ import { spawn as spawn6 } from "child_process";
8552
8607
  import path20 from "path";
8553
8608
  import os14 from "os";
8554
8609
 
@@ -8928,6 +8983,37 @@ RAW: ${raw}
8928
8983
  process.exit(0);
8929
8984
  }
8930
8985
  const config = getConfig(payload.cwd || void 0);
8986
+ if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
8987
+ try {
8988
+ const scriptPath = process.argv[1];
8989
+ if (typeof scriptPath !== "string" || !path20.isAbsolute(scriptPath))
8990
+ throw new Error("node9: argv[1] is not an absolute path");
8991
+ const resolvedScript = fs18.realpathSync(scriptPath);
8992
+ const expectedCli = fs18.realpathSync(path20.resolve(__dirname, "../../cli.js"));
8993
+ if (resolvedScript !== expectedCli)
8994
+ throw new Error(
8995
+ "node9: daemon spawn aborted \u2014 argv[1] does not resolve to the node9 CLI"
8996
+ );
8997
+ const safeEnv = { ...process.env };
8998
+ for (const key of [
8999
+ "NODE_OPTIONS",
9000
+ "LD_PRELOAD",
9001
+ "LD_LIBRARY_PATH",
9002
+ "DYLD_INSERT_LIBRARIES",
9003
+ "NODE_PATH",
9004
+ "ELECTRON_RUN_AS_NODE"
9005
+ ]) {
9006
+ delete safeEnv[key];
9007
+ }
9008
+ const d = spawn6(process.execPath, [scriptPath, "daemon"], {
9009
+ detached: true,
9010
+ stdio: "ignore",
9011
+ env: { ...safeEnv, NODE9_AUTO_STARTED: "1", NODE9_BROWSER_OPENED: "1" }
9012
+ });
9013
+ d.unref();
9014
+ } catch {
9015
+ }
9016
+ }
8931
9017
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
8932
9018
  const logPath = path20.join(os14.homedir(), ".node9", "hook-debug.log");
8933
9019
  if (!fs18.existsSync(path20.dirname(logPath)))
@@ -9839,7 +9925,7 @@ function registerAuditCommand(program2) {
9839
9925
  init_daemon2();
9840
9926
  init_daemon();
9841
9927
  import chalk9 from "chalk";
9842
- import { spawn as spawn6 } from "child_process";
9928
+ import { spawn as spawn7 } from "child_process";
9843
9929
  function registerDaemonCommand(program2) {
9844
9930
  program2.command("daemon").description("Run the local approval server").argument("[action]", "start | stop | status (default: start)").option("-b, --background", "Start the daemon in the background (detached)").option("-o, --openui", "Start in background and open browser").option(
9845
9931
  "-w, --watch",
@@ -9870,7 +9956,7 @@ function registerDaemonCommand(program2) {
9870
9956
  console.log(chalk9.green(`\u{1F310} Opened browser: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
9871
9957
  process.exit(0);
9872
9958
  }
9873
- const child = spawn6(process.execPath, [process.argv[1], "daemon"], {
9959
+ const child = spawn7(process.execPath, [process.argv[1], "daemon"], {
9874
9960
  detached: true,
9875
9961
  stdio: "ignore"
9876
9962
  });
@@ -9885,7 +9971,7 @@ function registerDaemonCommand(program2) {
9885
9971
  process.exit(0);
9886
9972
  }
9887
9973
  if (options.background) {
9888
- const child = spawn6(process.execPath, [process.argv[1], "daemon"], {
9974
+ const child = spawn7(process.execPath, [process.argv[1], "daemon"], {
9889
9975
  detached: true,
9890
9976
  stdio: "ignore"
9891
9977
  });
@@ -10474,7 +10560,7 @@ function registerUndoCommand(program2) {
10474
10560
  // src/cli/commands/watch.ts
10475
10561
  init_daemon();
10476
10562
  import chalk14 from "chalk";
10477
- import { spawn as spawn7, spawnSync as spawnSync5 } from "child_process";
10563
+ import { spawn as spawn8, spawnSync as spawnSync5 } from "child_process";
10478
10564
  function registerWatchCommand(program2) {
10479
10565
  program2.command("watch").description("Run a command under Node9 watch mode (daemon stays alive for the session)").argument("<command>", "Command to run").argument("[args...]", "Arguments for the command").action(async (cmd, args) => {
10480
10566
  let port = DAEMON_PORT;
@@ -10490,7 +10576,7 @@ function registerWatchCommand(program2) {
10490
10576
  }
10491
10577
  } catch {
10492
10578
  console.error(chalk14.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
10493
- const child = spawn7(process.execPath, [process.argv[1], "daemon"], {
10579
+ const child = spawn8(process.execPath, [process.argv[1], "daemon"], {
10494
10580
  detached: true,
10495
10581
  stdio: "ignore",
10496
10582
  env: { ...process.env, NODE9_AUTO_STARTED: "1", NODE9_WATCH_MODE: "1" }
@@ -10536,7 +10622,7 @@ function registerWatchCommand(program2) {
10536
10622
  init_orchestrator();
10537
10623
  import readline3 from "readline";
10538
10624
  import chalk15 from "chalk";
10539
- import { spawn as spawn8 } from "child_process";
10625
+ import { spawn as spawn9 } from "child_process";
10540
10626
  import { execa as execa2 } from "execa";
10541
10627
  init_provenance();
10542
10628
  function sanitize4(value) {
@@ -10623,7 +10709,7 @@ async function runMcpGateway(upstreamCommand) {
10623
10709
  const safeEnv = Object.fromEntries(
10624
10710
  Object.entries(process.env).filter(([k]) => !UPSTREAM_INJECTOR_VARS.has(k))
10625
10711
  );
10626
- const child = spawn8(executable, cmdArgs, {
10712
+ const child = spawn9(executable, cmdArgs, {
10627
10713
  stdio: ["pipe", "pipe", "inherit"],
10628
10714
  // control stdin/stdout; inherit stderr
10629
10715
  shell: false,
@@ -10706,8 +10792,11 @@ async function runMcpGateway(upstreamCommand) {
10706
10792
  return;
10707
10793
  } finally {
10708
10794
  authPending = false;
10709
- agentIn.resume();
10710
- if (deferredStdinEnd) child.stdin.end();
10795
+ if (deferredStdinEnd) {
10796
+ child.stdin.end();
10797
+ } else {
10798
+ agentIn.resume();
10799
+ }
10711
10800
  if (deferredExitCode !== null) process.exit(deferredExitCode);
10712
10801
  }
10713
10802
  return;
@@ -11442,7 +11531,43 @@ registerMcpGatewayCommand(program);
11442
11531
  registerMcpServerCommand(program);
11443
11532
  registerCheckCommand(program);
11444
11533
  registerLogCommand(program);
11445
- program.command("hud").description("Render node9 security statusline (spawned by Claude Code statusLine)").action(async () => {
11534
+ program.command("hud").description("Render node9 security statusline (spawned by Claude Code statusLine)").addHelpText(
11535
+ "after",
11536
+ `
11537
+ Outputs up to 3 lines to stdout, then exits:
11538
+
11539
+ Line 1 \u2014 Security state (always shown):
11540
+ \u{1F6E1} node9 | <mode> [shields] | \u2705 allowed \u{1F6D1} blocked \u{1F6A8} dlp ~$cost
11541
+ Shows "offline" if the node9 daemon is not running.
11542
+
11543
+ Line 2 \u2014 Claude context & rate limits (shown when available):
11544
+ <model> \u2502 ctx \u2588\u2588\u2588\u2588\u2588\u2588\u2591\u2591\u2591\u2591 61% \u2502 5h \u2588\u2588\u2588\u2588\u2591\u2591\u2591\u2591\u2591\u2591 40% (2h 10m left)
11545
+ Only appears when Claude Code passes context_window / rate_limits data via stdin.
11546
+
11547
+ Line 3 \u2014 Environment counts (shown when non-zero):
11548
+ 2 CLAUDE.md | 5 rules | 4 MCPs | 3 hooks
11549
+ Counts CLAUDE.md files, rules/, MCP servers, and hook entries across user + project scope.
11550
+ Disable with: { "settings": { "hud": { "showEnvironmentCounts": false } } } in node9.config.json
11551
+
11552
+ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
11553
+ Run "node9 addto claude" to register it as the statusLine.`
11554
+ ).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
11555
+ if (subcommand === "debug") {
11556
+ const flagFile = path30.join(os23.homedir(), ".node9", "hud-debug");
11557
+ if (state === "on") {
11558
+ fs27.mkdirSync(path30.dirname(flagFile), { recursive: true });
11559
+ fs27.writeFileSync(flagFile, "");
11560
+ console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
11561
+ console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
11562
+ } else if (state === "off") {
11563
+ if (fs27.existsSync(flagFile)) fs27.unlinkSync(flagFile);
11564
+ console.log("HUD debug logging disabled.");
11565
+ } else {
11566
+ console.error("Usage: node9 hud debug on|off");
11567
+ process.exit(1);
11568
+ }
11569
+ return;
11570
+ }
11446
11571
  const { main: main2 } = await Promise.resolve().then(() => (init_hud(), hud_exports));
11447
11572
  await main2();
11448
11573
  });
package/dist/index.js CHANGED
@@ -2382,9 +2382,7 @@ end run`;
2382
2382
  "--text",
2383
2383
  pangoMessage,
2384
2384
  "--ok-label",
2385
- locked ? "Waiting..." : "Allow \u21B5",
2386
- "--timeout",
2387
- "300"
2385
+ locked ? "Waiting..." : "Allow \u21B5"
2388
2386
  ];
2389
2387
  if (!locked) {
2390
2388
  argsList.push("--cancel-label", "Block \u238B");
package/dist/index.mjs CHANGED
@@ -2352,9 +2352,7 @@ end run`;
2352
2352
  "--text",
2353
2353
  pangoMessage,
2354
2354
  "--ok-label",
2355
- locked ? "Waiting..." : "Allow \u21B5",
2356
- "--timeout",
2357
- "300"
2355
+ locked ? "Waiting..." : "Allow \u21B5"
2358
2356
  ];
2359
2357
  if (!locked) {
2360
2358
  argsList.push("--cancel-label", "Block \u238B");
@@ -0,0 +1,120 @@
1
+ {
2
+ "name": "docker",
3
+ "description": "Protects Docker environments from destructive AI operations",
4
+ "aliases": [],
5
+ "smartRules": [
6
+ {
7
+ "name": "shield:docker:block-system-prune",
8
+ "tool": "*",
9
+ "conditions": [
10
+ {
11
+ "field": "command",
12
+ "op": "matches",
13
+ "value": "docker\\s+system\\s+prune",
14
+ "flags": "i"
15
+ }
16
+ ],
17
+ "verdict": "block",
18
+ "reason": "docker system prune removes all unused containers, images, and volumes — blocked by Docker shield"
19
+ },
20
+ {
21
+ "name": "shield:docker:block-volume-prune",
22
+ "tool": "*",
23
+ "conditions": [
24
+ {
25
+ "field": "command",
26
+ "op": "matches",
27
+ "value": "docker\\s+volume\\s+prune",
28
+ "flags": "i"
29
+ }
30
+ ],
31
+ "verdict": "block",
32
+ "reason": "docker volume prune destroys all unused volumes and their data — blocked by Docker shield"
33
+ },
34
+ {
35
+ "name": "shield:docker:block-rm-force",
36
+ "tool": "*",
37
+ "conditionMode": "all",
38
+ "conditions": [
39
+ {
40
+ "field": "command",
41
+ "op": "matches",
42
+ "value": "docker\\s+rm\\b",
43
+ "flags": "i"
44
+ },
45
+ {
46
+ "field": "command",
47
+ "op": "matches",
48
+ "value": "(^|\\s)(-f|--force)(\\s|$)",
49
+ "flags": "i"
50
+ }
51
+ ],
52
+ "verdict": "block",
53
+ "reason": "Force-removing running containers is destructive — blocked by Docker shield"
54
+ },
55
+ {
56
+ "name": "shield:docker:review-volume-rm",
57
+ "tool": "*",
58
+ "conditions": [
59
+ {
60
+ "field": "command",
61
+ "op": "matches",
62
+ "value": "docker\\s+volume\\s+rm\\s+",
63
+ "flags": "i"
64
+ }
65
+ ],
66
+ "verdict": "review",
67
+ "reason": "Volume removal deletes persistent data and requires human approval (Docker shield)"
68
+ },
69
+ {
70
+ "name": "shield:docker:review-stop-kill",
71
+ "tool": "*",
72
+ "conditions": [
73
+ {
74
+ "field": "command",
75
+ "op": "matches",
76
+ "value": "docker\\s+(stop|kill)\\s+",
77
+ "flags": "i"
78
+ }
79
+ ],
80
+ "verdict": "review",
81
+ "reason": "Stopping or killing containers requires human approval (Docker shield)"
82
+ },
83
+ {
84
+ "name": "shield:docker:review-image-rm",
85
+ "tool": "*",
86
+ "conditions": [
87
+ {
88
+ "field": "command",
89
+ "op": "matches",
90
+ "value": "docker\\s+image\\s+rm\\b",
91
+ "flags": "i"
92
+ }
93
+ ],
94
+ "verdict": "review",
95
+ "reason": "Image removal requires human approval (Docker shield)"
96
+ },
97
+ {
98
+ "name": "shield:docker:review-rmi-force",
99
+ "tool": "*",
100
+ "conditionMode": "all",
101
+ "conditions": [
102
+ {
103
+ "field": "command",
104
+ "op": "matches",
105
+ "value": "docker\\s+rmi\\b",
106
+ "flags": "i"
107
+ },
108
+ {
109
+ "field": "command",
110
+ "op": "matches",
111
+ "value": "(^|\\s)(-f|--force)(\\s|$)",
112
+ "flags": "i"
113
+ }
114
+ ],
115
+ "verdict": "review",
116
+ "reason": "Force image removal requires human approval (Docker shield)"
117
+ }
118
+ ],
119
+ "dangerousWords": []
120
+ }