@node9/proxy 1.36.0 → 1.37.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 (3) hide show
  1. package/dist/cli.js +1662 -530
  2. package/dist/cli.mjs +1662 -530
  3. package/package.json +3 -2
package/dist/cli.mjs CHANGED
@@ -185,8 +185,8 @@ function sanitizeConfig(raw) {
185
185
  }
186
186
  }
187
187
  const lines = result.error.issues.map((issue) => {
188
- const path55 = issue.path.length > 0 ? issue.path.join(".") : "root";
189
- return ` \u2022 ${path55}: ${issue.message}`;
188
+ const path58 = issue.path.length > 0 ? issue.path.join(".") : "root";
189
+ return ` \u2022 ${path58}: ${issue.message}`;
190
190
  });
191
191
  return {
192
192
  sanitized,
@@ -1240,9 +1240,9 @@ function matchesPattern(text, patterns) {
1240
1240
  const withoutDotSlash = text.replace(/^\.\//, "");
1241
1241
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1242
1242
  }
1243
- function getNestedValue(obj, path55) {
1243
+ function getNestedValue(obj, path58) {
1244
1244
  if (!obj || typeof obj !== "object") return null;
1245
- const segments = path55.split(".");
1245
+ const segments = path58.split(".");
1246
1246
  for (const seg of segments) {
1247
1247
  if (FORBIDDEN_PATH_SEGMENTS.has(seg)) return null;
1248
1248
  }
@@ -1755,8 +1755,8 @@ function narrativeRuleLabel(name) {
1755
1755
  "eval-dynamic": "dynamic eval",
1756
1756
  "config-set": "Redis CONFIG SET"
1757
1757
  };
1758
- for (const [key, label] of Object.entries(map)) {
1759
- if (stripped.includes(key)) return label;
1758
+ for (const [key, label2] of Object.entries(map)) {
1759
+ if (stripped.includes(key)) return label2;
1760
1760
  }
1761
1761
  return stripped;
1762
1762
  }
@@ -1768,6 +1768,17 @@ function stripRulePrefixes(name) {
1768
1768
  n = n.replace(/^(block|review|allow)-/, "");
1769
1769
  return n;
1770
1770
  }
1771
+ function computeSecurityScore(opts) {
1772
+ const { critical, high, medium, total } = opts;
1773
+ if (total === 0) return { score: 100, tier: "good" };
1774
+ const criticalRate = critical / total;
1775
+ const highRate = high / total;
1776
+ const mediumRate = medium / total;
1777
+ const deduction = Math.min(criticalRate * 3e3, 60) + Math.min(highRate * 500, 30) + Math.min(mediumRate * 100, 15);
1778
+ const score = Math.max(0, Math.min(100, Math.round(100 - deduction)));
1779
+ const tier = score >= 80 ? "good" : score >= 50 ? "at-risk" : "critical";
1780
+ return { score, tier };
1781
+ }
1771
1782
  function truncateBlastPath(full) {
1772
1783
  if (!full) return "";
1773
1784
  const cleaned = full.replace(/[/\\]+$/, "");
@@ -4922,12 +4933,12 @@ async function explainPolicy(toolName, args) {
4922
4933
  (rule) => matchesPattern(toolName, rule.tool) && evaluateSmartConditions(args, rule)
4923
4934
  );
4924
4935
  if (matchedRule) {
4925
- const label = `Smart Rule: ${matchedRule.name ?? matchedRule.tool}`;
4936
+ const label2 = `Smart Rule: ${matchedRule.name ?? matchedRule.tool}`;
4926
4937
  if (matchedRule.verdict === "allow") {
4927
4938
  steps.push({
4928
4939
  name: "Smart rules",
4929
4940
  outcome: "allow",
4930
- detail: `${label} \u2192 allow`,
4941
+ detail: `${label2} \u2192 allow`,
4931
4942
  isFinal: true
4932
4943
  });
4933
4944
  return { tool: toolName, args, waterfall, steps, decision: "allow" };
@@ -4935,7 +4946,7 @@ async function explainPolicy(toolName, args) {
4935
4946
  steps.push({
4936
4947
  name: "Smart rules",
4937
4948
  outcome: matchedRule.verdict,
4938
- detail: `${label} \u2192 ${matchedRule.verdict}${matchedRule.reason ? `: ${matchedRule.reason}` : ""}`,
4949
+ detail: `${label2} \u2192 ${matchedRule.verdict}${matchedRule.reason ? `: ${matchedRule.reason}` : ""}`,
4939
4950
  isFinal: true
4940
4951
  });
4941
4952
  return {
@@ -4944,7 +4955,7 @@ async function explainPolicy(toolName, args) {
4944
4955
  waterfall,
4945
4956
  steps,
4946
4957
  decision: matchedRule.verdict,
4947
- blockedByLabel: label
4958
+ blockedByLabel: label2
4948
4959
  };
4949
4960
  }
4950
4961
  steps.push({
@@ -4996,7 +5007,7 @@ async function explainPolicy(toolName, args) {
4996
5007
  });
4997
5008
  const evalVerdict = detectDangerousShellExec(shellCommand);
4998
5009
  if (evalVerdict) {
4999
- const label = evalVerdict === "block" ? "Node9: Eval Remote Execution" : "Node9: Eval Dynamic Content";
5010
+ const label2 = evalVerdict === "block" ? "Node9: Eval Remote Execution" : "Node9: Eval Dynamic Content";
5000
5011
  const detail = evalVerdict === "block" ? "eval of remote download (curl/wget) \u2014 near-certain supply-chain attack" : "eval of dynamic content (variable or subshell expansion) \u2014 requires approval";
5001
5012
  steps.push({ name: "AST eval detection", outcome: evalVerdict, detail, isFinal: true });
5002
5013
  return {
@@ -5005,7 +5016,7 @@ async function explainPolicy(toolName, args) {
5005
5016
  waterfall,
5006
5017
  steps,
5007
5018
  decision: evalVerdict,
5008
- blockedByLabel: label
5019
+ blockedByLabel: label2
5009
5020
  };
5010
5021
  }
5011
5022
  steps.push({
@@ -6941,10 +6952,10 @@ function checkPin(serverKey, currentHash, cwd) {
6941
6952
  if (!homeEntry) return "new";
6942
6953
  return homeEntry.toolsHash === currentHash ? "match" : "mismatch";
6943
6954
  }
6944
- function updatePin(serverKey, label, toolsHash, toolNames) {
6955
+ function updatePin(serverKey, label2, toolsHash, toolNames) {
6945
6956
  const pins = readMcpPins();
6946
6957
  pins.servers[serverKey] = {
6947
- label,
6958
+ label: label2,
6948
6959
  toolsHash,
6949
6960
  toolNames,
6950
6961
  toolCount: toolNames.length,
@@ -8275,9 +8286,9 @@ function writeToml(filePath, data) {
8275
8286
  async function setupCodex() {
8276
8287
  seedMcpPinsIfMissing();
8277
8288
  const homeDir2 = os12.homedir();
8278
- const configPath = path15.join(homeDir2, ".codex", "config.toml");
8289
+ const configPath2 = path15.join(homeDir2, ".codex", "config.toml");
8279
8290
  const hooksPath = path15.join(homeDir2, ".codex", "hooks.json");
8280
- const config = readToml(configPath) ?? {};
8291
+ const config = readToml(configPath2) ?? {};
8281
8292
  const servers = config.mcp_servers ?? {};
8282
8293
  let anythingChanged = false;
8283
8294
  const hooksFile = readJson(hooksPath) ?? {};
@@ -8352,7 +8363,7 @@ async function setupCodex() {
8352
8363
  if (!hasNode9McpServer(servers)) {
8353
8364
  servers["node9"] = NODE9_MCP_SERVER_ENTRY;
8354
8365
  config.mcp_servers = servers;
8355
- writeToml(configPath, config);
8366
+ writeToml(configPath2, config);
8356
8367
  console.log(chalk.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
8357
8368
  anythingChanged = true;
8358
8369
  }
@@ -8364,7 +8375,7 @@ async function setupCodex() {
8364
8375
  }
8365
8376
  if (serversToWrap.length > 0) {
8366
8377
  console.log(chalk.bold("The following existing entries will be modified:\n"));
8367
- console.log(chalk.white(` ${configPath}`));
8378
+ console.log(chalk.white(` ${configPath2}`));
8368
8379
  for (const { name, upstream } of serversToWrap) {
8369
8380
  console.log(chalk.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
8370
8381
  }
@@ -8379,7 +8390,7 @@ async function setupCodex() {
8379
8390
  };
8380
8391
  }
8381
8392
  config.mcp_servers = servers;
8382
- writeToml(configPath, config);
8393
+ writeToml(configPath2, config);
8383
8394
  console.log(chalk.green(`
8384
8395
  \u2705 ${serversToWrap.length} MCP server(s) wrapped`));
8385
8396
  anythingChanged = true;
@@ -8427,7 +8438,7 @@ async function setupCodex() {
8427
8438
  }
8428
8439
  function teardownCodex() {
8429
8440
  const homeDir2 = os12.homedir();
8430
- const configPath = path15.join(homeDir2, ".codex", "config.toml");
8441
+ const configPath2 = path15.join(homeDir2, ".codex", "config.toml");
8431
8442
  const hooksPath = path15.join(homeDir2, ".codex", "hooks.json");
8432
8443
  const hooksFile = readJson(hooksPath);
8433
8444
  if (hooksFile?.hooks) {
@@ -8445,7 +8456,7 @@ function teardownCodex() {
8445
8456
  console.log(chalk.green(" \u2705 Removed Node9 hooks from ~/.codex/hooks.json"));
8446
8457
  }
8447
8458
  }
8448
- const config = readToml(configPath);
8459
+ const config = readToml(configPath2);
8449
8460
  if (!config?.mcp_servers) {
8450
8461
  console.log(chalk.blue(" \u2139\uFE0F ~/.codex/config.toml not found \u2014 nothing to remove"));
8451
8462
  return;
@@ -8468,7 +8479,7 @@ function teardownCodex() {
8468
8479
  }
8469
8480
  }
8470
8481
  if (changed) {
8471
- writeToml(configPath, config);
8482
+ writeToml(configPath2, config);
8472
8483
  console.log(chalk.green(" \u2705 Unwrapped MCP servers in ~/.codex/config.toml"));
8473
8484
  } else {
8474
8485
  console.log(chalk.blue(" \u2139\uFE0F No Node9-wrapped MCP servers found in ~/.codex/config.toml"));
@@ -8723,18 +8734,18 @@ function teardownVSCode() {
8723
8734
  }
8724
8735
  async function setupClaudeDesktop() {
8725
8736
  seedMcpPinsIfMissing();
8726
- const configPath = claudeDesktopConfigPath();
8727
- if (!configPath) {
8737
+ const configPath2 = claudeDesktopConfigPath();
8738
+ if (!configPath2) {
8728
8739
  console.log(chalk.yellow(" \u26A0\uFE0F Claude Desktop is not supported on this platform."));
8729
8740
  return;
8730
8741
  }
8731
- const config = readJson(configPath) ?? {};
8742
+ const config = readJson(configPath2) ?? {};
8732
8743
  const servers = config.mcpServers ?? {};
8733
8744
  let anythingChanged = false;
8734
8745
  if (!hasNode9McpServer(servers)) {
8735
8746
  servers["node9"] = NODE9_MCP_SERVER_ENTRY;
8736
8747
  config.mcpServers = servers;
8737
- writeJson(configPath, config);
8748
+ writeJson(configPath2, config);
8738
8749
  console.log(chalk.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
8739
8750
  anythingChanged = true;
8740
8751
  }
@@ -8745,7 +8756,7 @@ async function setupClaudeDesktop() {
8745
8756
  }
8746
8757
  if (serversToWrap.length > 0) {
8747
8758
  console.log(chalk.bold("The following existing entries will be modified:\n"));
8748
- console.log(chalk.white(` ${configPath}`));
8759
+ console.log(chalk.white(` ${configPath2}`));
8749
8760
  for (const { name, upstream } of serversToWrap) {
8750
8761
  console.log(chalk.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
8751
8762
  }
@@ -8760,7 +8771,7 @@ async function setupClaudeDesktop() {
8760
8771
  };
8761
8772
  }
8762
8773
  config.mcpServers = servers;
8763
- writeJson(configPath, config);
8774
+ writeJson(configPath2, config);
8764
8775
  console.log(chalk.green(`
8765
8776
  \u2705 ${serversToWrap.length} MCP server(s) wrapped`));
8766
8777
  anythingChanged = true;
@@ -8787,12 +8798,12 @@ async function setupClaudeDesktop() {
8787
8798
  }
8788
8799
  }
8789
8800
  function teardownClaudeDesktop() {
8790
- const configPath = claudeDesktopConfigPath();
8791
- if (!configPath) {
8801
+ const configPath2 = claudeDesktopConfigPath();
8802
+ if (!configPath2) {
8792
8803
  console.log(chalk.yellow(" \u26A0\uFE0F Claude Desktop is not supported on this platform."));
8793
8804
  return;
8794
8805
  }
8795
- const config = readJson(configPath);
8806
+ const config = readJson(configPath2);
8796
8807
  if (!config?.mcpServers) {
8797
8808
  console.log(chalk.blue(" \u2139\uFE0F Claude Desktop config not found \u2014 nothing to remove"));
8798
8809
  return;
@@ -8800,7 +8811,7 @@ function teardownClaudeDesktop() {
8800
8811
  let changed = false;
8801
8812
  if (removeNode9McpServer(config.mcpServers)) {
8802
8813
  changed = true;
8803
- console.log(chalk.green(` \u2705 Removed node9 MCP server entry from ${configPath}`));
8814
+ console.log(chalk.green(` \u2705 Removed node9 MCP server entry from ${configPath2}`));
8804
8815
  }
8805
8816
  for (const [name, server] of Object.entries(config.mcpServers)) {
8806
8817
  const args = server.args;
@@ -8815,7 +8826,7 @@ function teardownClaudeDesktop() {
8815
8826
  }
8816
8827
  }
8817
8828
  if (changed) {
8818
- writeJson(configPath, config);
8829
+ writeJson(configPath2, config);
8819
8830
  console.log(chalk.green(" \u2705 Unwrapped MCP servers in Claude Desktop config"));
8820
8831
  } else {
8821
8832
  console.log(chalk.blue(" \u2139\uFE0F No Node9-wrapped MCP servers found in Claude Desktop config"));
@@ -8843,7 +8854,7 @@ async function setupOpencode() {
8843
8854
  const homeDir2 = os12.homedir();
8844
8855
  const configDir = path15.join(homeDir2, ".config", "opencode");
8845
8856
  const pluginsDir = path15.join(configDir, "plugins");
8846
- const configPath = path15.join(configDir, "opencode.json");
8857
+ const configPath2 = path15.join(configDir, "opencode.json");
8847
8858
  const pluginPath = path15.join(pluginsDir, OPENCODE_PLUGIN_NAME);
8848
8859
  try {
8849
8860
  fs13.mkdirSync(pluginsDir, { recursive: true });
@@ -8877,7 +8888,7 @@ async function setupOpencode() {
8877
8888
  );
8878
8889
  }
8879
8890
  }
8880
- const config = readJson(configPath) ?? {};
8891
+ const config = readJson(configPath2) ?? {};
8881
8892
  const mcp = config.mcp ?? {};
8882
8893
  let configChanged = false;
8883
8894
  const desiredCommand = [...node9ArgvForShim(), "mcp-server"];
@@ -8898,7 +8909,7 @@ async function setupOpencode() {
8898
8909
  console.log(chalk.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
8899
8910
  }
8900
8911
  }
8901
- if (configChanged) writeJson(configPath, config);
8912
+ if (configChanged) writeJson(configPath2, config);
8902
8913
  if (pluginChanged || configChanged) {
8903
8914
  console.log(chalk.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Opencode!"));
8904
8915
  console.log(chalk.gray(" Restart Opencode for changes to take effect."));
@@ -8911,7 +8922,7 @@ function teardownOpencode() {
8911
8922
  const homeDir2 = os12.homedir();
8912
8923
  const configDir = path15.join(homeDir2, ".config", "opencode");
8913
8924
  const pluginsDir = path15.join(configDir, "plugins");
8914
- const configPath = path15.join(configDir, "opencode.json");
8925
+ const configPath2 = path15.join(configDir, "opencode.json");
8915
8926
  const pluginPath = path15.join(pluginsDir, OPENCODE_PLUGIN_NAME);
8916
8927
  try {
8917
8928
  if (fs13.existsSync(pluginPath)) {
@@ -8921,7 +8932,7 @@ function teardownOpencode() {
8921
8932
  } catch (err2) {
8922
8933
  console.log(chalk.yellow(` \u26A0\uFE0F Could not remove ${pluginPath}: ${String(err2)}`));
8923
8934
  }
8924
- const config = readJson(configPath);
8935
+ const config = readJson(configPath2);
8925
8936
  if (!config) {
8926
8937
  console.log(chalk.blue(" \u2139\uFE0F ~/.config/opencode/opencode.json not found \u2014 nothing to remove"));
8927
8938
  return;
@@ -8937,7 +8948,7 @@ function teardownOpencode() {
8937
8948
  }
8938
8949
  if (changed) {
8939
8950
  config.mcp = mcp;
8940
- writeJson(configPath, config);
8951
+ writeJson(configPath2, config);
8941
8952
  } else {
8942
8953
  console.log(chalk.blue(" \u2139\uFE0F No node9 entries found in ~/.config/opencode/opencode.json"));
8943
8954
  }
@@ -9008,15 +9019,15 @@ function hermesAllowlistPath(homeDir2 = os12.homedir()) {
9008
9019
  }
9009
9020
  function setupHermes() {
9010
9021
  const homeDir2 = os12.homedir();
9011
- const configPath = hermesConfigPath(homeDir2);
9022
+ const configPath2 = hermesConfigPath(homeDir2);
9012
9023
  const allowlistPath = hermesAllowlistPath(homeDir2);
9013
- if (!fs13.existsSync(configPath)) {
9014
- console.log(chalk.yellow(` \u26A0\uFE0F Hermes config not found at ${configPath}`));
9015
- console.log(chalk.gray(" Run `hermes setup` first, then re-run node9 setup hermes."));
9024
+ if (!fs13.existsSync(configPath2)) {
9025
+ console.log(chalk.yellow(` \u26A0\uFE0F Hermes config not found at ${configPath2}`));
9026
+ console.log(chalk.gray(" Run `hermes setup` first, then re-run node9 agents add hermes."));
9016
9027
  return;
9017
9028
  }
9018
9029
  let anythingChanged = false;
9019
- const raw = fs13.readFileSync(configPath, "utf-8");
9030
+ const raw = fs13.readFileSync(configPath2, "utf-8");
9020
9031
  const doc = yaml.parseDocument(raw);
9021
9032
  if (doc.errors.length > 0) {
9022
9033
  console.log(chalk.yellow(` \u26A0\uFE0F Hermes config.yaml has YAML parse errors:`));
@@ -9024,7 +9035,9 @@ function setupHermes() {
9024
9035
  console.log(chalk.gray(` \u2022 ${err2.message}`));
9025
9036
  }
9026
9037
  console.log(
9027
- chalk.gray(" Fix the file (or run `hermes config edit`), then re-run node9 setup hermes.")
9038
+ chalk.gray(
9039
+ " Fix the file (or run `hermes config edit`), then re-run node9 agents add hermes."
9040
+ )
9028
9041
  );
9029
9042
  return;
9030
9043
  }
@@ -9054,7 +9067,7 @@ function setupHermes() {
9054
9067
  anythingChanged = true;
9055
9068
  }
9056
9069
  if (anythingChanged) {
9057
- fs13.writeFileSync(configPath, doc.toString());
9070
+ fs13.writeFileSync(configPath2, doc.toString());
9058
9071
  }
9059
9072
  let allowlist = {};
9060
9073
  if (fs13.existsSync(allowlistPath)) {
@@ -9097,24 +9110,24 @@ function setupHermes() {
9097
9110
  }
9098
9111
  function teardownHermes() {
9099
9112
  const homeDir2 = os12.homedir();
9100
- const configPath = hermesConfigPath(homeDir2);
9113
+ const configPath2 = hermesConfigPath(homeDir2);
9101
9114
  const allowlistPath = hermesAllowlistPath(homeDir2);
9102
- if (!fs13.existsSync(configPath)) {
9103
- console.log(chalk.blue(` \u2139\uFE0F ${configPath} not found \u2014 nothing to remove`));
9115
+ if (!fs13.existsSync(configPath2)) {
9116
+ console.log(chalk.blue(` \u2139\uFE0F ${configPath2} not found \u2014 nothing to remove`));
9104
9117
  return;
9105
9118
  }
9106
- const raw = fs13.readFileSync(configPath, "utf-8");
9119
+ const raw = fs13.readFileSync(configPath2, "utf-8");
9107
9120
  const doc = yaml.parseDocument(raw);
9108
9121
  if (doc.errors.length > 0) {
9109
9122
  console.log(
9110
- chalk.yellow(` \u26A0\uFE0F Skipping ${configPath} \u2014 file has YAML parse errors, fix it manually.`)
9123
+ chalk.yellow(` \u26A0\uFE0F Skipping ${configPath2} \u2014 file has YAML parse errors, fix it manually.`)
9111
9124
  );
9112
9125
  } else {
9113
- teardownHermesConfigDoc(doc, configPath);
9126
+ teardownHermesConfigDoc(doc, configPath2);
9114
9127
  }
9115
9128
  teardownHermesAllowlist(allowlistPath);
9116
9129
  }
9117
- function teardownHermesConfigDoc(doc, configPath) {
9130
+ function teardownHermesConfigDoc(doc, configPath2) {
9118
9131
  let anythingChanged = false;
9119
9132
  const current = doc.toJS() ?? {};
9120
9133
  for (const { event } of HERMES_HOOK_PLAN) {
@@ -9136,10 +9149,10 @@ function teardownHermesConfigDoc(doc, configPath) {
9136
9149
  anythingChanged = true;
9137
9150
  }
9138
9151
  if (anythingChanged) {
9139
- fs13.writeFileSync(configPath, doc.toString());
9140
- console.log(chalk.green(` \u2705 Removed Node9 hooks from ${configPath}`));
9152
+ fs13.writeFileSync(configPath2, doc.toString());
9153
+ console.log(chalk.green(` \u2705 Removed Node9 hooks from ${configPath2}`));
9141
9154
  } else {
9142
- console.log(chalk.blue(` \u2139\uFE0F No Node9 hooks found in ${configPath}`));
9155
+ console.log(chalk.blue(` \u2139\uFE0F No Node9 hooks found in ${configPath2}`));
9143
9156
  }
9144
9157
  }
9145
9158
  function teardownHermesAllowlist(allowlistPath) {
@@ -9974,12 +9987,12 @@ function buildScanSummary(agents) {
9974
9987
  }
9975
9988
  function buildSections(findings) {
9976
9989
  const sectionMap = /* @__PURE__ */ new Map();
9977
- function ensureSection(id, label, subtitle, sourceType, shieldKey) {
9990
+ function ensureSection(id, label2, subtitle, sourceType, shieldKey) {
9978
9991
  let s = sectionMap.get(id);
9979
9992
  if (!s) {
9980
9993
  s = {
9981
9994
  id,
9982
- label,
9995
+ label: label2,
9983
9996
  subtitle,
9984
9997
  sourceType,
9985
9998
  shieldKey,
@@ -13076,9 +13089,9 @@ function printRuleGroup(rule, topN, drillDown, previewWidth) {
13076
13089
  }
13077
13090
  }
13078
13091
  function compactRuleLabel(name) {
13079
- let label = name.replace(/^shield:[^:]+:/, "");
13080
- label = label.replace(/^(block|review|allow)-/, "");
13081
- return label.replace(/-+/g, "-");
13092
+ let label2 = name.replace(/^shield:[^:]+:/, "");
13093
+ label2 = label2.replace(/^(block|review|allow)-/, "");
13094
+ return label2.replace(/-+/g, "-");
13082
13095
  }
13083
13096
  function renderCompactScorecard(input) {
13084
13097
  const { scan, summary, blast, blastExposures, blockedCount, reviewCount } = input;
@@ -13179,9 +13192,9 @@ function renderNarrativeScorecard(input) {
13179
13192
  for (const section of summary.sections) {
13180
13193
  for (const rule of section.rules) {
13181
13194
  const sev = classifyRuleSeverity2(rule.name, rule.verdict);
13182
- const label = narrativeRuleLabel2(rule.name);
13195
+ const label2 = narrativeRuleLabel2(rule.name);
13183
13196
  const count = rule.findings.length;
13184
- const display = count > 1 ? `${label} \xD7${count}` : label;
13197
+ const display = count > 1 ? `${label2} \xD7${count}` : label2;
13185
13198
  const entry = { label: display, count };
13186
13199
  if (sev === "critical") critical.push(entry);
13187
13200
  else if (sev === "high") high.push(entry);
@@ -14010,7 +14023,7 @@ function registerScanCommand(program2) {
14010
14023
  console.log(chalk5.bold(" Enable real-time protection:"));
14011
14024
  console.log("");
14012
14025
  console.log(
14013
- " " + chalk5.cyan("npm install -g @node9/proxy") + chalk5.dim(" && ") + chalk5.cyan("node9 init --recommended")
14026
+ " " + chalk5.cyan("npm install -g node9-ai") + chalk5.dim(" && ") + chalk5.cyan("node9 init --recommended")
14014
14027
  );
14015
14028
  console.log("");
14016
14029
  console.log(
@@ -14311,8 +14324,8 @@ var init_session_counters = __esm({
14311
14324
  if (!isFinite(amount) || amount < 0) return;
14312
14325
  this._estimatedCost += amount;
14313
14326
  }
14314
- recordRuleHit(label) {
14315
- this._lastRuleHit = label;
14327
+ recordRuleHit(label2) {
14328
+ this._lastRuleHit = label2;
14316
14329
  }
14317
14330
  recordBlockedTool(toolName) {
14318
14331
  this._lastBlockedTool = toolName;
@@ -14591,10 +14604,10 @@ function broadcast(event, data) {
14591
14604
  activityRing.push({ event, data });
14592
14605
  if (activityRing.length > ACTIVITY_RING_SIZE) activityRing.shift();
14593
14606
  } else if (event === "activity-result") {
14594
- const { id, status, label, costEstimate } = data;
14607
+ const { id, status, label: label2, costEstimate } = data;
14595
14608
  for (let i = activityRing.length - 1; i >= 0; i--) {
14596
14609
  if (activityRing[i].data.id === id) {
14597
- Object.assign(activityRing[i].data, { status, label, costEstimate });
14610
+ Object.assign(activityRing[i].data, { status, label: label2, costEstimate });
14598
14611
  break;
14599
14612
  }
14600
14613
  }
@@ -16902,11 +16915,11 @@ __export(tail_exports, {
16902
16915
  shortenPathSummary: () => shortenPathSummary,
16903
16916
  startTail: () => startTail
16904
16917
  });
16905
- import http2 from "http";
16906
- import chalk29 from "chalk";
16907
- import fs51 from "fs";
16908
- import os46 from "os";
16909
- import path52 from "path";
16918
+ import http3 from "http";
16919
+ import chalk32 from "chalk";
16920
+ import fs56 from "fs";
16921
+ import os51 from "os";
16922
+ import path55 from "path";
16910
16923
  import readline6 from "readline";
16911
16924
  import { spawn as spawn8 } from "child_process";
16912
16925
  function shortenPathSummary(s) {
@@ -16930,20 +16943,20 @@ function getModelContextLimit(model) {
16930
16943
  return 2e5;
16931
16944
  }
16932
16945
  function readSessionUsage() {
16933
- const projectsDir = path52.join(os46.homedir(), ".claude", "projects");
16934
- if (!fs51.existsSync(projectsDir)) return null;
16946
+ const projectsDir = path55.join(os51.homedir(), ".claude", "projects");
16947
+ if (!fs56.existsSync(projectsDir)) return null;
16935
16948
  let latestFile = null;
16936
16949
  let latestMtime = 0;
16937
16950
  try {
16938
- for (const dir of fs51.readdirSync(projectsDir)) {
16939
- const dirPath = path52.join(projectsDir, dir);
16951
+ for (const dir of fs56.readdirSync(projectsDir)) {
16952
+ const dirPath = path55.join(projectsDir, dir);
16940
16953
  try {
16941
- if (!fs51.statSync(dirPath).isDirectory()) continue;
16942
- for (const file of fs51.readdirSync(dirPath)) {
16954
+ if (!fs56.statSync(dirPath).isDirectory()) continue;
16955
+ for (const file of fs56.readdirSync(dirPath)) {
16943
16956
  if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
16944
- const filePath = path52.join(dirPath, file);
16957
+ const filePath = path55.join(dirPath, file);
16945
16958
  try {
16946
- const mtime = fs51.statSync(filePath).mtimeMs;
16959
+ const mtime = fs56.statSync(filePath).mtimeMs;
16947
16960
  if (mtime > latestMtime) {
16948
16961
  latestMtime = mtime;
16949
16962
  latestFile = filePath;
@@ -16958,7 +16971,7 @@ function readSessionUsage() {
16958
16971
  }
16959
16972
  if (!latestFile) return null;
16960
16973
  try {
16961
- const lines = fs51.readFileSync(latestFile, "utf-8").split("\n");
16974
+ const lines = fs56.readFileSync(latestFile, "utf-8").split("\n");
16962
16975
  let lastModel = "";
16963
16976
  let lastInput = 0;
16964
16977
  let lastOutput = 0;
@@ -16983,10 +16996,10 @@ function readSessionUsage() {
16983
16996
  }
16984
16997
  }
16985
16998
  function formatContextStat(stat) {
16986
- const pctColor = stat.fillPct >= 80 ? chalk29.red : stat.fillPct >= 50 ? chalk29.yellow : chalk29.cyan;
16999
+ const pctColor = stat.fillPct >= 80 ? chalk32.red : stat.fillPct >= 50 ? chalk32.yellow : chalk32.cyan;
16987
17000
  const k = (n) => `${Math.round(n / 1e3)}k`;
16988
17001
  const modelShort = stat.model.replace(/@.*$/, "").replace(/-\d{8}$/, "").replace(/^claude-/, "");
16989
- return chalk29.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + chalk29.dim(
17002
+ return chalk32.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + chalk32.dim(
16990
17003
  ` (${k(stat.inputTokens)}/${k(getModelContextLimit(stat.model))} out ${k(stat.outputTokens)} \xB7 ${modelShort})`
16991
17004
  );
16992
17005
  }
@@ -17009,32 +17022,32 @@ function agentLabel(agent, mcpServer, sessionId) {
17009
17022
  const tag = sessionTag(sessionId);
17010
17023
  const tagSuffix = tag ? `\xB7${tag}` : "";
17011
17024
  if (!agent || agent === "Terminal") {
17012
- return mcpServer ? chalk29.dim(`[\u2192 ${mcpServer}] `) : "";
17025
+ return mcpServer ? chalk32.dim(`[\u2192 ${mcpServer}] `) : "";
17013
17026
  }
17014
17027
  const short = agent === "Claude Code" ? "Claude" : agent === "Gemini CLI" ? "Gemini" : agent === "Unknown Agent" ? "" : agent.split(" ")[0];
17015
- if (!short) return mcpServer ? chalk29.dim(`[\u2192 ${mcpServer}] `) : "";
17016
- return mcpServer ? chalk29.dim(`[${short}${tagSuffix} \u2192 ${mcpServer}] `) : chalk29.dim(`[${short}${tagSuffix}] `);
17028
+ if (!short) return mcpServer ? chalk32.dim(`[\u2192 ${mcpServer}] `) : "";
17029
+ return mcpServer ? chalk32.dim(`[${short}${tagSuffix} \u2192 ${mcpServer}] `) : chalk32.dim(`[${short}${tagSuffix}] `);
17017
17030
  }
17018
17031
  function formatBase(activity) {
17019
17032
  const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
17020
17033
  const icon = getIcon(activity.tool);
17021
17034
  const toolName = activity.tool.slice(0, 16).padEnd(16);
17022
- const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os46.homedir(), "~");
17035
+ const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os51.homedir(), "~");
17023
17036
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
17024
- return `${chalk29.gray(time)} ${icon} ${agentLabel(activity.agent, activity.mcpServer, activity.sessionId)}${chalk29.white.bold(toolName)} ${chalk29.dim(argsPreview)}`;
17037
+ return `${chalk32.gray(time)} ${icon} ${agentLabel(activity.agent, activity.mcpServer, activity.sessionId)}${chalk32.white.bold(toolName)} ${chalk32.dim(argsPreview)}`;
17025
17038
  }
17026
17039
  function renderResult(activity, result) {
17027
17040
  const base = formatBase(activity);
17028
17041
  let status;
17029
17042
  if (result.status === "allow") {
17030
- status = chalk29.green("\u2713 ALLOW");
17043
+ status = chalk32.green("\u2713 ALLOW");
17031
17044
  } else if (result.status === "dlp") {
17032
- status = chalk29.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
17045
+ status = chalk32.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
17033
17046
  } else {
17034
- status = chalk29.red("\u2717 BLOCK");
17047
+ status = chalk32.red("\u2717 BLOCK");
17035
17048
  }
17036
17049
  const cost = result.costEstimate ?? activity.costEstimate;
17037
- const costSuffix = cost == null ? "" : chalk29.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
17050
+ const costSuffix = cost == null ? "" : chalk32.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
17038
17051
  if (process.stdout.isTTY) {
17039
17052
  if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
17040
17053
  readline6.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
@@ -17051,19 +17064,19 @@ function renderResult(activity, result) {
17051
17064
  }
17052
17065
  function renderPending(activity) {
17053
17066
  if (!process.stdout.isTTY) return;
17054
- const line = `${formatBase(activity)} ${chalk29.yellow("\u25CF \u2026")}`;
17067
+ const line = `${formatBase(activity)} ${chalk32.yellow("\u25CF \u2026")}`;
17055
17068
  pendingShownForId = activity.id;
17056
17069
  pendingWrappedLines = wrappedLineCount(line);
17057
17070
  process.stdout.write(`${line}\r`);
17058
17071
  }
17059
17072
  async function ensureDaemon() {
17060
17073
  let pidPort = null;
17061
- if (fs51.existsSync(PID_FILE)) {
17074
+ if (fs56.existsSync(PID_FILE)) {
17062
17075
  try {
17063
- const { port } = JSON.parse(fs51.readFileSync(PID_FILE, "utf-8"));
17076
+ const { port } = JSON.parse(fs56.readFileSync(PID_FILE, "utf-8"));
17064
17077
  pidPort = port;
17065
17078
  } catch {
17066
- console.error(chalk29.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
17079
+ console.error(chalk32.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
17067
17080
  }
17068
17081
  }
17069
17082
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -17074,7 +17087,7 @@ async function ensureDaemon() {
17074
17087
  if (res.ok) return checkPort;
17075
17088
  } catch {
17076
17089
  }
17077
- console.log(chalk29.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
17090
+ console.log(chalk32.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
17078
17091
  const child = spawn8(process.execPath, [process.argv[1], "daemon"], {
17079
17092
  detached: true,
17080
17093
  stdio: "ignore",
@@ -17091,7 +17104,7 @@ async function ensureDaemon() {
17091
17104
  } catch {
17092
17105
  }
17093
17106
  }
17094
- console.error(chalk29.red("\u274C Daemon failed to start. Try: node9 daemon start"));
17107
+ console.error(chalk32.red("\u274C Daemon failed to start. Try: node9 daemon start"));
17095
17108
  process.exit(1);
17096
17109
  }
17097
17110
  function postDecisionHttp(id, decision, authToken, port, opts) {
@@ -17101,7 +17114,7 @@ function postDecisionHttp(id, decision, authToken, port, opts) {
17101
17114
  if (opts?.trustDuration) bodyObj.trustDuration = opts.trustDuration;
17102
17115
  if (opts?.reason) bodyObj.reason = opts.reason;
17103
17116
  const body = JSON.stringify(bodyObj);
17104
- const req = http2.request(
17117
+ const req = http3.request(
17105
17118
  {
17106
17119
  hostname: "127.0.0.1",
17107
17120
  port,
@@ -17160,7 +17173,7 @@ function buildCardLines(req, localCount = 0) {
17160
17173
  const severityIcon = isBlock ? `${RED}\u{1F6D1}` : `${YELLOW}\u26A0 `;
17161
17174
  const rawDesc = req.riskMetadata?.ruleDescription ?? "";
17162
17175
  const description = rawDesc ? cleanReason(rawDesc) : "";
17163
- const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${chalk29.dim(`(${req.agent})`)}` : "";
17176
+ const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${chalk32.dim(`(${req.agent})`)}` : "";
17164
17177
  const lines = [
17165
17178
  ``,
17166
17179
  `${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
@@ -17216,9 +17229,9 @@ function buildRecoveryCardLines(req) {
17216
17229
  ];
17217
17230
  }
17218
17231
  function readApproversFromDisk() {
17219
- const configPath = path52.join(os46.homedir(), ".node9", "config.json");
17232
+ const configPath2 = path55.join(os51.homedir(), ".node9", "config.json");
17220
17233
  try {
17221
- const raw = JSON.parse(fs51.readFileSync(configPath, "utf-8"));
17234
+ const raw = JSON.parse(fs56.readFileSync(configPath2, "utf-8"));
17222
17235
  const settings = raw.settings ?? {};
17223
17236
  return settings.approvers ?? {};
17224
17237
  } catch {
@@ -17227,22 +17240,22 @@ function readApproversFromDisk() {
17227
17240
  }
17228
17241
  function approverStatusLine() {
17229
17242
  const a = readApproversFromDisk();
17230
- const fmt = (label, key) => {
17243
+ const fmt = (label2, key) => {
17231
17244
  const on = a[key] !== false;
17232
- return `[${key[0]}]${label.slice(1)} ${on ? chalk29.green("\u2713") : chalk29.dim("\u2717")}`;
17245
+ return `[${key[0]}]${label2.slice(1)} ${on ? chalk32.green("\u2713") : chalk32.dim("\u2717")}`;
17233
17246
  };
17234
17247
  return `${fmt("native", "native")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
17235
17248
  }
17236
17249
  function toggleApprover(channel) {
17237
- const configPath = path52.join(os46.homedir(), ".node9", "config.json");
17250
+ const configPath2 = path55.join(os51.homedir(), ".node9", "config.json");
17238
17251
  try {
17239
- const raw = JSON.parse(fs51.readFileSync(configPath, "utf-8"));
17252
+ const raw = JSON.parse(fs56.readFileSync(configPath2, "utf-8"));
17240
17253
  const settings = raw.settings ?? {};
17241
17254
  const approvers = settings.approvers ?? {};
17242
17255
  approvers[channel] = approvers[channel] === false;
17243
17256
  settings.approvers = approvers;
17244
17257
  raw.settings = settings;
17245
- fs51.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
17258
+ fs56.writeFileSync(configPath2, JSON.stringify(raw, null, 2) + "\n");
17246
17259
  } catch (err2) {
17247
17260
  process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
17248
17261
  `);
@@ -17252,7 +17265,7 @@ async function startTail(options = {}) {
17252
17265
  const port = await ensureDaemon();
17253
17266
  if (options.clear) {
17254
17267
  const result = await new Promise((resolve) => {
17255
- const req2 = http2.request(
17268
+ const req2 = http3.request(
17256
17269
  { method: "POST", hostname: "127.0.0.1", port, path: "/events/clear" },
17257
17270
  (res) => {
17258
17271
  const status = res.statusCode ?? 0;
@@ -17274,7 +17287,7 @@ async function startTail(options = {}) {
17274
17287
  req2.end();
17275
17288
  });
17276
17289
  if (result.ok) {
17277
- console.log(chalk29.green("\u2713 Flight Recorder buffer cleared."));
17290
+ console.log(chalk32.green("\u2713 Flight Recorder buffer cleared."));
17278
17291
  } else if (result.code === "ECONNREFUSED") {
17279
17292
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
17280
17293
  } else if (result.code === "ETIMEDOUT") {
@@ -17320,7 +17333,7 @@ async function startTail(options = {}) {
17320
17333
  const channel = name === "n" ? "native" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
17321
17334
  if (channel) {
17322
17335
  toggleApprover(channel);
17323
- console.log(chalk29.dim(` Approvers: ${approverStatusLine()}`));
17336
+ console.log(chalk32.dim(` Approvers: ${approverStatusLine()}`));
17324
17337
  }
17325
17338
  };
17326
17339
  process.stdin.on("keypress", idleKeypressHandler);
@@ -17386,7 +17399,7 @@ async function startTail(options = {}) {
17386
17399
  localAllowCounts.get(req2.toolName) ?? 0
17387
17400
  )
17388
17401
  );
17389
- const decisionStamp = action === "always-allow" ? chalk29.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? chalk29.cyan("\u23F1 TRUST 30m") : action === "allow" ? chalk29.green("\u2713 ALLOWED") : action === "redirect" ? chalk29.yellow("\u21A9 REDIRECT AI") : chalk29.red("\u2717 DENIED");
17402
+ const decisionStamp = action === "always-allow" ? chalk32.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? chalk32.cyan("\u23F1 TRUST 30m") : action === "allow" ? chalk32.green("\u2713 ALLOWED") : action === "redirect" ? chalk32.yellow("\u21A9 REDIRECT AI") : chalk32.red("\u2717 DENIED");
17390
17403
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
17391
17404
  for (const line of stampedLines) process.stdout.write(line + "\n");
17392
17405
  process.stdout.write(SHOW_CURSOR);
@@ -17414,8 +17427,8 @@ async function startTail(options = {}) {
17414
17427
  }
17415
17428
  postDecisionHttp(req2.id, httpDecision, authToken, port, httpOpts).catch((err2) => {
17416
17429
  try {
17417
- fs51.appendFileSync(
17418
- path52.join(os46.homedir(), ".node9", "hook-debug.log"),
17430
+ fs56.appendFileSync(
17431
+ path55.join(os51.homedir(), ".node9", "hook-debug.log"),
17419
17432
  `[tail] POST /decision failed: ${String(err2)}
17420
17433
  `
17421
17434
  );
@@ -17437,7 +17450,7 @@ async function startTail(options = {}) {
17437
17450
  );
17438
17451
  const stampedLines = buildCardLines(req2, priorCount);
17439
17452
  if (externalDecision) {
17440
- const source = externalDecision === "allow" ? chalk29.green("\u2713 ALLOWED") : chalk29.red("\u2717 DENIED");
17453
+ const source = externalDecision === "allow" ? chalk32.green("\u2713 ALLOWED") : chalk32.red("\u2717 DENIED");
17441
17454
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
17442
17455
  }
17443
17456
  for (const line of stampedLines) process.stdout.write(line + "\n");
@@ -17479,31 +17492,31 @@ async function startTail(options = {}) {
17479
17492
  };
17480
17493
  process.stdin.on("keypress", onKeypress);
17481
17494
  }
17482
- const auditLog = path52.join(os46.homedir(), ".node9", "audit.log");
17495
+ const auditLog = path55.join(os51.homedir(), ".node9", "audit.log");
17483
17496
  try {
17484
- const unackedDlp = fs51.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
17497
+ const unackedDlp = fs56.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
17485
17498
  if (unackedDlp > 0) {
17486
17499
  console.log("");
17487
17500
  console.log(
17488
- chalk29.bgRed.white.bold(
17501
+ chalk32.bgRed.white.bold(
17489
17502
  ` \u26A0\uFE0F DLP ALERT: ${unackedDlp} secret${unackedDlp !== 1 ? "s" : ""} found in Claude response text \u2014 run: node9 dlp `
17490
17503
  )
17491
17504
  );
17492
17505
  }
17493
17506
  } catch {
17494
17507
  }
17495
- console.log(chalk29.cyan.bold(`
17508
+ console.log(chalk32.cyan.bold(`
17496
17509
  \u{1F6F0}\uFE0F Node9 tail`));
17497
17510
  if (canApprove) {
17498
- console.log(chalk29.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
17499
- console.log(chalk29.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
17511
+ console.log(chalk32.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
17512
+ console.log(chalk32.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
17500
17513
  }
17501
17514
  const ctxStat = readSessionUsage();
17502
17515
  if (ctxStat) console.log(" " + formatContextStat(ctxStat));
17503
17516
  if (options.history) {
17504
- console.log(chalk29.dim("Showing history + live events.\n"));
17517
+ console.log(chalk32.dim("Showing history + live events.\n"));
17505
17518
  } else {
17506
- console.log(chalk29.dim("Showing live events only. Use --history to include past.\n"));
17519
+ console.log(chalk32.dim("Showing live events only. Use --history to include past.\n"));
17507
17520
  }
17508
17521
  process.on("SIGINT", () => {
17509
17522
  exitIdleMode();
@@ -17513,7 +17526,7 @@ async function startTail(options = {}) {
17513
17526
  readline6.clearLine(process.stdout, 0);
17514
17527
  readline6.cursorTo(process.stdout, 0);
17515
17528
  }
17516
- console.log(chalk29.dim("\n\u{1F6F0}\uFE0F Disconnected."));
17529
+ console.log(chalk32.dim("\n\u{1F6F0}\uFE0F Disconnected."));
17517
17530
  process.exit(0);
17518
17531
  });
17519
17532
  const STALL_THRESHOLD_MS = 6e4;
@@ -17521,11 +17534,11 @@ async function startTail(options = {}) {
17521
17534
  if (stallWarned) return;
17522
17535
  if (Date.now() - lastActivityFromDaemon < STALL_THRESHOLD_MS) return;
17523
17536
  try {
17524
- const auditMtime = fs51.statSync(auditLog).mtimeMs;
17537
+ const auditMtime = fs56.statSync(auditLog).mtimeMs;
17525
17538
  if (Date.now() - auditMtime >= STALL_THRESHOLD_MS) return;
17526
17539
  console.log("");
17527
17540
  console.log(
17528
- chalk29.yellow(
17541
+ chalk32.yellow(
17529
17542
  "\u26A0\uFE0F Tail appears stalled \u2014 hooks are firing but no events are arriving. Try: node9 daemon restart"
17530
17543
  )
17531
17544
  );
@@ -17535,14 +17548,14 @@ async function startTail(options = {}) {
17535
17548
  }, STALL_THRESHOLD_MS / 2);
17536
17549
  stallWatchdog.unref();
17537
17550
  const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
17538
- const req = http2.get(
17551
+ const req = http3.get(
17539
17552
  sseUrl,
17540
17553
  {
17541
17554
  headers: authToken ? { "X-Node9-Internal": authToken } : {}
17542
17555
  },
17543
17556
  (res) => {
17544
17557
  if (res.statusCode !== 200) {
17545
- console.error(chalk29.red(`Failed to connect: HTTP ${res.statusCode}`));
17558
+ console.error(chalk32.red(`Failed to connect: HTTP ${res.statusCode}`));
17546
17559
  process.exit(1);
17547
17560
  }
17548
17561
  if (canApprove) enterIdleMode();
@@ -17573,7 +17586,7 @@ async function startTail(options = {}) {
17573
17586
  readline6.clearLine(process.stdout, 0);
17574
17587
  readline6.cursorTo(process.stdout, 0);
17575
17588
  }
17576
- console.log(chalk29.red("\n\u274C Daemon disconnected."));
17589
+ console.log(chalk32.red("\n\u274C Daemon disconnected."));
17577
17590
  process.exit(1);
17578
17591
  });
17579
17592
  }
@@ -17586,7 +17599,7 @@ async function startTail(options = {}) {
17586
17599
  const parsed = JSON.parse(rawData);
17587
17600
  const msg = parsed.message ?? "Flight recorder is down \u2014 run: node9 daemon restart";
17588
17601
  console.log("");
17589
- console.log(chalk29.bgRed.white.bold(` \u26A0\uFE0F ${msg} `));
17602
+ console.log(chalk32.bgRed.white.bold(` \u26A0\uFE0F ${msg} `));
17590
17603
  } catch {
17591
17604
  }
17592
17605
  return;
@@ -17671,9 +17684,9 @@ async function startTail(options = {}) {
17671
17684
  const rawSummary = data.argsSummary ?? data.tool;
17672
17685
  const summary = shortenPathSummary(rawSummary);
17673
17686
  const fileCount = data.fileCount ?? 0;
17674
- const files = fileCount > 0 ? chalk29.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
17687
+ const files = fileCount > 0 ? chalk32.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
17675
17688
  process.stdout.write(
17676
- `${chalk29.dim(time)} ${chalk29.cyan("\u{1F4F8} snapshot")} ${chalk29.dim(hash)} ${summary}${files}
17689
+ `${chalk32.dim(time)} ${chalk32.cyan("\u{1F4F8} snapshot")} ${chalk32.dim(hash)} ${summary}${files}
17677
17690
  `
17678
17691
  );
17679
17692
  return;
@@ -17690,18 +17703,18 @@ async function startTail(options = {}) {
17690
17703
  if (event === "execution-result") {
17691
17704
  const exec = data;
17692
17705
  const time = new Date(Date.now()).toLocaleTimeString([], { hour12: false });
17693
- const arrow = exec.isError ? chalk29.red(" \u21B3 \u2717") : chalk29.green(" \u21B3 \u2713");
17694
- const label = agentLabel(exec.agent, exec.mcpServer);
17706
+ const arrow = exec.isError ? chalk32.red(" \u21B3 \u2717") : chalk32.green(" \u21B3 \u2713");
17707
+ const label2 = agentLabel(exec.agent, exec.mcpServer);
17695
17708
  const tool = (exec.tool ?? "").slice(0, 16);
17696
- const duration = typeof exec.durationMs === "number" ? chalk29.dim(` (${exec.durationMs}ms)`) : "";
17709
+ const duration = typeof exec.durationMs === "number" ? chalk32.dim(` (${exec.durationMs}ms)`) : "";
17697
17710
  console.log(
17698
- `${chalk29.gray(time)} ${arrow} ${label}${chalk29.dim(tool)}${chalk29.dim(" completed")}${duration}`
17711
+ `${chalk32.gray(time)} ${arrow} ${label2}${chalk32.dim(tool)}${chalk32.dim(" completed")}${duration}`
17699
17712
  );
17700
17713
  }
17701
17714
  }
17702
17715
  req.on("error", (err2) => {
17703
17716
  const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
17704
- console.error(chalk29.red(`
17717
+ console.error(chalk32.red(`
17705
17718
  \u274C ${msg}`));
17706
17719
  process.exit(1);
17707
17720
  });
@@ -17712,7 +17725,7 @@ var init_tail = __esm({
17712
17725
  "use strict";
17713
17726
  init_daemon2();
17714
17727
  init_daemon();
17715
- PID_FILE = path52.join(os46.homedir(), ".node9", "daemon.pid");
17728
+ PID_FILE = path55.join(os51.homedir(), ".node9", "daemon.pid");
17716
17729
  ICONS = {
17717
17730
  bash: "\u{1F4BB}",
17718
17731
  shell: "\u{1F4BB}",
@@ -17760,10 +17773,10 @@ __export(hud_exports, {
17760
17773
  main: () => main,
17761
17774
  renderEnvironmentLine: () => renderEnvironmentLine
17762
17775
  });
17763
- import fs52 from "fs";
17764
- import path53 from "path";
17765
- import os47 from "os";
17766
- import http3 from "http";
17776
+ import fs57 from "fs";
17777
+ import path56 from "path";
17778
+ import os52 from "os";
17779
+ import http4 from "http";
17767
17780
  async function readStdin() {
17768
17781
  const chunks = [];
17769
17782
  for await (const chunk2 of process.stdin) {
@@ -17781,7 +17794,7 @@ function queryDaemon() {
17781
17794
  return new Promise((resolve) => {
17782
17795
  const timeout = setTimeout(() => resolve(null), 50);
17783
17796
  try {
17784
- const req = http3.get(
17797
+ const req = http4.get(
17785
17798
  `http://${DAEMON_HOST}:${DAEMON_PORT}/status`,
17786
17799
  { timeout: 50 },
17787
17800
  (res) => {
@@ -17838,9 +17851,9 @@ function formatTimeLeft(resetsAt) {
17838
17851
  return ` (${m}m left)`;
17839
17852
  }
17840
17853
  function safeReadJson(filePath) {
17841
- if (!fs52.existsSync(filePath)) return null;
17854
+ if (!fs57.existsSync(filePath)) return null;
17842
17855
  try {
17843
- return JSON.parse(fs52.readFileSync(filePath, "utf-8"));
17856
+ return JSON.parse(fs57.readFileSync(filePath, "utf-8"));
17844
17857
  } catch {
17845
17858
  return null;
17846
17859
  }
@@ -17861,12 +17874,12 @@ function countHooksInFile(filePath) {
17861
17874
  return Object.keys(cfg.hooks).length;
17862
17875
  }
17863
17876
  function countRulesInDir(rulesDir) {
17864
- if (!fs52.existsSync(rulesDir)) return 0;
17877
+ if (!fs57.existsSync(rulesDir)) return 0;
17865
17878
  let count = 0;
17866
17879
  try {
17867
- for (const entry of fs52.readdirSync(rulesDir, { withFileTypes: true })) {
17880
+ for (const entry of fs57.readdirSync(rulesDir, { withFileTypes: true })) {
17868
17881
  if (entry.isDirectory()) {
17869
- count += countRulesInDir(path53.join(rulesDir, entry.name));
17882
+ count += countRulesInDir(path56.join(rulesDir, entry.name));
17870
17883
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
17871
17884
  count++;
17872
17885
  }
@@ -17877,46 +17890,46 @@ function countRulesInDir(rulesDir) {
17877
17890
  }
17878
17891
  function isSamePath(a, b) {
17879
17892
  try {
17880
- return path53.resolve(a) === path53.resolve(b);
17893
+ return path56.resolve(a) === path56.resolve(b);
17881
17894
  } catch {
17882
17895
  return false;
17883
17896
  }
17884
17897
  }
17885
17898
  function countConfigs(cwd) {
17886
- const homeDir2 = os47.homedir();
17887
- const claudeDir = path53.join(homeDir2, ".claude");
17899
+ const homeDir2 = os52.homedir();
17900
+ const claudeDir = path56.join(homeDir2, ".claude");
17888
17901
  let claudeMdCount = 0;
17889
17902
  let rulesCount = 0;
17890
17903
  let hooksCount = 0;
17891
17904
  const userMcpServers = /* @__PURE__ */ new Set();
17892
17905
  const projectMcpServers = /* @__PURE__ */ new Set();
17893
- if (fs52.existsSync(path53.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
17894
- rulesCount += countRulesInDir(path53.join(claudeDir, "rules"));
17895
- const userSettings = path53.join(claudeDir, "settings.json");
17906
+ if (fs57.existsSync(path56.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
17907
+ rulesCount += countRulesInDir(path56.join(claudeDir, "rules"));
17908
+ const userSettings = path56.join(claudeDir, "settings.json");
17896
17909
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
17897
17910
  hooksCount += countHooksInFile(userSettings);
17898
- const userClaudeJson = path53.join(homeDir2, ".claude.json");
17911
+ const userClaudeJson = path56.join(homeDir2, ".claude.json");
17899
17912
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
17900
17913
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
17901
17914
  userMcpServers.delete(name);
17902
17915
  }
17903
17916
  if (cwd) {
17904
- if (fs52.existsSync(path53.join(cwd, "CLAUDE.md"))) claudeMdCount++;
17905
- if (fs52.existsSync(path53.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
17906
- const projectClaudeDir = path53.join(cwd, ".claude");
17917
+ if (fs57.existsSync(path56.join(cwd, "CLAUDE.md"))) claudeMdCount++;
17918
+ if (fs57.existsSync(path56.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
17919
+ const projectClaudeDir = path56.join(cwd, ".claude");
17907
17920
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
17908
17921
  if (!overlapsUserScope) {
17909
- if (fs52.existsSync(path53.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
17910
- rulesCount += countRulesInDir(path53.join(projectClaudeDir, "rules"));
17911
- const projSettings = path53.join(projectClaudeDir, "settings.json");
17922
+ if (fs57.existsSync(path56.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
17923
+ rulesCount += countRulesInDir(path56.join(projectClaudeDir, "rules"));
17924
+ const projSettings = path56.join(projectClaudeDir, "settings.json");
17912
17925
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
17913
17926
  hooksCount += countHooksInFile(projSettings);
17914
17927
  }
17915
- if (fs52.existsSync(path53.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
17916
- const localSettings = path53.join(projectClaudeDir, "settings.local.json");
17928
+ if (fs57.existsSync(path56.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
17929
+ const localSettings = path56.join(projectClaudeDir, "settings.local.json");
17917
17930
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
17918
17931
  hooksCount += countHooksInFile(localSettings);
17919
- const mcpJsonServers = getMcpServerNames(path53.join(cwd, ".mcp.json"));
17932
+ const mcpJsonServers = getMcpServerNames(path56.join(cwd, ".mcp.json"));
17920
17933
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
17921
17934
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
17922
17935
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -17949,12 +17962,12 @@ function readActiveShieldsHud() {
17949
17962
  return shieldsCache.value;
17950
17963
  }
17951
17964
  try {
17952
- const shieldsPath = path53.join(os47.homedir(), ".node9", "shields.json");
17953
- if (!fs52.existsSync(shieldsPath)) {
17965
+ const shieldsPath = path56.join(os52.homedir(), ".node9", "shields.json");
17966
+ if (!fs57.existsSync(shieldsPath)) {
17954
17967
  shieldsCache = { value: [], ts: now };
17955
17968
  return [];
17956
17969
  }
17957
- const parsed = JSON.parse(fs52.readFileSync(shieldsPath, "utf-8"));
17970
+ const parsed = JSON.parse(fs57.readFileSync(shieldsPath, "utf-8"));
17958
17971
  if (!Array.isArray(parsed.active)) {
17959
17972
  shieldsCache = { value: [], ts: now };
17960
17973
  return [];
@@ -18056,17 +18069,17 @@ function renderContextLine(stdin) {
18056
18069
  async function main() {
18057
18070
  try {
18058
18071
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
18059
- if (fs52.existsSync(path53.join(os47.homedir(), ".node9", "hud-debug"))) {
18072
+ if (fs57.existsSync(path56.join(os52.homedir(), ".node9", "hud-debug"))) {
18060
18073
  try {
18061
- const logPath = path53.join(os47.homedir(), ".node9", "hud-debug.log");
18074
+ const logPath = path56.join(os52.homedir(), ".node9", "hud-debug.log");
18062
18075
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
18063
18076
  let size = 0;
18064
18077
  try {
18065
- size = fs52.statSync(logPath).size;
18078
+ size = fs57.statSync(logPath).size;
18066
18079
  } catch {
18067
18080
  }
18068
18081
  if (size < MAX_LOG_SIZE) {
18069
- fs52.appendFileSync(
18082
+ fs57.appendFileSync(
18070
18083
  logPath,
18071
18084
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
18072
18085
  );
@@ -18086,12 +18099,12 @@ async function main() {
18086
18099
  const showEnvCounts = (() => {
18087
18100
  try {
18088
18101
  const cwd = stdin.cwd ?? process.cwd();
18089
- for (const configPath of [
18090
- path53.join(cwd, "node9.config.json"),
18091
- path53.join(os47.homedir(), ".node9", "config.json")
18102
+ for (const configPath2 of [
18103
+ path56.join(cwd, "node9.config.json"),
18104
+ path56.join(os52.homedir(), ".node9", "config.json")
18092
18105
  ]) {
18093
- if (!fs52.existsSync(configPath)) continue;
18094
- const cfg = JSON.parse(fs52.readFileSync(configPath, "utf-8"));
18106
+ if (!fs57.existsSync(configPath2)) continue;
18107
+ const cfg = JSON.parse(fs57.readFileSync(configPath2, "utf-8"));
18095
18108
  const hud = cfg.settings?.hud;
18096
18109
  if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
18097
18110
  }
@@ -18137,10 +18150,10 @@ init_core();
18137
18150
  init_setup();
18138
18151
  init_daemon2();
18139
18152
  import { Command } from "commander";
18140
- import chalk30 from "chalk";
18141
- import fs53 from "fs";
18142
- import path54 from "path";
18143
- import os48 from "os";
18153
+ import chalk33 from "chalk";
18154
+ import fs58 from "fs";
18155
+ import path57 from "path";
18156
+ import os53 from "os";
18144
18157
  import { confirm as confirm2 } from "@inquirer/prompts";
18145
18158
 
18146
18159
  // src/utils/duration.ts
@@ -18180,8 +18193,8 @@ INSTRUCTIONS:
18180
18193
  - Acknowledge the block to the user and ask if there is an alternative approach.
18181
18194
  - If you believe this action is critical, explain your reasoning and ask them to run "node9 pause 15m" to proceed.`;
18182
18195
  }
18183
- const label = blockedByLabel.toLowerCase();
18184
- if (label.includes("dlp") || label.includes("secret detected") || label.includes("credential review")) {
18196
+ const label2 = blockedByLabel.toLowerCase();
18197
+ if (label2.includes("dlp") || label2.includes("secret detected") || label2.includes("credential review")) {
18185
18198
  return `NODE9 SECURITY ALERT: A sensitive credential (API key, token, or private key) was found in your tool call arguments.
18186
18199
  CRITICAL INSTRUCTION: Do NOT retry this action.
18187
18200
  REQUIRED ACTIONS:
@@ -18190,37 +18203,37 @@ REQUIRED ACTIONS:
18190
18203
  3. Treat the leaked credential as compromised and rotate it immediately.
18191
18204
  Do NOT attempt to bypass this check or pass the credential through another tool.`;
18192
18205
  }
18193
- if (label.includes("sql safety") && label.includes("delete without where")) {
18206
+ if (label2.includes("sql safety") && label2.includes("delete without where")) {
18194
18207
  return `NODE9: Blocked \u2014 DELETE without WHERE clause would wipe the entire table.
18195
18208
  INSTRUCTION: Add a WHERE clause to scope the deletion (e.g. WHERE id = <value>).
18196
18209
  Do NOT retry without a WHERE clause.`;
18197
18210
  }
18198
- if (label.includes("sql safety") && label.includes("update without where")) {
18211
+ if (label2.includes("sql safety") && label2.includes("update without where")) {
18199
18212
  return `NODE9: Blocked \u2014 UPDATE without WHERE clause would update every row.
18200
18213
  INSTRUCTION: Add a WHERE clause to scope the update (e.g. WHERE id = <value>).
18201
18214
  Do NOT retry without a WHERE clause.`;
18202
18215
  }
18203
- if (label.includes("dangerous word")) {
18216
+ if (label2.includes("dangerous word")) {
18204
18217
  const match = blockedByLabel.match(/dangerous word: "([^"]+)"/i);
18205
18218
  const word = match?.[1] ?? "a dangerous keyword";
18206
18219
  return `NODE9: Blocked \u2014 command contains forbidden keyword "${word}".
18207
18220
  INSTRUCTION: Do NOT use "${word}". Use a non-destructive alternative.
18208
18221
  Do NOT attempt to bypass this with shell tricks or aliases \u2014 it will be blocked again.`;
18209
18222
  }
18210
- if (label.includes("path blocked") || label.includes("sandbox")) {
18223
+ if (label2.includes("path blocked") || label2.includes("sandbox")) {
18211
18224
  return `NODE9: Blocked \u2014 operation targets a path outside the allowed sandbox.
18212
18225
  INSTRUCTION: Move your output to an allowed directory such as /tmp/ or the project directory.
18213
18226
  Do NOT retry on the same path.`;
18214
18227
  }
18215
- if (label.includes("inline execution")) {
18228
+ if (label2.includes("inline execution")) {
18216
18229
  return `NODE9: Blocked \u2014 inline code execution (e.g. bash -c "...") is not allowed.
18217
18230
  INSTRUCTION: Use individual tool calls instead of embedding code in a shell string.`;
18218
18231
  }
18219
- if (label.includes("strict mode")) {
18232
+ if (label2.includes("strict mode")) {
18220
18233
  return `NODE9: Blocked \u2014 strict mode is active. All tool calls require explicit human approval.
18221
18234
  INSTRUCTION: Inform the user this action is pending approval. Wait for them to approve via the dashboard or run "node9 pause".`;
18222
18235
  }
18223
- if (label.includes("rule") && label.includes("default block")) {
18236
+ if (label2.includes("rule") && label2.includes("default block")) {
18224
18237
  const match = blockedByLabel.match(/rule "([^"]+)"/i);
18225
18238
  const rule = match?.[1] ?? "a policy rule";
18226
18239
  return `NODE9: Blocked \u2014 action "${rule}" is forbidden by security policy.
@@ -20011,7 +20024,7 @@ var AGENT_SPECS = [
20011
20024
  {
20012
20025
  id: "claude",
20013
20026
  label: "Claude Code",
20014
- setupCommand: "node9 setup claude",
20027
+ setupCommand: "node9 agents add claude",
20015
20028
  hookFile: (h) => path39.join(h, ".claude", "settings.json"),
20016
20029
  hookFormat: "matcher",
20017
20030
  hookEvents: [ck("PreToolUse"), lg("PostToolUse")],
@@ -20021,7 +20034,7 @@ var AGENT_SPECS = [
20021
20034
  {
20022
20035
  id: "gemini",
20023
20036
  label: "Gemini CLI",
20024
- setupCommand: "node9 setup gemini",
20037
+ setupCommand: "node9 agents add gemini",
20025
20038
  hookFile: (h) => path39.join(h, ".gemini", "settings.json"),
20026
20039
  hookFormat: "matcher",
20027
20040
  hookEvents: [ck("BeforeTool"), lg("AfterTool")],
@@ -20031,7 +20044,7 @@ var AGENT_SPECS = [
20031
20044
  {
20032
20045
  id: "codex",
20033
20046
  label: "Codex",
20034
- setupCommand: "node9 setup codex",
20047
+ setupCommand: "node9 agents add codex",
20035
20048
  hookFile: (h) => path39.join(h, ".codex", "hooks.json"),
20036
20049
  hookFormat: "matcher",
20037
20050
  hookEvents: [ck("PreToolUse"), ck("UserPromptSubmit")],
@@ -20042,7 +20055,7 @@ var AGENT_SPECS = [
20042
20055
  {
20043
20056
  id: "antigravity",
20044
20057
  label: "Antigravity",
20045
- setupCommand: "node9 setup antigravity",
20058
+ setupCommand: "node9 agents add antigravity",
20046
20059
  hookFile: (h) => path39.join(h, ".gemini", "config", "hooks.json"),
20047
20060
  hookFormat: "matcher",
20048
20061
  hookEvents: [ck("PreToolUse"), lg("PostToolUse")],
@@ -20052,7 +20065,7 @@ var AGENT_SPECS = [
20052
20065
  {
20053
20066
  id: "copilot",
20054
20067
  label: "GitHub Copilot",
20055
- setupCommand: "node9 setup copilot",
20068
+ setupCommand: "node9 agents add copilot",
20056
20069
  hookFile: (h) => path39.join(h, ".copilot", "hooks", "node9.json"),
20057
20070
  hookFormat: "flat",
20058
20071
  hookEvents: [ck("PreToolUse"), lg("PostToolUse"), ck("UserPromptSubmit")],
@@ -20062,7 +20075,7 @@ var AGENT_SPECS = [
20062
20075
  {
20063
20076
  id: "cursor",
20064
20077
  label: "Cursor",
20065
- setupCommand: "node9 setup cursor",
20078
+ setupCommand: "node9 agents add cursor",
20066
20079
  // MCP-only — no hook file (see note above).
20067
20080
  hookFormat: "flat",
20068
20081
  hookEvents: [],
@@ -20072,7 +20085,7 @@ var AGENT_SPECS = [
20072
20085
  {
20073
20086
  id: "hermes",
20074
20087
  label: "Hermes Agent",
20075
- setupCommand: "node9 setup hermes",
20088
+ setupCommand: "node9 agents add hermes",
20076
20089
  hookFile: (h) => hermesConfigPath(h),
20077
20090
  hookFormat: "yaml",
20078
20091
  hookEvents: [ck("pre_tool_call"), lg("post_tool_call")],
@@ -20085,7 +20098,7 @@ var AGENT_SPECS = [
20085
20098
  // (no hooks, no MCP). hookFormat is unused for these (shimFile drives it).
20086
20099
  id: "opencode",
20087
20100
  label: "OpenCode",
20088
- setupCommand: "node9 setup opencode",
20101
+ setupCommand: "node9 agents add opencode",
20089
20102
  hookFormat: "flat",
20090
20103
  hookEvents: [],
20091
20104
  shimFile: (h) => path39.join(h, ".config", "opencode", "plugins", "node9.js"),
@@ -20094,7 +20107,7 @@ var AGENT_SPECS = [
20094
20107
  {
20095
20108
  id: "pi",
20096
20109
  label: "Pi",
20097
- setupCommand: "node9 setup pi",
20110
+ setupCommand: "node9 agents add pi",
20098
20111
  hookFormat: "flat",
20099
20112
  hookEvents: [],
20100
20113
  shimFile: (h) => path39.join(h, ".pi", "agent", "extensions", "node9.js"),
@@ -20178,10 +20191,7 @@ function registerDoctorCommand(program2, version2) {
20178
20191
  const which = execSync("which node9", { encoding: "utf-8", timeout: 3e3 }).trim();
20179
20192
  pass(`node9 found at ${which}`);
20180
20193
  } catch {
20181
- warn(
20182
- "node9 not found in $PATH \u2014 hooks may not find it",
20183
- "Run: npm install -g @node9/proxy"
20184
- );
20194
+ warn("node9 not found in $PATH \u2014 hooks may not find it", "Run: npm install -g node9-ai");
20185
20195
  }
20186
20196
  const nodeMajor = parseInt(process.versions.node.split(".")[0], 10);
20187
20197
  if (nodeMajor >= 18) {
@@ -20251,7 +20261,7 @@ function registerDoctorCommand(program2, version2) {
20251
20261
  if (notConfigured.length > 0) {
20252
20262
  console.log(
20253
20263
  chalk11.gray(
20254
- ` \xB7 Not configured: ${notConfigured.join(", ")} \u2014 run \`node9 setup <agent>\` if you use one`
20264
+ ` \xB7 Not configured: ${notConfigured.join(", ")} \u2014 run \`node9 agents add <agent>\` if you use one`
20255
20265
  )
20256
20266
  );
20257
20267
  }
@@ -21232,10 +21242,10 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
21232
21242
  );
21233
21243
  console.log("");
21234
21244
  const COL1 = 18;
21235
- const summaryRow = (icon, label, count, note, colorFn = (s) => s) => {
21245
+ const summaryRow = (icon, label2, count, note, colorFn = (s) => s) => {
21236
21246
  const countStr = colorFn(num2(count));
21237
21247
  const noteStr = note ? chalk13.dim(" " + note) : "";
21238
- console.log(" " + icon + " " + chalk13.white(label.padEnd(COL1)) + countStr + noteStr);
21248
+ console.log(" " + icon + " " + chalk13.white(label2.padEnd(COL1)) + countStr + noteStr);
21239
21249
  };
21240
21250
  summaryRow(
21241
21251
  userApproved > 0 ? chalk13.green("\u2705") : chalk13.dim("\u2705"),
@@ -21302,21 +21312,21 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
21302
21312
  let leftStyled = " ".repeat(COL);
21303
21313
  if (i < topTools.length) {
21304
21314
  const [tool, { calls }] = topTools[i];
21305
- const label = tool.length > LABEL - 1 ? tool.slice(0, LABEL - 2) + "\u2026" : tool;
21315
+ const label2 = tool.length > LABEL - 1 ? tool.slice(0, LABEL - 2) + "\u2026" : tool;
21306
21316
  const countStr = num2(calls).padStart(TOOL_COUNT_W);
21307
21317
  const b = colorBar(calls, maxTool, BAR);
21308
21318
  const rawLen = LABEL + BAR + 1 + TOOL_COUNT_W;
21309
21319
  const pad = Math.max(0, COL - rawLen);
21310
- leftStyled = chalk13.white(label.padEnd(LABEL)) + b + " " + chalk13.white(countStr) + " ".repeat(pad);
21320
+ leftStyled = chalk13.white(label2.padEnd(LABEL)) + b + " " + chalk13.white(countStr) + " ".repeat(pad);
21311
21321
  }
21312
21322
  let rightStyled = "";
21313
21323
  if (i < topBlocks.length) {
21314
21324
  const [reason, count] = topBlocks[i];
21315
21325
  const readable = humanBlockReason(reason);
21316
- const label = readable.length > LABEL - 1 ? readable.slice(0, LABEL - 2) + "\u2026" : readable;
21326
+ const label2 = readable.length > LABEL - 1 ? readable.slice(0, LABEL - 2) + "\u2026" : readable;
21317
21327
  const countStr = num2(count).padStart(BLOCK_COUNT_W);
21318
21328
  const b = colorBar(count, maxBlock, BAR);
21319
- rightStyled = chalk13.white(label.padEnd(LABEL)) + b + " " + chalk13.red(countStr);
21329
+ rightStyled = chalk13.white(label2.padEnd(LABEL)) + b + " " + chalk13.red(countStr);
21320
21330
  }
21321
21331
  console.log(" " + leftStyled + " " + rightStyled);
21322
21332
  }
@@ -21329,9 +21339,9 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
21329
21339
  console.log(" " + chalk13.dim("\u2500".repeat(Math.min(50, W - 4))));
21330
21340
  const maxAgent = Math.max(...agentMap.values(), 1);
21331
21341
  for (const [agent, count] of [...agentMap.entries()].sort((a, b) => b[1] - a[1])) {
21332
- const label = agent.slice(0, LABEL - 1);
21342
+ const label2 = agent.slice(0, LABEL - 1);
21333
21343
  const b = colorBar(count, maxAgent, BAR);
21334
- console.log(" " + chalk13.white(label.padEnd(LABEL)) + b + " " + chalk13.white(num2(count)));
21344
+ console.log(" " + chalk13.white(label2.padEnd(LABEL)) + b + " " + chalk13.white(num2(count)));
21335
21345
  }
21336
21346
  }
21337
21347
  if (mcpMap.size > 0) {
@@ -21340,9 +21350,9 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
21340
21350
  console.log(" " + chalk13.dim("\u2500".repeat(Math.min(50, W - 4))));
21341
21351
  const maxMcp = Math.max(...mcpMap.values(), 1);
21342
21352
  for (const [server, count] of [...mcpMap.entries()].sort((a, b) => b[1] - a[1])) {
21343
- const label = server.slice(0, LABEL - 1).padEnd(LABEL);
21353
+ const label2 = server.slice(0, LABEL - 1).padEnd(LABEL);
21344
21354
  const b = colorBar(count, maxMcp, BAR);
21345
- console.log(" " + chalk13.white(label) + b + " " + chalk13.white(num2(count)));
21355
+ console.log(" " + chalk13.white(label2) + b + " " + chalk13.white(num2(count)));
21346
21356
  }
21347
21357
  }
21348
21358
  if (hourMap.size > 0) {
@@ -21363,13 +21373,13 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
21363
21373
  console.log(" " + chalk13.dim("\u2500".repeat(W - 2)));
21364
21374
  const DAY_BAR = Math.max(8, Math.min(30, W - 36));
21365
21375
  for (const [dateKey, { calls, blocked: db }] of dailyList) {
21366
- const label = fmtDate(dateKey).padEnd(10);
21376
+ const label2 = fmtDate(dateKey).padEnd(10);
21367
21377
  const b = colorBar(calls, maxDaily, DAY_BAR);
21368
21378
  const dayCost = costByDay.get(dateKey);
21369
21379
  const costNote = dayCost ? chalk13.magenta(` ${fmtCost2(dayCost)}`) : "";
21370
21380
  const blockNote = db > 0 ? chalk13.red(` ${db} blocked`) : "";
21371
21381
  console.log(
21372
- " " + chalk13.dim(label) + " " + b + " " + chalk13.white(num2(calls)) + blockNote + costNote
21382
+ " " + chalk13.dim(label2) + " " + b + " " + chalk13.white(num2(calls)) + blockNote + costNote
21373
21383
  );
21374
21384
  }
21375
21385
  }
@@ -21387,10 +21397,10 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
21387
21397
  ["Output", costOutputTokens, chalk13.white(num2(costOutputTokens))],
21388
21398
  ["Cache write", costCacheWrite, chalk13.yellow(num2(costCacheWrite))]
21389
21399
  ];
21390
- for (const [label, count, colored] of nonCacheRows) {
21400
+ for (const [label2, count, colored] of nonCacheRows) {
21391
21401
  if (count === 0) continue;
21392
21402
  const b = colorBar(count, maxNonCache, TOK_BAR);
21393
- console.log(" " + chalk13.white(label.padEnd(TOK_LABEL)) + b + " " + colored);
21403
+ console.log(" " + chalk13.white(label2.padEnd(TOK_LABEL)) + b + " " + colored);
21394
21404
  }
21395
21405
  if (costCacheRead > 0) {
21396
21406
  const cacheBar = colorBar(costCacheRead, costCacheRead, TOK_BAR);
@@ -21421,10 +21431,10 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
21421
21431
  const MODEL_LABEL = 22;
21422
21432
  const MODEL_BAR = Math.max(6, Math.min(20, W - MODEL_LABEL - 12));
21423
21433
  for (const [model, cost] of modelList) {
21424
- const label = model.length > MODEL_LABEL - 1 ? model.slice(0, MODEL_LABEL - 2) + "\u2026" : model;
21434
+ const label2 = model.length > MODEL_LABEL - 1 ? model.slice(0, MODEL_LABEL - 2) + "\u2026" : model;
21425
21435
  const b = colorBar(cost, maxModelCost, MODEL_BAR);
21426
21436
  console.log(
21427
- " " + chalk13.white(label.padEnd(MODEL_LABEL)) + b + " " + chalk13.yellow(fmtCost2(cost))
21437
+ " " + chalk13.white(label2.padEnd(MODEL_LABEL)) + b + " " + chalk13.yellow(fmtCost2(cost))
21428
21438
  );
21429
21439
  }
21430
21440
  }
@@ -21549,8 +21559,8 @@ import chalk15 from "chalk";
21549
21559
  import fs42 from "fs";
21550
21560
  import path43 from "path";
21551
21561
  import os38 from "os";
21552
- function printAgentSection(label, hookPairs, wrapped) {
21553
- console.log(chalk15.bold(` ${label}`));
21562
+ function printAgentSection(label2, hookPairs, wrapped) {
21563
+ console.log(chalk15.bold(` ${label2}`));
21554
21564
  for (const { name, present } of hookPairs) {
21555
21565
  if (present) {
21556
21566
  console.log(chalk15.green(` \u2713 ${name}`));
@@ -21742,32 +21752,32 @@ function registerInitCommand(program2) {
21742
21752
  }
21743
21753
  console.log("");
21744
21754
  }
21745
- const configPath = path44.join(os39.homedir(), ".node9", "config.json");
21746
- const isFirstInstall = !fs43.existsSync(configPath);
21747
- if (fs43.existsSync(configPath) && !options.force) {
21755
+ const configPath2 = path44.join(os39.homedir(), ".node9", "config.json");
21756
+ const isFirstInstall = !fs43.existsSync(configPath2);
21757
+ if (fs43.existsSync(configPath2) && !options.force) {
21748
21758
  try {
21749
- const existing = JSON.parse(fs43.readFileSync(configPath, "utf-8"));
21759
+ const existing = JSON.parse(fs43.readFileSync(configPath2, "utf-8"));
21750
21760
  const settings = existing.settings ?? {};
21751
21761
  if (settings.mode !== chosenMode) {
21752
21762
  settings.mode = chosenMode;
21753
21763
  existing.settings = settings;
21754
- fs43.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
21764
+ fs43.writeFileSync(configPath2, JSON.stringify(existing, null, 2) + "\n");
21755
21765
  console.log(chalk16.green(`\u2705 Mode updated: ${chosenMode}`));
21756
21766
  } else {
21757
- console.log(chalk16.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
21767
+ console.log(chalk16.blue(`\u2139\uFE0F Config already exists: ${configPath2}`));
21758
21768
  }
21759
21769
  } catch {
21760
- console.log(chalk16.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
21770
+ console.log(chalk16.blue(`\u2139\uFE0F Config already exists: ${configPath2}`));
21761
21771
  }
21762
21772
  } else {
21763
21773
  const configToSave = {
21764
21774
  ...DEFAULT_CONFIG,
21765
21775
  settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
21766
21776
  };
21767
- const dir = path44.dirname(configPath);
21777
+ const dir = path44.dirname(configPath2);
21768
21778
  if (!fs43.existsSync(dir)) fs43.mkdirSync(dir, { recursive: true });
21769
- fs43.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
21770
- console.log(chalk16.green(`\u2705 Config created: ${configPath}`));
21779
+ fs43.writeFileSync(configPath2, JSON.stringify(configToSave, null, 2) + "\n");
21780
+ console.log(chalk16.green(`\u2705 Config created: ${configPath2}`));
21771
21781
  console.log(chalk16.gray(` Mode: ${chosenMode}`));
21772
21782
  }
21773
21783
  if (options.skipSetup) return;
@@ -22078,13 +22088,13 @@ function registerUndoCommand(program2) {
22078
22088
  const e = display[i];
22079
22089
  const isGap = prevTs !== null && prevTs - e.timestamp > 6e4;
22080
22090
  if (isGap) console.log(chalk18.gray(" \u2500\u2500 earlier \u2500\u2500"));
22081
- const label = (e.argsSummary || e.files?.[0] || "\u2014").slice(0, 30).padEnd(30);
22091
+ const label2 = (e.argsSummary || e.files?.[0] || "\u2014").slice(0, 30).padEnd(30);
22082
22092
  const tool = e.tool.slice(0, 8).padEnd(8);
22083
22093
  const when = formatAge2(e.timestamp).padEnd(10);
22084
22094
  const dir = e.cwd.length > 30 ? "\u2026" + e.cwd.slice(-29) : e.cwd;
22085
22095
  console.log(
22086
22096
  chalk18.white(
22087
- ` ${String(i + 1).padEnd(3)} ${label} ${chalk18.cyan(tool)} ${chalk18.gray(when)} ${chalk18.gray(dir)}`
22097
+ ` ${String(i + 1).padEnd(3)} ${label2} ${chalk18.cyan(tool)} ${chalk18.gray(when)} ${chalk18.gray(dir)}`
22088
22098
  )
22089
22099
  );
22090
22100
  prevTs = e.timestamp;
@@ -23499,11 +23509,11 @@ function registerMcpPinCommand(program2) {
23499
23509
  `);
23500
23510
  process.exit(1);
23501
23511
  }
23502
- const label = pins.servers[serverKey].label;
23512
+ const label2 = pins.servers[serverKey].label;
23503
23513
  removePin(serverKey);
23504
23514
  console.log(chalk21.green(`
23505
23515
  \u{1F513} Pin removed for ${chalk21.cyan(serverKey)}`));
23506
- console.log(chalk21.gray(` Server: ${label}`));
23516
+ console.log(chalk21.gray(` Server: ${label2}`));
23507
23517
  console.log(chalk21.gray(" Next connection will re-pin with current tool definitions.\n"));
23508
23518
  });
23509
23519
  pinSubCmd.command("reset").description("Clear all MCP pins (next connection to each server will re-pin)").action(() => {
@@ -23715,15 +23725,1135 @@ function registerAgentsCommand(program2) {
23715
23725
  // src/cli.ts
23716
23726
  init_scan();
23717
23727
 
23728
+ // src/cli/commands/posture.ts
23729
+ import chalk25 from "chalk";
23730
+
23731
+ // src/posture/index.ts
23732
+ import os44 from "os";
23733
+
23734
+ // src/posture/secrets.ts
23735
+ init_dist();
23736
+ import fs46 from "fs";
23737
+ import path47 from "path";
23738
+ import os41 from "os";
23739
+ var MAX_FILE_BYTES = 256 * 1024;
23740
+ function displayPath(p, home) {
23741
+ if (p === home) return "~";
23742
+ const prefix = home.endsWith(path47.sep) ? home : home + path47.sep;
23743
+ if (p.startsWith(prefix)) return "~" + path47.sep + p.slice(prefix.length);
23744
+ return p;
23745
+ }
23746
+ function safeRead(file) {
23747
+ try {
23748
+ const stat = fs46.statSync(file);
23749
+ if (!stat.isFile() || stat.size === 0 || stat.size > MAX_FILE_BYTES) return null;
23750
+ return fs46.readFileSync(file, "utf8");
23751
+ } catch {
23752
+ return null;
23753
+ }
23754
+ }
23755
+ function candidateFiles(home, cwd) {
23756
+ const files = /* @__PURE__ */ new Set();
23757
+ try {
23758
+ for (const name of fs46.readdirSync(cwd)) {
23759
+ if (name === ".env" || name.startsWith(".env.")) files.add(path47.join(cwd, name));
23760
+ }
23761
+ } catch {
23762
+ }
23763
+ for (const spec of AGENT_SPECS) {
23764
+ if (spec.hookFile) files.add(spec.hookFile(home));
23765
+ if (spec.mcpFile) files.add(spec.mcpFile(home));
23766
+ }
23767
+ files.add(path47.join(home, ".env"));
23768
+ return [...files];
23769
+ }
23770
+ function credentialMaterial(home) {
23771
+ return [
23772
+ path47.join(home, ".ssh", "id_rsa"),
23773
+ path47.join(home, ".ssh", "id_dsa"),
23774
+ path47.join(home, ".ssh", "id_ecdsa"),
23775
+ path47.join(home, ".ssh", "id_ed25519"),
23776
+ path47.join(home, ".aws", "credentials"),
23777
+ path47.join(home, ".config", "gcloud", "application_default_credentials.json")
23778
+ ];
23779
+ }
23780
+ function checkSecrets(ctx) {
23781
+ const home = ctx.home || os41.homedir();
23782
+ const findings = [];
23783
+ const plaintext = [];
23784
+ const plaintextPaths = [];
23785
+ for (const file of candidateFiles(home, ctx.cwd)) {
23786
+ const text = safeRead(file);
23787
+ if (!text) continue;
23788
+ const match = scanText(text);
23789
+ if (match) {
23790
+ plaintext.push(`${match.patternName} in ${displayPath(file, home)}`);
23791
+ plaintextPaths.push(file);
23792
+ }
23793
+ }
23794
+ if (plaintext.length > 0) {
23795
+ findings.push({
23796
+ category: "Secrets",
23797
+ severity: "critical",
23798
+ title: `${plaintext.length} plaintext secret${plaintext.length === 1 ? "" : "s"} on disk`,
23799
+ what: "API keys/tokens are sitting unencrypted in files on disk.",
23800
+ why: "They were saved in plaintext config / .env files.",
23801
+ who: "A tricked agent (or any program you run) could read and leak them.",
23802
+ detail: plaintext,
23803
+ fix: "Fix it now: run `node9 shield enable project-jail` (blocks credential-file reads in-path).",
23804
+ // Coverage is decided at the DLP layer — does node9 block the agent
23805
+ // reading these? (See enforcement.ts.)
23806
+ owner: "node9",
23807
+ coverageProbe: { kind: "fileRead", paths: plaintextPaths }
23808
+ });
23809
+ }
23810
+ const creds = [];
23811
+ const credPaths = [];
23812
+ for (const file of credentialMaterial(home)) {
23813
+ try {
23814
+ if (fs46.statSync(file).isFile()) {
23815
+ creds.push(displayPath(file, home));
23816
+ credPaths.push(file);
23817
+ }
23818
+ } catch {
23819
+ }
23820
+ }
23821
+ if (creds.length > 0) {
23822
+ findings.push({
23823
+ category: "Secrets",
23824
+ severity: "high",
23825
+ title: `${creds.length} credential file${creds.length === 1 ? "" : "s"} readable by the agent`,
23826
+ what: "Your SSH keys / cloud login files can be read by programs you run.",
23827
+ why: "They sit unlocked in your home folder.",
23828
+ who: "An unsandboxed agent could read them and use them to reach your servers / cloud.",
23829
+ detail: creds,
23830
+ fix: "Fix it now: run `node9 shield enable project-jail` (blocks ~/.ssh, ~/.aws, .env reads in-path).",
23831
+ owner: "node9",
23832
+ coverageProbe: { kind: "fileRead", paths: credPaths }
23833
+ });
23834
+ }
23835
+ return findings;
23836
+ }
23837
+
23838
+ // src/posture/egress.ts
23839
+ init_config();
23840
+ function evaluateEgressConfig(egress) {
23841
+ if (egress.enabled && egress.mode === "block") {
23842
+ return {
23843
+ category: "Egress",
23844
+ severity: "high",
23845
+ title: "Egress is locked, but node9 is not enforcing it",
23846
+ what: "Egress is set to block, but node9 is not applying the policy.",
23847
+ why: "node9 isn't wired in (or is in observe mode), so the lock has no effect.",
23848
+ who: "The lock protects nothing until node9 is enforcing in-path.",
23849
+ owner: "node9",
23850
+ detail: [],
23851
+ fix: "Run `node9 init` and ensure node9 is in enforcing mode.",
23852
+ coverageProbe: { kind: "egress" },
23853
+ // Open here means only "node9 isn't enforcing" — Coverage already says
23854
+ // that, so drop this row when open to avoid double-surfacing.
23855
+ redundantWhenOpen: true
23856
+ };
23857
+ }
23858
+ if (egress.enabled && egress.mode === "review") {
23859
+ return {
23860
+ category: "Egress",
23861
+ severity: "medium",
23862
+ title: "Egress is in review, but node9 is not enforcing it",
23863
+ what: "Egress is set to review (approval-gate), but node9 is not applying the policy.",
23864
+ why: "node9 isn't wired in (or is in observe mode), so the gate has no effect.",
23865
+ who: "Nothing gates outbound until node9 is enforcing in-path.",
23866
+ owner: "node9",
23867
+ detail: [],
23868
+ fix: "Run `node9 init` and ensure node9 is in enforcing mode.",
23869
+ coverageProbe: { kind: "egress" },
23870
+ // Open here means only "node9 isn't enforcing" — Coverage already says
23871
+ // that, so drop this row when open to avoid double-surfacing.
23872
+ redundantWhenOpen: true
23873
+ };
23874
+ }
23875
+ return {
23876
+ category: "Egress",
23877
+ severity: "high",
23878
+ title: "Egress is open",
23879
+ what: "Your agent can connect to any server on the internet.",
23880
+ why: "node9 isn't restricting where its network tools (curl, wget, ssh) can reach.",
23881
+ who: "If the agent is ever tricked, nothing stops it sending your data out.",
23882
+ owner: "node9",
23883
+ detail: [],
23884
+ fix: "Fix it now: run `node9 egress watch` (or `node9 egress lock` to hard-block).",
23885
+ coverageProbe: { kind: "egress" }
23886
+ };
23887
+ }
23888
+ function checkEgress(ctx) {
23889
+ const config = getConfig(ctx.cwd);
23890
+ const egress = config.policy.egress;
23891
+ return [evaluateEgressConfig({ enabled: egress.enabled, mode: egress.mode })];
23892
+ }
23893
+
23894
+ // src/posture/gate.ts
23895
+ init_policy();
23896
+ var BASELINE = ["rm", "-rf", "/"].join(" ");
23897
+ async function checkGate(ctx) {
23898
+ const verdict = await evaluatePolicy2("Bash", { command: BASELINE }, ctx.agent, ctx.cwd);
23899
+ if (verdict.decision !== "block") {
23900
+ return [
23901
+ {
23902
+ category: "Approval gate",
23903
+ severity: "critical",
23904
+ title: "No approval gate is active \u2014 destructive commands run unchecked",
23905
+ what: "Dangerous shell commands aren't gated \u2014 even `rm -rf /` would run.",
23906
+ why: "No enforcing shield or smart rule is gating Bash.",
23907
+ who: "A confused or tricked agent could damage the machine with one command.",
23908
+ detail: [],
23909
+ owner: "node9",
23910
+ fix: "Turn on the gate: run `node9 shield enable bash-safe` (or add a smart rule). node9 then blocks dangerous commands and the negotiation loop tells the agent what's allowed."
23911
+ }
23912
+ ];
23913
+ }
23914
+ return [
23915
+ {
23916
+ category: "Approval gate",
23917
+ severity: "advisory",
23918
+ title: "node9 is your approval gate \u2014 destructive commands are blocked",
23919
+ what: "Dangerous shell commands are blocked in-path by your shields and smart rules; when node9 blocks, the negotiation loop tells the agent what is allowed.",
23920
+ detail: [],
23921
+ owner: "node9",
23922
+ coverageProbe: { kind: "command", command: BASELINE },
23923
+ redundantWhenOpen: true
23924
+ }
23925
+ ];
23926
+ }
23927
+
23928
+ // src/posture/supply-chain.ts
23929
+ init_provenance();
23930
+ import fs47 from "fs";
23931
+ import os42 from "os";
23932
+ import path48 from "path";
23933
+ import { parse as parseToml3 } from "smol-toml";
23934
+ var PACKAGE_RUNNERS = /* @__PURE__ */ new Set(["npx", "pnpx", "bunx", "dlx", "yarn", "pnpm", "bun"]);
23935
+ function isNode9Managed(command, args = []) {
23936
+ if (!command) return false;
23937
+ if (path48.basename(command).toLowerCase() === "node9") return true;
23938
+ if (PACKAGE_RUNNERS.has(path48.basename(command).toLowerCase())) {
23939
+ return args.some((a) => a === "node9" || path48.basename(a).toLowerCase() === "node9");
23940
+ }
23941
+ return false;
23942
+ }
23943
+ var MAX_CONFIG_BYTES = 10 * 1024 * 1024;
23944
+ function readServers(file, format, agent) {
23945
+ try {
23946
+ const stat = fs47.statSync(file);
23947
+ if (!stat.isFile() || stat.size > MAX_CONFIG_BYTES) return [];
23948
+ const text = fs47.readFileSync(file, "utf8");
23949
+ const map = format === "toml" ? parseToml3(text)?.mcp_servers : JSON.parse(text)?.mcpServers;
23950
+ if (!map || typeof map !== "object") return [];
23951
+ return Object.entries(map).map(([name, v]) => ({
23952
+ name,
23953
+ command: v?.command,
23954
+ args: Array.isArray(v?.args) ? v.args : void 0,
23955
+ agent
23956
+ }));
23957
+ } catch {
23958
+ return [];
23959
+ }
23960
+ }
23961
+ function checkSupplyChain(ctx) {
23962
+ const home = ctx.home || os42.homedir();
23963
+ const servers = [];
23964
+ for (const spec of AGENT_SPECS) {
23965
+ if (!spec.mcpFile) continue;
23966
+ servers.push(...readServers(spec.mcpFile(home), spec.mcpFormat ?? "json", spec.label));
23967
+ }
23968
+ if (servers.length === 0) return [];
23969
+ const findings = [];
23970
+ const unmanaged = servers.filter((s) => s.command && !isNode9Managed(s.command, s.args));
23971
+ const suspect = unmanaged.filter(
23972
+ (s) => checkProvenance(s.command, ctx.cwd).trustLevel === "suspect"
23973
+ );
23974
+ if (suspect.length > 0) {
23975
+ findings.push({
23976
+ category: "Supply chain",
23977
+ severity: "high",
23978
+ title: `${suspect.length} MCP server${suspect.length === 1 ? "" : "s"} launched from an untrusted path`,
23979
+ what: "An MCP tool-server runs from an untrusted location.",
23980
+ why: "Its binary lives in /tmp or a world-writable directory.",
23981
+ who: "Anything on the machine could swap that binary for malware the agent then runs.",
23982
+ detail: suspect.map((s) => `${s.name} \u2192 ${s.command} (${s.agent})`),
23983
+ owner: "node9",
23984
+ fix: "node9 can pin + provenance-check MCP servers before they run."
23985
+ });
23986
+ }
23987
+ if (unmanaged.length > 0) {
23988
+ findings.push({
23989
+ category: "Supply chain",
23990
+ severity: "medium",
23991
+ title: `${unmanaged.length} of ${servers.length} MCP server${servers.length === 1 ? "" : "s"} run outside node9`,
23992
+ what: "Some MCP tool-servers run without node9 watching their tool calls.",
23993
+ why: "They're launched directly, not wrapped by node9.",
23994
+ who: "A poisoned or silently-updated server could act freely (tool-poisoning / rug-pull).",
23995
+ detail: unmanaged.slice(0, 5).map((s) => `${s.name} (${s.agent})`),
23996
+ owner: "node9",
23997
+ fix: "node9 can wrap MCP servers so every tool call is gated + pinned."
23998
+ });
23999
+ }
24000
+ return findings;
24001
+ }
24002
+
24003
+ // src/posture/privilege.ts
24004
+ init_policy();
24005
+ var SUDO_PROBE = "sudo chmod 777 /etc/passwd";
24006
+ async function checkPrivilege(ctx) {
24007
+ const findings = [];
24008
+ const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
24009
+ const isRoot = uid === 0;
24010
+ if (isRoot) {
24011
+ findings.push({
24012
+ category: "Privilege",
24013
+ severity: "high",
24014
+ title: "Running as root",
24015
+ what: "The agent process is running as root (full system rights).",
24016
+ why: "It was started as uid 0.",
24017
+ who: "One bad command can change any file, user, or service on the machine.",
24018
+ detail: [],
24019
+ owner: "node9",
24020
+ fix: "node9 can block privileged commands (sudo, system-path writes) in-path."
24021
+ });
24022
+ }
24023
+ const verdict = await evaluatePolicy2("Bash", { command: SUDO_PROBE }, ctx.agent, ctx.cwd);
24024
+ if (verdict.decision !== "block") {
24025
+ findings.push({
24026
+ category: "Privilege",
24027
+ severity: isRoot ? "high" : "medium",
24028
+ title: "Privilege escalation is not gated",
24029
+ what: "node9 isn't gating `sudo`.",
24030
+ why: "No sudo rule is active in the current policy.",
24031
+ // Calibrated: don't claim the agent CAN become root — it depends on sudo config.
24032
+ who: "If `sudo` is passwordless (NOPASSWD), an agent could become root; with a password prompt the risk is lower.",
24033
+ detail: [],
24034
+ fix: "node9 can gate sudo / privilege-escalation in-path.",
24035
+ // Coverage probes the real policy: block OR review = gated (covered).
24036
+ owner: "node9",
24037
+ coverageProbe: { kind: "command", command: SUDO_PROBE }
24038
+ });
24039
+ }
24040
+ return findings;
24041
+ }
24042
+
24043
+ // src/posture/containment.ts
24044
+ import fs48 from "fs";
24045
+ function inContainer() {
24046
+ if (fs48.existsSync("/.dockerenv") || fs48.existsSync("/run/.containerenv")) return true;
24047
+ try {
24048
+ const cgroup = fs48.readFileSync("/proc/1/cgroup", "utf8");
24049
+ if (/docker|kubepods|containerd|lxc|libpod/.test(cgroup)) return true;
24050
+ } catch {
24051
+ }
24052
+ return false;
24053
+ }
24054
+ function checkContainment(_ctx) {
24055
+ if (inContainer()) return [];
24056
+ return [
24057
+ {
24058
+ category: "Isolation",
24059
+ severity: "advisory",
24060
+ title: "Running directly on the host \u2014 no container",
24061
+ what: "The agent runs loose on your whole machine, not in a sandbox.",
24062
+ why: "It's started on the bare host, not inside a container or VM.",
24063
+ who: "If it gets tricked, the damage reaches every file and program \u2014 not one room.",
24064
+ detail: [],
24065
+ owner: "os",
24066
+ node9Reduces: true,
24067
+ fix: "node9 can shrink the blast radius without a container \u2014 you keep every tool:\n \u2022 node9 shield enable project-jail \u2014 block credential reads\n \u2022 node9 egress lock \u2014 block data exfil\nA container/VM adds full isolation, but you lose host access.",
24068
+ coverageProbe: { kind: "cantFix" }
24069
+ }
24070
+ ];
24071
+ }
24072
+
24073
+ // src/posture/inbound.ts
24074
+ import fs49 from "fs";
24075
+ var KNOWN_SERVICE_PORTS = {
24076
+ 5432: "PostgreSQL",
24077
+ 6379: "Redis",
24078
+ 3306: "MySQL/MariaDB",
24079
+ 27017: "MongoDB",
24080
+ 9200: "Elasticsearch",
24081
+ 11211: "Memcached",
24082
+ 5672: "RabbitMQ",
24083
+ 9092: "Kafka",
24084
+ 2379: "etcd",
24085
+ 8086: "InfluxDB"
24086
+ };
24087
+ var KNOWN_SERVICE_COMMS = {
24088
+ postgres: "PostgreSQL",
24089
+ "redis-server": "Redis",
24090
+ mysqld: "MySQL",
24091
+ mariadbd: "MariaDB",
24092
+ mongod: "MongoDB"
24093
+ };
24094
+ var DB_LABEL = /PostgreSQL|Redis|MySQL|MariaDB|MongoDB/;
24095
+ var SHIELD_FOR_SERVICE = {
24096
+ PostgreSQL: {
24097
+ shield: "postgres",
24098
+ blocks: "DROP TABLE / TRUNCATE",
24099
+ rebind: "PostgreSQL \u2192 listen_addresses='localhost'"
24100
+ },
24101
+ Redis: { shield: "redis", blocks: "FLUSHALL / FLUSHDB", rebind: "Redis \u2192 bind 127.0.0.1" }
24102
+ };
24103
+ function buildNetworkFix(labels) {
24104
+ const shielded = [
24105
+ ...new Map(
24106
+ labels.map((label2) => {
24107
+ const key = Object.keys(SHIELD_FOR_SERVICE).find((k) => label2.includes(k));
24108
+ return key ? SHIELD_FOR_SERVICE[key] : null;
24109
+ }).filter((s) => s !== null).map((s) => [s.shield, s])
24110
+ ).values()
24111
+ ];
24112
+ if (shielded.length === 0) {
24113
+ return {
24114
+ fix: "Bind to 127.0.0.1 or firewall the port; node9 gates the agent, not the socket.",
24115
+ reduces: false
24116
+ };
24117
+ }
24118
+ const protectLines = shielded.map((s) => ` \u2022 node9 shield enable ${s.shield} \u2014 blocks ${s.blocks}`).join("\n");
24119
+ const rebindLines = shielded.map((s) => ` \u2022 ${s.rebind}`).join("\n");
24120
+ return {
24121
+ fix: "Protect the agent now \u2014 node9 blocks destructive DB ops:\n" + protectLines + "\nClose the port to other machines (your part):\n" + rebindLines + "\n \u2022 or firewall the port",
24122
+ reduces: true
24123
+ };
24124
+ }
24125
+ function parseListeners(procText) {
24126
+ const out = [];
24127
+ for (const line of procText.split("\n").slice(1)) {
24128
+ const cols = line.trim().split(/\s+/);
24129
+ if (cols.length < 10) continue;
24130
+ if (cols[3] !== "0A") continue;
24131
+ const local = cols[1];
24132
+ const sep = local.lastIndexOf(":");
24133
+ if (sep < 0) continue;
24134
+ const addrHex = local.slice(0, sep);
24135
+ const port = parseInt(local.slice(sep + 1), 16);
24136
+ if (!/^0+$/.test(addrHex) || !Number.isFinite(port)) continue;
24137
+ out.push({ port, inode: cols[9] });
24138
+ }
24139
+ return out;
24140
+ }
24141
+ function tiesToAgent(proc, agentName) {
24142
+ const needle = agentName.trim().toLowerCase();
24143
+ if (needle.length < 4) return false;
24144
+ const escaped = needle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
24145
+ const boundary = new RegExp(`(^|[^a-z0-9])${escaped}([^a-z0-9]|$)`);
24146
+ return boundary.test(proc.comm.toLowerCase()) || boundary.test(proc.cmdline.toLowerCase());
24147
+ }
24148
+ function classifyListener(port, proc, agentName) {
24149
+ if (agentName && proc && tiesToAgent(proc, agentName)) {
24150
+ return { kind: "agent", label: `${proc.comm} on :${port}` };
24151
+ }
24152
+ const service = KNOWN_SERVICE_PORTS[port] ?? (proc ? KNOWN_SERVICE_COMMS[proc.comm] : void 0);
24153
+ if (service) return { kind: "service", label: `${service} on :${port}` };
24154
+ return { kind: "unknown", label: `${proc?.comm || "unknown process"} on :${port}` };
24155
+ }
24156
+ function collectListeners() {
24157
+ const byPort = /* @__PURE__ */ new Map();
24158
+ for (const file of ["/proc/net/tcp", "/proc/net/tcp6"]) {
24159
+ try {
24160
+ for (const l of parseListeners(fs49.readFileSync(file, "utf8"))) {
24161
+ if (!byPort.has(l.port)) byPort.set(l.port, l);
24162
+ }
24163
+ } catch {
24164
+ }
24165
+ }
24166
+ return [...byPort.values()].sort((a, b) => a.port - b.port);
24167
+ }
24168
+ function readProc(pid) {
24169
+ let comm = "unknown";
24170
+ let cmdline = "";
24171
+ try {
24172
+ comm = fs49.readFileSync(`/proc/${pid}/comm`, "utf8").trim().replace(/-MainThread$/, "") || "unknown";
24173
+ } catch {
24174
+ }
24175
+ try {
24176
+ cmdline = fs49.readFileSync(`/proc/${pid}/cmdline`).toString().replace(/\0/g, " ").trim();
24177
+ } catch {
24178
+ }
24179
+ return { comm, cmdline };
24180
+ }
24181
+ function resolveProcesses(inodes) {
24182
+ const map = /* @__PURE__ */ new Map();
24183
+ if (inodes.size === 0) return map;
24184
+ let pids;
24185
+ try {
24186
+ pids = fs49.readdirSync("/proc").filter((d) => /^\d+$/.test(d));
24187
+ } catch {
24188
+ return map;
24189
+ }
24190
+ for (const pid of pids) {
24191
+ let fds;
24192
+ try {
24193
+ fds = fs49.readdirSync(`/proc/${pid}/fd`);
24194
+ } catch {
24195
+ continue;
24196
+ }
24197
+ for (const fd of fds) {
24198
+ let link;
24199
+ try {
24200
+ link = fs49.readlinkSync(`/proc/${pid}/fd/${fd}`);
24201
+ } catch {
24202
+ continue;
24203
+ }
24204
+ const m = /^socket:\[(\d+)\]$/.exec(link);
24205
+ if (m && inodes.has(m[1]) && !map.has(m[1])) {
24206
+ map.set(m[1], readProc(pid));
24207
+ }
24208
+ }
24209
+ if (map.size === inodes.size) break;
24210
+ }
24211
+ return map;
24212
+ }
24213
+ function checkInbound(ctx) {
24214
+ const listeners = collectListeners();
24215
+ if (listeners.length === 0) return [];
24216
+ const procByInode = resolveProcesses(new Set(listeners.map((l) => l.inode)));
24217
+ const classified = listeners.map((l) => ({
24218
+ port: l.port,
24219
+ ...classifyListener(l.port, procByInode.get(l.inode) ?? null, ctx.agent)
24220
+ }));
24221
+ const findings = [];
24222
+ const agentPorts = classified.filter((c) => c.kind === "agent");
24223
+ if (agentPorts.length > 0) {
24224
+ findings.push({
24225
+ category: "Agent inbound",
24226
+ severity: "advisory",
24227
+ title: `Your agent is reachable on 0.0.0.0 (port${agentPorts.length === 1 ? "" : "s"} ${agentPorts.map((a) => a.port).join(", ")})`,
24228
+ what: "Your agent itself is listening for incoming network connections.",
24229
+ why: "It's bound to 0.0.0.0, so other devices on the network can reach it.",
24230
+ who: "Anyone who can reach the port could send it instructions (pilot it). Confirm it requires an auth token.",
24231
+ detail: agentPorts.map((a) => a.label),
24232
+ owner: "os",
24233
+ fix: "Bind the agent port to 127.0.0.1, or require an auth token on inbound requests.",
24234
+ coverageProbe: { kind: "cantFix" }
24235
+ });
24236
+ }
24237
+ const exposed = classified.filter((c) => c.kind !== "agent");
24238
+ if (exposed.length > 0) {
24239
+ const hasDb = exposed.some((e) => DB_LABEL.test(e.label));
24240
+ const { fix, reduces } = buildNetworkFix(exposed.map((e) => e.label));
24241
+ findings.push({
24242
+ category: "Network exposure",
24243
+ severity: "advisory",
24244
+ title: `${exposed.length} service${exposed.length === 1 ? "" : "s"} reachable on 0.0.0.0`,
24245
+ what: "These services accept connections from your whole network, not just this laptop.",
24246
+ why: "They listen on 0.0.0.0 (all interfaces) instead of 127.0.0.1 (this machine only).",
24247
+ // Calibrated: 0.0.0.0 = your local network (WiFi), not the public internet
24248
+ // unless the box has a public IP.
24249
+ who: "Other devices on your network (e.g. your WiFi) can connect \u2014 usually not the whole internet unless this box has a public IP." + (hasDb ? " An open, unauthenticated database is a direct data-theft path." : ""),
24250
+ detail: exposed.map((e) => e.label),
24251
+ owner: "os",
24252
+ // Only when node9 actually has a shield for an exposed service — otherwise
24253
+ // (bare dev servers) it stays purely the user's to rebind.
24254
+ node9Reduces: reduces,
24255
+ fix,
24256
+ coverageProbe: { kind: "cantFix" }
24257
+ });
24258
+ }
24259
+ return findings;
24260
+ }
24261
+
24262
+ // src/posture/coverage.ts
24263
+ init_config();
24264
+ import os43 from "os";
24265
+ function checkCoverage(ctx) {
24266
+ const home = ctx.home || os43.homedir();
24267
+ const findings = [];
24268
+ const protectedAgents = getAgentWiring(home).filter((r) => r.isProtected);
24269
+ if (protectedAgents.length === 0) {
24270
+ findings.push({
24271
+ category: "Coverage",
24272
+ severity: "critical",
24273
+ title: "node9 is not in-path for any agent",
24274
+ what: "node9 isn't actually in the loop for any agent on this machine.",
24275
+ why: "No agent has node9 hooks or MCP wired in.",
24276
+ who: "Everything else here is unenforced \u2014 node9 can only report, not block.",
24277
+ detail: [],
24278
+ owner: "node9",
24279
+ fix: "Run `node9 init` to put node9 in-path for your agents."
24280
+ });
24281
+ return findings;
24282
+ }
24283
+ const mode = getConfig(ctx.cwd).settings.mode;
24284
+ if (mode === "observe" || mode === "audit") {
24285
+ findings.push({
24286
+ category: "Coverage",
24287
+ severity: "high",
24288
+ title: `node9 is in ${mode} mode \u2014 watching, not blocking`,
24289
+ what: "node9 is watching but not actually blocking anything.",
24290
+ why: `It's in ${mode} mode, which logs risky actions but lets them through.`,
24291
+ who: "The guardrails above are observed, not enforced.",
24292
+ detail: [],
24293
+ owner: "node9",
24294
+ fix: "Set mode to `standard` (or `strict`) to enforce in-path."
24295
+ });
24296
+ }
24297
+ return findings;
24298
+ }
24299
+
24300
+ // src/posture/score.ts
24301
+ init_dist();
24302
+ function scorePosture(findings, checksRun) {
24303
+ const open = findings.filter(
24304
+ (f) => f.coverage?.state !== "covered" && f.coverage?.state !== "cant-fix"
24305
+ );
24306
+ const count = (sev) => open.filter((f) => f.severity === sev).length;
24307
+ return computeSecurityScore({
24308
+ critical: count("critical"),
24309
+ high: count("high"),
24310
+ medium: count("medium"),
24311
+ // Denominator = number of checks evaluated. With computeSecurityScore's
24312
+ // caps this makes any critical → critical tier, any high → at-risk, and a
24313
+ // fully clean run (0 findings, checksRun > 0) → 100/good.
24314
+ total: Math.max(checksRun, 1)
24315
+ });
24316
+ }
24317
+
24318
+ // src/posture/headline.ts
24319
+ var SEVERITY_RANK = {
24320
+ critical: 0,
24321
+ high: 1,
24322
+ medium: 2,
24323
+ advisory: 3
24324
+ };
24325
+ function worstFinding(findings) {
24326
+ return [...findings].sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity])[0];
24327
+ }
24328
+ function deriveHeadline(allFindings) {
24329
+ const findings = allFindings.filter(
24330
+ (f) => f.coverage?.state !== "covered" && f.coverage?.state !== "cant-fix"
24331
+ );
24332
+ if (findings.length === 0 || findings.every((f) => f.severity === "advisory")) return null;
24333
+ const has = (category) => findings.some((f) => f.category === category);
24334
+ const secrets = has("Secrets");
24335
+ const egressOpen = has("Egress");
24336
+ const noIsolation = has("Isolation");
24337
+ const gateWeak = has("Approval gate");
24338
+ const notWired = findings.some((f) => f.category === "Coverage" && f.severity === "critical");
24339
+ const observeOnly = findings.some((f) => f.category === "Coverage" && f.severity === "high");
24340
+ let risk;
24341
+ if (secrets && egressOpen) {
24342
+ risk = "An agent on this host can read the credentials on this box and send them to any host" + (noIsolation ? ", and there is no container around it" : "") + ". One poisoned input \u2014 a malicious file, or a prompt-injection in a page it reads \u2014 is all it takes.";
24343
+ } else if (secrets) {
24344
+ risk = "An agent on this host can read the credentials on this box" + (noIsolation ? " with no sandbox around it" : "") + ". A single poisoned instruction would expose those keys.";
24345
+ } else if (egressOpen && gateWeak) {
24346
+ risk = "An agent here can run unrestricted commands and reach any host \u2014 an open path for a poisoned instruction to exfiltrate data or damage the box.";
24347
+ } else if (egressOpen) {
24348
+ risk = "An agent here can reach any host on the internet \u2014 an open exfiltration path the moment it is compromised.";
24349
+ } else if (gateWeak) {
24350
+ risk = "Destructive commands are not reliably blocked here \u2014 an agent given a bad instruction could damage the box.";
24351
+ } else {
24352
+ risk = worstFinding(findings)?.title ?? "Review the findings below.";
24353
+ }
24354
+ let action;
24355
+ if (notWired) {
24356
+ action = "Run `node9 init` \u2014 node9 is not in-path yet, so nothing here is enforced.";
24357
+ } else if (observeOnly) {
24358
+ action = "Switch node9 to enforcing mode \u2014 right now it is only watching, not blocking.";
24359
+ } else if (egressOpen) {
24360
+ action = "lock egress to an allowlist (node9 can enforce it) \u2014 it closes the exit the exfiltration needs.";
24361
+ } else if (secrets) {
24362
+ action = "node9 can block reads of sensitive paths (~/.ssh, ~/.aws) in-path.";
24363
+ } else if (gateWeak) {
24364
+ action = "node9 can enforce destructive-command blocking in-path.";
24365
+ } else {
24366
+ action = worstFinding(findings)?.fix ?? "Review the findings below.";
24367
+ }
24368
+ return { risk, action };
24369
+ }
24370
+
24371
+ // src/posture/enforcement.ts
24372
+ init_dlp();
24373
+ init_policy();
24374
+ init_config();
24375
+ function egressCoverage(env) {
24376
+ if (env.enforcing && env.egressBlocking) {
24377
+ return { state: "covered", level: "block", via: "node9 egress" };
24378
+ }
24379
+ if (env.enforcing && env.egressReviewing) {
24380
+ return { state: "covered", level: "review", via: "node9 egress" };
24381
+ }
24382
+ return { state: "open" };
24383
+ }
24384
+ function coverageFromVerdict(verdict, env, via) {
24385
+ if (!env.enforcing) return { state: "open" };
24386
+ if (verdict === "block") return { state: "covered", level: "block", via };
24387
+ if (verdict === "review") return { state: "covered", level: "review", via };
24388
+ return { state: "open" };
24389
+ }
24390
+ function viaFromRule(ruleName) {
24391
+ if (!ruleName) return void 0;
24392
+ const m = /^shield:([^:]+):/.exec(ruleName);
24393
+ return m ? `${m[1]} shield` : void 0;
24394
+ }
24395
+ async function annotateCoverage(findings, ctx) {
24396
+ const config = getConfig(ctx.cwd);
24397
+ const mode = config.settings.mode;
24398
+ const wired = getAgentWiring(ctx.home).some((r) => r.isProtected);
24399
+ const env = {
24400
+ enforcing: wired && mode !== "observe" && mode !== "audit",
24401
+ egressBlocking: config.policy.egress.enabled && config.policy.egress.mode === "block",
24402
+ egressReviewing: config.policy.egress.enabled && config.policy.egress.mode === "review"
24403
+ };
24404
+ for (const f of findings) {
24405
+ const probe = f.coverageProbe;
24406
+ if (!probe) continue;
24407
+ if (probe.kind === "cantFix") {
24408
+ f.coverage = { state: "cant-fix" };
24409
+ continue;
24410
+ }
24411
+ if (probe.kind === "egress") {
24412
+ f.coverage = egressCoverage(env);
24413
+ continue;
24414
+ }
24415
+ if (probe.kind === "fileRead") {
24416
+ const verdicts = probe.paths.map((p) => scanFilePath(p)?.severity ?? null);
24417
+ if (verdicts.length === 0 || verdicts.some((v) => v === null)) {
24418
+ f.coverage = coverageFromVerdict("allow", env);
24419
+ } else {
24420
+ const worst = verdicts.some((v) => v === "review") ? "review" : "block";
24421
+ f.coverage = coverageFromVerdict(worst, env, "node9 DLP");
24422
+ }
24423
+ continue;
24424
+ }
24425
+ const verdict = await evaluatePolicy2("Bash", { command: probe.command }, ctx.agent, ctx.cwd);
24426
+ f.coverage = coverageFromVerdict(
24427
+ verdict.decision,
24428
+ env,
24429
+ viaFromRule(verdict.ruleName)
24430
+ );
24431
+ }
24432
+ }
24433
+
24434
+ // src/posture/index.ts
24435
+ var POSTURE_CHECKS = [
24436
+ { category: "Secrets", run: checkSecrets },
24437
+ { category: "Egress", run: checkEgress },
24438
+ { category: "Approval gate", run: checkGate },
24439
+ { category: "Supply chain", run: checkSupplyChain },
24440
+ { category: "Privilege", run: checkPrivilege },
24441
+ { category: "Isolation", run: checkContainment },
24442
+ { category: "Inbound", run: checkInbound },
24443
+ { category: "Coverage", run: checkCoverage }
24444
+ ];
24445
+ function dropEnforcementRedundant(findings) {
24446
+ const coveragePresent = findings.some((f) => f.category === "Coverage");
24447
+ if (!coveragePresent) return findings;
24448
+ return findings.filter((f) => !(f.redundantWhenOpen && f.coverage?.state === "open"));
24449
+ }
24450
+ async function runChecks(checks, ctx) {
24451
+ const findings = [];
24452
+ const passedCategories = [];
24453
+ const erroredCategories = [];
24454
+ for (const check of checks) {
24455
+ try {
24456
+ const result = await check.run(ctx);
24457
+ if (result.length === 0) passedCategories.push(check.category);
24458
+ else findings.push(...result);
24459
+ } catch (err2) {
24460
+ erroredCategories.push(check.category);
24461
+ if (process.env.NODE9_DEBUG) {
24462
+ console.error(`[posture] check "${check.category}" failed:`, err2?.message);
24463
+ }
24464
+ }
24465
+ }
24466
+ return { findings, passedCategories, erroredCategories };
24467
+ }
24468
+ async function runPosture(opts = {}) {
24469
+ const ctx = {
24470
+ home: opts.home ?? os44.homedir(),
24471
+ cwd: opts.cwd ?? process.cwd(),
24472
+ agent: opts.agent
24473
+ };
24474
+ const {
24475
+ findings: rawFindings,
24476
+ passedCategories,
24477
+ erroredCategories
24478
+ } = await runChecks(POSTURE_CHECKS, ctx);
24479
+ await annotateCoverage(rawFindings, ctx);
24480
+ const findings = dropEnforcementRedundant(rawFindings);
24481
+ const { score, tier } = scorePosture(findings, POSTURE_CHECKS.length);
24482
+ return {
24483
+ agent: opts.agent ? `${opts.agent} on this host` : "agent on this host",
24484
+ findings,
24485
+ passedCategories,
24486
+ erroredCategories,
24487
+ headline: deriveHeadline(findings),
24488
+ score,
24489
+ tier,
24490
+ checksRun: POSTURE_CHECKS.length
24491
+ };
24492
+ }
24493
+
24494
+ // src/posture/render.ts
24495
+ import chalk24 from "chalk";
24496
+ var ICON = {
24497
+ critical: chalk24.red("\u274C"),
24498
+ high: chalk24.red("\u274C"),
24499
+ medium: chalk24.yellow("\u26A0\uFE0F "),
24500
+ advisory: chalk24.gray("\u26A0\uFE0F ")
24501
+ };
24502
+ var TIER_LABEL = {
24503
+ good: chalk24.green("Good"),
24504
+ "at-risk": chalk24.yellow("At risk"),
24505
+ critical: chalk24.red("Critical")
24506
+ };
24507
+ function wrap(text, width) {
24508
+ const out = [];
24509
+ let cur = "";
24510
+ for (const word of text.split(" ")) {
24511
+ if (cur && (cur + " " + word).length > width) {
24512
+ out.push(cur);
24513
+ cur = word;
24514
+ } else {
24515
+ cur = cur ? cur + " " + word : word;
24516
+ }
24517
+ }
24518
+ if (cur) out.push(cur);
24519
+ return out;
24520
+ }
24521
+ var LABEL_WIDTH = 14;
24522
+ function label(category) {
24523
+ return chalk24.bold(category.padEnd(Math.max(LABEL_WIDTH, category.length + 1)));
24524
+ }
24525
+ function renderFinding(f) {
24526
+ const lines = [];
24527
+ lines.push(` ${ICON[f.severity]} ${label(f.category)}${f.title}`);
24528
+ const indent = " ".repeat(2 + 3 + LABEL_WIDTH);
24529
+ const width = 80 - indent.length;
24530
+ for (const s of [f.what, f.why, f.who]) {
24531
+ if (s) for (const l of wrap(s, width)) lines.push(indent + chalk24.gray(l));
24532
+ }
24533
+ for (const d of f.detail) lines.push(indent + chalk24.gray(d));
24534
+ if (f.fix) {
24535
+ let first = true;
24536
+ for (const seg of f.fix.split("\n")) {
24537
+ for (const l of wrap(seg, width - 2)) {
24538
+ lines.push(indent + chalk24.cyan(first ? "\u2192 " + l : " " + l));
24539
+ first = false;
24540
+ }
24541
+ }
24542
+ }
24543
+ return lines;
24544
+ }
24545
+ function renderPosture(result) {
24546
+ const lines = [];
24547
+ const tier = TIER_LABEL[result.tier];
24548
+ lines.push("");
24549
+ lines.push(
24550
+ chalk24.cyan.bold(`\u{1F6E1}\uFE0F Node9 Posture`) + chalk24.gray(` \u2014 ${result.agent}`) + ` ${chalk24.bold(`Score: ${result.score}/100`)} (${tier})`
24551
+ );
24552
+ const advisories = result.findings.filter(
24553
+ (f) => f.severity === "advisory" && f.coverage?.state !== "covered"
24554
+ ).length;
24555
+ if (advisories > 0) {
24556
+ const word = advisories === 1 ? "advisory" : "advisories";
24557
+ const verb = advisories === 1 ? "doesn't" : "don't";
24558
+ lines.push(
24559
+ " " + chalk24.gray(
24560
+ `${advisories} ${word} below ${verb} affect the score \u2014 OS-level exposure node9 can't enforce, yours to weigh.`
24561
+ )
24562
+ );
24563
+ }
24564
+ lines.push("");
24565
+ if (result.headline) {
24566
+ const indent = " ";
24567
+ lines.push(` ${chalk24.red.bold("\u{1F525} Biggest risk")}`);
24568
+ for (const l of wrap(result.headline.risk, 74)) lines.push(indent + chalk24.white(l));
24569
+ const action = wrap(`Do this first: ${result.headline.action}`, 72);
24570
+ action.forEach((l, i) => lines.push(indent + chalk24.cyan(i === 0 ? "\u2192 " + l : " " + l)));
24571
+ lines.push("");
24572
+ }
24573
+ const covered = result.findings.filter((f) => f.coverage?.state === "covered");
24574
+ const open = result.findings.filter((f) => f.coverage?.state !== "covered");
24575
+ if (covered.length > 0) {
24576
+ lines.push(" " + chalk24.green("\u{1F7E2} node9 is already protecting you"));
24577
+ for (const f of covered) {
24578
+ const gated = f.coverage?.level === "review" ? "approval-gating" : "blocking";
24579
+ const via = f.coverage?.via ?? "node9";
24580
+ lines.push(
24581
+ ` ${chalk24.green("\u2705")} ${label(f.category)}${chalk24.gray(`${via} is ${gated} this`)}`
24582
+ );
24583
+ }
24584
+ lines.push("");
24585
+ }
24586
+ const node9Open = open.filter((f) => f.owner === "node9");
24587
+ const reduceOpen = open.filter((f) => f.owner !== "node9" && f.node9Reduces);
24588
+ const osOpen = open.filter((f) => f.owner !== "node9" && !f.node9Reduces);
24589
+ if (node9Open.length > 0) {
24590
+ lines.push(" " + chalk24.cyan.bold("\u{1F527} node9 can fix these \u2014 run the command"));
24591
+ for (const f of node9Open) lines.push(...renderFinding(f));
24592
+ }
24593
+ if (reduceOpen.length > 0) {
24594
+ if (node9Open.length > 0) lines.push("");
24595
+ lines.push(
24596
+ " " + chalk24.yellow.bold("\u{1F512} node9 reduces these \u2014 run the command, the rest is yours")
24597
+ );
24598
+ for (const f of reduceOpen) lines.push(...renderFinding(f));
24599
+ }
24600
+ if (osOpen.length > 0) {
24601
+ if (node9Open.length > 0 || reduceOpen.length > 0) lines.push("");
24602
+ lines.push(" " + chalk24.bold("\u{1F9F1} Only you can fix these \u2014 node9 can't"));
24603
+ for (const f of osOpen) lines.push(...renderFinding(f));
24604
+ }
24605
+ for (const cat of result.passedCategories) {
24606
+ lines.push(` ${chalk24.green("\u2705")} ${label(cat)}${chalk24.gray("no issues found")}`);
24607
+ }
24608
+ for (const cat of result.erroredCategories) {
24609
+ lines.push(` ${chalk24.gray("\u2022")} ${label(cat)}${chalk24.gray("could not be checked")}`);
24610
+ }
24611
+ lines.push("");
24612
+ const crit = open.filter((f) => f.severity === "critical").length;
24613
+ const high = open.filter((f) => f.severity === "high").length;
24614
+ const med = open.filter((f) => f.severity === "medium").length;
24615
+ const adv = open.filter((f) => f.severity === "advisory").length;
24616
+ const parts = [];
24617
+ if (crit) parts.push(chalk24.red(`${crit} critical`));
24618
+ if (high) parts.push(chalk24.red(`${high} high`));
24619
+ if (med) parts.push(chalk24.yellow(`${med} medium`));
24620
+ if (adv) parts.push(chalk24.gray(`${adv} advisory`));
24621
+ const summary = parts.length ? parts.join(" \xB7 ") : chalk24.green("no findings");
24622
+ lines.push(` ${summary} \xB7 ${chalk24.gray("track your fleet at app.node9.ai/posture")}`);
24623
+ lines.push("");
24624
+ return lines.join("\n");
24625
+ }
24626
+
24627
+ // src/posture/ship.ts
24628
+ import http2 from "http";
24629
+ import https5 from "https";
24630
+ import { URL as URL2 } from "url";
24631
+ function buildShipBody(result) {
24632
+ return {
24633
+ score: result.score,
24634
+ tier: result.tier,
24635
+ agent: result.agent,
24636
+ headline: result.headline,
24637
+ // { risk, action } | null — both safe strings
24638
+ findings: result.findings.map((f) => ({
24639
+ category: f.category,
24640
+ severity: f.severity,
24641
+ title: f.title,
24642
+ // Coverage state so the SaaS counts OPEN-only (matching the local score).
24643
+ // A non-sensitive enum — no values or paths. Default 'open' if unannotated.
24644
+ coverage: f.coverage?.state ?? "open",
24645
+ // Plain-language parity with the CLI report. Prose only, no paths.
24646
+ what: f.what,
24647
+ why: f.why,
24648
+ who: f.who,
24649
+ // The runnable fix / OS action — commands + advice, never a path.
24650
+ fix: f.fix,
24651
+ // Whose job it is. Default 'os' so the SaaS never falsely claims node9 can fix it.
24652
+ owner: f.owner ?? "os"
24653
+ }))
24654
+ };
24655
+ }
24656
+ function postureUrlFrom(apiUrl) {
24657
+ return apiUrl.endsWith("/policies/sync") ? apiUrl.replace(/\/policies\/sync$/, "/posture/report") : null;
24658
+ }
24659
+ async function shipPosture(result, creds) {
24660
+ const url = postureUrlFrom(creds.apiUrl);
24661
+ if (!url) return false;
24662
+ const body = JSON.stringify(buildShipBody(result));
24663
+ const parsed = new URL2(url);
24664
+ const transport = parsed.protocol === "http:" ? http2 : https5;
24665
+ return new Promise((resolve) => {
24666
+ const req = transport.request(
24667
+ {
24668
+ hostname: parsed.hostname,
24669
+ port: parsed.port ? parseInt(parsed.port, 10) : void 0,
24670
+ path: parsed.pathname + parsed.search,
24671
+ method: "POST",
24672
+ headers: {
24673
+ "Content-Type": "application/json",
24674
+ "Content-Length": Buffer.byteLength(body),
24675
+ Authorization: `Bearer ${creds.apiKey}`
24676
+ },
24677
+ timeout: 1e4
24678
+ },
24679
+ (res) => {
24680
+ const ok2 = !!res.statusCode && res.statusCode >= 200 && res.statusCode < 300;
24681
+ res.resume();
24682
+ res.on("end", () => resolve(ok2));
24683
+ res.on("error", () => resolve(false));
24684
+ }
24685
+ );
24686
+ req.on("error", () => resolve(false));
24687
+ req.on("timeout", () => {
24688
+ req.destroy();
24689
+ resolve(false);
24690
+ });
24691
+ req.write(body);
24692
+ req.end();
24693
+ });
24694
+ }
24695
+
24696
+ // src/cli/commands/posture.ts
24697
+ init_sync();
24698
+ function registerPostureCommand(program2) {
24699
+ program2.command("posture").description("Security scorecard for the agent on this host (secrets, egress, gate)").option("--agent <name>", "label / policy scope for the agent being graded").option("--json", "emit the raw result as JSON instead of the scorecard").option("--ship", "send a redacted snapshot to your node9 dashboard").action(async (opts) => {
24700
+ const result = await runPosture({ agent: opts.agent });
24701
+ if (opts.json) {
24702
+ console.log(JSON.stringify(result, null, 2));
24703
+ } else {
24704
+ console.log(renderPosture(result));
24705
+ }
24706
+ if (opts.ship) {
24707
+ const creds = readCredentials();
24708
+ if (!creds) {
24709
+ console.error(chalk25.gray(" Run `node9 login` to ship this to your dashboard."));
24710
+ } else {
24711
+ const ok2 = await shipPosture(result, creds);
24712
+ console.error(
24713
+ ok2 ? chalk25.gray(" \u2713 Shipped to your node9 dashboard.") : chalk25.gray(" Could not reach the dashboard \u2014 saved locally only.")
24714
+ );
24715
+ }
24716
+ }
24717
+ if (result.tier === "critical") process.exitCode = 2;
24718
+ });
24719
+ }
24720
+
24721
+ // src/cli/commands/egress.ts
24722
+ init_config();
24723
+ init_dist();
24724
+ import chalk26 from "chalk";
24725
+ import fs50 from "fs";
24726
+ import os45 from "os";
24727
+ import path49 from "path";
24728
+ var DEFAULT_EGRESS = {
24729
+ enabled: false,
24730
+ mode: "review",
24731
+ allow: [],
24732
+ deny: [],
24733
+ allowPrivate: true
24734
+ };
24735
+ function configPath() {
24736
+ return path49.join(os45.homedir(), ".node9", "config.json");
24737
+ }
24738
+ function readRawConfig() {
24739
+ let text;
24740
+ try {
24741
+ text = fs50.readFileSync(configPath(), "utf8");
24742
+ } catch (err2) {
24743
+ if (err2.code === "ENOENT") return {};
24744
+ throw err2;
24745
+ }
24746
+ try {
24747
+ return JSON.parse(text);
24748
+ } catch {
24749
+ throw new Error(
24750
+ `${configPath()} is not valid JSON \u2014 fix it before changing egress (refusing to overwrite).`
24751
+ );
24752
+ }
24753
+ }
24754
+ function writeRawConfig(config) {
24755
+ const p = configPath();
24756
+ fs50.mkdirSync(path49.dirname(p), { recursive: true });
24757
+ fs50.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
24758
+ }
24759
+ function applyEgress(config, change) {
24760
+ const policy = config.policy = config.policy ?? {};
24761
+ const existing = policy.egress ?? {};
24762
+ policy.egress = { ...DEFAULT_EGRESS, ...existing, ...change };
24763
+ return config;
24764
+ }
24765
+ function withConfig(fn) {
24766
+ let config;
24767
+ try {
24768
+ config = readRawConfig();
24769
+ } catch (err2) {
24770
+ console.error(chalk26.red(`
24771
+ \u2717 ${err2.message}
24772
+ `));
24773
+ process.exitCode = 1;
24774
+ return false;
24775
+ }
24776
+ fn(config);
24777
+ writeRawConfig(config);
24778
+ return true;
24779
+ }
24780
+ function mutate(change) {
24781
+ return withConfig((config) => applyEgress(config, change));
24782
+ }
24783
+ function addHost(list, host) {
24784
+ return withConfig((config) => {
24785
+ const existing = config.policy?.egress ?? {};
24786
+ const current = { ...DEFAULT_EGRESS, ...existing };
24787
+ const updated = current[list].includes(host) ? current[list] : [...current[list], host];
24788
+ applyEgress(config, { [list]: updated });
24789
+ });
24790
+ }
24791
+ function showStatus() {
24792
+ const e = getConfig().policy.egress;
24793
+ const state = !e.enabled ? chalk26.red("OFF \u2014 your agent can reach any host") : e.mode === "block" ? chalk26.green("LOCKED (block) \u2014 unknown hosts are denied") : chalk26.yellow("WATCHING (review) \u2014 unknown hosts prompt you");
24794
+ console.log(chalk26.cyan.bold("\n\u{1F310} Egress control"));
24795
+ console.log(" State: " + state);
24796
+ console.log(
24797
+ chalk26.gray(
24798
+ ` ${DEFAULT_EGRESS_ALLOWLIST.length} common dev/LLM hosts are always allowed (github, npm, pypi, anthropic, \u2026).`
24799
+ )
24800
+ );
24801
+ if (e.allow.length) console.log(" Your allow: " + e.allow.join(", "));
24802
+ if (e.deny.length) console.log(" Your deny: " + e.deny.join(", "));
24803
+ if (!e.enabled) {
24804
+ console.log(chalk26.gray("\n Turn it on: node9 egress watch (prompt on unknown hosts)"));
24805
+ console.log(chalk26.gray(" node9 egress lock (hard-block unknown hosts)"));
24806
+ }
24807
+ console.log("");
24808
+ }
24809
+ function registerEgressCommand(program2) {
24810
+ const egress = program2.command("egress").description("Control where your agent can send data (egress allowlist)");
24811
+ egress.command("watch").description("Prompt before the agent reaches an unknown host (review mode)").action(() => {
24812
+ if (!mutate({ enabled: true, mode: "review" })) return;
24813
+ console.log(chalk26.green("\n\u2713 Egress is now watched (review mode)."));
24814
+ console.log(
24815
+ chalk26.gray(" Routine hosts (LLM APIs, package registries, localhost) are allowed.")
24816
+ );
24817
+ console.log(
24818
+ chalk26.gray(" An unknown host will prompt you \u2014 run `node9 egress lock` to hard-block.\n")
24819
+ );
24820
+ });
24821
+ egress.command("lock").description("Block the agent from reaching unknown hosts (block mode)").action(() => {
24822
+ if (!mutate({ enabled: true, mode: "block" })) return;
24823
+ console.log(chalk26.green("\n\u2713 Egress is now locked (block mode)."));
24824
+ console.log(chalk26.gray(" Routine hosts are still allowed; unknown hosts are denied."));
24825
+ console.log(chalk26.gray(" Allow a specific host with `node9 egress allow <host>`.\n"));
24826
+ });
24827
+ egress.command("allow <host>").description("Allow an extra host (glob, e.g. *.mycorp.com)").action((host) => {
24828
+ if (!addHost("allow", host)) return;
24829
+ console.log(chalk26.green(`
24830
+ \u2713 Allowed egress to ${host}.
24831
+ `));
24832
+ });
24833
+ egress.command("deny <host>").description("Block an extra host (deny always wins)").action((host) => {
24834
+ if (!addHost("deny", host)) return;
24835
+ console.log(chalk26.green(`
24836
+ \u2713 Denied egress to ${host}.
24837
+ `));
24838
+ });
24839
+ egress.command("off").description("Turn egress control off").action(() => {
24840
+ if (!mutate({ enabled: false })) return;
24841
+ console.log(
24842
+ chalk26.yellow("\n\u2713 Egress control is off \u2014 the agent can reach any host again.\n")
24843
+ );
24844
+ });
24845
+ egress.action(showStatus);
24846
+ }
24847
+
23718
24848
  // src/cli/commands/sessions.ts
23719
24849
  init_scan_summary();
23720
24850
  init_litellm();
23721
24851
  init_cost_gemini();
23722
24852
  init_cost_codex();
23723
- import chalk24 from "chalk";
23724
- import fs46 from "fs";
23725
- import path47 from "path";
23726
- import os41 from "os";
24853
+ import chalk27 from "chalk";
24854
+ import fs51 from "fs";
24855
+ import path50 from "path";
24856
+ import os46 from "os";
23727
24857
  function modelPrice(model) {
23728
24858
  const t = pricingFor(model);
23729
24859
  if (!t) return null;
@@ -23740,10 +24870,10 @@ function encodeProjectPath(projectPath) {
23740
24870
  }
23741
24871
  function sessionJsonlPath(projectPath, sessionId) {
23742
24872
  const encoded = encodeProjectPath(projectPath);
23743
- return path47.join(os41.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
24873
+ return path50.join(os46.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
23744
24874
  }
23745
24875
  function projectLabel(projectPath) {
23746
- return projectPath.replace(os41.homedir(), "~");
24876
+ return projectPath.replace(os46.homedir(), "~");
23747
24877
  }
23748
24878
  function parseHistoryLines(lines) {
23749
24879
  const entries = [];
@@ -23812,10 +24942,10 @@ function parseSessionLines(lines) {
23812
24942
  return { toolCalls, costUSD, hasSnapshot, modifiedFiles };
23813
24943
  }
23814
24944
  function loadAuditEntries(auditPath) {
23815
- const aPath = auditPath ?? path47.join(os41.homedir(), ".node9", "audit.log");
24945
+ const aPath = auditPath ?? path50.join(os46.homedir(), ".node9", "audit.log");
23816
24946
  let raw;
23817
24947
  try {
23818
- raw = fs46.readFileSync(aPath, "utf-8");
24948
+ raw = fs51.readFileSync(aPath, "utf-8");
23819
24949
  } catch {
23820
24950
  return [];
23821
24951
  }
@@ -23851,8 +24981,8 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
23851
24981
  return result;
23852
24982
  }
23853
24983
  function buildGeminiSessions(days, allAuditEntries) {
23854
- const tmpDir = path47.join(os41.homedir(), ".gemini", "tmp");
23855
- if (!fs46.existsSync(tmpDir)) return [];
24984
+ const tmpDir = path50.join(os46.homedir(), ".gemini", "tmp");
24985
+ if (!fs51.existsSync(tmpDir)) return [];
23856
24986
  const cutoff = days !== null ? (() => {
23857
24987
  const d = /* @__PURE__ */ new Date();
23858
24988
  d.setDate(d.getDate() - days);
@@ -23861,35 +24991,35 @@ function buildGeminiSessions(days, allAuditEntries) {
23861
24991
  })() : null;
23862
24992
  let slugDirs;
23863
24993
  try {
23864
- slugDirs = fs46.readdirSync(tmpDir);
24994
+ slugDirs = fs51.readdirSync(tmpDir);
23865
24995
  } catch {
23866
24996
  return [];
23867
24997
  }
23868
24998
  const summaries = [];
23869
24999
  for (const slug of slugDirs) {
23870
- const slugPath = path47.join(tmpDir, slug);
25000
+ const slugPath = path50.join(tmpDir, slug);
23871
25001
  try {
23872
- if (!fs46.statSync(slugPath).isDirectory()) continue;
25002
+ if (!fs51.statSync(slugPath).isDirectory()) continue;
23873
25003
  } catch {
23874
25004
  continue;
23875
25005
  }
23876
- let projectRoot = path47.join(os41.homedir(), slug);
25006
+ let projectRoot = path50.join(os46.homedir(), slug);
23877
25007
  try {
23878
- projectRoot = fs46.readFileSync(path47.join(slugPath, ".project_root"), "utf-8").trim();
25008
+ projectRoot = fs51.readFileSync(path50.join(slugPath, ".project_root"), "utf-8").trim();
23879
25009
  } catch {
23880
25010
  }
23881
- const chatsDir = path47.join(slugPath, "chats");
23882
- if (!fs46.existsSync(chatsDir)) continue;
25011
+ const chatsDir = path50.join(slugPath, "chats");
25012
+ if (!fs51.existsSync(chatsDir)) continue;
23883
25013
  let chatFiles;
23884
25014
  try {
23885
- chatFiles = fs46.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
25015
+ chatFiles = fs51.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
23886
25016
  } catch {
23887
25017
  continue;
23888
25018
  }
23889
25019
  for (const chatFile of chatFiles) {
23890
25020
  let raw;
23891
25021
  try {
23892
- raw = fs46.readFileSync(path47.join(chatsDir, chatFile), "utf-8");
25022
+ raw = fs51.readFileSync(path50.join(chatsDir, chatFile), "utf-8");
23893
25023
  } catch {
23894
25024
  continue;
23895
25025
  }
@@ -23969,8 +25099,8 @@ function buildGeminiSessions(days, allAuditEntries) {
23969
25099
  return summaries;
23970
25100
  }
23971
25101
  function buildCodexSessions(days, allAuditEntries) {
23972
- const sessionsBase = path47.join(os41.homedir(), ".codex", "sessions");
23973
- if (!fs46.existsSync(sessionsBase)) return [];
25102
+ const sessionsBase = path50.join(os46.homedir(), ".codex", "sessions");
25103
+ if (!fs51.existsSync(sessionsBase)) return [];
23974
25104
  const cutoff = days !== null ? (() => {
23975
25105
  const d = /* @__PURE__ */ new Date();
23976
25106
  d.setDate(d.getDate() - days);
@@ -23979,29 +25109,29 @@ function buildCodexSessions(days, allAuditEntries) {
23979
25109
  })() : null;
23980
25110
  const jsonlFiles = [];
23981
25111
  try {
23982
- for (const year of fs46.readdirSync(sessionsBase)) {
23983
- const yearPath = path47.join(sessionsBase, year);
25112
+ for (const year of fs51.readdirSync(sessionsBase)) {
25113
+ const yearPath = path50.join(sessionsBase, year);
23984
25114
  try {
23985
- if (!fs46.statSync(yearPath).isDirectory()) continue;
25115
+ if (!fs51.statSync(yearPath).isDirectory()) continue;
23986
25116
  } catch {
23987
25117
  continue;
23988
25118
  }
23989
- for (const month of fs46.readdirSync(yearPath)) {
23990
- const monthPath = path47.join(yearPath, month);
25119
+ for (const month of fs51.readdirSync(yearPath)) {
25120
+ const monthPath = path50.join(yearPath, month);
23991
25121
  try {
23992
- if (!fs46.statSync(monthPath).isDirectory()) continue;
25122
+ if (!fs51.statSync(monthPath).isDirectory()) continue;
23993
25123
  } catch {
23994
25124
  continue;
23995
25125
  }
23996
- for (const day of fs46.readdirSync(monthPath)) {
23997
- const dayPath = path47.join(monthPath, day);
25126
+ for (const day of fs51.readdirSync(monthPath)) {
25127
+ const dayPath = path50.join(monthPath, day);
23998
25128
  try {
23999
- if (!fs46.statSync(dayPath).isDirectory()) continue;
25129
+ if (!fs51.statSync(dayPath).isDirectory()) continue;
24000
25130
  } catch {
24001
25131
  continue;
24002
25132
  }
24003
- for (const file of fs46.readdirSync(dayPath)) {
24004
- if (file.endsWith(".jsonl")) jsonlFiles.push(path47.join(dayPath, file));
25133
+ for (const file of fs51.readdirSync(dayPath)) {
25134
+ if (file.endsWith(".jsonl")) jsonlFiles.push(path50.join(dayPath, file));
24005
25135
  }
24006
25136
  }
24007
25137
  }
@@ -24013,7 +25143,7 @@ function buildCodexSessions(days, allAuditEntries) {
24013
25143
  for (const filePath of jsonlFiles) {
24014
25144
  let lines;
24015
25145
  try {
24016
- lines = fs46.readFileSync(filePath, "utf-8").split("\n");
25146
+ lines = fs51.readFileSync(filePath, "utf-8").split("\n");
24017
25147
  } catch {
24018
25148
  continue;
24019
25149
  }
@@ -24099,10 +25229,10 @@ function buildCodexSessions(days, allAuditEntries) {
24099
25229
  return summaries;
24100
25230
  }
24101
25231
  function buildSessions(days, historyPath) {
24102
- const hPath = historyPath ?? path47.join(os41.homedir(), ".claude", "history.jsonl");
25232
+ const hPath = historyPath ?? path50.join(os46.homedir(), ".claude", "history.jsonl");
24103
25233
  let historyRaw = "";
24104
25234
  try {
24105
- historyRaw = fs46.readFileSync(hPath, "utf-8");
25235
+ historyRaw = fs51.readFileSync(hPath, "utf-8");
24106
25236
  } catch {
24107
25237
  }
24108
25238
  const cutoff = days !== null ? (() => {
@@ -24126,7 +25256,7 @@ function buildSessions(days, historyPath) {
24126
25256
  const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
24127
25257
  let sessionLines = [];
24128
25258
  try {
24129
- sessionLines = fs46.readFileSync(jsonlFile, "utf-8").split("\n");
25259
+ sessionLines = fs51.readFileSync(jsonlFile, "utf-8").split("\n");
24130
25260
  } catch {
24131
25261
  }
24132
25262
  const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
@@ -24212,11 +25342,11 @@ function toolInputSummary(tool, input) {
24212
25342
  }
24213
25343
  function toolColor(tool) {
24214
25344
  const t = tool.toLowerCase();
24215
- if (t === "bash" || t === "execute_bash") return chalk24.red;
24216
- if (t === "write") return chalk24.green;
24217
- if (t === "edit" || t === "notebookedit") return chalk24.yellow;
24218
- if (t === "read") return chalk24.cyan;
24219
- return chalk24.gray;
25345
+ if (t === "bash" || t === "execute_bash") return chalk27.red;
25346
+ if (t === "write") return chalk27.green;
25347
+ if (t === "edit" || t === "notebookedit") return chalk27.yellow;
25348
+ if (t === "read") return chalk27.cyan;
25349
+ return chalk27.gray;
24220
25350
  }
24221
25351
  function barStr2(value, max, width) {
24222
25352
  if (max === 0 || width <= 0) return "\u2591".repeat(width);
@@ -24226,7 +25356,7 @@ function barStr2(value, max, width) {
24226
25356
  function colorBar2(value, max, width) {
24227
25357
  const s = barStr2(value, max, width);
24228
25358
  const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
24229
- return chalk24.cyan(s.slice(0, filled)) + chalk24.dim(s.slice(filled));
25359
+ return chalk27.cyan(s.slice(0, filled)) + chalk27.dim(s.slice(filled));
24230
25360
  }
24231
25361
  function renderSummary(summaries) {
24232
25362
  const totalTools = summaries.reduce((n, s) => n + s.toolCalls.length, 0);
@@ -24256,45 +25386,45 @@ function renderSummary(summaries) {
24256
25386
  }
24257
25387
  const topProjects = [...projCosts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
24258
25388
  const W = 20;
24259
- console.log(chalk24.dim(" " + "\u2500".repeat(70)));
25389
+ console.log(chalk27.dim(" " + "\u2500".repeat(70)));
24260
25390
  console.log(
24261
- " " + chalk24.bold.white(String(summaries.length).padEnd(4)) + chalk24.dim("sessions ") + chalk24.bold.yellow(fmtCost3(totalCost).padEnd(10)) + chalk24.dim("total ") + chalk24.bold.white(String(totalTools).padEnd(6)) + chalk24.dim("tool calls ") + chalk24.bold.white(String(totalFiles)) + chalk24.dim(" files modified") + (totalBlocked > 0 ? chalk24.dim(" ") + chalk24.red.bold(String(totalBlocked)) + chalk24.dim(" blocked by node9") : "")
25391
+ " " + chalk27.bold.white(String(summaries.length).padEnd(4)) + chalk27.dim("sessions ") + chalk27.bold.yellow(fmtCost3(totalCost).padEnd(10)) + chalk27.dim("total ") + chalk27.bold.white(String(totalTools).padEnd(6)) + chalk27.dim("tool calls ") + chalk27.bold.white(String(totalFiles)) + chalk27.dim(" files modified") + (totalBlocked > 0 ? chalk27.dim(" ") + chalk27.red.bold(String(totalBlocked)) + chalk27.dim(" blocked by node9") : "")
24262
25392
  );
24263
25393
  console.log(
24264
- " " + chalk24.dim("avg ") + chalk24.white(fmtCost3(avgCost).padEnd(10)) + chalk24.dim("/session ") + chalk24.green(String(snapshots)) + chalk24.dim(` of ${summaries.length} sessions had snapshots`)
25394
+ " " + chalk27.dim("avg ") + chalk27.white(fmtCost3(avgCost).padEnd(10)) + chalk27.dim("/session ") + chalk27.green(String(snapshots)) + chalk27.dim(` of ${summaries.length} sessions had snapshots`)
24265
25395
  );
24266
25396
  console.log("");
24267
- console.log(" " + chalk24.dim("Tool breakdown:"));
25397
+ console.log(" " + chalk27.dim("Tool breakdown:"));
24268
25398
  const maxGroup = Math.max(...Object.values(groups));
24269
- for (const [label, count] of Object.entries(groups)) {
25399
+ for (const [label2, count] of Object.entries(groups)) {
24270
25400
  if (count === 0) continue;
24271
25401
  const pct = totalTools > 0 ? Math.round(count / totalTools * 100) : 0;
24272
25402
  console.log(
24273
- " " + label.padEnd(6) + " " + colorBar2(count, maxGroup, W) + " " + chalk24.white(String(count).padStart(4)) + chalk24.dim(` (${String(pct)}%)`)
25403
+ " " + label2.padEnd(6) + " " + colorBar2(count, maxGroup, W) + " " + chalk27.white(String(count).padStart(4)) + chalk27.dim(` (${String(pct)}%)`)
24274
25404
  );
24275
25405
  }
24276
25406
  console.log("");
24277
25407
  if (topProjects.length > 1) {
24278
- console.log(" " + chalk24.dim("Cost by project:"));
25408
+ console.log(" " + chalk27.dim("Cost by project:"));
24279
25409
  const maxProjCost = topProjects[0][1];
24280
25410
  for (const [proj, cost] of topProjects) {
24281
25411
  console.log(
24282
- " " + proj.slice(0, 28).padEnd(28) + " " + colorBar2(cost, maxProjCost, W) + " " + chalk24.yellow(fmtCost3(cost))
25412
+ " " + proj.slice(0, 28).padEnd(28) + " " + colorBar2(cost, maxProjCost, W) + " " + chalk27.yellow(fmtCost3(cost))
24283
25413
  );
24284
25414
  }
24285
25415
  console.log("");
24286
25416
  }
24287
- console.log(chalk24.dim(" " + "\u2500".repeat(70)));
25417
+ console.log(chalk27.dim(" " + "\u2500".repeat(70)));
24288
25418
  console.log("");
24289
25419
  }
24290
25420
  function renderList(summaries, totalCost) {
24291
25421
  if (summaries.length === 0) {
24292
- console.log(chalk24.yellow(" No sessions found in the requested range.\n"));
25422
+ console.log(chalk27.yellow(" No sessions found in the requested range.\n"));
24293
25423
  return;
24294
25424
  }
24295
- const totalLabel = totalCost > 0 ? chalk24.dim(" ~" + fmtCost3(totalCost) + " total") : "";
25425
+ const totalLabel = totalCost > 0 ? chalk27.dim(" ~" + fmtCost3(totalCost) + " total") : "";
24296
25426
  console.log(
24297
- " " + chalk24.white(String(summaries.length)) + chalk24.dim(` session${summaries.length !== 1 ? "s" : ""}`) + totalLabel
25427
+ " " + chalk27.white(String(summaries.length)) + chalk27.dim(` session${summaries.length !== 1 ? "s" : ""}`) + totalLabel
24298
25428
  );
24299
25429
  console.log("");
24300
25430
  let lastGroup = "";
@@ -24302,51 +25432,51 @@ function renderList(summaries, totalCost) {
24302
25432
  const activeDate = fmtDate2(s.lastActiveTime);
24303
25433
  const group = activeDate + " " + s.projectLabel;
24304
25434
  if (group !== lastGroup) {
24305
- console.log(chalk24.dim(" \u2500\u2500\u2500 ") + chalk24.bold(activeDate) + chalk24.dim(" " + s.projectLabel));
25435
+ console.log(chalk27.dim(" \u2500\u2500\u2500 ") + chalk27.bold(activeDate) + chalk27.dim(" " + s.projectLabel));
24306
25436
  lastGroup = group;
24307
25437
  }
24308
25438
  const startDate = fmtDate2(s.startTime);
24309
- const dateRange = startDate !== activeDate ? chalk24.dim(" (" + startDate + " \u2192 " + activeDate + ")") : "";
24310
- const timeStr = chalk24.dim(fmtTime(s.startTime));
24311
- const prompt = chalk24.white(truncate(s.firstPrompt.replace(/\n/g, " "), 50).padEnd(50));
24312
- const tools = s.toolCalls.length > 0 ? chalk24.dim(String(s.toolCalls.length).padStart(3) + " tools") : chalk24.dim(" 0 tools");
24313
- const cost = s.costUSD > 0 ? chalk24.dim(" " + fmtCost3(s.costUSD).padEnd(8)) : " ";
24314
- const blocked = s.blockedCalls.length > 0 ? chalk24.red(" \u{1F6D1} " + String(s.blockedCalls.length)) : "";
24315
- const snap = s.hasSnapshot ? chalk24.green(" \u{1F4F8}") : "";
24316
- const agentBadge = chalk24[agentColorName(s.agent ?? "claude")](
25439
+ const dateRange = startDate !== activeDate ? chalk27.dim(" (" + startDate + " \u2192 " + activeDate + ")") : "";
25440
+ const timeStr = chalk27.dim(fmtTime(s.startTime));
25441
+ const prompt = chalk27.white(truncate(s.firstPrompt.replace(/\n/g, " "), 50).padEnd(50));
25442
+ const tools = s.toolCalls.length > 0 ? chalk27.dim(String(s.toolCalls.length).padStart(3) + " tools") : chalk27.dim(" 0 tools");
25443
+ const cost = s.costUSD > 0 ? chalk27.dim(" " + fmtCost3(s.costUSD).padEnd(8)) : " ";
25444
+ const blocked = s.blockedCalls.length > 0 ? chalk27.red(" \u{1F6D1} " + String(s.blockedCalls.length)) : "";
25445
+ const snap = s.hasSnapshot ? chalk27.green(" \u{1F4F8}") : "";
25446
+ const agentBadge = chalk27[agentColorName(s.agent ?? "claude")](
24317
25447
  " " + agentBadgeText(s.agent ?? "claude", 0)
24318
25448
  );
24319
- const sid = chalk24.dim(" " + s.sessionId.slice(0, 8));
25449
+ const sid = chalk27.dim(" " + s.sessionId.slice(0, 8));
24320
25450
  console.log(
24321
25451
  ` ${timeStr} ${prompt} ${tools}${cost}${blocked}${snap}${agentBadge}${sid}${dateRange}`
24322
25452
  );
24323
25453
  }
24324
25454
  console.log("");
24325
25455
  console.log(
24326
- chalk24.dim(" Run") + " " + chalk24.cyan("node9 sessions --detail <session-id>") + chalk24.dim(" for full tool trace.")
25456
+ chalk27.dim(" Run") + " " + chalk27.cyan("node9 sessions --detail <session-id>") + chalk27.dim(" for full tool trace.")
24327
25457
  );
24328
25458
  console.log("");
24329
25459
  }
24330
25460
  function renderDetail(s) {
24331
25461
  console.log("");
24332
- console.log(chalk24.bold(" Session ") + chalk24.dim(s.sessionId));
25462
+ console.log(chalk27.bold(" Session ") + chalk27.dim(s.sessionId));
24333
25463
  console.log(
24334
- chalk24.bold(" Prompt ") + chalk24.white(s.firstPrompt.replace(/\n/g, " ").slice(0, 120))
25464
+ chalk27.bold(" Prompt ") + chalk27.white(s.firstPrompt.replace(/\n/g, " ").slice(0, 120))
24335
25465
  );
24336
- console.log(chalk24.bold(" Project ") + chalk24.white(s.projectLabel));
25466
+ console.log(chalk27.bold(" Project ") + chalk27.white(s.projectLabel));
24337
25467
  if (s.agent) {
24338
- const agentLabel2 = chalk24[agentColorName(s.agent)](agentDisplayName(s.agent));
24339
- console.log(chalk24.bold(" Agent ") + agentLabel2);
25468
+ const agentLabel2 = chalk27[agentColorName(s.agent)](agentDisplayName(s.agent));
25469
+ console.log(chalk27.bold(" Agent ") + agentLabel2);
24340
25470
  }
24341
- console.log(chalk24.bold(" When ") + chalk24.white(fmtDateTime(s.startTime)));
25471
+ console.log(chalk27.bold(" When ") + chalk27.white(fmtDateTime(s.startTime)));
24342
25472
  if (s.costUSD > 0)
24343
- console.log(chalk24.bold(" Cost ") + chalk24.yellow("~" + fmtCost3(s.costUSD)));
25473
+ console.log(chalk27.bold(" Cost ") + chalk27.yellow("~" + fmtCost3(s.costUSD)));
24344
25474
  console.log(
24345
- chalk24.bold(" Snapshot ") + (s.hasSnapshot ? chalk24.green("\u2713 taken") : chalk24.dim("none"))
25475
+ chalk27.bold(" Snapshot ") + (s.hasSnapshot ? chalk27.green("\u2713 taken") : chalk27.dim("none"))
24346
25476
  );
24347
25477
  console.log("");
24348
25478
  if (s.toolCalls.length === 0 && s.blockedCalls.length === 0) {
24349
- console.log(chalk24.dim(" No tool calls recorded.\n"));
25479
+ console.log(chalk27.dim(" No tool calls recorded.\n"));
24350
25480
  return;
24351
25481
  }
24352
25482
  const timeline = [
@@ -24359,32 +25489,32 @@ function renderDetail(s) {
24359
25489
  });
24360
25490
  const headerParts = [`Tool calls (${s.toolCalls.length})`];
24361
25491
  if (s.blockedCalls.length > 0)
24362
- headerParts.push(chalk24.red(`${s.blockedCalls.length} blocked by node9`));
24363
- console.log(chalk24.bold(" " + headerParts.join(" \xB7 ")));
25492
+ headerParts.push(chalk27.red(`${s.blockedCalls.length} blocked by node9`));
25493
+ console.log(chalk27.bold(" " + headerParts.join(" \xB7 ")));
24364
25494
  console.log("");
24365
25495
  for (const entry of timeline) {
24366
25496
  if (entry.kind === "tool") {
24367
25497
  const tc = entry.tc;
24368
25498
  const colorFn = toolColor(tc.tool);
24369
25499
  const toolPad = colorFn(tc.tool.padEnd(16));
24370
- const detail = chalk24.gray(truncate(toolInputSummary(tc.tool, tc.input), 70));
24371
- const ts = tc.timestamp ? chalk24.dim(fmtTime(tc.timestamp) + " ") : " ";
25500
+ const detail = chalk27.gray(truncate(toolInputSummary(tc.tool, tc.input), 70));
25501
+ const ts = tc.timestamp ? chalk27.dim(fmtTime(tc.timestamp) + " ") : " ";
24372
25502
  console.log(` ${ts}${toolPad} ${detail}`);
24373
25503
  } else {
24374
25504
  const bc = entry.bc;
24375
- const ts = bc.timestamp ? chalk24.dim(fmtTime(bc.timestamp) + " ") : " ";
24376
- const label = chalk24.red("\u{1F6D1} BLOCKED".padEnd(16));
24377
- const toolName = chalk24.red(bc.tool.padEnd(10));
24378
- const argsSummary = bc.args ? chalk24.gray(truncate(toolInputSummary(bc.tool, bc.args), 40)) : chalk24.dim("[args not logged]");
24379
- const reason = bc.checkedBy ? chalk24.dim(" \u2190 " + bc.checkedBy) : "";
24380
- console.log(` ${ts}${label} ${toolName} ${argsSummary}${reason}`);
25505
+ const ts = bc.timestamp ? chalk27.dim(fmtTime(bc.timestamp) + " ") : " ";
25506
+ const label2 = chalk27.red("\u{1F6D1} BLOCKED".padEnd(16));
25507
+ const toolName = chalk27.red(bc.tool.padEnd(10));
25508
+ const argsSummary = bc.args ? chalk27.gray(truncate(toolInputSummary(bc.tool, bc.args), 40)) : chalk27.dim("[args not logged]");
25509
+ const reason = bc.checkedBy ? chalk27.dim(" \u2190 " + bc.checkedBy) : "";
25510
+ console.log(` ${ts}${label2} ${toolName} ${argsSummary}${reason}`);
24381
25511
  }
24382
25512
  }
24383
25513
  console.log("");
24384
25514
  if (s.modifiedFiles.length > 0) {
24385
- console.log(chalk24.bold(` Files modified (${s.modifiedFiles.length}):`));
25515
+ console.log(chalk27.bold(` Files modified (${s.modifiedFiles.length}):`));
24386
25516
  for (const f of s.modifiedFiles) {
24387
- console.log(" " + chalk24.yellow(f));
25517
+ console.log(" " + chalk27.yellow(f));
24388
25518
  }
24389
25519
  console.log("");
24390
25520
  }
@@ -24392,13 +25522,13 @@ function renderDetail(s) {
24392
25522
  function registerSessionsCommand(program2) {
24393
25523
  program2.command("sessions").description("Show what your AI agent did \u2014 sessions, tool calls, cost, and file changes").option("--all", "Show all sessions (default: last 7 days)").option("--days <n>", "Show last N days of sessions", "7").option("--detail <sessionId>", "Show full tool trace for a session").action((options) => {
24394
25524
  console.log("");
24395
- console.log(chalk24.cyan.bold("\u{1F4CB} node9 sessions") + chalk24.dim(" \u2014 what your AI agent did"));
25525
+ console.log(chalk27.cyan.bold("\u{1F4CB} node9 sessions") + chalk27.dim(" \u2014 what your AI agent did"));
24396
25526
  console.log("");
24397
25527
  const days = options.detail || options.all ? null : Math.max(1, parseInt(options.days, 10) || 7);
24398
25528
  const rangeLabel = options.detail ? "all time" : options.all ? "all time" : `last ${String(days)} days`;
24399
- console.log(chalk24.dim(" " + rangeLabel));
25529
+ console.log(chalk27.dim(" " + rangeLabel));
24400
25530
  console.log("");
24401
- process.stdout.write(chalk24.dim(" Loading\u2026"));
25531
+ process.stdout.write(chalk27.dim(" Loading\u2026"));
24402
25532
  const summaries = buildSessions(days);
24403
25533
  if (process.stdout.isTTY) {
24404
25534
  process.stdout.clearLine(0);
@@ -24411,8 +25541,8 @@ function registerSessionsCommand(program2) {
24411
25541
  (s) => s.sessionId === options.detail || s.sessionId.startsWith(options.detail)
24412
25542
  );
24413
25543
  if (!target) {
24414
- console.log(chalk24.red(` Session not found: ${options.detail}`));
24415
- console.log(chalk24.dim(" Run `node9 sessions` to list recent sessions.\n"));
25544
+ console.log(chalk27.red(` Session not found: ${options.detail}`));
25545
+ console.log(chalk27.dim(" Run `node9 sessions` to list recent sessions.\n"));
24416
25546
  return;
24417
25547
  }
24418
25548
  renderDetail(target);
@@ -24425,13 +25555,13 @@ function registerSessionsCommand(program2) {
24425
25555
  }
24426
25556
 
24427
25557
  // src/cli/commands/skill-pin.ts
24428
- import chalk25 from "chalk";
24429
- import fs47 from "fs";
24430
- import os42 from "os";
24431
- import path48 from "path";
25558
+ import chalk28 from "chalk";
25559
+ import fs52 from "fs";
25560
+ import os47 from "os";
25561
+ import path51 from "path";
24432
25562
  function wipeSkillSessions() {
24433
25563
  try {
24434
- fs47.rmSync(path48.join(os42.homedir(), ".node9", "skill-sessions"), {
25564
+ fs52.rmSync(path51.join(os47.homedir(), ".node9", "skill-sessions"), {
24435
25565
  recursive: true,
24436
25566
  force: true
24437
25567
  });
@@ -24445,29 +25575,29 @@ function registerSkillPinCommand(program2) {
24445
25575
  const result = readSkillPinsSafe();
24446
25576
  if (!result.ok) {
24447
25577
  if (result.reason === "missing") {
24448
- console.log(chalk25.gray("\nNo skill roots are pinned yet."));
25578
+ console.log(chalk28.gray("\nNo skill roots are pinned yet."));
24449
25579
  console.log(
24450
- chalk25.gray("Pins are created automatically on the first tool call of each session.\n")
25580
+ chalk28.gray("Pins are created automatically on the first tool call of each session.\n")
24451
25581
  );
24452
25582
  return;
24453
25583
  }
24454
- console.error(chalk25.red(`
25584
+ console.error(chalk28.red(`
24455
25585
  \u274C Pin file is corrupt: ${result.detail}`));
24456
- console.error(chalk25.yellow(" Run: node9 skill pin reset\n"));
25586
+ console.error(chalk28.yellow(" Run: node9 skill pin reset\n"));
24457
25587
  process.exit(1);
24458
25588
  }
24459
25589
  const entries = Object.entries(result.pins.roots);
24460
25590
  if (entries.length === 0) {
24461
- console.log(chalk25.gray("\nNo skill roots are pinned yet.\n"));
25591
+ console.log(chalk28.gray("\nNo skill roots are pinned yet.\n"));
24462
25592
  return;
24463
25593
  }
24464
- console.log(chalk25.bold("\n\u{1F512} Pinned Skill Roots\n"));
25594
+ console.log(chalk28.bold("\n\u{1F512} Pinned Skill Roots\n"));
24465
25595
  for (const [key, entry] of entries) {
24466
- const missing = entry.exists ? "" : chalk25.yellow(" (not present at pin time)");
24467
- console.log(` ${chalk25.cyan(key)} ${chalk25.gray(entry.rootPath)}${missing}`);
25596
+ const missing = entry.exists ? "" : chalk28.yellow(" (not present at pin time)");
25597
+ console.log(` ${chalk28.cyan(key)} ${chalk28.gray(entry.rootPath)}${missing}`);
24468
25598
  console.log(` Files (${entry.fileCount})`);
24469
- console.log(` Hash: ${chalk25.gray(entry.contentHash.slice(0, 16))}...`);
24470
- console.log(` Pinned: ${chalk25.gray(entry.pinnedAt)}
25599
+ console.log(` Hash: ${chalk28.gray(entry.contentHash.slice(0, 16))}...`);
25600
+ console.log(` Pinned: ${chalk28.gray(entry.pinnedAt)}
24471
25601
  `);
24472
25602
  }
24473
25603
  });
@@ -24476,52 +25606,52 @@ function registerSkillPinCommand(program2) {
24476
25606
  try {
24477
25607
  pins = readSkillPins();
24478
25608
  } catch {
24479
- console.error(chalk25.red("\n\u274C Pin file is corrupt."));
24480
- console.error(chalk25.yellow(" Run: node9 skill pin reset\n"));
25609
+ console.error(chalk28.red("\n\u274C Pin file is corrupt."));
25610
+ console.error(chalk28.yellow(" Run: node9 skill pin reset\n"));
24481
25611
  process.exit(1);
24482
25612
  }
24483
25613
  if (!pins.roots[rootKey]) {
24484
- console.error(chalk25.red(`
25614
+ console.error(chalk28.red(`
24485
25615
  \u274C No pin found for root key "${rootKey}"
24486
25616
  `));
24487
- console.error(`Run ${chalk25.cyan("node9 skill pin list")} to see pinned roots.
25617
+ console.error(`Run ${chalk28.cyan("node9 skill pin list")} to see pinned roots.
24488
25618
  `);
24489
25619
  process.exit(1);
24490
25620
  }
24491
25621
  const rootPath = pins.roots[rootKey].rootPath;
24492
25622
  removePin2(rootKey);
24493
25623
  wipeSkillSessions();
24494
- console.log(chalk25.green(`
24495
- \u{1F513} Pin removed for ${chalk25.cyan(rootKey)}`));
24496
- console.log(chalk25.gray(` ${rootPath}`));
24497
- console.log(chalk25.gray(" Next session will re-pin with current state.\n"));
25624
+ console.log(chalk28.green(`
25625
+ \u{1F513} Pin removed for ${chalk28.cyan(rootKey)}`));
25626
+ console.log(chalk28.gray(` ${rootPath}`));
25627
+ console.log(chalk28.gray(" Next session will re-pin with current state.\n"));
24498
25628
  });
24499
25629
  pinSubCmd.command("reset").description("Clear all skill pins and wipe session verification flags").action(() => {
24500
25630
  const result = readSkillPinsSafe();
24501
25631
  if (!result.ok && result.reason === "missing") {
24502
25632
  wipeSkillSessions();
24503
- console.log(chalk25.gray("\nNo pins to clear.\n"));
25633
+ console.log(chalk28.gray("\nNo pins to clear.\n"));
24504
25634
  return;
24505
25635
  }
24506
25636
  const count = result.ok ? Object.keys(result.pins.roots).length : "?";
24507
25637
  clearAllPins2();
24508
25638
  wipeSkillSessions();
24509
- console.log(chalk25.green(`
25639
+ console.log(chalk28.green(`
24510
25640
  \u{1F513} Cleared ${count} skill pin(s).`));
24511
- console.log(chalk25.gray(" Next session will re-pin with current state.\n"));
25641
+ console.log(chalk28.gray(" Next session will re-pin with current state.\n"));
24512
25642
  });
24513
25643
  }
24514
25644
 
24515
25645
  // src/cli/commands/decisions.ts
24516
- import fs48 from "fs";
24517
- import os43 from "os";
24518
- import path49 from "path";
24519
- import chalk26 from "chalk";
24520
- var DECISIONS_FILE2 = path49.join(os43.homedir(), ".node9", "decisions.json");
25646
+ import fs53 from "fs";
25647
+ import os48 from "os";
25648
+ import path52 from "path";
25649
+ import chalk29 from "chalk";
25650
+ var DECISIONS_FILE2 = path52.join(os48.homedir(), ".node9", "decisions.json");
24521
25651
  function readDecisions() {
24522
25652
  try {
24523
- if (!fs48.existsSync(DECISIONS_FILE2)) return {};
24524
- const raw = fs48.readFileSync(DECISIONS_FILE2, "utf-8");
25653
+ if (!fs53.existsSync(DECISIONS_FILE2)) return {};
25654
+ const raw = fs53.readFileSync(DECISIONS_FILE2, "utf-8");
24525
25655
  const parsed = JSON.parse(raw);
24526
25656
  const out = {};
24527
25657
  for (const [k, v] of Object.entries(parsed)) {
@@ -24533,11 +25663,11 @@ function readDecisions() {
24533
25663
  }
24534
25664
  }
24535
25665
  function writeDecisions(d) {
24536
- const dir = path49.dirname(DECISIONS_FILE2);
24537
- if (!fs48.existsSync(dir)) fs48.mkdirSync(dir, { recursive: true });
25666
+ const dir = path52.dirname(DECISIONS_FILE2);
25667
+ if (!fs53.existsSync(dir)) fs53.mkdirSync(dir, { recursive: true });
24538
25668
  const tmp = `${DECISIONS_FILE2}.${process.pid}.tmp`;
24539
- fs48.writeFileSync(tmp, JSON.stringify(d, null, 2));
24540
- fs48.renameSync(tmp, DECISIONS_FILE2);
25669
+ fs53.writeFileSync(tmp, JSON.stringify(d, null, 2));
25670
+ fs53.renameSync(tmp, DECISIONS_FILE2);
24541
25671
  }
24542
25672
  function registerDecisionsCommand(program2) {
24543
25673
  const cmd = program2.command("decisions").description('Manage persistent "Always Allow" / "Always Deny" tool decisions');
@@ -24545,67 +25675,67 @@ function registerDecisionsCommand(program2) {
24545
25675
  const decisions = readDecisions();
24546
25676
  const entries = Object.entries(decisions);
24547
25677
  if (entries.length === 0) {
24548
- console.log(chalk26.gray(" No persistent decisions stored."));
25678
+ console.log(chalk29.gray(" No persistent decisions stored."));
24549
25679
  console.log(
24550
- chalk26.gray(` File: ${DECISIONS_FILE2}
24551
- `) + chalk26.gray(' Decisions are written when you click "Always Allow" or')
25680
+ chalk29.gray(` File: ${DECISIONS_FILE2}
25681
+ `) + chalk29.gray(' Decisions are written when you click "Always Allow" or')
24552
25682
  );
24553
- console.log(chalk26.gray(' "Always Deny" in node9 tail or the native popup.'));
25683
+ console.log(chalk29.gray(' "Always Deny" in node9 tail or the native popup.'));
24554
25684
  return;
24555
25685
  }
24556
- console.log(chalk26.bold(`
25686
+ console.log(chalk29.bold(`
24557
25687
  Persistent decisions (${entries.length})
24558
25688
  `));
24559
25689
  const w = Math.max(...entries.map(([k]) => k.length));
24560
25690
  for (const [tool, verdict] of entries.sort()) {
24561
- const colored = verdict === "allow" ? chalk26.green(verdict) : chalk26.red(verdict);
25691
+ const colored = verdict === "allow" ? chalk29.green(verdict) : chalk29.red(verdict);
24562
25692
  console.log(` ${tool.padEnd(w)} ${colored}`);
24563
25693
  }
24564
25694
  console.log(
24565
- chalk26.gray(`
25695
+ chalk29.gray(`
24566
25696
  Stored in ${DECISIONS_FILE2}
24567
- `) + chalk26.gray(" Run `node9 decisions clear <tool>` to remove an entry.")
25697
+ `) + chalk29.gray(" Run `node9 decisions clear <tool>` to remove an entry.")
24568
25698
  );
24569
25699
  });
24570
25700
  cmd.command("clear <toolName>").description("Remove a persistent decision for one tool").action((toolName) => {
24571
25701
  const decisions = readDecisions();
24572
25702
  if (!(toolName in decisions)) {
24573
- console.log(chalk26.yellow(` No persistent decision for "${toolName}". Nothing to clear.`));
25703
+ console.log(chalk29.yellow(` No persistent decision for "${toolName}". Nothing to clear.`));
24574
25704
  process.exitCode = 1;
24575
25705
  return;
24576
25706
  }
24577
25707
  delete decisions[toolName];
24578
25708
  writeDecisions(decisions);
24579
- console.log(chalk26.green(` \u2713 Cleared persistent decision for "${toolName}".`));
25709
+ console.log(chalk29.green(` \u2713 Cleared persistent decision for "${toolName}".`));
24580
25710
  });
24581
25711
  cmd.command("clear-all").description("Remove every persistent decision (irreversible)").action(() => {
24582
25712
  const decisions = readDecisions();
24583
25713
  const count = Object.keys(decisions).length;
24584
25714
  if (count === 0) {
24585
- console.log(chalk26.gray(" Nothing to clear \u2014 no persistent decisions stored."));
25715
+ console.log(chalk29.gray(" Nothing to clear \u2014 no persistent decisions stored."));
24586
25716
  return;
24587
25717
  }
24588
25718
  writeDecisions({});
24589
25719
  console.log(
24590
- chalk26.green(` \u2713 Cleared ${count} persistent decision${count === 1 ? "" : "s"}.`)
25720
+ chalk29.green(` \u2713 Cleared ${count} persistent decision${count === 1 ? "" : "s"}.`)
24591
25721
  );
24592
25722
  });
24593
25723
  }
24594
25724
 
24595
25725
  // src/cli/commands/dlp.ts
24596
- import chalk27 from "chalk";
24597
- import fs49 from "fs";
24598
- import path50 from "path";
24599
- import os44 from "os";
24600
- var AUDIT_LOG = path50.join(os44.homedir(), ".node9", "audit.log");
24601
- var RESOLVED_FILE = path50.join(os44.homedir(), ".node9", "dlp-resolved.json");
25726
+ import chalk30 from "chalk";
25727
+ import fs54 from "fs";
25728
+ import path53 from "path";
25729
+ import os49 from "os";
25730
+ var AUDIT_LOG = path53.join(os49.homedir(), ".node9", "audit.log");
25731
+ var RESOLVED_FILE = path53.join(os49.homedir(), ".node9", "dlp-resolved.json");
24602
25732
  var ANSI_RE = /\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g;
24603
25733
  function stripAnsi(s) {
24604
25734
  return s.replace(ANSI_RE, "");
24605
25735
  }
24606
25736
  function loadResolved() {
24607
25737
  try {
24608
- const raw = JSON.parse(fs49.readFileSync(RESOLVED_FILE, "utf-8"));
25738
+ const raw = JSON.parse(fs54.readFileSync(RESOLVED_FILE, "utf-8"));
24609
25739
  return new Set(raw);
24610
25740
  } catch {
24611
25741
  return /* @__PURE__ */ new Set();
@@ -24613,13 +25743,13 @@ function loadResolved() {
24613
25743
  }
24614
25744
  function saveResolved(resolved) {
24615
25745
  try {
24616
- fs49.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
25746
+ fs54.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
24617
25747
  } catch {
24618
25748
  }
24619
25749
  }
24620
25750
  function loadDlpFindings() {
24621
- if (!fs49.existsSync(AUDIT_LOG)) return [];
24622
- return fs49.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
25751
+ if (!fs54.existsSync(AUDIT_LOG)) return [];
25752
+ return fs54.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
24623
25753
  if (!line.trim()) return [];
24624
25754
  try {
24625
25755
  const e = JSON.parse(line);
@@ -24648,14 +25778,14 @@ function registerDlpCommand(program2) {
24648
25778
  cmd.command("resolve").description("Mark all current DLP findings as resolved").action(() => {
24649
25779
  const findings = loadDlpFindings();
24650
25780
  if (findings.length === 0) {
24651
- console.log(chalk27.green("\n \u2705 No response-DLP findings to resolve.\n"));
25781
+ console.log(chalk30.green("\n \u2705 No response-DLP findings to resolve.\n"));
24652
25782
  return;
24653
25783
  }
24654
25784
  const resolved = loadResolved();
24655
25785
  for (const e of findings) resolved.add(entryKey2(e));
24656
25786
  saveResolved(resolved);
24657
25787
  console.log(
24658
- chalk27.green(
25788
+ chalk30.green(
24659
25789
  `
24660
25790
  \u2705 ${findings.length} finding${findings.length !== 1 ? "s" : ""} marked as resolved.
24661
25791
  `
@@ -24669,47 +25799,47 @@ function registerDlpCommand(program2) {
24669
25799
  const resolvedCount = findings.length - open.length;
24670
25800
  console.log("");
24671
25801
  console.log(
24672
- chalk27.bold.cyan("\u{1F510} node9 dlp") + chalk27.dim(" \u2014 secrets found in Claude response text")
25802
+ chalk30.bold.cyan("\u{1F510} node9 dlp") + chalk30.dim(" \u2014 secrets found in Claude response text")
24673
25803
  );
24674
25804
  console.log("");
24675
25805
  if (open.length === 0) {
24676
25806
  if (resolvedCount > 0) {
24677
- console.log(chalk27.green(` \u2705 No open findings \xB7 ${resolvedCount} previously resolved`));
25807
+ console.log(chalk30.green(` \u2705 No open findings \xB7 ${resolvedCount} previously resolved`));
24678
25808
  } else {
24679
25809
  console.log(
24680
- chalk27.green(" \u2705 No findings \u2014 Claude has not leaked secrets in response text")
25810
+ chalk30.green(" \u2705 No findings \u2014 Claude has not leaked secrets in response text")
24681
25811
  );
24682
25812
  }
24683
25813
  console.log("");
24684
25814
  return;
24685
25815
  }
24686
25816
  console.log(
24687
- chalk27.bgRed.white.bold(` \u26A0\uFE0F ${open.length} open finding${open.length !== 1 ? "s" : ""} `) + chalk27.dim(resolvedCount > 0 ? ` (${resolvedCount} resolved)` : "")
25817
+ chalk30.bgRed.white.bold(` \u26A0\uFE0F ${open.length} open finding${open.length !== 1 ? "s" : ""} `) + chalk30.dim(resolvedCount > 0 ? ` (${resolvedCount} resolved)` : "")
24688
25818
  );
24689
25819
  console.log("");
24690
25820
  console.log(
24691
- chalk27.dim(" These secrets were included in Claude's response text \u2014 NOT blocked.")
25821
+ chalk30.dim(" These secrets were included in Claude's response text \u2014 NOT blocked.")
24692
25822
  );
24693
- console.log(chalk27.dim(" Rotate each affected key immediately.\n"));
25823
+ console.log(chalk30.dim(" Rotate each affected key immediately.\n"));
24694
25824
  for (const e of open) {
24695
25825
  console.log(
24696
- " " + chalk27.red("\u25CF") + " " + chalk27.white(e.dlpPattern ?? "Secret") + chalk27.dim(" " + fmtDate3(e.ts))
25826
+ " " + chalk30.red("\u25CF") + " " + chalk30.white(e.dlpPattern ?? "Secret") + chalk30.dim(" " + fmtDate3(e.ts))
24697
25827
  );
24698
25828
  if (e.dlpSample) {
24699
- console.log(" " + chalk27.dim("Sample: ") + chalk27.yellow(stripAnsi(e.dlpSample)));
25829
+ console.log(" " + chalk30.dim("Sample: ") + chalk30.yellow(stripAnsi(e.dlpSample)));
24700
25830
  }
24701
25831
  if (e.project) {
24702
- console.log(" " + chalk27.dim("Project: ") + chalk27.dim(stripAnsi(e.project)));
25832
+ console.log(" " + chalk30.dim("Project: ") + chalk30.dim(stripAnsi(e.project)));
24703
25833
  }
24704
25834
  console.log("");
24705
25835
  }
24706
- console.log(" " + chalk27.bold("Next steps:"));
24707
- console.log(" " + chalk27.cyan("1.") + " Rotate any exposed keys shown above");
25836
+ console.log(" " + chalk30.bold("Next steps:"));
25837
+ console.log(" " + chalk30.cyan("1.") + " Rotate any exposed keys shown above");
24708
25838
  console.log(
24709
- " " + chalk27.cyan("2.") + " Run " + chalk27.white("node9 dlp resolve") + " to acknowledge"
25839
+ " " + chalk30.cyan("2.") + " Run " + chalk30.white("node9 dlp resolve") + " to acknowledge"
24710
25840
  );
24711
25841
  console.log(
24712
- " " + chalk27.cyan("3.") + " Run " + chalk27.white("node9 report") + " for full audit history"
25842
+ " " + chalk30.cyan("3.") + " Run " + chalk30.white("node9 report") + " for full audit history"
24713
25843
  );
24714
25844
  console.log("");
24715
25845
  });
@@ -24717,15 +25847,15 @@ function registerDlpCommand(program2) {
24717
25847
 
24718
25848
  // src/cli/commands/mask.ts
24719
25849
  init_dlp();
24720
- import chalk28 from "chalk";
24721
- import fs50 from "fs";
24722
- import path51 from "path";
24723
- import os45 from "os";
25850
+ import chalk31 from "chalk";
25851
+ import fs55 from "fs";
25852
+ import path54 from "path";
25853
+ import os50 from "os";
24724
25854
  function findJsonlFiles(dir) {
24725
25855
  const results = [];
24726
- if (!fs50.existsSync(dir)) return results;
24727
- for (const entry of fs50.readdirSync(dir, { withFileTypes: true })) {
24728
- const full = path51.join(dir, entry.name);
25856
+ if (!fs55.existsSync(dir)) return results;
25857
+ for (const entry of fs55.readdirSync(dir, { withFileTypes: true })) {
25858
+ const full = path54.join(dir, entry.name);
24729
25859
  if (entry.isDirectory()) results.push(...findJsonlFiles(full));
24730
25860
  else if (entry.isFile() && entry.name.endsWith(".jsonl")) results.push(full);
24731
25861
  }
@@ -24768,7 +25898,7 @@ function redactJson(obj) {
24768
25898
  function processFile(filePath, dryRun) {
24769
25899
  let raw;
24770
25900
  try {
24771
- raw = fs50.readFileSync(filePath, "utf-8");
25901
+ raw = fs55.readFileSync(filePath, "utf-8");
24772
25902
  } catch {
24773
25903
  return { redactedLines: 0, patterns: [] };
24774
25904
  }
@@ -24800,14 +25930,14 @@ function processFile(filePath, dryRun) {
24800
25930
  }
24801
25931
  }
24802
25932
  if (!dryRun && redactedLines > 0) {
24803
- fs50.writeFileSync(filePath, newLines.join("\n"), "utf-8");
25933
+ fs55.writeFileSync(filePath, newLines.join("\n"), "utf-8");
24804
25934
  }
24805
25935
  return { redactedLines, patterns };
24806
25936
  }
24807
25937
  function processJsonFile(filePath, dryRun) {
24808
25938
  let raw;
24809
25939
  try {
24810
- raw = fs50.readFileSync(filePath, "utf-8");
25940
+ raw = fs55.readFileSync(filePath, "utf-8");
24811
25941
  } catch {
24812
25942
  return { redactedLines: 0, patterns: [] };
24813
25943
  }
@@ -24820,15 +25950,15 @@ function processJsonFile(filePath, dryRun) {
24820
25950
  const { value, modified, found } = redactJson(parsed);
24821
25951
  if (!modified) return { redactedLines: 0, patterns: [] };
24822
25952
  if (!dryRun) {
24823
- fs50.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
25953
+ fs55.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
24824
25954
  }
24825
25955
  return { redactedLines: 1, patterns: found };
24826
25956
  }
24827
25957
  function findJsonFiles(dir) {
24828
25958
  const results = [];
24829
- if (!fs50.existsSync(dir)) return results;
24830
- for (const entry of fs50.readdirSync(dir, { withFileTypes: true })) {
24831
- const full = path51.join(dir, entry.name);
25959
+ if (!fs55.existsSync(dir)) return results;
25960
+ for (const entry of fs55.readdirSync(dir, { withFileTypes: true })) {
25961
+ const full = path54.join(dir, entry.name);
24832
25962
  if (entry.isDirectory()) results.push(...findJsonFiles(full));
24833
25963
  else if (entry.isFile() && entry.name.endsWith(".json")) results.push(full);
24834
25964
  }
@@ -24837,9 +25967,9 @@ function findJsonFiles(dir) {
24837
25967
  function registerMaskCommand(program2) {
24838
25968
  program2.command("mask").description("Redact plaintext secrets from local AI session history files").option("--dry-run", "show what would be redacted without making changes").option("--all", "scan all history (default: last 30 days)").action(async (options) => {
24839
25969
  const dryRun = !!options.dryRun;
24840
- const home = os45.homedir();
24841
- const claudeDir = path51.join(home, ".claude", "projects");
24842
- const geminiDir = path51.join(home, ".gemini", "tmp");
25970
+ const home = os50.homedir();
25971
+ const claudeDir = path54.join(home, ".claude", "projects");
25972
+ const geminiDir = path54.join(home, ".gemini", "tmp");
24843
25973
  const allFiles = [
24844
25974
  ...findJsonlFiles(claudeDir).map((p) => ({ path: p, type: "jsonl" })),
24845
25975
  ...findJsonFiles(geminiDir).map((p) => ({ path: p, type: "json" }))
@@ -24847,18 +25977,18 @@ function registerMaskCommand(program2) {
24847
25977
  const cutoff = options.all ? null : new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3);
24848
25978
  const filtered = cutoff ? allFiles.filter((f) => {
24849
25979
  try {
24850
- return fs50.statSync(f.path).mtime >= cutoff;
25980
+ return fs55.statSync(f.path).mtime >= cutoff;
24851
25981
  } catch {
24852
25982
  return false;
24853
25983
  }
24854
25984
  }) : allFiles;
24855
25985
  if (filtered.length === 0) {
24856
- console.log(chalk28.yellow(" No session files found."));
25986
+ console.log(chalk31.yellow(" No session files found."));
24857
25987
  return;
24858
25988
  }
24859
25989
  console.log("");
24860
25990
  if (dryRun) {
24861
- console.log(chalk28.dim(" Dry run \u2014 no files will be modified.\n"));
25991
+ console.log(chalk31.dim(" Dry run \u2014 no files will be modified.\n"));
24862
25992
  }
24863
25993
  let totalFiles = 0;
24864
25994
  let totalLines = 0;
@@ -24874,23 +26004,23 @@ function registerMaskCommand(program2) {
24874
26004
  });
24875
26005
  const verb = dryRun ? "Would redact" : "Redacted";
24876
26006
  console.log(
24877
- " " + chalk28.dim(shortPath.slice(0, 60).padEnd(62)) + chalk28.red(`${verb}: `) + chalk28.yellow(patterns.join(", ")) + chalk28.dim(` (${redactedLines} line${redactedLines !== 1 ? "s" : ""})`)
26007
+ " " + chalk31.dim(shortPath.slice(0, 60).padEnd(62)) + chalk31.red(`${verb}: `) + chalk31.yellow(patterns.join(", ")) + chalk31.dim(` (${redactedLines} line${redactedLines !== 1 ? "s" : ""})`)
24878
26008
  );
24879
26009
  }
24880
26010
  }
24881
26011
  console.log("");
24882
26012
  if (totalFiles === 0) {
24883
- console.log(chalk28.green(" No secrets found in session history."));
26013
+ console.log(chalk31.green(" No secrets found in session history."));
24884
26014
  } else {
24885
26015
  const verb = dryRun ? "would be modified" : "modified";
24886
26016
  console.log(
24887
- chalk28.bold(` ${totalFiles} file${totalFiles !== 1 ? "s" : ""} ${verb}`) + chalk28.dim(`, ${totalLines} line${totalLines !== 1 ? "s" : ""} redacted`)
26017
+ chalk31.bold(` ${totalFiles} file${totalFiles !== 1 ? "s" : ""} ${verb}`) + chalk31.dim(`, ${totalLines} line${totalLines !== 1 ? "s" : ""} redacted`)
24888
26018
  );
24889
- console.log(" Patterns: " + chalk28.yellow(totalPatterns.join(", ")));
26019
+ console.log(" Patterns: " + chalk31.yellow(totalPatterns.join(", ")));
24890
26020
  if (!dryRun) {
24891
26021
  console.log("");
24892
26022
  console.log(
24893
- chalk28.dim(
26023
+ chalk31.dim(
24894
26024
  " Note: secrets were already sent to the AI provider during the active session.\n This cleans your local disk only. Rotate any exposed keys."
24895
26025
  )
24896
26026
  );
@@ -24903,20 +26033,20 @@ function registerMaskCommand(program2) {
24903
26033
  // src/cli.ts
24904
26034
  init_blast();
24905
26035
  var { version } = JSON.parse(
24906
- fs53.readFileSync(path54.join(__dirname, "../package.json"), "utf-8")
26036
+ fs58.readFileSync(path57.join(__dirname, "../package.json"), "utf-8")
24907
26037
  );
24908
26038
  var program = new Command();
24909
26039
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
24910
26040
  program.command("login").argument("<apiKey>").option("--local", "Save key for audit/logging only \u2014 local config still controls all decisions").option("--profile <name>", 'Save as a named profile (default: "default")').action((apiKey, options) => {
24911
26041
  const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
24912
- const credPath = path54.join(os48.homedir(), ".node9", "credentials.json");
24913
- if (!fs53.existsSync(path54.dirname(credPath)))
24914
- fs53.mkdirSync(path54.dirname(credPath), { recursive: true });
26042
+ const credPath = path57.join(os53.homedir(), ".node9", "credentials.json");
26043
+ if (!fs58.existsSync(path57.dirname(credPath)))
26044
+ fs58.mkdirSync(path57.dirname(credPath), { recursive: true });
24915
26045
  const profileName = options.profile || "default";
24916
26046
  let existingCreds = {};
24917
26047
  try {
24918
- if (fs53.existsSync(credPath)) {
24919
- const raw = JSON.parse(fs53.readFileSync(credPath, "utf-8"));
26048
+ if (fs58.existsSync(credPath)) {
26049
+ const raw = JSON.parse(fs58.readFileSync(credPath, "utf-8"));
24920
26050
  if (raw.apiKey) {
24921
26051
  existingCreds = {
24922
26052
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
@@ -24928,14 +26058,14 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
24928
26058
  } catch {
24929
26059
  }
24930
26060
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
24931
- fs53.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
26061
+ fs58.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
24932
26062
  let effectiveCloud = null;
24933
26063
  if (profileName === "default") {
24934
- const configPath = path54.join(os48.homedir(), ".node9", "config.json");
26064
+ const configPath2 = path57.join(os53.homedir(), ".node9", "config.json");
24935
26065
  let config = {};
24936
26066
  try {
24937
- if (fs53.existsSync(configPath))
24938
- config = JSON.parse(fs53.readFileSync(configPath, "utf-8"));
26067
+ if (fs58.existsSync(configPath2))
26068
+ config = JSON.parse(fs58.readFileSync(configPath2, "utf-8"));
24939
26069
  } catch {
24940
26070
  }
24941
26071
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -24950,28 +26080,28 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
24950
26080
  approvers.cloud = false;
24951
26081
  }
24952
26082
  s.approvers = approvers;
24953
- if (!fs53.existsSync(path54.dirname(configPath)))
24954
- fs53.mkdirSync(path54.dirname(configPath), { recursive: true });
24955
- fs53.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
26083
+ if (!fs58.existsSync(path57.dirname(configPath2)))
26084
+ fs58.mkdirSync(path57.dirname(configPath2), { recursive: true });
26085
+ fs58.writeFileSync(configPath2, JSON.stringify(config, null, 2), { mode: 384 });
24956
26086
  effectiveCloud = approvers.cloud === true;
24957
26087
  }
24958
26088
  if (options.profile && profileName !== "default") {
24959
- console.log(chalk30.green(`\u2705 Profile "${profileName}" saved`));
24960
- console.log(chalk30.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
26089
+ console.log(chalk33.green(`\u2705 Profile "${profileName}" saved`));
26090
+ console.log(chalk33.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
24961
26091
  } else if (options.local || effectiveCloud === false) {
24962
- console.log(chalk30.green(`\u2705 Key saved \u2014 Privacy mode \u{1F6E1}\uFE0F`));
24963
- console.log(chalk30.gray(` All decisions stay on this machine. Nothing syncs to the cloud.`));
26092
+ console.log(chalk33.green(`\u2705 Key saved \u2014 Privacy mode \u{1F6E1}\uFE0F`));
26093
+ console.log(chalk33.gray(` All decisions stay on this machine. Nothing syncs to the cloud.`));
24964
26094
  if (!options.local) {
24965
26095
  console.log(
24966
- chalk30.yellow(` Your config has cloud approvals OFF (settings.approvers.cloud).`)
26096
+ chalk33.yellow(` Your config has cloud approvals OFF (settings.approvers.cloud).`)
24967
26097
  );
24968
26098
  console.log(
24969
- chalk30.gray(` To enable team policy + dashboard sync: set it to true, or re-init.`)
26099
+ chalk33.gray(` To enable team policy + dashboard sync: set it to true, or re-init.`)
24970
26100
  );
24971
26101
  }
24972
26102
  } else {
24973
- console.log(chalk30.green(`\u2705 Logged in \u2014 agent mode`));
24974
- console.log(chalk30.gray(` Team policy enforced for all calls via Node9 cloud.`));
26103
+ console.log(chalk33.green(`\u2705 Logged in \u2014 agent mode`));
26104
+ console.log(chalk33.gray(` Team policy enforced for all calls via Node9 cloud.`));
24975
26105
  }
24976
26106
  });
24977
26107
  program.command("addto", { hidden: true }).description("Integrate Node9 with an AI agent").addHelpText(
@@ -24992,7 +26122,7 @@ program.command("addto", { hidden: true }).description("Integrate Node9 with an
24992
26122
  if (target === "hermes") return setupHermes();
24993
26123
  if (target === "hud") return setupHud();
24994
26124
  console.error(
24995
- chalk30.red(
26125
+ chalk33.red(
24996
26126
  `Unknown target: "${target}". Supported: claude, antigravity, copilot, gemini, cursor, codex, windsurf, vscode, hermes, hud`
24997
26127
  )
24998
26128
  );
@@ -25006,20 +26136,20 @@ program.command("setup", { hidden: true }).description('Alias for "addto" \u2014
25006
26136
  "The agent to protect: claude | antigravity | copilot | gemini | cursor | codex | windsurf | vscode | hud"
25007
26137
  ).action(async (target) => {
25008
26138
  if (!target) {
25009
- console.log(chalk30.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
25010
- console.log(" Usage: " + chalk30.white("node9 setup <target>") + "\n");
26139
+ console.log(chalk33.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
26140
+ console.log(" Usage: " + chalk33.white("node9 setup <target>") + "\n");
25011
26141
  console.log(" Targets:");
25012
- console.log(" " + chalk30.green("claude") + " \u2014 Claude Code (hook mode)");
25013
- console.log(" " + chalk30.green("gemini") + " \u2014 Gemini CLI (hook mode)");
25014
- console.log(" " + chalk30.green("antigravity") + " \u2014 Antigravity / agy (hook mode)");
25015
- console.log(" " + chalk30.green("copilot") + " \u2014 GitHub Copilot CLI (hook mode)");
25016
- console.log(" " + chalk30.green("cursor") + " \u2014 Cursor (MCP proxy)");
25017
- console.log(" " + chalk30.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
25018
- console.log(" " + chalk30.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
25019
- console.log(" " + chalk30.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
25020
- console.log(" " + chalk30.green("hermes") + " \u2014 Hermes Agent (hook mode)");
26142
+ console.log(" " + chalk33.green("claude") + " \u2014 Claude Code (hook mode)");
26143
+ console.log(" " + chalk33.green("gemini") + " \u2014 Gemini CLI (hook mode)");
26144
+ console.log(" " + chalk33.green("antigravity") + " \u2014 Antigravity / agy (hook mode)");
26145
+ console.log(" " + chalk33.green("copilot") + " \u2014 GitHub Copilot CLI (hook mode)");
26146
+ console.log(" " + chalk33.green("cursor") + " \u2014 Cursor (MCP proxy)");
26147
+ console.log(" " + chalk33.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
26148
+ console.log(" " + chalk33.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
26149
+ console.log(" " + chalk33.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
26150
+ console.log(" " + chalk33.green("hermes") + " \u2014 Hermes Agent (hook mode)");
25021
26151
  process.stdout.write(
25022
- " " + chalk30.green("hud") + " \u2014 Claude Code security statusline\n"
26152
+ " " + chalk33.green("hud") + " \u2014 Claude Code security statusline\n"
25023
26153
  );
25024
26154
  console.log("");
25025
26155
  return;
@@ -25036,7 +26166,7 @@ program.command("setup", { hidden: true }).description('Alias for "addto" \u2014
25036
26166
  if (t === "hermes") return setupHermes();
25037
26167
  if (t === "hud") return setupHud();
25038
26168
  console.error(
25039
- chalk30.red(
26169
+ chalk33.red(
25040
26170
  `Unknown target: "${target}". Supported: claude, antigravity, copilot, gemini, cursor, codex, windsurf, vscode, hermes, hud`
25041
26171
  )
25042
26172
  );
@@ -25062,35 +26192,35 @@ program.command("removefrom", { hidden: true }).description("Remove Node9 hooks
25062
26192
  else if (target === "hud") fn = teardownHud;
25063
26193
  else {
25064
26194
  console.error(
25065
- chalk30.red(
26195
+ chalk33.red(
25066
26196
  `Unknown target: "${target}". Supported: claude, antigravity, copilot, gemini, cursor, codex, windsurf, vscode, hermes, hud`
25067
26197
  )
25068
26198
  );
25069
26199
  process.exit(1);
25070
26200
  }
25071
- console.log(chalk30.cyan(`
26201
+ console.log(chalk33.cyan(`
25072
26202
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
25073
26203
  `));
25074
26204
  try {
25075
26205
  fn();
25076
26206
  } catch (err2) {
25077
- console.error(chalk30.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
26207
+ console.error(chalk33.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
25078
26208
  process.exit(1);
25079
26209
  }
25080
- console.log(chalk30.gray("\n Restart the agent for changes to take effect."));
26210
+ console.log(chalk33.gray("\n Restart the agent for changes to take effect."));
25081
26211
  });
25082
26212
  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) => {
25083
- console.log(chalk30.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
25084
- console.log(chalk30.bold("Stopping daemon..."));
26213
+ console.log(chalk33.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
26214
+ console.log(chalk33.bold("Stopping daemon..."));
25085
26215
  try {
25086
26216
  stopDaemon();
25087
- console.log(chalk30.green(" \u2705 Daemon stopped"));
26217
+ console.log(chalk33.green(" \u2705 Daemon stopped"));
25088
26218
  } catch {
25089
- console.log(chalk30.blue(" \u2139\uFE0F Daemon was not running"));
26219
+ console.log(chalk33.blue(" \u2139\uFE0F Daemon was not running"));
25090
26220
  }
25091
- console.log(chalk30.bold("\nRemoving hooks..."));
26221
+ console.log(chalk33.bold("\nRemoving hooks..."));
25092
26222
  let teardownFailed = false;
25093
- for (const [label, fn] of [
26223
+ for (const [label2, fn] of [
25094
26224
  ["Claude", teardownClaude],
25095
26225
  ["Gemini", teardownGemini],
25096
26226
  ["Cursor", teardownCursor],
@@ -25104,45 +26234,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
25104
26234
  } catch (err2) {
25105
26235
  teardownFailed = true;
25106
26236
  console.error(
25107
- chalk30.red(
25108
- ` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
26237
+ chalk33.red(
26238
+ ` \u26A0\uFE0F Failed to remove ${label2} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
25109
26239
  )
25110
26240
  );
25111
26241
  }
25112
26242
  }
25113
26243
  if (options.purge) {
25114
- const node9Dir = path54.join(os48.homedir(), ".node9");
25115
- if (fs53.existsSync(node9Dir)) {
26244
+ const node9Dir = path57.join(os53.homedir(), ".node9");
26245
+ if (fs58.existsSync(node9Dir)) {
25116
26246
  const confirmed = await confirm2({
25117
26247
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
25118
26248
  default: false
25119
26249
  });
25120
26250
  if (confirmed) {
25121
- fs53.rmSync(node9Dir, { recursive: true });
25122
- if (fs53.existsSync(node9Dir)) {
26251
+ fs58.rmSync(node9Dir, { recursive: true });
26252
+ if (fs58.existsSync(node9Dir)) {
25123
26253
  console.error(
25124
- chalk30.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
26254
+ chalk33.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
25125
26255
  );
25126
26256
  } else {
25127
- console.log(chalk30.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
26257
+ console.log(chalk33.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
25128
26258
  }
25129
26259
  } else {
25130
- console.log(chalk30.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
26260
+ console.log(chalk33.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
25131
26261
  }
25132
26262
  } else {
25133
- console.log(chalk30.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
26263
+ console.log(chalk33.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
25134
26264
  }
25135
26265
  } else {
25136
26266
  console.log(
25137
- chalk30.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
26267
+ chalk33.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
25138
26268
  );
25139
26269
  }
25140
26270
  if (teardownFailed) {
25141
- console.error(chalk30.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
26271
+ console.error(chalk33.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
25142
26272
  process.exit(1);
25143
26273
  }
25144
- console.log(chalk30.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
25145
- console.log(chalk30.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
26274
+ console.log(chalk33.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g node9-ai"));
26275
+ console.log(chalk33.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
25146
26276
  });
25147
26277
  registerDoctorCommand(program, version);
25148
26278
  program.command("explain").description(
@@ -25155,7 +26285,7 @@ program.command("explain").description(
25155
26285
  try {
25156
26286
  args = JSON.parse(trimmed);
25157
26287
  } catch {
25158
- console.error(chalk30.red(`
26288
+ console.error(chalk33.red(`
25159
26289
  \u274C Invalid JSON: ${trimmed}
25160
26290
  `));
25161
26291
  process.exit(1);
@@ -25166,54 +26296,54 @@ program.command("explain").description(
25166
26296
  }
25167
26297
  const result = await explainPolicy(tool, args);
25168
26298
  console.log("");
25169
- console.log(chalk30.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
26299
+ console.log(chalk33.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
25170
26300
  console.log("");
25171
- console.log(` ${chalk30.bold("Tool:")} ${chalk30.white(result.tool)}`);
26301
+ console.log(` ${chalk33.bold("Tool:")} ${chalk33.white(result.tool)}`);
25172
26302
  if (argsRaw) {
25173
26303
  const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
25174
- console.log(` ${chalk30.bold("Input:")} ${chalk30.gray(preview2)}`);
26304
+ console.log(` ${chalk33.bold("Input:")} ${chalk33.gray(preview2)}`);
25175
26305
  }
25176
26306
  console.log("");
25177
- console.log(chalk30.bold("Config Sources (Waterfall):"));
26307
+ console.log(chalk33.bold("Config Sources (Waterfall):"));
25178
26308
  for (const tier of result.waterfall) {
25179
- const num3 = chalk30.gray(` ${tier.tier}.`);
25180
- const label = tier.label.padEnd(16);
26309
+ const num3 = chalk33.gray(` ${tier.tier}.`);
26310
+ const label2 = tier.label.padEnd(16);
25181
26311
  let statusStr;
25182
26312
  if (tier.tier === 1) {
25183
- statusStr = chalk30.gray(tier.note ?? "");
26313
+ statusStr = chalk33.gray(tier.note ?? "");
25184
26314
  } else if (tier.status === "active") {
25185
- const loc = tier.path ? chalk30.gray(tier.path) : "";
25186
- const note = tier.note ? chalk30.gray(`(${tier.note})`) : "";
25187
- statusStr = chalk30.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
26315
+ const loc = tier.path ? chalk33.gray(tier.path) : "";
26316
+ const note = tier.note ? chalk33.gray(`(${tier.note})`) : "";
26317
+ statusStr = chalk33.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
25188
26318
  } else {
25189
- statusStr = chalk30.gray("\u25CB " + (tier.note ?? "not found"));
26319
+ statusStr = chalk33.gray("\u25CB " + (tier.note ?? "not found"));
25190
26320
  }
25191
- console.log(`${num3} ${chalk30.white(label)} ${statusStr}`);
26321
+ console.log(`${num3} ${chalk33.white(label2)} ${statusStr}`);
25192
26322
  }
25193
26323
  console.log("");
25194
- console.log(chalk30.bold("Policy Evaluation:"));
26324
+ console.log(chalk33.bold("Policy Evaluation:"));
25195
26325
  for (const step of result.steps) {
25196
26326
  const isFinal = step.isFinal;
25197
26327
  let icon;
25198
- if (step.outcome === "allow") icon = chalk30.green(" \u2705");
25199
- else if (step.outcome === "review") icon = chalk30.red(" \u{1F534}");
25200
- else if (step.outcome === "skip") icon = chalk30.gray(" \u2500 ");
25201
- else icon = chalk30.gray(" \u25CB ");
26328
+ if (step.outcome === "allow") icon = chalk33.green(" \u2705");
26329
+ else if (step.outcome === "review") icon = chalk33.red(" \u{1F534}");
26330
+ else if (step.outcome === "skip") icon = chalk33.gray(" \u2500 ");
26331
+ else icon = chalk33.gray(" \u25CB ");
25202
26332
  const name = step.name.padEnd(18);
25203
- const nameStr = isFinal ? chalk30.white.bold(name) : chalk30.white(name);
25204
- const detail = isFinal ? chalk30.white(step.detail) : chalk30.gray(step.detail);
25205
- const arrow = isFinal ? chalk30.yellow(" \u2190 STOP") : "";
26333
+ const nameStr = isFinal ? chalk33.white.bold(name) : chalk33.white(name);
26334
+ const detail = isFinal ? chalk33.white(step.detail) : chalk33.gray(step.detail);
26335
+ const arrow = isFinal ? chalk33.yellow(" \u2190 STOP") : "";
25206
26336
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
25207
26337
  }
25208
26338
  console.log("");
25209
26339
  if (result.decision === "allow") {
25210
- console.log(chalk30.green.bold(" Decision: \u2705 ALLOW") + chalk30.gray(" \u2014 no approval needed"));
26340
+ console.log(chalk33.green.bold(" Decision: \u2705 ALLOW") + chalk33.gray(" \u2014 no approval needed"));
25211
26341
  } else {
25212
26342
  console.log(
25213
- chalk30.red.bold(" Decision: \u{1F534} REVIEW") + chalk30.gray(" \u2014 human approval required")
26343
+ chalk33.red.bold(" Decision: \u{1F534} REVIEW") + chalk33.gray(" \u2014 human approval required")
25214
26344
  );
25215
26345
  if (result.blockedByLabel) {
25216
- console.log(chalk30.gray(` Reason: ${result.blockedByLabel}`));
26346
+ console.log(chalk33.gray(` Reason: ${result.blockedByLabel}`));
25217
26347
  }
25218
26348
  }
25219
26349
  console.log("");
@@ -25228,18 +26358,18 @@ program.command("tail").description("Stream live agent activity to the terminal"
25228
26358
  try {
25229
26359
  await startTail2(options);
25230
26360
  } catch (err2) {
25231
- console.error(chalk30.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
26361
+ console.error(chalk33.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
25232
26362
  process.exit(1);
25233
26363
  }
25234
26364
  });
25235
26365
  program.command("monitor").description("Live interactive dashboard \u2014 activity feed, approvals, security signals").action(async () => {
25236
26366
  try {
25237
- const dashboardPath = path54.join(__dirname, "dashboard.mjs");
26367
+ const dashboardPath = path57.join(__dirname, "dashboard.mjs");
25238
26368
  const dynamicImport = new Function("id", "return import(id)");
25239
26369
  const mod = await dynamicImport(`file://${dashboardPath}`);
25240
26370
  await mod.startMonitor();
25241
26371
  } catch (err2) {
25242
- console.error(chalk30.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
26372
+ console.error(chalk33.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
25243
26373
  process.exit(1);
25244
26374
  }
25245
26375
  });
@@ -25272,14 +26402,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
25272
26402
  Run "node9 addto claude" to register it as the statusLine.`
25273
26403
  ).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
25274
26404
  if (subcommand === "debug") {
25275
- const flagFile = path54.join(os48.homedir(), ".node9", "hud-debug");
26405
+ const flagFile = path57.join(os53.homedir(), ".node9", "hud-debug");
25276
26406
  if (state === "on") {
25277
- fs53.mkdirSync(path54.dirname(flagFile), { recursive: true });
25278
- fs53.writeFileSync(flagFile, "");
26407
+ fs58.mkdirSync(path57.dirname(flagFile), { recursive: true });
26408
+ fs58.writeFileSync(flagFile, "");
25279
26409
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
25280
26410
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
25281
26411
  } else if (state === "off") {
25282
- if (fs53.existsSync(flagFile)) fs53.unlinkSync(flagFile);
26412
+ if (fs58.existsSync(flagFile)) fs58.unlinkSync(flagFile);
25283
26413
  console.log("HUD debug logging disabled.");
25284
26414
  } else {
25285
26415
  console.error("Usage: node9 hud debug on|off");
@@ -25294,7 +26424,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
25294
26424
  const ms = parseDuration(options.duration);
25295
26425
  if (ms === null) {
25296
26426
  console.error(
25297
- chalk30.red(`
26427
+ chalk33.red(`
25298
26428
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
25299
26429
  `)
25300
26430
  );
@@ -25302,20 +26432,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
25302
26432
  }
25303
26433
  pauseNode9(ms, options.duration);
25304
26434
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
25305
- console.log(chalk30.yellow(`
26435
+ console.log(chalk33.yellow(`
25306
26436
  \u23F8 Node9 paused until ${expiresAt}`));
25307
- console.log(chalk30.gray(` All tool calls will be allowed without review.`));
25308
- console.log(chalk30.gray(` Run "node9 resume" to re-enable early.
26437
+ console.log(chalk33.gray(` All tool calls will be allowed without review.`));
26438
+ console.log(chalk33.gray(` Run "node9 resume" to re-enable early.
25309
26439
  `));
25310
26440
  });
25311
26441
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
25312
26442
  const { paused } = checkPause();
25313
26443
  if (!paused) {
25314
- console.log(chalk30.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
26444
+ console.log(chalk33.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
25315
26445
  return;
25316
26446
  }
25317
26447
  resumeNode9();
25318
- console.log(chalk30.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
26448
+ console.log(chalk33.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
25319
26449
  });
25320
26450
  var HOOK_BASED_AGENTS = {
25321
26451
  claude: "claude",
@@ -25331,15 +26461,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
25331
26461
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
25332
26462
  const target = HOOK_BASED_AGENTS[firstArg2];
25333
26463
  console.error(
25334
- chalk30.yellow(`
26464
+ chalk33.yellow(`
25335
26465
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
25336
26466
  );
25337
- console.error(chalk30.white(`
26467
+ console.error(chalk33.white(`
25338
26468
  "${target}" uses its own hook system. Use:`));
25339
26469
  console.error(
25340
- chalk30.green(` node9 addto ${target} `) + chalk30.gray("# one-time setup")
26470
+ chalk33.green(` node9 addto ${target} `) + chalk33.gray("# one-time setup")
25341
26471
  );
25342
- console.error(chalk30.green(` ${target} `) + chalk30.gray("# run normally"));
26472
+ console.error(chalk33.green(` ${target} `) + chalk33.gray("# run normally"));
25343
26473
  process.exit(1);
25344
26474
  }
25345
26475
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -25356,7 +26486,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
25356
26486
  }
25357
26487
  );
25358
26488
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
25359
- console.error(chalk30.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
26489
+ console.error(chalk33.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
25360
26490
  const daemonReady = await autoStartDaemonAndWait();
25361
26491
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
25362
26492
  }
@@ -25369,12 +26499,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
25369
26499
  }
25370
26500
  if (!result.approved) {
25371
26501
  console.error(
25372
- chalk30.red(`
26502
+ chalk33.red(`
25373
26503
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
25374
26504
  );
25375
26505
  process.exit(1);
25376
26506
  }
25377
- console.error(chalk30.green("\n\u2705 Approved \u2014 running command...\n"));
26507
+ console.error(chalk33.green("\n\u2705 Approved \u2014 running command...\n"));
25378
26508
  await runProxy(fullCommand);
25379
26509
  } else {
25380
26510
  program.help();
@@ -25387,6 +26517,8 @@ registerTrustCommand(program);
25387
26517
  registerSyncCommand(program);
25388
26518
  registerAgentsCommand(program);
25389
26519
  registerScanCommand(program);
26520
+ registerPostureCommand(program);
26521
+ registerEgressCommand(program);
25390
26522
  registerSessionsCommand(program);
25391
26523
  registerDlpCommand(program);
25392
26524
  registerMaskCommand(program);
@@ -25396,9 +26528,9 @@ if (process.argv[2] !== "daemon") {
25396
26528
  const isCheckHook = process.argv[2] === "check";
25397
26529
  if (isCheckHook) {
25398
26530
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
25399
- const logPath = path54.join(os48.homedir(), ".node9", "hook-debug.log");
26531
+ const logPath = path57.join(os53.homedir(), ".node9", "hook-debug.log");
25400
26532
  const msg = reason instanceof Error ? reason.message : String(reason);
25401
- fs53.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
26533
+ fs58.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
25402
26534
  `);
25403
26535
  }
25404
26536
  process.exit(0);