@node9/proxy 1.44.0 → 1.45.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 +1171 -666
  2. package/dist/cli.mjs +1171 -666
  3. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -219,8 +219,8 @@ function sanitizeConfig(raw) {
219
219
  }
220
220
  }
221
221
  const lines = result.error.issues.map((issue) => {
222
- const path62 = issue.path.length > 0 ? issue.path.join(".") : "root";
223
- return ` \u2022 ${path62}: ${issue.message}`;
222
+ const path64 = issue.path.length > 0 ? issue.path.join(".") : "root";
223
+ return ` \u2022 ${path64}: ${issue.message}`;
224
224
  });
225
225
  return {
226
226
  sanitized,
@@ -1341,9 +1341,9 @@ function matchesPattern(text, patterns) {
1341
1341
  const withoutDotSlash = text.replace(/^\.\//, "");
1342
1342
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1343
1343
  }
1344
- function getNestedValue(obj, path62) {
1344
+ function getNestedValue(obj, path64) {
1345
1345
  if (!obj || typeof obj !== "object") return null;
1346
- const segments = path62.split(".");
1346
+ const segments = path64.split(".");
1347
1347
  for (const seg of segments) {
1348
1348
  if (FORBIDDEN_PATH_SEGMENTS.has(seg)) return null;
1349
1349
  }
@@ -4153,7 +4153,7 @@ function installShield(name, shieldJson) {
4153
4153
  fs2.writeFileSync(tmp, JSON.stringify(shieldJson, null, 2), { mode: 384 });
4154
4154
  fs2.renameSync(tmp, filePath);
4155
4155
  }
4156
- var USER_SHIELDS_DIR, SHIELDS, SHIELDS_STATE_FILE, RULE_KEY_MIGRATIONS;
4156
+ var USER_SHIELDS_DIR, SHIELDS, SHIELDS_STATE_FILE, RULE_KEY_MIGRATIONS, USER_SHIELDS_DIR_PATH;
4157
4157
  var init_shields = __esm({
4158
4158
  "src/shields.ts"() {
4159
4159
  "use strict";
@@ -4170,6 +4170,7 @@ var init_shields = __esm({
4170
4170
  // around the review-read-credentials rule.
4171
4171
  ["shield:project-jail:review-read-env-any-tool", "shield:project-jail:block-read-env-any-tool"]
4172
4172
  ];
4173
+ USER_SHIELDS_DIR_PATH = USER_SHIELDS_DIR;
4173
4174
  }
4174
4175
  });
4175
4176
 
@@ -5040,7 +5041,30 @@ function explainIsSqlTool(toolName, toolInspection) {
5040
5041
  const fieldName = toolInspection[matchingPattern];
5041
5042
  return fieldName === "sql" || fieldName === "query";
5042
5043
  }
5043
- async function explainPolicy(toolName, args) {
5044
+ async function explainPolicy(toolName, args, agent = EXPLAIN_AGENT) {
5045
+ const derived = await deriveExplainTrace(toolName, args);
5046
+ const engine = await evaluatePolicy2(toolName, args, agent);
5047
+ if (derived.decision === engine.decision) return derived;
5048
+ if (process.env.NODE9_DEBUG) {
5049
+ console.error(
5050
+ `[node9 explain] decision drift: trace=${derived.decision} engine=${engine.decision} for ${toolName} \u2014 engine wins (${engine.blockedByLabel ?? "engine"}).`
5051
+ );
5052
+ }
5053
+ const engineStep = {
5054
+ name: "Engine verdict (authoritative)",
5055
+ outcome: engine.decision,
5056
+ detail: `${engine.blockedByLabel ?? "policy engine"}${engine.reason ? `: ${engine.reason}` : ""} \u2014 this is the verdict the live hook actually enforces. It differs from the preview trace above, which models the policy tiers separately and can drift; the engine is authoritative.`,
5057
+ isFinal: true
5058
+ };
5059
+ return {
5060
+ ...derived,
5061
+ steps: [...derived.steps, engineStep],
5062
+ decision: engine.decision,
5063
+ blockedByLabel: engine.blockedByLabel ?? derived.blockedByLabel,
5064
+ ruleDescription: engine.ruleDescription ?? derived.ruleDescription
5065
+ };
5066
+ }
5067
+ async function deriveExplainTrace(toolName, args) {
5044
5068
  const steps = [];
5045
5069
  const globalPath = path7.join(os6.homedir(), ".node9", "config.json");
5046
5070
  const projectPath = path7.join(process.cwd(), "node9.config.json");
@@ -5322,7 +5346,7 @@ async function explainPolicy(toolName, args) {
5322
5346
  });
5323
5347
  return { tool: toolName, args, waterfall, steps, decision: "allow" };
5324
5348
  }
5325
- var SQL_DML_KEYWORDS2;
5349
+ var SQL_DML_KEYWORDS2, EXPLAIN_AGENT;
5326
5350
  var init_policy = __esm({
5327
5351
  "src/policy/index.ts"() {
5328
5352
  "use strict";
@@ -5333,6 +5357,7 @@ var init_policy = __esm({
5333
5357
  init_dist();
5334
5358
  init_dist();
5335
5359
  SQL_DML_KEYWORDS2 = /* @__PURE__ */ new Set(["select", "insert", "update", "delete", "merge", "upsert"]);
5360
+ EXPLAIN_AGENT = "agent";
5336
5361
  }
5337
5362
  });
5338
5363
 
@@ -8611,9 +8636,9 @@ function writeToml(filePath, data) {
8611
8636
  async function setupCodex() {
8612
8637
  seedMcpPinsIfMissing();
8613
8638
  const homeDir2 = os12.homedir();
8614
- const configPath2 = path15.join(homeDir2, ".codex", "config.toml");
8639
+ const configPath = path15.join(homeDir2, ".codex", "config.toml");
8615
8640
  const hooksPath = path15.join(homeDir2, ".codex", "hooks.json");
8616
- const config = readToml(configPath2) ?? {};
8641
+ const config = readToml(configPath) ?? {};
8617
8642
  const servers = config.mcp_servers ?? {};
8618
8643
  let anythingChanged = false;
8619
8644
  const hooksFile = readJson(hooksPath) ?? {};
@@ -8688,7 +8713,7 @@ async function setupCodex() {
8688
8713
  if (!hasNode9McpServer(servers)) {
8689
8714
  servers["node9"] = NODE9_MCP_SERVER_ENTRY;
8690
8715
  config.mcp_servers = servers;
8691
- writeToml(configPath2, config);
8716
+ writeToml(configPath, config);
8692
8717
  console.log(chalk.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
8693
8718
  anythingChanged = true;
8694
8719
  }
@@ -8700,7 +8725,7 @@ async function setupCodex() {
8700
8725
  }
8701
8726
  if (serversToWrap.length > 0) {
8702
8727
  console.log(chalk.bold("The following existing entries will be modified:\n"));
8703
- console.log(chalk.white(` ${configPath2}`));
8728
+ console.log(chalk.white(` ${configPath}`));
8704
8729
  for (const { name, upstream } of serversToWrap) {
8705
8730
  console.log(chalk.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
8706
8731
  }
@@ -8715,7 +8740,7 @@ async function setupCodex() {
8715
8740
  };
8716
8741
  }
8717
8742
  config.mcp_servers = servers;
8718
- writeToml(configPath2, config);
8743
+ writeToml(configPath, config);
8719
8744
  console.log(chalk.green(`
8720
8745
  \u2705 ${serversToWrap.length} MCP server(s) wrapped`));
8721
8746
  anythingChanged = true;
@@ -8763,7 +8788,7 @@ async function setupCodex() {
8763
8788
  }
8764
8789
  function teardownCodex() {
8765
8790
  const homeDir2 = os12.homedir();
8766
- const configPath2 = path15.join(homeDir2, ".codex", "config.toml");
8791
+ const configPath = path15.join(homeDir2, ".codex", "config.toml");
8767
8792
  const hooksPath = path15.join(homeDir2, ".codex", "hooks.json");
8768
8793
  const hooksFile = readJson(hooksPath);
8769
8794
  if (hooksFile?.hooks) {
@@ -8781,7 +8806,7 @@ function teardownCodex() {
8781
8806
  console.log(chalk.green(" \u2705 Removed Node9 hooks from ~/.codex/hooks.json"));
8782
8807
  }
8783
8808
  }
8784
- const config = readToml(configPath2);
8809
+ const config = readToml(configPath);
8785
8810
  if (!config?.mcp_servers) {
8786
8811
  console.log(chalk.blue(" \u2139\uFE0F ~/.codex/config.toml not found \u2014 nothing to remove"));
8787
8812
  return;
@@ -8804,7 +8829,7 @@ function teardownCodex() {
8804
8829
  }
8805
8830
  }
8806
8831
  if (changed) {
8807
- writeToml(configPath2, config);
8832
+ writeToml(configPath, config);
8808
8833
  console.log(chalk.green(" \u2705 Unwrapped MCP servers in ~/.codex/config.toml"));
8809
8834
  } else {
8810
8835
  console.log(chalk.blue(" \u2139\uFE0F No Node9-wrapped MCP servers found in ~/.codex/config.toml"));
@@ -9059,18 +9084,18 @@ function teardownVSCode() {
9059
9084
  }
9060
9085
  async function setupClaudeDesktop() {
9061
9086
  seedMcpPinsIfMissing();
9062
- const configPath2 = claudeDesktopConfigPath();
9063
- if (!configPath2) {
9087
+ const configPath = claudeDesktopConfigPath();
9088
+ if (!configPath) {
9064
9089
  console.log(chalk.yellow(" \u26A0\uFE0F Claude Desktop is not supported on this platform."));
9065
9090
  return;
9066
9091
  }
9067
- const config = readJson(configPath2) ?? {};
9092
+ const config = readJson(configPath) ?? {};
9068
9093
  const servers = config.mcpServers ?? {};
9069
9094
  let anythingChanged = false;
9070
9095
  if (!hasNode9McpServer(servers)) {
9071
9096
  servers["node9"] = NODE9_MCP_SERVER_ENTRY;
9072
9097
  config.mcpServers = servers;
9073
- writeJson(configPath2, config);
9098
+ writeJson(configPath, config);
9074
9099
  console.log(chalk.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
9075
9100
  anythingChanged = true;
9076
9101
  }
@@ -9081,7 +9106,7 @@ async function setupClaudeDesktop() {
9081
9106
  }
9082
9107
  if (serversToWrap.length > 0) {
9083
9108
  console.log(chalk.bold("The following existing entries will be modified:\n"));
9084
- console.log(chalk.white(` ${configPath2}`));
9109
+ console.log(chalk.white(` ${configPath}`));
9085
9110
  for (const { name, upstream } of serversToWrap) {
9086
9111
  console.log(chalk.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
9087
9112
  }
@@ -9096,7 +9121,7 @@ async function setupClaudeDesktop() {
9096
9121
  };
9097
9122
  }
9098
9123
  config.mcpServers = servers;
9099
- writeJson(configPath2, config);
9124
+ writeJson(configPath, config);
9100
9125
  console.log(chalk.green(`
9101
9126
  \u2705 ${serversToWrap.length} MCP server(s) wrapped`));
9102
9127
  anythingChanged = true;
@@ -9123,12 +9148,12 @@ async function setupClaudeDesktop() {
9123
9148
  }
9124
9149
  }
9125
9150
  function teardownClaudeDesktop() {
9126
- const configPath2 = claudeDesktopConfigPath();
9127
- if (!configPath2) {
9151
+ const configPath = claudeDesktopConfigPath();
9152
+ if (!configPath) {
9128
9153
  console.log(chalk.yellow(" \u26A0\uFE0F Claude Desktop is not supported on this platform."));
9129
9154
  return;
9130
9155
  }
9131
- const config = readJson(configPath2);
9156
+ const config = readJson(configPath);
9132
9157
  if (!config?.mcpServers) {
9133
9158
  console.log(chalk.blue(" \u2139\uFE0F Claude Desktop config not found \u2014 nothing to remove"));
9134
9159
  return;
@@ -9136,7 +9161,7 @@ function teardownClaudeDesktop() {
9136
9161
  let changed = false;
9137
9162
  if (removeNode9McpServer(config.mcpServers)) {
9138
9163
  changed = true;
9139
- console.log(chalk.green(` \u2705 Removed node9 MCP server entry from ${configPath2}`));
9164
+ console.log(chalk.green(` \u2705 Removed node9 MCP server entry from ${configPath}`));
9140
9165
  }
9141
9166
  for (const [name, server] of Object.entries(config.mcpServers)) {
9142
9167
  const args = server.args;
@@ -9151,7 +9176,7 @@ function teardownClaudeDesktop() {
9151
9176
  }
9152
9177
  }
9153
9178
  if (changed) {
9154
- writeJson(configPath2, config);
9179
+ writeJson(configPath, config);
9155
9180
  console.log(chalk.green(" \u2705 Unwrapped MCP servers in Claude Desktop config"));
9156
9181
  } else {
9157
9182
  console.log(chalk.blue(" \u2139\uFE0F No Node9-wrapped MCP servers found in Claude Desktop config"));
@@ -9179,7 +9204,7 @@ async function setupOpencode() {
9179
9204
  const homeDir2 = os12.homedir();
9180
9205
  const configDir = path15.join(homeDir2, ".config", "opencode");
9181
9206
  const pluginsDir = path15.join(configDir, "plugins");
9182
- const configPath2 = path15.join(configDir, "opencode.json");
9207
+ const configPath = path15.join(configDir, "opencode.json");
9183
9208
  const pluginPath = path15.join(pluginsDir, OPENCODE_PLUGIN_NAME);
9184
9209
  try {
9185
9210
  fs13.mkdirSync(pluginsDir, { recursive: true });
@@ -9213,7 +9238,7 @@ async function setupOpencode() {
9213
9238
  );
9214
9239
  }
9215
9240
  }
9216
- const config = readJson(configPath2) ?? {};
9241
+ const config = readJson(configPath) ?? {};
9217
9242
  const mcp = config.mcp ?? {};
9218
9243
  let configChanged = false;
9219
9244
  const desiredCommand = [...node9ArgvForShim(), "mcp-server"];
@@ -9234,7 +9259,7 @@ async function setupOpencode() {
9234
9259
  console.log(chalk.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
9235
9260
  }
9236
9261
  }
9237
- if (configChanged) writeJson(configPath2, config);
9262
+ if (configChanged) writeJson(configPath, config);
9238
9263
  if (pluginChanged || configChanged) {
9239
9264
  console.log(chalk.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Opencode!"));
9240
9265
  console.log(chalk.gray(" Restart Opencode for changes to take effect."));
@@ -9247,7 +9272,7 @@ function teardownOpencode() {
9247
9272
  const homeDir2 = os12.homedir();
9248
9273
  const configDir = path15.join(homeDir2, ".config", "opencode");
9249
9274
  const pluginsDir = path15.join(configDir, "plugins");
9250
- const configPath2 = path15.join(configDir, "opencode.json");
9275
+ const configPath = path15.join(configDir, "opencode.json");
9251
9276
  const pluginPath = path15.join(pluginsDir, OPENCODE_PLUGIN_NAME);
9252
9277
  try {
9253
9278
  if (fs13.existsSync(pluginPath)) {
@@ -9257,7 +9282,7 @@ function teardownOpencode() {
9257
9282
  } catch (err2) {
9258
9283
  console.log(chalk.yellow(` \u26A0\uFE0F Could not remove ${pluginPath}: ${String(err2)}`));
9259
9284
  }
9260
- const config = readJson(configPath2);
9285
+ const config = readJson(configPath);
9261
9286
  if (!config) {
9262
9287
  console.log(chalk.blue(" \u2139\uFE0F ~/.config/opencode/opencode.json not found \u2014 nothing to remove"));
9263
9288
  return;
@@ -9273,7 +9298,7 @@ function teardownOpencode() {
9273
9298
  }
9274
9299
  if (changed) {
9275
9300
  config.mcp = mcp;
9276
- writeJson(configPath2, config);
9301
+ writeJson(configPath, config);
9277
9302
  } else {
9278
9303
  console.log(chalk.blue(" \u2139\uFE0F No node9 entries found in ~/.config/opencode/opencode.json"));
9279
9304
  }
@@ -9344,15 +9369,15 @@ function hermesAllowlistPath(homeDir2 = os12.homedir()) {
9344
9369
  }
9345
9370
  function setupHermes() {
9346
9371
  const homeDir2 = os12.homedir();
9347
- const configPath2 = hermesConfigPath(homeDir2);
9372
+ const configPath = hermesConfigPath(homeDir2);
9348
9373
  const allowlistPath = hermesAllowlistPath(homeDir2);
9349
- if (!fs13.existsSync(configPath2)) {
9350
- console.log(chalk.yellow(` \u26A0\uFE0F Hermes config not found at ${configPath2}`));
9374
+ if (!fs13.existsSync(configPath)) {
9375
+ console.log(chalk.yellow(` \u26A0\uFE0F Hermes config not found at ${configPath}`));
9351
9376
  console.log(chalk.gray(" Run `hermes setup` first, then re-run node9 agents add hermes."));
9352
9377
  return;
9353
9378
  }
9354
9379
  let anythingChanged = false;
9355
- const raw = fs13.readFileSync(configPath2, "utf-8");
9380
+ const raw = fs13.readFileSync(configPath, "utf-8");
9356
9381
  const doc = yaml.parseDocument(raw);
9357
9382
  if (doc.errors.length > 0) {
9358
9383
  console.log(chalk.yellow(` \u26A0\uFE0F Hermes config.yaml has YAML parse errors:`));
@@ -9392,7 +9417,7 @@ function setupHermes() {
9392
9417
  anythingChanged = true;
9393
9418
  }
9394
9419
  if (anythingChanged) {
9395
- fs13.writeFileSync(configPath2, doc.toString());
9420
+ fs13.writeFileSync(configPath, doc.toString());
9396
9421
  }
9397
9422
  let allowlist = {};
9398
9423
  if (fs13.existsSync(allowlistPath)) {
@@ -9435,24 +9460,24 @@ function setupHermes() {
9435
9460
  }
9436
9461
  function teardownHermes() {
9437
9462
  const homeDir2 = os12.homedir();
9438
- const configPath2 = hermesConfigPath(homeDir2);
9463
+ const configPath = hermesConfigPath(homeDir2);
9439
9464
  const allowlistPath = hermesAllowlistPath(homeDir2);
9440
- if (!fs13.existsSync(configPath2)) {
9441
- console.log(chalk.blue(` \u2139\uFE0F ${configPath2} not found \u2014 nothing to remove`));
9465
+ if (!fs13.existsSync(configPath)) {
9466
+ console.log(chalk.blue(` \u2139\uFE0F ${configPath} not found \u2014 nothing to remove`));
9442
9467
  return;
9443
9468
  }
9444
- const raw = fs13.readFileSync(configPath2, "utf-8");
9469
+ const raw = fs13.readFileSync(configPath, "utf-8");
9445
9470
  const doc = yaml.parseDocument(raw);
9446
9471
  if (doc.errors.length > 0) {
9447
9472
  console.log(
9448
- chalk.yellow(` \u26A0\uFE0F Skipping ${configPath2} \u2014 file has YAML parse errors, fix it manually.`)
9473
+ chalk.yellow(` \u26A0\uFE0F Skipping ${configPath} \u2014 file has YAML parse errors, fix it manually.`)
9449
9474
  );
9450
9475
  } else {
9451
- teardownHermesConfigDoc(doc, configPath2);
9476
+ teardownHermesConfigDoc(doc, configPath);
9452
9477
  }
9453
9478
  teardownHermesAllowlist(allowlistPath);
9454
9479
  }
9455
- function teardownHermesConfigDoc(doc, configPath2) {
9480
+ function teardownHermesConfigDoc(doc, configPath) {
9456
9481
  let anythingChanged = false;
9457
9482
  const current = doc.toJS() ?? {};
9458
9483
  for (const { event } of HERMES_HOOK_PLAN) {
@@ -9474,10 +9499,10 @@ function teardownHermesConfigDoc(doc, configPath2) {
9474
9499
  anythingChanged = true;
9475
9500
  }
9476
9501
  if (anythingChanged) {
9477
- fs13.writeFileSync(configPath2, doc.toString());
9478
- console.log(chalk.green(` \u2705 Removed Node9 hooks from ${configPath2}`));
9502
+ fs13.writeFileSync(configPath, doc.toString());
9503
+ console.log(chalk.green(` \u2705 Removed Node9 hooks from ${configPath}`));
9479
9504
  } else {
9480
- console.log(chalk.blue(` \u2139\uFE0F No Node9 hooks found in ${configPath2}`));
9505
+ console.log(chalk.blue(` \u2139\uFE0F No Node9 hooks found in ${configPath}`));
9481
9506
  }
9482
9507
  }
9483
9508
  function teardownHermesAllowlist(allowlistPath) {
@@ -12089,8 +12114,8 @@ function countScanFiles() {
12089
12114
  const geminiDir = path25.join(os22.homedir(), ".gemini", "tmp");
12090
12115
  if (fs23.existsSync(geminiDir)) {
12091
12116
  try {
12092
- for (const slug of fs23.readdirSync(geminiDir)) {
12093
- const p = path25.join(geminiDir, slug);
12117
+ for (const slug2 of fs23.readdirSync(geminiDir)) {
12118
+ const p = path25.join(geminiDir, slug2);
12094
12119
  try {
12095
12120
  if (!fs23.statSync(p).isDirectory()) continue;
12096
12121
  const chatsDir = path25.join(p, "chats");
@@ -12487,14 +12512,14 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
12487
12512
  return result;
12488
12513
  }
12489
12514
  const ruleSources = buildRuleSources();
12490
- for (const slug of slugDirs) {
12491
- const slugPath = path25.join(tmpDir, slug);
12515
+ for (const slug2 of slugDirs) {
12516
+ const slugPath = path25.join(tmpDir, slug2);
12492
12517
  try {
12493
12518
  if (!fs23.statSync(slugPath).isDirectory()) continue;
12494
12519
  } catch {
12495
12520
  continue;
12496
12521
  }
12497
- let projLabel = stripTerminalEscapes(slug).slice(0, 40);
12522
+ let projLabel = stripTerminalEscapes(slug2).slice(0, 40);
12498
12523
  try {
12499
12524
  projLabel = stripTerminalEscapes(
12500
12525
  fs23.readFileSync(path25.join(slugPath, ".project_root"), "utf-8").trim()
@@ -13833,7 +13858,7 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
13833
13858
  const hitShieldSet = new Set(
13834
13859
  shieldImpacts.filter((i) => i.totalCatches > 0).map((i) => i.shieldName)
13835
13860
  );
13836
- const zeroHitBuiltins = Object.keys(SHIELDS).filter((name) => !hitShieldSet.has(name)).sort();
13861
+ const zeroHitBuiltins = Object.keys(BUILTIN_SHIELDS).filter((name) => !hitShieldSet.has(name)).sort();
13837
13862
  if (zeroHitBuiltins.length > 0) {
13838
13863
  shieldLines.push(mkLine([""]));
13839
13864
  shieldLines.push(mkLine([zeroHitBuiltins.join(" \xB7 "), chalk5.dim]));
@@ -18696,10 +18721,10 @@ __export(tail_exports, {
18696
18721
  startTail: () => startTail
18697
18722
  });
18698
18723
  import http3 from "http";
18699
- import chalk34 from "chalk";
18700
- import fs61 from "fs";
18701
- import os53 from "os";
18702
- import path59 from "path";
18724
+ import chalk35 from "chalk";
18725
+ import fs64 from "fs";
18726
+ import os54 from "os";
18727
+ import path61 from "path";
18703
18728
  import readline6 from "readline";
18704
18729
  import { spawn as spawn8 } from "child_process";
18705
18730
  function shortenPathSummary(s) {
@@ -18723,20 +18748,20 @@ function getModelContextLimit(model) {
18723
18748
  return 2e5;
18724
18749
  }
18725
18750
  function readSessionUsage() {
18726
- const projectsDir = path59.join(os53.homedir(), ".claude", "projects");
18727
- if (!fs61.existsSync(projectsDir)) return null;
18751
+ const projectsDir = path61.join(os54.homedir(), ".claude", "projects");
18752
+ if (!fs64.existsSync(projectsDir)) return null;
18728
18753
  let latestFile = null;
18729
18754
  let latestMtime = 0;
18730
18755
  try {
18731
- for (const dir of fs61.readdirSync(projectsDir)) {
18732
- const dirPath = path59.join(projectsDir, dir);
18756
+ for (const dir of fs64.readdirSync(projectsDir)) {
18757
+ const dirPath = path61.join(projectsDir, dir);
18733
18758
  try {
18734
- if (!fs61.statSync(dirPath).isDirectory()) continue;
18735
- for (const file of fs61.readdirSync(dirPath)) {
18759
+ if (!fs64.statSync(dirPath).isDirectory()) continue;
18760
+ for (const file of fs64.readdirSync(dirPath)) {
18736
18761
  if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
18737
- const filePath = path59.join(dirPath, file);
18762
+ const filePath = path61.join(dirPath, file);
18738
18763
  try {
18739
- const mtime = fs61.statSync(filePath).mtimeMs;
18764
+ const mtime = fs64.statSync(filePath).mtimeMs;
18740
18765
  if (mtime > latestMtime) {
18741
18766
  latestMtime = mtime;
18742
18767
  latestFile = filePath;
@@ -18751,7 +18776,7 @@ function readSessionUsage() {
18751
18776
  }
18752
18777
  if (!latestFile) return null;
18753
18778
  try {
18754
- const lines = fs61.readFileSync(latestFile, "utf-8").split("\n");
18779
+ const lines = fs64.readFileSync(latestFile, "utf-8").split("\n");
18755
18780
  let lastModel = "";
18756
18781
  let lastInput = 0;
18757
18782
  let lastOutput = 0;
@@ -18776,10 +18801,10 @@ function readSessionUsage() {
18776
18801
  }
18777
18802
  }
18778
18803
  function formatContextStat(stat) {
18779
- const pctColor = stat.fillPct >= 80 ? chalk34.red : stat.fillPct >= 50 ? chalk34.yellow : chalk34.cyan;
18804
+ const pctColor = stat.fillPct >= 80 ? chalk35.red : stat.fillPct >= 50 ? chalk35.yellow : chalk35.cyan;
18780
18805
  const k = (n) => `${Math.round(n / 1e3)}k`;
18781
18806
  const modelShort = stat.model.replace(/@.*$/, "").replace(/-\d{8}$/, "").replace(/^claude-/, "");
18782
- return chalk34.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + chalk34.dim(
18807
+ return chalk35.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + chalk35.dim(
18783
18808
  ` (${k(stat.inputTokens)}/${k(getModelContextLimit(stat.model))} out ${k(stat.outputTokens)} \xB7 ${modelShort})`
18784
18809
  );
18785
18810
  }
@@ -18802,32 +18827,32 @@ function agentLabel(agent, mcpServer, sessionId) {
18802
18827
  const tag = sessionTag(sessionId);
18803
18828
  const tagSuffix = tag ? `\xB7${tag}` : "";
18804
18829
  if (!agent || agent === "Terminal") {
18805
- return mcpServer ? chalk34.dim(`[\u2192 ${mcpServer}] `) : "";
18830
+ return mcpServer ? chalk35.dim(`[\u2192 ${mcpServer}] `) : "";
18806
18831
  }
18807
18832
  const short = agent === "Claude Code" ? "Claude" : agent === "Gemini CLI" ? "Gemini" : agent === "Unknown Agent" ? "" : agent.split(" ")[0];
18808
- if (!short) return mcpServer ? chalk34.dim(`[\u2192 ${mcpServer}] `) : "";
18809
- return mcpServer ? chalk34.dim(`[${short}${tagSuffix} \u2192 ${mcpServer}] `) : chalk34.dim(`[${short}${tagSuffix}] `);
18833
+ if (!short) return mcpServer ? chalk35.dim(`[\u2192 ${mcpServer}] `) : "";
18834
+ return mcpServer ? chalk35.dim(`[${short}${tagSuffix} \u2192 ${mcpServer}] `) : chalk35.dim(`[${short}${tagSuffix}] `);
18810
18835
  }
18811
18836
  function formatBase(activity) {
18812
18837
  const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
18813
18838
  const icon = getIcon(activity.tool);
18814
18839
  const toolName = activity.tool.slice(0, 16).padEnd(16);
18815
- const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os53.homedir(), "~");
18840
+ const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os54.homedir(), "~");
18816
18841
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
18817
- return `${chalk34.gray(time)} ${icon} ${agentLabel(activity.agent, activity.mcpServer, activity.sessionId)}${chalk34.white.bold(toolName)} ${chalk34.dim(argsPreview)}`;
18842
+ return `${chalk35.gray(time)} ${icon} ${agentLabel(activity.agent, activity.mcpServer, activity.sessionId)}${chalk35.white.bold(toolName)} ${chalk35.dim(argsPreview)}`;
18818
18843
  }
18819
18844
  function renderResult(activity, result) {
18820
18845
  const base = formatBase(activity);
18821
18846
  let status;
18822
18847
  if (result.status === "allow") {
18823
- status = chalk34.green("\u2713 ALLOW");
18848
+ status = chalk35.green("\u2713 ALLOW");
18824
18849
  } else if (result.status === "dlp") {
18825
- status = chalk34.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
18850
+ status = chalk35.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
18826
18851
  } else {
18827
- status = chalk34.red("\u2717 BLOCK");
18852
+ status = chalk35.red("\u2717 BLOCK");
18828
18853
  }
18829
18854
  const cost = result.costEstimate ?? activity.costEstimate;
18830
- const costSuffix = cost == null ? "" : chalk34.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
18855
+ const costSuffix = cost == null ? "" : chalk35.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
18831
18856
  if (process.stdout.isTTY) {
18832
18857
  if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
18833
18858
  readline6.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
@@ -18844,19 +18869,19 @@ function renderResult(activity, result) {
18844
18869
  }
18845
18870
  function renderPending(activity) {
18846
18871
  if (!process.stdout.isTTY) return;
18847
- const line = `${formatBase(activity)} ${chalk34.yellow("\u25CF \u2026")}`;
18872
+ const line = `${formatBase(activity)} ${chalk35.yellow("\u25CF \u2026")}`;
18848
18873
  pendingShownForId = activity.id;
18849
18874
  pendingWrappedLines = wrappedLineCount(line);
18850
18875
  process.stdout.write(`${line}\r`);
18851
18876
  }
18852
18877
  async function ensureDaemon() {
18853
18878
  let pidPort = null;
18854
- if (fs61.existsSync(PID_FILE)) {
18879
+ if (fs64.existsSync(PID_FILE)) {
18855
18880
  try {
18856
- const { port } = JSON.parse(fs61.readFileSync(PID_FILE, "utf-8"));
18881
+ const { port } = JSON.parse(fs64.readFileSync(PID_FILE, "utf-8"));
18857
18882
  pidPort = port;
18858
18883
  } catch {
18859
- console.error(chalk34.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
18884
+ console.error(chalk35.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
18860
18885
  }
18861
18886
  }
18862
18887
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -18867,7 +18892,7 @@ async function ensureDaemon() {
18867
18892
  if (res.ok) return checkPort;
18868
18893
  } catch {
18869
18894
  }
18870
- console.log(chalk34.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
18895
+ console.log(chalk35.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
18871
18896
  const child = spawn8(process.execPath, [process.argv[1], "daemon"], {
18872
18897
  detached: true,
18873
18898
  stdio: "ignore",
@@ -18884,7 +18909,7 @@ async function ensureDaemon() {
18884
18909
  } catch {
18885
18910
  }
18886
18911
  }
18887
- console.error(chalk34.red("\u274C Daemon failed to start. Try: node9 daemon start"));
18912
+ console.error(chalk35.red("\u274C Daemon failed to start. Try: node9 daemon start"));
18888
18913
  process.exit(1);
18889
18914
  }
18890
18915
  function postDecisionHttp(id, decision, authToken, port, opts) {
@@ -18953,7 +18978,7 @@ function buildCardLines(req, localCount = 0) {
18953
18978
  const severityIcon = isBlock ? `${RED}\u{1F6D1}` : `${YELLOW}\u26A0 `;
18954
18979
  const rawDesc = req.riskMetadata?.ruleDescription ?? "";
18955
18980
  const description = rawDesc ? cleanReason(rawDesc) : "";
18956
- const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${chalk34.dim(`(${req.agent})`)}` : "";
18981
+ const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${chalk35.dim(`(${req.agent})`)}` : "";
18957
18982
  const lines = [
18958
18983
  ``,
18959
18984
  `${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
@@ -19009,9 +19034,9 @@ function buildRecoveryCardLines(req) {
19009
19034
  ];
19010
19035
  }
19011
19036
  function readApproversFromDisk() {
19012
- const configPath2 = path59.join(os53.homedir(), ".node9", "config.json");
19037
+ const configPath = path61.join(os54.homedir(), ".node9", "config.json");
19013
19038
  try {
19014
- const raw = JSON.parse(fs61.readFileSync(configPath2, "utf-8"));
19039
+ const raw = JSON.parse(fs64.readFileSync(configPath, "utf-8"));
19015
19040
  const settings = raw.settings ?? {};
19016
19041
  return settings.approvers ?? {};
19017
19042
  } catch {
@@ -19022,20 +19047,20 @@ function approverStatusLine() {
19022
19047
  const a = readApproversFromDisk();
19023
19048
  const fmt = (label2, key) => {
19024
19049
  const on = a[key] !== false;
19025
- return `[${key[0]}]${label2.slice(1)} ${on ? chalk34.green("\u2713") : chalk34.dim("\u2717")}`;
19050
+ return `[${key[0]}]${label2.slice(1)} ${on ? chalk35.green("\u2713") : chalk35.dim("\u2717")}`;
19026
19051
  };
19027
19052
  return `${fmt("native", "native")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
19028
19053
  }
19029
19054
  function toggleApprover(channel) {
19030
- const configPath2 = path59.join(os53.homedir(), ".node9", "config.json");
19055
+ const configPath = path61.join(os54.homedir(), ".node9", "config.json");
19031
19056
  try {
19032
- const raw = JSON.parse(fs61.readFileSync(configPath2, "utf-8"));
19057
+ const raw = JSON.parse(fs64.readFileSync(configPath, "utf-8"));
19033
19058
  const settings = raw.settings ?? {};
19034
19059
  const approvers = settings.approvers ?? {};
19035
19060
  approvers[channel] = approvers[channel] === false;
19036
19061
  settings.approvers = approvers;
19037
19062
  raw.settings = settings;
19038
- fs61.writeFileSync(configPath2, JSON.stringify(raw, null, 2) + "\n");
19063
+ fs64.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
19039
19064
  } catch (err2) {
19040
19065
  process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
19041
19066
  `);
@@ -19067,7 +19092,7 @@ async function startTail(options = {}) {
19067
19092
  req2.end();
19068
19093
  });
19069
19094
  if (result.ok) {
19070
- console.log(chalk34.green("\u2713 Flight Recorder buffer cleared."));
19095
+ console.log(chalk35.green("\u2713 Flight Recorder buffer cleared."));
19071
19096
  } else if (result.code === "ECONNREFUSED") {
19072
19097
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
19073
19098
  } else if (result.code === "ETIMEDOUT") {
@@ -19113,7 +19138,7 @@ async function startTail(options = {}) {
19113
19138
  const channel = name === "n" ? "native" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
19114
19139
  if (channel) {
19115
19140
  toggleApprover(channel);
19116
- console.log(chalk34.dim(` Approvers: ${approverStatusLine()}`));
19141
+ console.log(chalk35.dim(` Approvers: ${approverStatusLine()}`));
19117
19142
  }
19118
19143
  };
19119
19144
  process.stdin.on("keypress", idleKeypressHandler);
@@ -19179,7 +19204,7 @@ async function startTail(options = {}) {
19179
19204
  localAllowCounts.get(req2.toolName) ?? 0
19180
19205
  )
19181
19206
  );
19182
- const decisionStamp = action === "always-allow" ? chalk34.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? chalk34.cyan("\u23F1 TRUST 30m") : action === "allow" ? chalk34.green("\u2713 ALLOWED") : action === "redirect" ? chalk34.yellow("\u21A9 REDIRECT AI") : chalk34.red("\u2717 DENIED");
19207
+ const decisionStamp = action === "always-allow" ? chalk35.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? chalk35.cyan("\u23F1 TRUST 30m") : action === "allow" ? chalk35.green("\u2713 ALLOWED") : action === "redirect" ? chalk35.yellow("\u21A9 REDIRECT AI") : chalk35.red("\u2717 DENIED");
19183
19208
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
19184
19209
  for (const line of stampedLines) process.stdout.write(line + "\n");
19185
19210
  process.stdout.write(SHOW_CURSOR);
@@ -19207,8 +19232,8 @@ async function startTail(options = {}) {
19207
19232
  }
19208
19233
  postDecisionHttp(req2.id, httpDecision, authToken, port, httpOpts).catch((err2) => {
19209
19234
  try {
19210
- fs61.appendFileSync(
19211
- path59.join(os53.homedir(), ".node9", "hook-debug.log"),
19235
+ fs64.appendFileSync(
19236
+ path61.join(os54.homedir(), ".node9", "hook-debug.log"),
19212
19237
  `[tail] POST /decision failed: ${String(err2)}
19213
19238
  `
19214
19239
  );
@@ -19230,7 +19255,7 @@ async function startTail(options = {}) {
19230
19255
  );
19231
19256
  const stampedLines = buildCardLines(req2, priorCount);
19232
19257
  if (externalDecision) {
19233
- const source = externalDecision === "allow" ? chalk34.green("\u2713 ALLOWED") : chalk34.red("\u2717 DENIED");
19258
+ const source = externalDecision === "allow" ? chalk35.green("\u2713 ALLOWED") : chalk35.red("\u2717 DENIED");
19234
19259
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
19235
19260
  }
19236
19261
  for (const line of stampedLines) process.stdout.write(line + "\n");
@@ -19272,31 +19297,31 @@ async function startTail(options = {}) {
19272
19297
  };
19273
19298
  process.stdin.on("keypress", onKeypress);
19274
19299
  }
19275
- const auditLog = path59.join(os53.homedir(), ".node9", "audit.log");
19300
+ const auditLog = path61.join(os54.homedir(), ".node9", "audit.log");
19276
19301
  try {
19277
- const unackedDlp = fs61.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
19302
+ const unackedDlp = fs64.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
19278
19303
  if (unackedDlp > 0) {
19279
19304
  console.log("");
19280
19305
  console.log(
19281
- chalk34.bgRed.white.bold(
19306
+ chalk35.bgRed.white.bold(
19282
19307
  ` \u26A0\uFE0F DLP ALERT: ${unackedDlp} secret${unackedDlp !== 1 ? "s" : ""} found in Claude response text \u2014 run: node9 dlp `
19283
19308
  )
19284
19309
  );
19285
19310
  }
19286
19311
  } catch {
19287
19312
  }
19288
- console.log(chalk34.cyan.bold(`
19313
+ console.log(chalk35.cyan.bold(`
19289
19314
  \u{1F6F0}\uFE0F Node9 tail`));
19290
19315
  if (canApprove) {
19291
- console.log(chalk34.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
19292
- console.log(chalk34.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
19316
+ console.log(chalk35.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
19317
+ console.log(chalk35.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
19293
19318
  }
19294
19319
  const ctxStat = readSessionUsage();
19295
19320
  if (ctxStat) console.log(" " + formatContextStat(ctxStat));
19296
19321
  if (options.history) {
19297
- console.log(chalk34.dim("Showing history + live events.\n"));
19322
+ console.log(chalk35.dim("Showing history + live events.\n"));
19298
19323
  } else {
19299
- console.log(chalk34.dim("Showing live events only. Use --history to include past.\n"));
19324
+ console.log(chalk35.dim("Showing live events only. Use --history to include past.\n"));
19300
19325
  }
19301
19326
  process.on("SIGINT", () => {
19302
19327
  exitIdleMode();
@@ -19306,7 +19331,7 @@ async function startTail(options = {}) {
19306
19331
  readline6.clearLine(process.stdout, 0);
19307
19332
  readline6.cursorTo(process.stdout, 0);
19308
19333
  }
19309
- console.log(chalk34.dim("\n\u{1F6F0}\uFE0F Disconnected."));
19334
+ console.log(chalk35.dim("\n\u{1F6F0}\uFE0F Disconnected."));
19310
19335
  process.exit(0);
19311
19336
  });
19312
19337
  const STALL_THRESHOLD_MS = 6e4;
@@ -19314,11 +19339,11 @@ async function startTail(options = {}) {
19314
19339
  if (stallWarned) return;
19315
19340
  if (Date.now() - lastActivityFromDaemon < STALL_THRESHOLD_MS) return;
19316
19341
  try {
19317
- const auditMtime = fs61.statSync(auditLog).mtimeMs;
19342
+ const auditMtime = fs64.statSync(auditLog).mtimeMs;
19318
19343
  if (Date.now() - auditMtime >= STALL_THRESHOLD_MS) return;
19319
19344
  console.log("");
19320
19345
  console.log(
19321
- chalk34.yellow(
19346
+ chalk35.yellow(
19322
19347
  "\u26A0\uFE0F Tail appears stalled \u2014 hooks are firing but no events are arriving. Try: node9 daemon restart"
19323
19348
  )
19324
19349
  );
@@ -19335,7 +19360,7 @@ async function startTail(options = {}) {
19335
19360
  },
19336
19361
  (res) => {
19337
19362
  if (res.statusCode !== 200) {
19338
- console.error(chalk34.red(`Failed to connect: HTTP ${res.statusCode}`));
19363
+ console.error(chalk35.red(`Failed to connect: HTTP ${res.statusCode}`));
19339
19364
  process.exit(1);
19340
19365
  }
19341
19366
  if (canApprove) enterIdleMode();
@@ -19366,7 +19391,7 @@ async function startTail(options = {}) {
19366
19391
  readline6.clearLine(process.stdout, 0);
19367
19392
  readline6.cursorTo(process.stdout, 0);
19368
19393
  }
19369
- console.log(chalk34.red("\n\u274C Daemon disconnected."));
19394
+ console.log(chalk35.red("\n\u274C Daemon disconnected."));
19370
19395
  process.exit(1);
19371
19396
  });
19372
19397
  }
@@ -19379,7 +19404,7 @@ async function startTail(options = {}) {
19379
19404
  const parsed = JSON.parse(rawData);
19380
19405
  const msg = parsed.message ?? "Flight recorder is down \u2014 run: node9 daemon restart";
19381
19406
  console.log("");
19382
- console.log(chalk34.bgRed.white.bold(` \u26A0\uFE0F ${msg} `));
19407
+ console.log(chalk35.bgRed.white.bold(` \u26A0\uFE0F ${msg} `));
19383
19408
  } catch {
19384
19409
  }
19385
19410
  return;
@@ -19464,9 +19489,9 @@ async function startTail(options = {}) {
19464
19489
  const rawSummary = data.argsSummary ?? data.tool;
19465
19490
  const summary = shortenPathSummary(rawSummary);
19466
19491
  const fileCount = data.fileCount ?? 0;
19467
- const files = fileCount > 0 ? chalk34.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
19492
+ const files = fileCount > 0 ? chalk35.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
19468
19493
  process.stdout.write(
19469
- `${chalk34.dim(time)} ${chalk34.cyan("\u{1F4F8} snapshot")} ${chalk34.dim(hash)} ${summary}${files}
19494
+ `${chalk35.dim(time)} ${chalk35.cyan("\u{1F4F8} snapshot")} ${chalk35.dim(hash)} ${summary}${files}
19470
19495
  `
19471
19496
  );
19472
19497
  return;
@@ -19483,18 +19508,18 @@ async function startTail(options = {}) {
19483
19508
  if (event === "execution-result") {
19484
19509
  const exec = data;
19485
19510
  const time = new Date(Date.now()).toLocaleTimeString([], { hour12: false });
19486
- const arrow = exec.isError ? chalk34.red(" \u21B3 \u2717") : chalk34.green(" \u21B3 \u2713");
19511
+ const arrow = exec.isError ? chalk35.red(" \u21B3 \u2717") : chalk35.green(" \u21B3 \u2713");
19487
19512
  const label2 = agentLabel(exec.agent, exec.mcpServer);
19488
19513
  const tool = (exec.tool ?? "").slice(0, 16);
19489
- const duration = typeof exec.durationMs === "number" ? chalk34.dim(` (${exec.durationMs}ms)`) : "";
19514
+ const duration = typeof exec.durationMs === "number" ? chalk35.dim(` (${exec.durationMs}ms)`) : "";
19490
19515
  console.log(
19491
- `${chalk34.gray(time)} ${arrow} ${label2}${chalk34.dim(tool)}${chalk34.dim(" completed")}${duration}`
19516
+ `${chalk35.gray(time)} ${arrow} ${label2}${chalk35.dim(tool)}${chalk35.dim(" completed")}${duration}`
19492
19517
  );
19493
19518
  }
19494
19519
  }
19495
19520
  req.on("error", (err2) => {
19496
19521
  const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
19497
- console.error(chalk34.red(`
19522
+ console.error(chalk35.red(`
19498
19523
  \u274C ${msg}`));
19499
19524
  process.exit(1);
19500
19525
  });
@@ -19505,7 +19530,7 @@ var init_tail = __esm({
19505
19530
  "use strict";
19506
19531
  init_daemon2();
19507
19532
  init_daemon();
19508
- PID_FILE = path59.join(os53.homedir(), ".node9", "daemon.pid");
19533
+ PID_FILE = path61.join(os54.homedir(), ".node9", "daemon.pid");
19509
19534
  ICONS = {
19510
19535
  bash: "\u{1F4BB}",
19511
19536
  shell: "\u{1F4BB}",
@@ -19553,9 +19578,9 @@ __export(hud_exports, {
19553
19578
  main: () => main,
19554
19579
  renderEnvironmentLine: () => renderEnvironmentLine
19555
19580
  });
19556
- import fs62 from "fs";
19557
- import path60 from "path";
19558
- import os54 from "os";
19581
+ import fs65 from "fs";
19582
+ import path62 from "path";
19583
+ import os55 from "os";
19559
19584
  import http4 from "http";
19560
19585
  async function readStdin() {
19561
19586
  const chunks = [];
@@ -19631,9 +19656,9 @@ function formatTimeLeft(resetsAt) {
19631
19656
  return ` (${m}m left)`;
19632
19657
  }
19633
19658
  function safeReadJson(filePath) {
19634
- if (!fs62.existsSync(filePath)) return null;
19659
+ if (!fs65.existsSync(filePath)) return null;
19635
19660
  try {
19636
- return JSON.parse(fs62.readFileSync(filePath, "utf-8"));
19661
+ return JSON.parse(fs65.readFileSync(filePath, "utf-8"));
19637
19662
  } catch {
19638
19663
  return null;
19639
19664
  }
@@ -19654,12 +19679,12 @@ function countHooksInFile(filePath) {
19654
19679
  return Object.keys(cfg.hooks).length;
19655
19680
  }
19656
19681
  function countRulesInDir(rulesDir) {
19657
- if (!fs62.existsSync(rulesDir)) return 0;
19682
+ if (!fs65.existsSync(rulesDir)) return 0;
19658
19683
  let count = 0;
19659
19684
  try {
19660
- for (const entry of fs62.readdirSync(rulesDir, { withFileTypes: true })) {
19685
+ for (const entry of fs65.readdirSync(rulesDir, { withFileTypes: true })) {
19661
19686
  if (entry.isDirectory()) {
19662
- count += countRulesInDir(path60.join(rulesDir, entry.name));
19687
+ count += countRulesInDir(path62.join(rulesDir, entry.name));
19663
19688
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
19664
19689
  count++;
19665
19690
  }
@@ -19670,46 +19695,46 @@ function countRulesInDir(rulesDir) {
19670
19695
  }
19671
19696
  function isSamePath(a, b) {
19672
19697
  try {
19673
- return path60.resolve(a) === path60.resolve(b);
19698
+ return path62.resolve(a) === path62.resolve(b);
19674
19699
  } catch {
19675
19700
  return false;
19676
19701
  }
19677
19702
  }
19678
19703
  function countConfigs(cwd) {
19679
- const homeDir2 = os54.homedir();
19680
- const claudeDir = path60.join(homeDir2, ".claude");
19704
+ const homeDir2 = os55.homedir();
19705
+ const claudeDir = path62.join(homeDir2, ".claude");
19681
19706
  let claudeMdCount = 0;
19682
19707
  let rulesCount = 0;
19683
19708
  let hooksCount = 0;
19684
19709
  const userMcpServers = /* @__PURE__ */ new Set();
19685
19710
  const projectMcpServers = /* @__PURE__ */ new Set();
19686
- if (fs62.existsSync(path60.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
19687
- rulesCount += countRulesInDir(path60.join(claudeDir, "rules"));
19688
- const userSettings = path60.join(claudeDir, "settings.json");
19711
+ if (fs65.existsSync(path62.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
19712
+ rulesCount += countRulesInDir(path62.join(claudeDir, "rules"));
19713
+ const userSettings = path62.join(claudeDir, "settings.json");
19689
19714
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
19690
19715
  hooksCount += countHooksInFile(userSettings);
19691
- const userClaudeJson = path60.join(homeDir2, ".claude.json");
19716
+ const userClaudeJson = path62.join(homeDir2, ".claude.json");
19692
19717
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
19693
19718
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
19694
19719
  userMcpServers.delete(name);
19695
19720
  }
19696
19721
  if (cwd) {
19697
- if (fs62.existsSync(path60.join(cwd, "CLAUDE.md"))) claudeMdCount++;
19698
- if (fs62.existsSync(path60.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
19699
- const projectClaudeDir = path60.join(cwd, ".claude");
19722
+ if (fs65.existsSync(path62.join(cwd, "CLAUDE.md"))) claudeMdCount++;
19723
+ if (fs65.existsSync(path62.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
19724
+ const projectClaudeDir = path62.join(cwd, ".claude");
19700
19725
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
19701
19726
  if (!overlapsUserScope) {
19702
- if (fs62.existsSync(path60.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
19703
- rulesCount += countRulesInDir(path60.join(projectClaudeDir, "rules"));
19704
- const projSettings = path60.join(projectClaudeDir, "settings.json");
19727
+ if (fs65.existsSync(path62.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
19728
+ rulesCount += countRulesInDir(path62.join(projectClaudeDir, "rules"));
19729
+ const projSettings = path62.join(projectClaudeDir, "settings.json");
19705
19730
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
19706
19731
  hooksCount += countHooksInFile(projSettings);
19707
19732
  }
19708
- if (fs62.existsSync(path60.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
19709
- const localSettings = path60.join(projectClaudeDir, "settings.local.json");
19733
+ if (fs65.existsSync(path62.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
19734
+ const localSettings = path62.join(projectClaudeDir, "settings.local.json");
19710
19735
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
19711
19736
  hooksCount += countHooksInFile(localSettings);
19712
- const mcpJsonServers = getMcpServerNames(path60.join(cwd, ".mcp.json"));
19737
+ const mcpJsonServers = getMcpServerNames(path62.join(cwd, ".mcp.json"));
19713
19738
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
19714
19739
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
19715
19740
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -19742,12 +19767,12 @@ function readActiveShieldsHud() {
19742
19767
  return shieldsCache.value;
19743
19768
  }
19744
19769
  try {
19745
- const shieldsPath = path60.join(os54.homedir(), ".node9", "shields.json");
19746
- if (!fs62.existsSync(shieldsPath)) {
19770
+ const shieldsPath = path62.join(os55.homedir(), ".node9", "shields.json");
19771
+ if (!fs65.existsSync(shieldsPath)) {
19747
19772
  shieldsCache = { value: [], ts: now };
19748
19773
  return [];
19749
19774
  }
19750
- const parsed = JSON.parse(fs62.readFileSync(shieldsPath, "utf-8"));
19775
+ const parsed = JSON.parse(fs65.readFileSync(shieldsPath, "utf-8"));
19751
19776
  if (!Array.isArray(parsed.active)) {
19752
19777
  shieldsCache = { value: [], ts: now };
19753
19778
  return [];
@@ -19849,17 +19874,17 @@ function renderContextLine(stdin) {
19849
19874
  async function main() {
19850
19875
  try {
19851
19876
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
19852
- if (fs62.existsSync(path60.join(os54.homedir(), ".node9", "hud-debug"))) {
19877
+ if (fs65.existsSync(path62.join(os55.homedir(), ".node9", "hud-debug"))) {
19853
19878
  try {
19854
- const logPath = path60.join(os54.homedir(), ".node9", "hud-debug.log");
19879
+ const logPath = path62.join(os55.homedir(), ".node9", "hud-debug.log");
19855
19880
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
19856
19881
  let size = 0;
19857
19882
  try {
19858
- size = fs62.statSync(logPath).size;
19883
+ size = fs65.statSync(logPath).size;
19859
19884
  } catch {
19860
19885
  }
19861
19886
  if (size < MAX_LOG_SIZE) {
19862
- fs62.appendFileSync(
19887
+ fs65.appendFileSync(
19863
19888
  logPath,
19864
19889
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
19865
19890
  );
@@ -19879,12 +19904,12 @@ async function main() {
19879
19904
  const showEnvCounts = (() => {
19880
19905
  try {
19881
19906
  const cwd = stdin.cwd ?? process.cwd();
19882
- for (const configPath2 of [
19883
- path60.join(cwd, "node9.config.json"),
19884
- path60.join(os54.homedir(), ".node9", "config.json")
19907
+ for (const configPath of [
19908
+ path62.join(cwd, "node9.config.json"),
19909
+ path62.join(os55.homedir(), ".node9", "config.json")
19885
19910
  ]) {
19886
- if (!fs62.existsSync(configPath2)) continue;
19887
- const cfg = JSON.parse(fs62.readFileSync(configPath2, "utf-8"));
19911
+ if (!fs65.existsSync(configPath)) continue;
19912
+ const cfg = JSON.parse(fs65.readFileSync(configPath, "utf-8"));
19888
19913
  const hud = cfg.settings?.hud;
19889
19914
  if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
19890
19915
  }
@@ -19930,10 +19955,10 @@ init_core();
19930
19955
  init_setup();
19931
19956
  init_daemon2();
19932
19957
  import { Command } from "commander";
19933
- import chalk35 from "chalk";
19934
- import fs63 from "fs";
19935
- import path61 from "path";
19936
- import os55 from "os";
19958
+ import chalk36 from "chalk";
19959
+ import fs66 from "fs";
19960
+ import path63 from "path";
19961
+ import os56 from "os";
19937
19962
  import { spawn as spawn9 } from "child_process";
19938
19963
  import { confirm as confirm2 } from "@inquirer/prompts";
19939
19964
 
@@ -21562,9 +21587,135 @@ function registerLogCommand(program2) {
21562
21587
 
21563
21588
  // src/cli/commands/shield.ts
21564
21589
  init_shields();
21590
+ import chalk10 from "chalk";
21591
+ import fs46 from "fs";
21592
+
21593
+ // src/shields/build.ts
21594
+ function escapeRegex(s) {
21595
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
21596
+ }
21597
+ function slug(s) {
21598
+ return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "rule";
21599
+ }
21600
+ var B = "[\\s/\\\\]";
21601
+ var SEP = "[/\\\\]";
21602
+ function pathToRegexFragment(rawPath) {
21603
+ const tail = rawPath.trim().replace(/^~[\\/]?/, "").replace(/^\$\{?HOME\}?[\\/]?/, "").replace(/^\/(?:home|Users)\/[^\\/]+[\\/]?/, "").replace(/^[\\/]+/, "").replace(/[\\/]+$/, "");
21604
+ const segments = tail.split(/[\\/]+/).filter(Boolean).map(escapeRegex);
21605
+ if (segments.length === 0) return "";
21606
+ return `(^|${B})${segments.join(SEP)}(${B}|$)`;
21607
+ }
21608
+ function toolRule(tool, verdict, reason) {
21609
+ return {
21610
+ name: `${verdict}-${slug(tool)}`,
21611
+ tool,
21612
+ conditions: [],
21613
+ verdict,
21614
+ reason: reason ?? `${tool} is restricted by this shield`
21615
+ };
21616
+ }
21617
+ function pathRules(rawPath, verdict, reason) {
21618
+ const value = pathToRegexFragment(rawPath);
21619
+ if (!value) return [];
21620
+ const why = reason ?? `Accessing ${rawPath} is restricted by this shield`;
21621
+ const s = slug(rawPath);
21622
+ return [
21623
+ {
21624
+ name: `${verdict}-path-${s}-bash`,
21625
+ tool: "bash",
21626
+ conditions: [{ field: "command", op: "matches", value }],
21627
+ verdict,
21628
+ reason: why
21629
+ },
21630
+ {
21631
+ name: `${verdict}-path-${s}-anytool`,
21632
+ tool: "*",
21633
+ conditions: [{ field: "file_path", op: "matches", value }],
21634
+ verdict,
21635
+ reason: why
21636
+ }
21637
+ ];
21638
+ }
21639
+ function buildShield(input) {
21640
+ const smartRules = [
21641
+ ...(input.blockTools ?? []).map((t) => toolRule(t, "block")),
21642
+ ...(input.reviewTools ?? []).map((t) => toolRule(t, "review")),
21643
+ ...(input.blockPaths ?? []).flatMap((p) => pathRules(p, "block")),
21644
+ ...(input.reviewPaths ?? []).flatMap((p) => pathRules(p, "review"))
21645
+ ];
21646
+ return {
21647
+ name: input.name,
21648
+ description: input.description ?? `Custom shield "${input.name}" created with node9 shield create`,
21649
+ aliases: input.aliases ?? [],
21650
+ smartRules,
21651
+ dangerousWords: []
21652
+ };
21653
+ }
21654
+
21655
+ // src/shields/create.ts
21656
+ init_dist();
21657
+ init_shields();
21658
+ init_audit();
21659
+ import fs45 from "fs";
21660
+ import path43 from "path";
21661
+ function builtinNames() {
21662
+ const names = /* @__PURE__ */ new Set();
21663
+ for (const def of Object.values(BUILTIN_SHIELDS)) {
21664
+ names.add(def.name.toLowerCase());
21665
+ for (const a of def.aliases ?? []) names.add(a.toLowerCase());
21666
+ }
21667
+ return names;
21668
+ }
21669
+ function createShield(def, opts = {}) {
21670
+ const name = def.name;
21671
+ if (builtinNames().has(name.toLowerCase())) {
21672
+ return {
21673
+ ok: false,
21674
+ error: `"${name}" is a built-in shield \u2014 choose a different name (a user shield with this name would shadow the built-in).`
21675
+ };
21676
+ }
21677
+ const filePath = path43.join(USER_SHIELDS_DIR_PATH, `${name}.json`);
21678
+ if (!opts.overwrite && fs45.existsSync(filePath)) {
21679
+ return {
21680
+ ok: false,
21681
+ error: `Shield "${name}" already exists at ${filePath}. Pass --overwrite to replace it.`
21682
+ };
21683
+ }
21684
+ if (def.smartRules.length === 0) {
21685
+ return {
21686
+ ok: false,
21687
+ error: "Shield has no rules \u2014 add at least one --block/--review tool or path."
21688
+ };
21689
+ }
21690
+ if (opts.viaMcp && def.smartRules.some((r) => r.verdict === "allow")) {
21691
+ return {
21692
+ ok: false,
21693
+ error: "allow-verdict rules are not permitted over MCP (they would weaken node9). Use the CLI."
21694
+ };
21695
+ }
21696
+ const v = validateShieldDefinition(def);
21697
+ if ("error" in v) {
21698
+ return { ok: false, error: v.error };
21699
+ }
21700
+ installShield(name, def);
21701
+ let enabled = false;
21702
+ if (opts.enable) {
21703
+ const active = readActiveShields();
21704
+ if (!active.includes(name)) writeActiveShields([...active, name]);
21705
+ enabled = true;
21706
+ }
21707
+ appendConfigAudit({
21708
+ event: "shield-create",
21709
+ shield: name,
21710
+ via: opts.viaMcp ? "mcp" : "cli",
21711
+ enabled
21712
+ });
21713
+ return { ok: true, path: filePath, enabled, ruleCount: def.smartRules.length };
21714
+ }
21715
+
21716
+ // src/cli/commands/shield.ts
21565
21717
  init_audit();
21566
21718
  init_config();
21567
- import chalk10 from "chalk";
21568
21719
 
21569
21720
  // src/utils/https-fetch.ts
21570
21721
  import https4 from "https";
@@ -21867,6 +22018,73 @@ function registerShieldCommand(program2) {
21867
22018
  process.exit(1);
21868
22019
  });
21869
22020
  });
22021
+ const collect = (val, prev) => [...prev, val];
22022
+ shieldCmd.command("create <name>").description("Author a new shield from block/review tools and paths").option("--desc <text>", "Shield description").option("--block-tool <tool>", "Block a tool entirely (repeatable)", collect, []).option("--review-tool <tool>", "Require approval for a tool (repeatable)", collect, []).option("--block-path <path>", "Block access to a path (repeatable)", collect, []).option("--review-path <path>", "Require approval to access a path (repeatable)", collect, []).option("--from-file <path>", "Build from a shield JSON file (overrides inline flags)").option("--enable", "Activate the shield immediately").option("--overwrite", "Replace an existing user shield of the same name").action(
22023
+ (name, opts) => {
22024
+ if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
22025
+ console.error(
22026
+ chalk10.red(
22027
+ `
22028
+ \u274C Invalid shield name: only alphanumeric characters, hyphens, and underscores are allowed
22029
+ `
22030
+ )
22031
+ );
22032
+ process.exit(1);
22033
+ }
22034
+ let def;
22035
+ if (opts.fromFile) {
22036
+ let raw;
22037
+ try {
22038
+ raw = JSON.parse(fs46.readFileSync(opts.fromFile, "utf-8"));
22039
+ } catch (err2) {
22040
+ console.error(
22041
+ chalk10.red(`
22042
+ \u274C Could not read/parse ${opts.fromFile}: ${String(err2)}
22043
+ `)
22044
+ );
22045
+ process.exit(1);
22046
+ return;
22047
+ }
22048
+ def = { ...raw, name };
22049
+ } else {
22050
+ def = buildShield({
22051
+ name,
22052
+ description: opts.desc,
22053
+ blockTools: opts.blockTool,
22054
+ reviewTools: opts.reviewTool,
22055
+ blockPaths: opts.blockPath,
22056
+ reviewPaths: opts.reviewPath
22057
+ });
22058
+ }
22059
+ const res = createShield(def, {
22060
+ enable: opts.enable,
22061
+ overwrite: opts.overwrite,
22062
+ viaMcp: false
22063
+ });
22064
+ if (!res.ok) {
22065
+ console.error(chalk10.red(`
22066
+ \u274C ${res.error}
22067
+ `));
22068
+ process.exit(1);
22069
+ return;
22070
+ }
22071
+ console.log(
22072
+ chalk10.green(`
22073
+ \u2705 Shield "${name}" created`) + chalk10.gray(` \u2014 ${res.ruleCount} rule(s) \u2192 ${res.path}`)
22074
+ );
22075
+ if (res.enabled) {
22076
+ console.log(chalk10.gray(` Active now.`));
22077
+ } else {
22078
+ console.log(
22079
+ chalk10.gray(` Activate it with: ${chalk10.cyan(`node9 shield enable ${name}`)}`)
22080
+ );
22081
+ }
22082
+ console.log(
22083
+ chalk10.gray(` Preview a call: ${chalk10.cyan(`node9 explain bash "<command>"`)}
22084
+ `)
22085
+ );
22086
+ }
22087
+ );
21870
22088
  }
21871
22089
  function registerConfigShowCommand(program2) {
21872
22090
  program2.command("config show").description(
@@ -21940,8 +22158,8 @@ init_daemon();
21940
22158
  init_config();
21941
22159
  init_agent_wiring();
21942
22160
  import chalk11 from "chalk";
21943
- import fs45 from "fs";
21944
- import path43 from "path";
22161
+ import fs47 from "fs";
22162
+ import path44 from "path";
21945
22163
  import os40 from "os";
21946
22164
  import { execSync } from "child_process";
21947
22165
  function registerDoctorCommand(program2, version2) {
@@ -21992,10 +22210,10 @@ function registerDoctorCommand(program2, version2) {
21992
22210
  );
21993
22211
  }
21994
22212
  section("Configuration");
21995
- const globalConfigPath = path43.join(homeDir2, ".node9", "config.json");
21996
- if (fs45.existsSync(globalConfigPath)) {
22213
+ const globalConfigPath = path44.join(homeDir2, ".node9", "config.json");
22214
+ if (fs47.existsSync(globalConfigPath)) {
21997
22215
  try {
21998
- JSON.parse(fs45.readFileSync(globalConfigPath, "utf-8"));
22216
+ JSON.parse(fs47.readFileSync(globalConfigPath, "utf-8"));
21999
22217
  pass("~/.node9/config.json found and valid");
22000
22218
  } catch {
22001
22219
  fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
@@ -22003,10 +22221,10 @@ function registerDoctorCommand(program2, version2) {
22003
22221
  } else {
22004
22222
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
22005
22223
  }
22006
- const projectConfigPath = path43.join(process.cwd(), "node9.config.json");
22007
- if (fs45.existsSync(projectConfigPath)) {
22224
+ const projectConfigPath = path44.join(process.cwd(), "node9.config.json");
22225
+ if (fs47.existsSync(projectConfigPath)) {
22008
22226
  try {
22009
- JSON.parse(fs45.readFileSync(projectConfigPath, "utf-8"));
22227
+ JSON.parse(fs47.readFileSync(projectConfigPath, "utf-8"));
22010
22228
  pass("node9.config.json found and valid (project)");
22011
22229
  } catch {
22012
22230
  fail(
@@ -22015,8 +22233,8 @@ function registerDoctorCommand(program2, version2) {
22015
22233
  );
22016
22234
  }
22017
22235
  }
22018
- const credsPath = path43.join(homeDir2, ".node9", "credentials.json");
22019
- if (fs45.existsSync(credsPath)) {
22236
+ const credsPath = path44.join(homeDir2, ".node9", "credentials.json");
22237
+ if (fs47.existsSync(credsPath)) {
22020
22238
  pass("Cloud credentials found (~/.node9/credentials.json)");
22021
22239
  } else {
22022
22240
  warn(
@@ -22060,7 +22278,7 @@ function registerDoctorCommand(program2, version2) {
22060
22278
  try {
22061
22279
  const { shipLagBytes: shipLagBytes2, readWatermark: readWatermark2, AUDIT_SHIP_WATERMARK: AUDIT_SHIP_WATERMARK2 } = await Promise.resolve().then(() => (init_audit_shipper(), audit_shipper_exports));
22062
22280
  const cfg = getConfig();
22063
- const creds = fs45.existsSync(path43.join(os40.homedir(), ".node9", "credentials.json"));
22281
+ const creds = fs47.existsSync(path44.join(os40.homedir(), ".node9", "credentials.json"));
22064
22282
  if (!creds) {
22065
22283
  warn("Not logged in \u2014 audit rows stay local", "Run: node9 login <api-key>");
22066
22284
  } else if (!cfg.settings.approvers.cloud) {
@@ -22110,8 +22328,8 @@ function registerDoctorCommand(program2, version2) {
22110
22328
 
22111
22329
  // src/cli/commands/audit.ts
22112
22330
  import chalk12 from "chalk";
22113
- import fs46 from "fs";
22114
- import path44 from "path";
22331
+ import fs48 from "fs";
22332
+ import path45 from "path";
22115
22333
  import os41 from "os";
22116
22334
  function formatRelativeTime(timestamp) {
22117
22335
  const diff = Date.now() - new Date(timestamp).getTime();
@@ -22125,14 +22343,14 @@ function formatRelativeTime(timestamp) {
22125
22343
  }
22126
22344
  function registerAuditCommand(program2) {
22127
22345
  program2.command("audit").description("View local execution audit log").option("--tail <n>", "Number of entries to show", "20").option("--tool <pattern>", "Filter by tool name (substring match)").option("--deny", "Show only denied actions").option("--json", "Output raw JSON").action((options) => {
22128
- const logPath = path44.join(os41.homedir(), ".node9", "audit.log");
22129
- if (!fs46.existsSync(logPath)) {
22346
+ const logPath = path45.join(os41.homedir(), ".node9", "audit.log");
22347
+ if (!fs48.existsSync(logPath)) {
22130
22348
  console.log(
22131
22349
  chalk12.yellow("No audit logs found. Run node9 with an agent to generate entries.")
22132
22350
  );
22133
22351
  return;
22134
22352
  }
22135
- const raw = fs46.readFileSync(logPath, "utf-8");
22353
+ const raw = fs48.readFileSync(logPath, "utf-8");
22136
22354
  const lines = raw.split("\n").filter((l) => l.trim() !== "");
22137
22355
  let entries = lines.flatMap((line) => {
22138
22356
  try {
@@ -22191,9 +22409,9 @@ import chalk13 from "chalk";
22191
22409
  init_costSync();
22192
22410
  init_litellm();
22193
22411
  init_cost_codex();
22194
- import fs47 from "fs";
22412
+ import fs49 from "fs";
22195
22413
  import os42 from "os";
22196
- import path45 from "path";
22414
+ import path46 from "path";
22197
22415
  var TEST_COMMAND_RE3 = /(?:^|\s)(npm\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|yarn\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|vitest|jest|mocha|pytest|py\.test|cargo\s+test|go\s+test|bundle\s+exec\s+rspec|rspec|phpunit|dotnet\s+test)\b/i;
22198
22416
  function buildTestTimestamps(allEntries) {
22199
22417
  const testTs = /* @__PURE__ */ new Set();
@@ -22273,8 +22491,8 @@ function getDateRange(period, now) {
22273
22491
  }
22274
22492
  }
22275
22493
  function parseAuditLog(logPath) {
22276
- if (!fs47.existsSync(logPath)) return [];
22277
- const raw = fs47.readFileSync(logPath, "utf-8");
22494
+ if (!fs49.existsSync(logPath)) return [];
22495
+ const raw = fs49.readFileSync(logPath, "utf-8");
22278
22496
  return raw.split("\n").flatMap((line) => {
22279
22497
  if (!line.trim()) return [];
22280
22498
  try {
@@ -22321,25 +22539,25 @@ function freezeClaudeCost(acc) {
22321
22539
  };
22322
22540
  }
22323
22541
  function processClaudeCostProject(proj, projectsDir, start, end, acc) {
22324
- const projPath = path45.join(projectsDir, proj);
22542
+ const projPath = path46.join(projectsDir, proj);
22325
22543
  let files;
22326
22544
  try {
22327
- const stat = fs47.statSync(projPath);
22545
+ const stat = fs49.statSync(projPath);
22328
22546
  if (!stat.isDirectory()) return;
22329
- files = fs47.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
22547
+ files = fs49.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
22330
22548
  } catch {
22331
22549
  return;
22332
22550
  }
22333
22551
  const startMs = start.getTime();
22334
22552
  for (const file of files) {
22335
- const filePath = path45.join(projPath, file);
22553
+ const filePath = path46.join(projPath, file);
22336
22554
  try {
22337
- if (fs47.statSync(filePath).mtimeMs < startMs) continue;
22555
+ if (fs49.statSync(filePath).mtimeMs < startMs) continue;
22338
22556
  } catch {
22339
22557
  continue;
22340
22558
  }
22341
22559
  try {
22342
- const raw = fs47.readFileSync(filePath, "utf-8");
22560
+ const raw = fs49.readFileSync(filePath, "utf-8");
22343
22561
  for (const line of raw.split("\n")) {
22344
22562
  if (!line.trim()) continue;
22345
22563
  let entry;
@@ -22389,10 +22607,10 @@ function processClaudeCostProject(proj, projectsDir, start, end, acc) {
22389
22607
  }
22390
22608
  function loadClaudeCost(start, end, projectsDir) {
22391
22609
  const acc = emptyClaudeCostAccumulator();
22392
- if (!fs47.existsSync(projectsDir)) return freezeClaudeCost(acc);
22610
+ if (!fs49.existsSync(projectsDir)) return freezeClaudeCost(acc);
22393
22611
  let dirs;
22394
22612
  try {
22395
- dirs = fs47.readdirSync(projectsDir);
22613
+ dirs = fs49.readdirSync(projectsDir);
22396
22614
  } catch {
22397
22615
  return freezeClaudeCost(acc);
22398
22616
  }
@@ -22404,7 +22622,7 @@ function loadClaudeCost(start, end, projectsDir) {
22404
22622
  function processCodexCostFile(filePath, start, end, acc) {
22405
22623
  let lines;
22406
22624
  try {
22407
- lines = fs47.readFileSync(filePath, "utf-8").split("\n");
22625
+ lines = fs49.readFileSync(filePath, "utf-8").split("\n");
22408
22626
  } catch {
22409
22627
  return;
22410
22628
  }
@@ -22459,31 +22677,31 @@ function processCodexCostFile(filePath, start, end, acc) {
22459
22677
  }
22460
22678
  function listCodexSessionFiles2(sessionsBase) {
22461
22679
  const jsonlFiles = [];
22462
- if (!fs47.existsSync(sessionsBase)) return jsonlFiles;
22680
+ if (!fs49.existsSync(sessionsBase)) return jsonlFiles;
22463
22681
  try {
22464
- for (const year of fs47.readdirSync(sessionsBase)) {
22465
- const yearPath = path45.join(sessionsBase, year);
22682
+ for (const year of fs49.readdirSync(sessionsBase)) {
22683
+ const yearPath = path46.join(sessionsBase, year);
22466
22684
  try {
22467
- if (!fs47.statSync(yearPath).isDirectory()) continue;
22685
+ if (!fs49.statSync(yearPath).isDirectory()) continue;
22468
22686
  } catch {
22469
22687
  continue;
22470
22688
  }
22471
- for (const month of fs47.readdirSync(yearPath)) {
22472
- const monthPath = path45.join(yearPath, month);
22689
+ for (const month of fs49.readdirSync(yearPath)) {
22690
+ const monthPath = path46.join(yearPath, month);
22473
22691
  try {
22474
- if (!fs47.statSync(monthPath).isDirectory()) continue;
22692
+ if (!fs49.statSync(monthPath).isDirectory()) continue;
22475
22693
  } catch {
22476
22694
  continue;
22477
22695
  }
22478
- for (const day of fs47.readdirSync(monthPath)) {
22479
- const dayPath = path45.join(monthPath, day);
22696
+ for (const day of fs49.readdirSync(monthPath)) {
22697
+ const dayPath = path46.join(monthPath, day);
22480
22698
  try {
22481
- if (!fs47.statSync(dayPath).isDirectory()) continue;
22699
+ if (!fs49.statSync(dayPath).isDirectory()) continue;
22482
22700
  } catch {
22483
22701
  continue;
22484
22702
  }
22485
- for (const file of fs47.readdirSync(dayPath)) {
22486
- if (file.endsWith(".jsonl")) jsonlFiles.push(path45.join(dayPath, file));
22703
+ for (const file of fs49.readdirSync(dayPath)) {
22704
+ if (file.endsWith(".jsonl")) jsonlFiles.push(path46.join(dayPath, file));
22487
22705
  }
22488
22706
  }
22489
22707
  }
@@ -22548,13 +22766,13 @@ function freezeGeminiCost(acc) {
22548
22766
  function processGeminiCostFile(filePath, projectKey, start, end, acc) {
22549
22767
  const startMs = start.getTime();
22550
22768
  try {
22551
- if (fs47.statSync(filePath).mtimeMs < startMs) return;
22769
+ if (fs49.statSync(filePath).mtimeMs < startMs) return;
22552
22770
  } catch {
22553
22771
  return;
22554
22772
  }
22555
22773
  let raw;
22556
22774
  try {
22557
- raw = fs47.readFileSync(filePath, "utf-8");
22775
+ raw = fs49.readFileSync(filePath, "utf-8");
22558
22776
  } catch {
22559
22777
  return;
22560
22778
  }
@@ -22603,30 +22821,30 @@ function listGeminiSessionFiles2(geminiTmpDir2) {
22603
22821
  const out = [];
22604
22822
  let dirs;
22605
22823
  try {
22606
- if (!fs47.statSync(geminiTmpDir2).isDirectory()) return out;
22607
- dirs = fs47.readdirSync(geminiTmpDir2);
22824
+ if (!fs49.statSync(geminiTmpDir2).isDirectory()) return out;
22825
+ dirs = fs49.readdirSync(geminiTmpDir2);
22608
22826
  } catch {
22609
22827
  return out;
22610
22828
  }
22611
22829
  for (const proj of dirs) {
22612
- const chatsDir = path45.join(geminiTmpDir2, proj, "chats");
22830
+ const chatsDir = path46.join(geminiTmpDir2, proj, "chats");
22613
22831
  let files;
22614
22832
  try {
22615
- if (!fs47.statSync(chatsDir).isDirectory()) continue;
22616
- files = fs47.readdirSync(chatsDir);
22833
+ if (!fs49.statSync(chatsDir).isDirectory()) continue;
22834
+ files = fs49.readdirSync(chatsDir);
22617
22835
  } catch {
22618
22836
  continue;
22619
22837
  }
22620
22838
  for (const f of files) {
22621
22839
  if (!f.endsWith(".jsonl")) continue;
22622
- out.push({ projectKey: proj, file: path45.join(chatsDir, f) });
22840
+ out.push({ projectKey: proj, file: path46.join(chatsDir, f) });
22623
22841
  }
22624
22842
  }
22625
22843
  return out;
22626
22844
  }
22627
22845
  function loadGeminiCost(start, end, geminiTmpDir2) {
22628
22846
  const acc = emptyGeminiAccumulator();
22629
- if (!fs47.existsSync(geminiTmpDir2)) return freezeGeminiCost(acc);
22847
+ if (!fs49.existsSync(geminiTmpDir2)) return freezeGeminiCost(acc);
22630
22848
  for (const { projectKey, file } of listGeminiSessionFiles2(geminiTmpDir2)) {
22631
22849
  processGeminiCostFile(file, projectKey, start, end, acc);
22632
22850
  }
@@ -22634,11 +22852,11 @@ function loadGeminiCost(start, end, geminiTmpDir2) {
22634
22852
  }
22635
22853
  function aggregateReportFromAudit(period, opts = {}) {
22636
22854
  const now = opts.now ?? /* @__PURE__ */ new Date();
22637
- const auditLogPath = opts.auditLogPath ?? path45.join(os42.homedir(), ".node9", "audit.log");
22638
- const claudeProjectsDir = opts.claudeProjectsDir ?? path45.join(os42.homedir(), ".claude", "projects");
22639
- const codexSessionsDir2 = opts.codexSessionsDir ?? path45.join(os42.homedir(), ".codex", "sessions");
22640
- const geminiTmpDir2 = opts.geminiTmpDir ?? path45.join(os42.homedir(), ".gemini", "tmp");
22641
- const hasAuditFile = fs47.existsSync(auditLogPath);
22855
+ const auditLogPath = opts.auditLogPath ?? path46.join(os42.homedir(), ".node9", "audit.log");
22856
+ const claudeProjectsDir = opts.claudeProjectsDir ?? path46.join(os42.homedir(), ".claude", "projects");
22857
+ const codexSessionsDir2 = opts.codexSessionsDir ?? path46.join(os42.homedir(), ".codex", "sessions");
22858
+ const geminiTmpDir2 = opts.geminiTmpDir ?? path46.join(os42.homedir(), ".gemini", "tmp");
22859
+ const hasAuditFile = fs49.existsSync(auditLogPath);
22642
22860
  const allEntries = opts.preloadedAuditEntries ?? parseAuditLog(auditLogPath);
22643
22861
  const unackedDlp = allEntries.filter((e) => e.source === "response-dlp");
22644
22862
  const { start, end } = getDateRange(period, now);
@@ -23337,8 +23555,8 @@ init_core();
23337
23555
  init_daemon();
23338
23556
  init_agent_wiring();
23339
23557
  import chalk15 from "chalk";
23340
- import fs48 from "fs";
23341
- import path46 from "path";
23558
+ import fs50 from "fs";
23559
+ import path47 from "path";
23342
23560
  import os43 from "os";
23343
23561
  function printAgentSection(label2, hookPairs, wrapped) {
23344
23562
  console.log(chalk15.bold(` ${label2}`));
@@ -23393,13 +23611,13 @@ function registerStatusCommand(program2) {
23393
23611
  console.log("");
23394
23612
  const modeLabel = settings.mode === "audit" ? chalk15.blue("audit") : settings.mode === "strict" ? chalk15.red("strict") : chalk15.white("standard");
23395
23613
  console.log(` Mode: ${modeLabel}`);
23396
- const projectConfig = path46.join(process.cwd(), "node9.config.json");
23397
- const globalConfig = path46.join(os43.homedir(), ".node9", "config.json");
23614
+ const projectConfig = path47.join(process.cwd(), "node9.config.json");
23615
+ const globalConfig = path47.join(os43.homedir(), ".node9", "config.json");
23398
23616
  console.log(
23399
- ` Local: ${fs48.existsSync(projectConfig) ? chalk15.green("Active (node9.config.json)") : chalk15.gray("Not present")}`
23617
+ ` Local: ${fs50.existsSync(projectConfig) ? chalk15.green("Active (node9.config.json)") : chalk15.gray("Not present")}`
23400
23618
  );
23401
23619
  console.log(
23402
- ` Global: ${fs48.existsSync(globalConfig) ? chalk15.green("Active (~/.node9/config.json)") : chalk15.gray("Not present")}`
23620
+ ` Global: ${fs50.existsSync(globalConfig) ? chalk15.green("Active (~/.node9/config.json)") : chalk15.gray("Not present")}`
23403
23621
  );
23404
23622
  if (mergedConfig.policy.sandboxPaths.length > 0) {
23405
23623
  console.log(
@@ -23445,8 +23663,8 @@ init_setup();
23445
23663
  init_shields();
23446
23664
  init_service();
23447
23665
  import chalk16 from "chalk";
23448
- import fs49 from "fs";
23449
- import path47 from "path";
23666
+ import fs51 from "fs";
23667
+ import path48 from "path";
23450
23668
  import os44 from "os";
23451
23669
  import https5 from "https";
23452
23670
  var DEFAULT_SHIELDS = ["bash-safe", "filesystem", "project-jail"];
@@ -23533,32 +23751,32 @@ function registerInitCommand(program2) {
23533
23751
  }
23534
23752
  console.log("");
23535
23753
  }
23536
- const configPath2 = path47.join(os44.homedir(), ".node9", "config.json");
23537
- const isFirstInstall = !fs49.existsSync(configPath2);
23538
- if (fs49.existsSync(configPath2) && !options.force) {
23754
+ const configPath = path48.join(os44.homedir(), ".node9", "config.json");
23755
+ const isFirstInstall = !fs51.existsSync(configPath);
23756
+ if (fs51.existsSync(configPath) && !options.force) {
23539
23757
  try {
23540
- const existing = JSON.parse(fs49.readFileSync(configPath2, "utf-8"));
23758
+ const existing = JSON.parse(fs51.readFileSync(configPath, "utf-8"));
23541
23759
  const settings = existing.settings ?? {};
23542
23760
  if (settings.mode !== chosenMode) {
23543
23761
  settings.mode = chosenMode;
23544
23762
  existing.settings = settings;
23545
- fs49.writeFileSync(configPath2, JSON.stringify(existing, null, 2) + "\n");
23763
+ fs51.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
23546
23764
  console.log(chalk16.green(`\u2705 Mode updated: ${chosenMode}`));
23547
23765
  } else {
23548
- console.log(chalk16.blue(`\u2139\uFE0F Config already exists: ${configPath2}`));
23766
+ console.log(chalk16.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
23549
23767
  }
23550
23768
  } catch {
23551
- console.log(chalk16.blue(`\u2139\uFE0F Config already exists: ${configPath2}`));
23769
+ console.log(chalk16.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
23552
23770
  }
23553
23771
  } else {
23554
23772
  const configToSave = {
23555
23773
  ...DEFAULT_CONFIG,
23556
23774
  settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
23557
23775
  };
23558
- const dir = path47.dirname(configPath2);
23559
- if (!fs49.existsSync(dir)) fs49.mkdirSync(dir, { recursive: true });
23560
- fs49.writeFileSync(configPath2, JSON.stringify(configToSave, null, 2) + "\n");
23561
- console.log(chalk16.green(`\u2705 Config created: ${configPath2}`));
23776
+ const dir = path48.dirname(configPath);
23777
+ if (!fs51.existsSync(dir)) fs51.mkdirSync(dir, { recursive: true });
23778
+ fs51.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
23779
+ console.log(chalk16.green(`\u2705 Config created: ${configPath}`));
23562
23780
  console.log(chalk16.gray(` Mode: ${chosenMode}`));
23563
23781
  }
23564
23782
  if (options.skipSetup) return;
@@ -23662,7 +23880,7 @@ function registerInitCommand(program2) {
23662
23880
  }
23663
23881
 
23664
23882
  // src/cli/commands/undo.ts
23665
- import path48 from "path";
23883
+ import path49 from "path";
23666
23884
  import chalk18 from "chalk";
23667
23885
 
23668
23886
  // src/tui/undo-navigator.ts
@@ -23821,7 +24039,7 @@ function findMatchingCwd(startDir, history) {
23821
24039
  let dir = startDir;
23822
24040
  while (true) {
23823
24041
  if (cwds.has(dir)) return dir;
23824
- const parent = path48.dirname(dir);
24042
+ const parent = path49.dirname(dir);
23825
24043
  if (parent === dir) return null;
23826
24044
  dir = parent;
23827
24045
  }
@@ -24456,13 +24674,83 @@ function registerMcpGatewayCommand(program2) {
24456
24674
 
24457
24675
  // src/mcp-server/index.ts
24458
24676
  import readline5 from "readline";
24459
- import fs50 from "fs";
24460
- import os45 from "os";
24461
- import path49 from "path";
24677
+ import fs53 from "fs";
24678
+ import os46 from "os";
24679
+ import path51 from "path";
24462
24680
  import { spawnSync as spawnSync4 } from "child_process";
24463
24681
  init_core();
24464
24682
  init_daemon();
24465
24683
  init_shields();
24684
+
24685
+ // src/auth/egress-config.ts
24686
+ import fs52 from "fs";
24687
+ import os45 from "os";
24688
+ import path50 from "path";
24689
+ var DEFAULT_EGRESS = {
24690
+ enabled: false,
24691
+ mode: "review",
24692
+ allow: [],
24693
+ deny: [],
24694
+ allowPrivate: true
24695
+ };
24696
+ function egressConfigPath() {
24697
+ return path50.join(os45.homedir(), ".node9", "config.json");
24698
+ }
24699
+ function readEgressRawConfig() {
24700
+ let text;
24701
+ try {
24702
+ text = fs52.readFileSync(egressConfigPath(), "utf8");
24703
+ } catch (err2) {
24704
+ if (err2.code === "ENOENT") return {};
24705
+ throw err2;
24706
+ }
24707
+ try {
24708
+ return JSON.parse(text);
24709
+ } catch {
24710
+ throw new Error(
24711
+ `${egressConfigPath()} is not valid JSON \u2014 fix it before changing egress (refusing to overwrite).`
24712
+ );
24713
+ }
24714
+ }
24715
+ function writeEgressRawConfig(config) {
24716
+ const p = egressConfigPath();
24717
+ fs52.mkdirSync(path50.dirname(p), { recursive: true });
24718
+ fs52.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
24719
+ }
24720
+ function applyEgress(config, change) {
24721
+ const policy = config.policy = config.policy ?? {};
24722
+ const existing = policy.egress ?? {};
24723
+ policy.egress = { ...DEFAULT_EGRESS, ...existing, ...change };
24724
+ return config;
24725
+ }
24726
+ function getEgress() {
24727
+ const raw = readEgressRawConfig();
24728
+ const existing = raw.policy?.egress ?? {};
24729
+ return { ...DEFAULT_EGRESS, ...existing };
24730
+ }
24731
+ function setEgress(change) {
24732
+ const config = readEgressRawConfig();
24733
+ applyEgress(config, change);
24734
+ writeEgressRawConfig(config);
24735
+ }
24736
+ function addEgressHost(list, host) {
24737
+ const config = readEgressRawConfig();
24738
+ const existing = config.policy?.egress ?? {};
24739
+ const current = { ...DEFAULT_EGRESS, ...existing };
24740
+ const updated = current[list].includes(host) ? current[list] : [...current[list], host];
24741
+ applyEgress(config, { [list]: updated });
24742
+ writeEgressRawConfig(config);
24743
+ }
24744
+ function normalizeEgressHost(host) {
24745
+ return host.trim().toLowerCase();
24746
+ }
24747
+ var EGRESS_HOST_RE = /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/;
24748
+ function isValidEgressHost(host) {
24749
+ return EGRESS_HOST_RE.test(host);
24750
+ }
24751
+
24752
+ // src/mcp-server/index.ts
24753
+ init_dist();
24466
24754
  function ok(id, result) {
24467
24755
  return JSON.stringify({ jsonrpc: "2.0", id: id ?? null, result });
24468
24756
  }
@@ -24473,12 +24761,20 @@ var TOOL_CAPABILITY = {
24473
24761
  // weaken — gated over MCP
24474
24762
  node9_shield_disable: "weaken",
24475
24763
  node9_approver_set: "weaken",
24764
+ // NOTE: egress LOOSENING (allow a host / turn egress off) is intentionally NOT
24765
+ // exposed over MCP — it has no legitimate agent use case (it's exactly the
24766
+ // exfil-exit the egress gate exists to prevent) and would be attack surface
24767
+ // even gated. A human loosens egress at the CLI: `node9 egress allow|off`.
24476
24768
  // add / restorative — always allowed
24477
24769
  node9_shield_enable: "add",
24478
24770
  node9_rule_add: "add",
24479
24771
  // already block/review-only — handleRuleAdd rejects "allow"
24480
24772
  node9_undo_revert: "add",
24481
24773
  // restorative
24774
+ node9_egress_protect: "add",
24775
+ // enable/strengthen egress (monotonic — never reduces)
24776
+ node9_egress_deny: "add",
24777
+ // add a deny host (deny always wins)
24482
24778
  // readonly
24483
24779
  node9_status: "readonly",
24484
24780
  node9_config_get: "readonly",
@@ -24492,7 +24788,8 @@ var TOOL_CAPABILITY = {
24492
24788
  node9_shield_list: "readonly",
24493
24789
  node9_approver_list: "readonly",
24494
24790
  node9_undo_list: "readonly",
24495
- node9_undo_detail: "readonly"
24791
+ node9_undo_detail: "readonly",
24792
+ node9_egress_status: "readonly"
24496
24793
  };
24497
24794
  function capabilityOf(tool) {
24498
24795
  return TOOL_CAPABILITY[tool] ?? "readonly";
@@ -24748,7 +25045,41 @@ var TOOLS = [
24748
25045
  },
24749
25046
  required: ["name", "tool", "field", "pattern", "verdict", "reason"]
24750
25047
  }
25048
+ },
25049
+ {
25050
+ name: "node9_egress_status",
25051
+ description: "Show egress (outbound network) control: whether it is enabled, the mode (off / review / block), and your allow + deny host lists. Common dev/LLM hosts (github, npm, pypi, anthropic, \u2026) are always allowed by a built-in list. Read-only.",
25052
+ inputSchema: { type: "object", properties: {}, required: [] }
25053
+ },
25054
+ {
25055
+ name: "node9_egress_protect",
25056
+ description: 'Turn on egress control (or strengthen it). mode="review" prompts on unknown hosts; mode="block" hard-blocks them. Monotonic \u2014 it only ever ADDS protection and never reduces it (a request for review will not downgrade an existing block). Only adds protection, so it is always allowed over MCP.',
25057
+ inputSchema: {
25058
+ type: "object",
25059
+ properties: {
25060
+ mode: {
25061
+ type: "string",
25062
+ enum: ["review", "block"],
25063
+ description: 'Enforcement to apply. "review" = prompt, "block" = deny. Defaults to review.'
25064
+ }
25065
+ },
25066
+ required: []
25067
+ }
25068
+ },
25069
+ {
25070
+ name: "node9_egress_deny",
25071
+ description: "Add a host to the egress deny list (deny always wins over allow). Only adds protection, so it is always allowed over MCP. Host is an FQDN or wildcard glob (e.g. evil.com or *.evil.com).",
25072
+ inputSchema: {
25073
+ type: "object",
25074
+ properties: {
25075
+ host: { type: "string", description: "Host to deny (FQDN or *.glob)." }
25076
+ },
25077
+ required: ["host"]
25078
+ }
24751
25079
  }
25080
+ // Egress LOOSENING (allow a host / turn egress off) is deliberately CLI-only —
25081
+ // see the note in TOOL_CAPABILITY. The agent can see and tighten egress over
25082
+ // MCP, but never loosen it.
24752
25083
  ];
24753
25084
  function handleStatus() {
24754
25085
  const config = getConfig();
@@ -24769,13 +25100,13 @@ function handleStatus() {
24769
25100
  lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
24770
25101
  lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
24771
25102
  lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
24772
- const projectConfig = path49.join(process.cwd(), "node9.config.json");
24773
- const globalConfig = path49.join(os45.homedir(), ".node9", "config.json");
25103
+ const projectConfig = path51.join(process.cwd(), "node9.config.json");
25104
+ const globalConfig = path51.join(os46.homedir(), ".node9", "config.json");
24774
25105
  lines.push(
24775
- `Project config (node9.config.json): ${fs50.existsSync(projectConfig) ? "present" : "not found"}`
25106
+ `Project config (node9.config.json): ${fs53.existsSync(projectConfig) ? "present" : "not found"}`
24776
25107
  );
24777
25108
  lines.push(
24778
- `Global config (~/.node9/config.json): ${fs50.existsSync(globalConfig) ? "present" : "not found"}`
25109
+ `Global config (~/.node9/config.json): ${fs53.existsSync(globalConfig) ? "present" : "not found"}`
24779
25110
  );
24780
25111
  return lines.join("\n");
24781
25112
  }
@@ -24849,21 +25180,53 @@ function handleShieldDisable(args) {
24849
25180
  writeActiveShields(active.filter((s) => s !== name));
24850
25181
  return `Shield "${name}" disabled.`;
24851
25182
  }
24852
- var GLOBAL_CONFIG_PATH = path49.join(os45.homedir(), ".node9", "config.json");
25183
+ function handleEgressStatus() {
25184
+ const e = getConfig().policy.egress;
25185
+ const state = !e.enabled ? "OFF \u2014 the agent can reach any host" : e.mode === "block" ? "LOCKED (block) \u2014 unknown hosts are denied" : "WATCHING (review) \u2014 unknown hosts prompt for approval";
25186
+ const lines = [
25187
+ `Egress control: ${state}`,
25188
+ `${DEFAULT_EGRESS_ALLOWLIST.length} common dev/LLM hosts are always allowed (github, npm, pypi, anthropic, \u2026).`,
25189
+ `Your allow list: ${e.allow.length ? e.allow.join(", ") : "(none)"}`,
25190
+ `Your deny list: ${e.deny.length ? e.deny.join(", ") : "(none)"}`
25191
+ ];
25192
+ return lines.join("\n");
25193
+ }
25194
+ function handleEgressProtect(args) {
25195
+ const rawMode = args.mode;
25196
+ if (rawMode !== void 0 && rawMode !== "review" && rawMode !== "block") {
25197
+ throw new Error('mode must be "review" or "block".');
25198
+ }
25199
+ const requested = rawMode ?? "review";
25200
+ const current = getEgress();
25201
+ const mode = current.enabled && current.mode === "block" ? "block" : requested;
25202
+ setEgress({ enabled: true, mode });
25203
+ return mode === "block" ? "Egress is now LOCKED (block) \u2014 unknown hosts are denied. Routine hosts stay allowed." : "Egress is now WATCHED (review) \u2014 unknown hosts prompt for approval.";
25204
+ }
25205
+ function handleEgressDeny(args) {
25206
+ const host = typeof args.host === "string" ? normalizeEgressHost(args.host) : "";
25207
+ if (!isValidEgressHost(host)) {
25208
+ throw new Error(
25209
+ `Invalid host: "${String(args.host)}". Use an FQDN or *.glob (e.g. *.evil.com).`
25210
+ );
25211
+ }
25212
+ addEgressHost("deny", host);
25213
+ return `Denied egress to ${host} (deny always wins over allow).`;
25214
+ }
25215
+ var GLOBAL_CONFIG_PATH = path51.join(os46.homedir(), ".node9", "config.json");
24853
25216
  var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
24854
25217
  function readGlobalConfigRaw() {
24855
25218
  try {
24856
- if (fs50.existsSync(GLOBAL_CONFIG_PATH)) {
24857
- return JSON.parse(fs50.readFileSync(GLOBAL_CONFIG_PATH, "utf-8"));
25219
+ if (fs53.existsSync(GLOBAL_CONFIG_PATH)) {
25220
+ return JSON.parse(fs53.readFileSync(GLOBAL_CONFIG_PATH, "utf-8"));
24858
25221
  }
24859
25222
  } catch {
24860
25223
  }
24861
25224
  return {};
24862
25225
  }
24863
25226
  function writeGlobalConfigRaw(data) {
24864
- const dir = path49.dirname(GLOBAL_CONFIG_PATH);
24865
- if (!fs50.existsSync(dir)) fs50.mkdirSync(dir, { recursive: true });
24866
- fs50.writeFileSync(GLOBAL_CONFIG_PATH, JSON.stringify(data, null, 2) + "\n");
25227
+ const dir = path51.dirname(GLOBAL_CONFIG_PATH);
25228
+ if (!fs53.existsSync(dir)) fs53.mkdirSync(dir, { recursive: true });
25229
+ fs53.writeFileSync(GLOBAL_CONFIG_PATH, JSON.stringify(data, null, 2) + "\n");
24867
25230
  }
24868
25231
  function handleApproverList() {
24869
25232
  const config = getConfig();
@@ -24907,9 +25270,9 @@ function handleApproverSet(args) {
24907
25270
  function handleAuditGet(args) {
24908
25271
  const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
24909
25272
  const filter = typeof args.filter === "string" && args.filter !== "all" ? args.filter : null;
24910
- const auditPath = path49.join(os45.homedir(), ".node9", "audit.log");
24911
- if (!fs50.existsSync(auditPath)) return "No audit log found.";
24912
- const rawLines = fs50.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
25273
+ const auditPath = path51.join(os46.homedir(), ".node9", "audit.log");
25274
+ if (!fs53.existsSync(auditPath)) return "No audit log found.";
25275
+ const rawLines = fs53.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
24913
25276
  const parsed = [];
24914
25277
  for (const line of rawLines) {
24915
25278
  try {
@@ -25187,6 +25550,12 @@ function runMcpServer() {
25187
25550
  text = handlePostureMcp(toolArgs);
25188
25551
  } else if (toolName === "node9_explain") {
25189
25552
  text = handleExplainMcp(toolArgs);
25553
+ } else if (toolName === "node9_egress_status") {
25554
+ text = handleEgressStatus();
25555
+ } else if (toolName === "node9_egress_protect") {
25556
+ text = handleEgressProtect(toolArgs);
25557
+ } else if (toolName === "node9_egress_deny") {
25558
+ text = handleEgressDeny(toolArgs);
25190
25559
  } else {
25191
25560
  process.stdout.write(err(id, -32601, `Unknown tool: ${toolName}`) + "\n");
25192
25561
  return;
@@ -25277,7 +25646,7 @@ function registerTrustCommand(program2) {
25277
25646
  // src/cli/commands/mcp-pin.ts
25278
25647
  init_mcp_pin();
25279
25648
  import chalk21 from "chalk";
25280
- import fs51 from "fs";
25649
+ import fs54 from "fs";
25281
25650
  function registerMcpPinCommand(program2) {
25282
25651
  const pinCmd = program2.command("mcp").description("Manage MCP server tool definition pinning (rug pull defense)");
25283
25652
  const pinSubCmd = pinCmd.command("pin").description("Manage pinned MCP server tool definitions");
@@ -25288,7 +25657,7 @@ function registerMcpPinCommand(program2) {
25288
25657
  let repoCorrupt = false;
25289
25658
  if (found.source === "repo") {
25290
25659
  try {
25291
- const raw = fs51.readFileSync(found.path, "utf-8");
25660
+ const raw = fs54.readFileSync(found.path, "utf-8");
25292
25661
  const parsed = JSON.parse(raw);
25293
25662
  repoEntries = parsed.servers ?? {};
25294
25663
  } catch {
@@ -25777,50 +26146,10 @@ function registerPostureCommand(program2) {
25777
26146
  init_config();
25778
26147
  init_dist();
25779
26148
  import chalk26 from "chalk";
25780
- import fs52 from "fs";
25781
- import os46 from "os";
25782
- import path50 from "path";
25783
- var DEFAULT_EGRESS = {
25784
- enabled: false,
25785
- mode: "review",
25786
- allow: [],
25787
- deny: [],
25788
- allowPrivate: true
25789
- };
25790
- function configPath() {
25791
- return path50.join(os46.homedir(), ".node9", "config.json");
25792
- }
25793
- function readRawConfig() {
25794
- let text;
25795
- try {
25796
- text = fs52.readFileSync(configPath(), "utf8");
25797
- } catch (err2) {
25798
- if (err2.code === "ENOENT") return {};
25799
- throw err2;
25800
- }
25801
- try {
25802
- return JSON.parse(text);
25803
- } catch {
25804
- throw new Error(
25805
- `${configPath()} is not valid JSON \u2014 fix it before changing egress (refusing to overwrite).`
25806
- );
25807
- }
25808
- }
25809
- function writeRawConfig(config) {
25810
- const p = configPath();
25811
- fs52.mkdirSync(path50.dirname(p), { recursive: true });
25812
- fs52.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
25813
- }
25814
- function applyEgress(config, change) {
25815
- const policy = config.policy = config.policy ?? {};
25816
- const existing = policy.egress ?? {};
25817
- policy.egress = { ...DEFAULT_EGRESS, ...existing, ...change };
25818
- return config;
25819
- }
25820
- function withConfig(fn) {
25821
- let config;
26149
+ function guard(fn) {
25822
26150
  try {
25823
- config = readRawConfig();
26151
+ fn();
26152
+ return true;
25824
26153
  } catch (err2) {
25825
26154
  console.error(chalk26.red(`
25826
26155
  \u2717 ${err2.message}
@@ -25828,20 +26157,12 @@ function withConfig(fn) {
25828
26157
  process.exitCode = 1;
25829
26158
  return false;
25830
26159
  }
25831
- fn(config);
25832
- writeRawConfig(config);
25833
- return true;
25834
26160
  }
25835
26161
  function mutate(change) {
25836
- return withConfig((config) => applyEgress(config, change));
26162
+ return guard(() => setEgress(change));
25837
26163
  }
25838
26164
  function addHost(list, host) {
25839
- return withConfig((config) => {
25840
- const existing = config.policy?.egress ?? {};
25841
- const current = { ...DEFAULT_EGRESS, ...existing };
25842
- const updated = current[list].includes(host) ? current[list] : [...current[list], host];
25843
- applyEgress(config, { [list]: updated });
25844
- });
26165
+ return guard(() => addEgressHost(list, host));
25845
26166
  }
25846
26167
  function showStatus() {
25847
26168
  const e = getConfig().policy.egress;
@@ -25900,16 +26221,191 @@ function registerEgressCommand(program2) {
25900
26221
  egress.action(showStatus);
25901
26222
  }
25902
26223
 
25903
- // src/cli/commands/sandbox.ts
25904
- init_config();
26224
+ // src/cli/commands/jail.ts
25905
26225
  import chalk27 from "chalk";
26226
+
26227
+ // src/shields/jail.ts
25906
26228
  import fs55 from "fs";
25907
- import path53 from "path";
26229
+ import os47 from "os";
26230
+ import path52 from "path";
26231
+ init_shields();
26232
+ var USER_JAIL_SHIELD = "user-jail";
26233
+ function jailStorePath() {
26234
+ return path52.join(os47.homedir(), ".node9", "jail-paths.json");
26235
+ }
26236
+ function readJailPaths() {
26237
+ let text;
26238
+ try {
26239
+ text = fs55.readFileSync(jailStorePath(), "utf8");
26240
+ } catch (err2) {
26241
+ if (err2.code === "ENOENT") return [];
26242
+ throw err2;
26243
+ }
26244
+ let parsed;
26245
+ try {
26246
+ parsed = JSON.parse(text);
26247
+ } catch {
26248
+ throw new Error(`${jailStorePath()} is not valid JSON \u2014 fix it before changing the jail.`);
26249
+ }
26250
+ if (!Array.isArray(parsed.paths)) return [];
26251
+ return parsed.paths.filter(
26252
+ (p) => !!p && typeof p.path === "string" && (p.verdict === "block" || p.verdict === "review")
26253
+ );
26254
+ }
26255
+ function writeJailPaths(paths) {
26256
+ const p = jailStorePath();
26257
+ fs55.mkdirSync(path52.dirname(p), { recursive: true });
26258
+ fs55.writeFileSync(p, JSON.stringify({ paths }, null, 2) + "\n", { mode: 384 });
26259
+ }
26260
+ function addJailPath(rawPath, verdict) {
26261
+ const norm = rawPath.trim();
26262
+ if (!pathToRegexFragment(norm)) {
26263
+ throw new Error(
26264
+ `"${rawPath}" is too broad to jail \u2014 give a specific path (e.g. ~/.gmail-mcp), not a home or root directory.`
26265
+ );
26266
+ }
26267
+ const next = [...readJailPaths().filter((p) => p.path !== norm), { path: norm, verdict }];
26268
+ writeJailPaths(next);
26269
+ return next;
26270
+ }
26271
+ function removeJailPath(rawPath) {
26272
+ const norm = rawPath.trim();
26273
+ const before = readJailPaths();
26274
+ const after = before.filter((p) => p.path !== norm);
26275
+ const removed = after.length !== before.length;
26276
+ if (removed) writeJailPaths(after);
26277
+ return { removed, paths: after };
26278
+ }
26279
+ function regenerateUserJail(paths) {
26280
+ const file = path52.join(USER_SHIELDS_DIR_PATH, `${USER_JAIL_SHIELD}.json`);
26281
+ if (paths.length === 0) {
26282
+ const active2 = readActiveShields();
26283
+ if (active2.includes(USER_JAIL_SHIELD)) {
26284
+ writeActiveShields(active2.filter((s) => s !== USER_JAIL_SHIELD));
26285
+ }
26286
+ try {
26287
+ fs55.rmSync(file, { force: true });
26288
+ } catch {
26289
+ }
26290
+ return;
26291
+ }
26292
+ const def = buildShield({
26293
+ name: USER_JAIL_SHIELD,
26294
+ description: "User-added credential jail paths (node9 jail add)",
26295
+ blockPaths: paths.filter((p) => p.verdict === "block").map((p) => p.path),
26296
+ reviewPaths: paths.filter((p) => p.verdict === "review").map((p) => p.path)
26297
+ });
26298
+ installShield(USER_JAIL_SHIELD, def);
26299
+ const active = readActiveShields();
26300
+ if (!active.includes(USER_JAIL_SHIELD)) writeActiveShields([...active, USER_JAIL_SHIELD]);
26301
+ }
26302
+
26303
+ // src/cli/commands/jail.ts
26304
+ init_audit();
26305
+ var BUILTIN_JAIL = [
26306
+ "~/.ssh \u2014 SSH private keys",
26307
+ "~/.aws \u2014 AWS credentials",
26308
+ ".env files",
26309
+ "credential files \u2014 credentials.json, .netrc, .npmrc, .docker, .kube, gcloud"
26310
+ ];
26311
+ function registerJailCommand(program2) {
26312
+ const jail = program2.command("jail").description("Grow the credential jail \u2014 block/review reads of your sensitive paths");
26313
+ jail.command("add <path>").description("Add a path to the jail (default: block reads; --review to soften)").option("--review", "Require human approval instead of hard-blocking reads").action((p, opts) => {
26314
+ const verdict = opts.review ? "review" : "block";
26315
+ try {
26316
+ const paths = addJailPath(p, verdict);
26317
+ regenerateUserJail(paths);
26318
+ appendConfigAudit({ event: "jail-add", path: p.trim(), verdict });
26319
+ } catch (err2) {
26320
+ console.error(chalk27.red(`
26321
+ \u274C ${err2.message}
26322
+ `));
26323
+ process.exit(1);
26324
+ return;
26325
+ }
26326
+ console.log(chalk27.green(`
26327
+ \u2705 Jailed ${p} (${verdict}).`));
26328
+ console.log(
26329
+ chalk27.gray(
26330
+ ` AI reads of this path now ${verdict === "block" ? "BLOCK" : "require approval"}.`
26331
+ )
26332
+ );
26333
+ console.log(chalk27.gray(` Preview: ${chalk27.cyan(`node9 explain bash "cat ${p}"`)}
26334
+ `));
26335
+ });
26336
+ jail.command("remove <path>").description("Remove a user-added jail path (built-in paths are not removable)").action((p) => {
26337
+ let result;
26338
+ try {
26339
+ result = removeJailPath(p);
26340
+ } catch (err2) {
26341
+ console.error(chalk27.red(`
26342
+ \u274C ${err2.message}
26343
+ `));
26344
+ process.exit(1);
26345
+ return;
26346
+ }
26347
+ if (!result.removed) {
26348
+ console.error(chalk27.yellow(`
26349
+ \u2139\uFE0F "${p}" is not a user-added jail path.
26350
+ `));
26351
+ console.error(chalk27.gray(` Run ${chalk27.cyan("node9 jail list")} to see your paths.
26352
+ `));
26353
+ process.exit(1);
26354
+ return;
26355
+ }
26356
+ try {
26357
+ regenerateUserJail(result.paths);
26358
+ appendConfigAudit({ event: "jail-remove", path: p.trim() });
26359
+ } catch (err2) {
26360
+ console.error(chalk27.red(`
26361
+ \u274C ${err2.message}
26362
+ `));
26363
+ process.exit(1);
26364
+ return;
26365
+ }
26366
+ console.log(chalk27.green(`
26367
+ \u2705 Removed ${p} from the jail.
26368
+ `));
26369
+ });
26370
+ jail.command("list").description("Show built-in + user-added jail paths").action(() => {
26371
+ console.log(chalk27.bold("\n\u{1F512} Credential Jail\n"));
26372
+ console.log(chalk27.gray(" Built-in (always on, not removable):"));
26373
+ for (const b of BUILTIN_JAIL) console.log(` ${chalk27.gray("\u2022")} ${b}`);
26374
+ console.log("");
26375
+ let user;
26376
+ try {
26377
+ user = readJailPaths();
26378
+ } catch (err2) {
26379
+ console.error(chalk27.red(` \u2717 ${err2.message}
26380
+ `));
26381
+ process.exit(1);
26382
+ return;
26383
+ }
26384
+ if (user.length === 0) {
26385
+ console.log(
26386
+ chalk27.gray(" Your paths: (none) \u2014 add one with ") + chalk27.cyan("node9 jail add <path>")
26387
+ );
26388
+ } else {
26389
+ console.log(chalk27.gray(" Your paths (removable):"));
26390
+ for (const u of user) {
26391
+ const v = u.verdict === "block" ? chalk27.red("block ") : chalk27.yellow("review");
26392
+ console.log(` ${v} ${chalk27.cyan(u.path)}`);
26393
+ }
26394
+ }
26395
+ console.log("");
26396
+ });
26397
+ }
26398
+
26399
+ // src/cli/commands/sandbox.ts
26400
+ init_config();
26401
+ import chalk28 from "chalk";
26402
+ import fs58 from "fs";
26403
+ import path55 from "path";
25908
26404
  import { spawnSync as spawnSync6 } from "child_process";
25909
26405
 
25910
26406
  // src/sandbox/config.ts
25911
- import fs53 from "fs";
25912
- import path51 from "path";
26407
+ import fs56 from "fs";
26408
+ import path53 from "path";
25913
26409
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
25914
26410
  var SANDBOX_CONFIG_FILE = "node9.sandbox.yaml";
25915
26411
  var FORBIDDEN_ENV = /* @__PURE__ */ new Set(["NODE9_API_KEY", "NODE9_API_URL"]);
@@ -25982,16 +26478,16 @@ function scaffoldSandboxYaml(agent) {
25982
26478
  return header + stringifyYaml(defaultSandboxConfig(agent));
25983
26479
  }
25984
26480
  function sandboxConfigPath(cwd = process.cwd()) {
25985
- return path51.join(cwd, SANDBOX_CONFIG_FILE);
26481
+ return path53.join(cwd, SANDBOX_CONFIG_FILE);
25986
26482
  }
25987
26483
  function loadSandboxConfig(cwd = process.cwd(), fallbackAgent = "claude") {
25988
26484
  const p = sandboxConfigPath(cwd);
25989
- if (!fs53.existsSync(p)) {
26485
+ if (!fs56.existsSync(p)) {
25990
26486
  throw new Error(`sandbox: ${SANDBOX_CONFIG_FILE} not found \u2014 run \`node9 sandbox new\` first.`);
25991
26487
  }
25992
26488
  let raw;
25993
26489
  try {
25994
- raw = parseYaml(fs53.readFileSync(p, "utf-8"));
26490
+ raw = parseYaml(fs56.readFileSync(p, "utf-8"));
25995
26491
  } catch (err2) {
25996
26492
  throw new Error(
25997
26493
  `sandbox: ${SANDBOX_CONFIG_FILE} is not valid YAML \u2014 ${err2.message}`
@@ -26050,13 +26546,13 @@ init_templates();
26050
26546
 
26051
26547
  // src/sandbox/runtime.ts
26052
26548
  init_templates();
26053
- import fs54 from "fs";
26054
- import os47 from "os";
26055
- import path52 from "path";
26549
+ import fs57 from "fs";
26550
+ import os48 from "os";
26551
+ import path54 from "path";
26056
26552
  import crypto8 from "crypto";
26057
26553
  import { spawnSync as spawnSync5 } from "child_process";
26058
26554
  function sandboxDataDir(cwd = process.cwd()) {
26059
- return path52.join(cwd, ".node9", "sandbox", "data");
26555
+ return path54.join(cwd, ".node9", "sandbox", "data");
26060
26556
  }
26061
26557
  function detectEngine(engine) {
26062
26558
  const r = spawnSync5(engine, ["--version"], { encoding: "utf-8" });
@@ -26067,7 +26563,7 @@ function detectEngine(engine) {
26067
26563
  }
26068
26564
  function agentCredentialsMount(agent) {
26069
26565
  const rel = agent === "codex" ? ".codex/auth.json" : ".claude/.credentials.json";
26070
- return { hostPath: path52.join(os47.homedir(), rel), target: `/home/${RUN_AS_USER}/${rel}` };
26566
+ return { hostPath: path54.join(os48.homedir(), rel), target: `/home/${RUN_AS_USER}/${rel}` };
26071
26567
  }
26072
26568
  function buildRunArgs(opts) {
26073
26569
  const { config, workspaceHostPath, dataHostPath, allowlistHostPath, agentArgs } = opts;
@@ -26077,7 +26573,7 @@ function buildRunArgs(opts) {
26077
26573
  args.push("-v", `${allowlistHostPath}:${ALLOWED_DOMAINS_PATH}:ro`);
26078
26574
  if (config.node9.mountAgentCredentials) {
26079
26575
  const creds = agentCredentialsMount(config.agent);
26080
- if (fs54.existsSync(creds.hostPath)) {
26576
+ if (fs57.existsSync(creds.hostPath)) {
26081
26577
  args.push("-v", `${creds.hostPath}:${creds.target}`);
26082
26578
  }
26083
26579
  }
@@ -26095,30 +26591,30 @@ function imageContentHash(dockerfile, entrypoint) {
26095
26591
  return crypto8.createHash("sha256").update(dockerfile).update("\0").update(entrypoint).digest("hex").slice(0, 16);
26096
26592
  }
26097
26593
  function sandboxBuildDir(cwd = process.cwd()) {
26098
- return path52.join(cwd, ".node9", "sandbox", "build");
26594
+ return path54.join(cwd, ".node9", "sandbox", "build");
26099
26595
  }
26100
26596
  function writeBuildContext(cwd, dockerfile, entrypoint) {
26101
26597
  const dir = sandboxBuildDir(cwd);
26102
- fs54.mkdirSync(dir, { recursive: true });
26103
- fs54.writeFileSync(path52.join(dir, "Dockerfile"), dockerfile);
26104
- fs54.writeFileSync(path52.join(dir, "entrypoint.sh"), entrypoint);
26598
+ fs57.mkdirSync(dir, { recursive: true });
26599
+ fs57.writeFileSync(path54.join(dir, "Dockerfile"), dockerfile);
26600
+ fs57.writeFileSync(path54.join(dir, "entrypoint.sh"), entrypoint);
26105
26601
  return dir;
26106
26602
  }
26107
26603
  function writeAllowlist(cwd, hosts) {
26108
- const dir = path52.join(cwd, ".node9", "sandbox");
26109
- fs54.mkdirSync(dir, { recursive: true });
26110
- const p = path52.join(dir, "allowed-domains.txt");
26111
- fs54.writeFileSync(p, hosts.join("\n") + "\n");
26604
+ const dir = path54.join(cwd, ".node9", "sandbox");
26605
+ fs57.mkdirSync(dir, { recursive: true });
26606
+ const p = path54.join(dir, "allowed-domains.txt");
26607
+ fs57.writeFileSync(p, hosts.join("\n") + "\n");
26112
26608
  return p;
26113
26609
  }
26114
26610
  function resolveHomePath(p) {
26115
- return p.startsWith("~") ? path52.join(os47.homedir(), p.slice(1)) : path52.resolve(p);
26611
+ return p.startsWith("~") ? path54.join(os48.homedir(), p.slice(1)) : path54.resolve(p);
26116
26612
  }
26117
26613
 
26118
26614
  // src/cli/commands/sandbox.ts
26119
26615
  function seedDataDirConfig(dataDir, sandbox) {
26120
- fs55.mkdirSync(dataDir, { recursive: true });
26121
- const configPath2 = path53.join(dataDir, "config.json");
26616
+ fs58.mkdirSync(dataDir, { recursive: true });
26617
+ const configPath = path55.join(dataDir, "config.json");
26122
26618
  const seed = {
26123
26619
  settings: {
26124
26620
  approvers: {
@@ -26129,7 +26625,7 @@ function seedDataDirConfig(dataDir, sandbox) {
26129
26625
  }
26130
26626
  }
26131
26627
  };
26132
- fs55.writeFileSync(configPath2, JSON.stringify(seed, null, 2), { mode: 384 });
26628
+ fs58.writeFileSync(configPath, JSON.stringify(seed, null, 2), { mode: 384 });
26133
26629
  }
26134
26630
  function registerSandboxCommand(program2, version2) {
26135
26631
  const node9Version2 = pinnedNode9Version(version2);
@@ -26137,18 +26633,18 @@ function registerSandboxCommand(program2, version2) {
26137
26633
  cmd.command("new").description(`Scaffold ${SANDBOX_CONFIG_FILE} in this project`).option("--agent <agent>", "claude (default) or codex", "claude").action((opts) => {
26138
26634
  const agent = opts.agent === "codex" ? "codex" : "claude";
26139
26635
  const p = sandboxConfigPath();
26140
- if (fs55.existsSync(p)) {
26636
+ if (fs58.existsSync(p)) {
26141
26637
  console.log(
26142
- chalk27.yellow(` ${SANDBOX_CONFIG_FILE} already exists \u2014 leaving it untouched.`)
26638
+ chalk28.yellow(` ${SANDBOX_CONFIG_FILE} already exists \u2014 leaving it untouched.`)
26143
26639
  );
26144
26640
  return;
26145
26641
  }
26146
- fs55.writeFileSync(p, scaffoldSandboxYaml(agent));
26642
+ fs58.writeFileSync(p, scaffoldSandboxYaml(agent));
26147
26643
  console.log(
26148
- chalk27.green(` \u2713 wrote ${SANDBOX_CONFIG_FILE}`) + chalk27.dim(` (agent: ${agent})`)
26644
+ chalk28.green(` \u2713 wrote ${SANDBOX_CONFIG_FILE}`) + chalk28.dim(` (agent: ${agent})`)
26149
26645
  );
26150
26646
  console.log(
26151
- chalk27.dim(" Edit it (mounts / allow / expose), then: ") + chalk27.cyan("node9 sandbox run")
26647
+ chalk28.dim(" Edit it (mounts / allow / expose), then: ") + chalk28.cyan("node9 sandbox run")
26152
26648
  );
26153
26649
  });
26154
26650
  cmd.command("run [agent]").description("Build (if needed) + run the agent jailed. Extra args after -- go to the agent.").allowUnknownOption(true).allowExcessArguments(true).action((agentArg, _opts, command) => {
@@ -26158,7 +26654,7 @@ function registerSandboxCommand(program2, version2) {
26158
26654
  const engine = detectEngine(sandbox.runtime.engine);
26159
26655
  if (!engine.available) {
26160
26656
  console.error(
26161
- chalk27.red(` ${sandbox.runtime.engine} not found.`) + chalk27.dim(` Install it first \u2014 node9 sandbox needs a container runtime.`)
26657
+ chalk28.red(` ${sandbox.runtime.engine} not found.`) + chalk28.dim(` Install it first \u2014 node9 sandbox needs a container runtime.`)
26162
26658
  );
26163
26659
  process.exit(1);
26164
26660
  }
@@ -26171,11 +26667,11 @@ function registerSandboxCommand(program2, version2) {
26171
26667
  });
26172
26668
  if (compiled.rejected.length) {
26173
26669
  console.log(
26174
- chalk27.yellow(` \u26A0 ignoring invalid allow hosts: ${compiled.rejected.join(", ")}`)
26670
+ chalk28.yellow(` \u26A0 ignoring invalid allow hosts: ${compiled.rejected.join(", ")}`)
26175
26671
  );
26176
26672
  }
26177
26673
  if (compiled.denied.length) {
26178
- console.log(chalk27.dim(` (denied: ${compiled.denied.join(", ")})`));
26674
+ console.log(chalk28.dim(` (denied: ${compiled.denied.join(", ")})`));
26179
26675
  }
26180
26676
  const allowlistPath = writeAllowlist(cwd, compiled.allow);
26181
26677
  const dockerfile = renderDockerfile(sandbox, node9Version2);
@@ -26183,20 +26679,20 @@ function registerSandboxCommand(program2, version2) {
26183
26679
  const buildDir = writeBuildContext(cwd, dockerfile, entrypoint);
26184
26680
  const hash = imageContentHash(dockerfile, entrypoint);
26185
26681
  const image = sandbox.runtime.image;
26186
- const hashFile = path53.join(sandboxBuildDir(cwd), ".image-hash");
26187
- const lastHash = fs55.existsSync(hashFile) ? fs55.readFileSync(hashFile, "utf-8").trim() : "";
26682
+ const hashFile = path55.join(sandboxBuildDir(cwd), ".image-hash");
26683
+ const lastHash = fs58.existsSync(hashFile) ? fs58.readFileSync(hashFile, "utf-8").trim() : "";
26188
26684
  const imageExists = spawnSync6(sandbox.runtime.engine, ["image", "inspect", image], { stdio: "ignore" }).status === 0;
26189
26685
  const needBuild = sandbox.runtime.rebuild === "always" || !imageExists || sandbox.runtime.rebuild !== "never" && lastHash !== hash;
26190
26686
  if (needBuild) {
26191
- console.log(chalk27.dim(` building ${image} \u2026`));
26687
+ console.log(chalk28.dim(` building ${image} \u2026`));
26192
26688
  const b = spawnSync6(sandbox.runtime.engine, ["build", "-t", image, buildDir], {
26193
26689
  stdio: "inherit"
26194
26690
  });
26195
26691
  if (b.status !== 0) {
26196
- console.error(chalk27.red(" build failed."));
26692
+ console.error(chalk28.red(" build failed."));
26197
26693
  process.exit(b.status ?? 1);
26198
26694
  }
26199
- fs55.writeFileSync(hashFile, hash);
26695
+ fs58.writeFileSync(hashFile, hash);
26200
26696
  }
26201
26697
  const dataDir = sandboxDataDir(cwd);
26202
26698
  seedDataDirConfig(dataDir, sandbox);
@@ -26210,36 +26706,36 @@ function registerSandboxCommand(program2, version2) {
26210
26706
  });
26211
26707
  if (sandbox.node9.mountAgentCredentials) {
26212
26708
  const creds = agentCredentialsMount(sandbox.agent);
26213
- if (fs55.existsSync(creds.hostPath)) {
26214
- console.log(chalk27.dim(` mounting ${creds.hostPath} (agent credentials, rw)`));
26709
+ if (fs58.existsSync(creds.hostPath)) {
26710
+ console.log(chalk28.dim(` mounting ${creds.hostPath} (agent credentials, rw)`));
26215
26711
  } else {
26216
26712
  console.log(
26217
- chalk27.yellow(` \u26A0 ${creds.hostPath} not found \u2014 `) + chalk27.dim(`the agent must auth via an env key in env.pass.`)
26713
+ chalk28.yellow(` \u26A0 ${creds.hostPath} not found \u2014 `) + chalk28.dim(`the agent must auth via an env key in env.pass.`)
26218
26714
  );
26219
26715
  }
26220
26716
  }
26221
26717
  console.log(
26222
- chalk27.green(` \u{1F6E1}\uFE0F ${sandbox.agent} jailed \u2014 ${compiled.allow.length} hosts allowed
26718
+ chalk28.green(` \u{1F6E1}\uFE0F ${sandbox.agent} jailed \u2014 ${compiled.allow.length} hosts allowed
26223
26719
  `)
26224
26720
  );
26225
26721
  const r = spawnSync6(sandbox.runtime.engine, runArgs, { stdio: "inherit" });
26226
26722
  process.exit(r.status ?? 0);
26227
26723
  });
26228
26724
  cmd.command("tail").description("Stream the sandbox's audit log (host-side)").action(() => {
26229
- const auditPath = path53.join(sandboxDataDir(), "audit.log");
26230
- if (!fs55.existsSync(auditPath)) {
26231
- console.log(chalk27.dim(" no sandbox audit yet."));
26725
+ const auditPath = path55.join(sandboxDataDir(), "audit.log");
26726
+ if (!fs58.existsSync(auditPath)) {
26727
+ console.log(chalk28.dim(" no sandbox audit yet."));
26232
26728
  return;
26233
26729
  }
26234
26730
  spawnSync6("tail", ["-f", auditPath], { stdio: "inherit" });
26235
26731
  });
26236
26732
  cmd.command("logs").description("Dump the sandbox's audit log").action(() => {
26237
- const auditPath = path53.join(sandboxDataDir(), "audit.log");
26238
- if (!fs55.existsSync(auditPath)) {
26239
- console.log(chalk27.dim(" no sandbox audit yet."));
26733
+ const auditPath = path55.join(sandboxDataDir(), "audit.log");
26734
+ if (!fs58.existsSync(auditPath)) {
26735
+ console.log(chalk28.dim(" no sandbox audit yet."));
26240
26736
  return;
26241
26737
  }
26242
- process.stdout.write(fs55.readFileSync(auditPath, "utf-8"));
26738
+ process.stdout.write(fs58.readFileSync(auditPath, "utf-8"));
26243
26739
  });
26244
26740
  cmd.command("clean").description("Remove the sandbox image, build context, and data").action(() => {
26245
26741
  const cwd = process.cwd();
@@ -26253,8 +26749,8 @@ function registerSandboxCommand(program2, version2) {
26253
26749
  stdio: "ignore"
26254
26750
  });
26255
26751
  }
26256
- fs55.rmSync(path53.join(cwd, ".node9", "sandbox"), { recursive: true, force: true });
26257
- console.log(chalk27.green(" \u2713 sandbox image + build + data removed."));
26752
+ fs58.rmSync(path55.join(cwd, ".node9", "sandbox"), { recursive: true, force: true });
26753
+ console.log(chalk28.green(" \u2713 sandbox image + build + data removed."));
26258
26754
  });
26259
26755
  }
26260
26756
 
@@ -26263,10 +26759,10 @@ init_scan_summary();
26263
26759
  init_litellm();
26264
26760
  init_cost_gemini();
26265
26761
  init_cost_codex();
26266
- import chalk28 from "chalk";
26267
- import fs56 from "fs";
26268
- import path54 from "path";
26269
- import os48 from "os";
26762
+ import chalk29 from "chalk";
26763
+ import fs59 from "fs";
26764
+ import path56 from "path";
26765
+ import os49 from "os";
26270
26766
  function modelPrice(model) {
26271
26767
  const t = pricingFor(model);
26272
26768
  if (!t) return null;
@@ -26283,10 +26779,10 @@ function encodeProjectPath(projectPath) {
26283
26779
  }
26284
26780
  function sessionJsonlPath(projectPath, sessionId) {
26285
26781
  const encoded = encodeProjectPath(projectPath);
26286
- return path54.join(os48.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
26782
+ return path56.join(os49.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
26287
26783
  }
26288
26784
  function projectLabel(projectPath) {
26289
- return projectPath.replace(os48.homedir(), "~");
26785
+ return projectPath.replace(os49.homedir(), "~");
26290
26786
  }
26291
26787
  function parseHistoryLines(lines) {
26292
26788
  const entries = [];
@@ -26355,10 +26851,10 @@ function parseSessionLines(lines) {
26355
26851
  return { toolCalls, costUSD, hasSnapshot, modifiedFiles };
26356
26852
  }
26357
26853
  function loadAuditEntries(auditPath) {
26358
- const aPath = auditPath ?? path54.join(os48.homedir(), ".node9", "audit.log");
26854
+ const aPath = auditPath ?? path56.join(os49.homedir(), ".node9", "audit.log");
26359
26855
  let raw;
26360
26856
  try {
26361
- raw = fs56.readFileSync(aPath, "utf-8");
26857
+ raw = fs59.readFileSync(aPath, "utf-8");
26362
26858
  } catch {
26363
26859
  return [];
26364
26860
  }
@@ -26394,8 +26890,8 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
26394
26890
  return result;
26395
26891
  }
26396
26892
  function buildGeminiSessions(days, allAuditEntries) {
26397
- const tmpDir = path54.join(os48.homedir(), ".gemini", "tmp");
26398
- if (!fs56.existsSync(tmpDir)) return [];
26893
+ const tmpDir = path56.join(os49.homedir(), ".gemini", "tmp");
26894
+ if (!fs59.existsSync(tmpDir)) return [];
26399
26895
  const cutoff = days !== null ? (() => {
26400
26896
  const d = /* @__PURE__ */ new Date();
26401
26897
  d.setDate(d.getDate() - days);
@@ -26404,35 +26900,35 @@ function buildGeminiSessions(days, allAuditEntries) {
26404
26900
  })() : null;
26405
26901
  let slugDirs;
26406
26902
  try {
26407
- slugDirs = fs56.readdirSync(tmpDir);
26903
+ slugDirs = fs59.readdirSync(tmpDir);
26408
26904
  } catch {
26409
26905
  return [];
26410
26906
  }
26411
26907
  const summaries = [];
26412
- for (const slug of slugDirs) {
26413
- const slugPath = path54.join(tmpDir, slug);
26908
+ for (const slug2 of slugDirs) {
26909
+ const slugPath = path56.join(tmpDir, slug2);
26414
26910
  try {
26415
- if (!fs56.statSync(slugPath).isDirectory()) continue;
26911
+ if (!fs59.statSync(slugPath).isDirectory()) continue;
26416
26912
  } catch {
26417
26913
  continue;
26418
26914
  }
26419
- let projectRoot = path54.join(os48.homedir(), slug);
26915
+ let projectRoot = path56.join(os49.homedir(), slug2);
26420
26916
  try {
26421
- projectRoot = fs56.readFileSync(path54.join(slugPath, ".project_root"), "utf-8").trim();
26917
+ projectRoot = fs59.readFileSync(path56.join(slugPath, ".project_root"), "utf-8").trim();
26422
26918
  } catch {
26423
26919
  }
26424
- const chatsDir = path54.join(slugPath, "chats");
26425
- if (!fs56.existsSync(chatsDir)) continue;
26920
+ const chatsDir = path56.join(slugPath, "chats");
26921
+ if (!fs59.existsSync(chatsDir)) continue;
26426
26922
  let chatFiles;
26427
26923
  try {
26428
- chatFiles = fs56.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
26924
+ chatFiles = fs59.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
26429
26925
  } catch {
26430
26926
  continue;
26431
26927
  }
26432
26928
  for (const chatFile of chatFiles) {
26433
26929
  let raw;
26434
26930
  try {
26435
- raw = fs56.readFileSync(path54.join(chatsDir, chatFile), "utf-8");
26931
+ raw = fs59.readFileSync(path56.join(chatsDir, chatFile), "utf-8");
26436
26932
  } catch {
26437
26933
  continue;
26438
26934
  }
@@ -26512,8 +27008,8 @@ function buildGeminiSessions(days, allAuditEntries) {
26512
27008
  return summaries;
26513
27009
  }
26514
27010
  function buildCodexSessions(days, allAuditEntries) {
26515
- const sessionsBase = path54.join(os48.homedir(), ".codex", "sessions");
26516
- if (!fs56.existsSync(sessionsBase)) return [];
27011
+ const sessionsBase = path56.join(os49.homedir(), ".codex", "sessions");
27012
+ if (!fs59.existsSync(sessionsBase)) return [];
26517
27013
  const cutoff = days !== null ? (() => {
26518
27014
  const d = /* @__PURE__ */ new Date();
26519
27015
  d.setDate(d.getDate() - days);
@@ -26522,29 +27018,29 @@ function buildCodexSessions(days, allAuditEntries) {
26522
27018
  })() : null;
26523
27019
  const jsonlFiles = [];
26524
27020
  try {
26525
- for (const year of fs56.readdirSync(sessionsBase)) {
26526
- const yearPath = path54.join(sessionsBase, year);
27021
+ for (const year of fs59.readdirSync(sessionsBase)) {
27022
+ const yearPath = path56.join(sessionsBase, year);
26527
27023
  try {
26528
- if (!fs56.statSync(yearPath).isDirectory()) continue;
27024
+ if (!fs59.statSync(yearPath).isDirectory()) continue;
26529
27025
  } catch {
26530
27026
  continue;
26531
27027
  }
26532
- for (const month of fs56.readdirSync(yearPath)) {
26533
- const monthPath = path54.join(yearPath, month);
27028
+ for (const month of fs59.readdirSync(yearPath)) {
27029
+ const monthPath = path56.join(yearPath, month);
26534
27030
  try {
26535
- if (!fs56.statSync(monthPath).isDirectory()) continue;
27031
+ if (!fs59.statSync(monthPath).isDirectory()) continue;
26536
27032
  } catch {
26537
27033
  continue;
26538
27034
  }
26539
- for (const day of fs56.readdirSync(monthPath)) {
26540
- const dayPath = path54.join(monthPath, day);
27035
+ for (const day of fs59.readdirSync(monthPath)) {
27036
+ const dayPath = path56.join(monthPath, day);
26541
27037
  try {
26542
- if (!fs56.statSync(dayPath).isDirectory()) continue;
27038
+ if (!fs59.statSync(dayPath).isDirectory()) continue;
26543
27039
  } catch {
26544
27040
  continue;
26545
27041
  }
26546
- for (const file of fs56.readdirSync(dayPath)) {
26547
- if (file.endsWith(".jsonl")) jsonlFiles.push(path54.join(dayPath, file));
27042
+ for (const file of fs59.readdirSync(dayPath)) {
27043
+ if (file.endsWith(".jsonl")) jsonlFiles.push(path56.join(dayPath, file));
26548
27044
  }
26549
27045
  }
26550
27046
  }
@@ -26556,7 +27052,7 @@ function buildCodexSessions(days, allAuditEntries) {
26556
27052
  for (const filePath of jsonlFiles) {
26557
27053
  let lines;
26558
27054
  try {
26559
- lines = fs56.readFileSync(filePath, "utf-8").split("\n");
27055
+ lines = fs59.readFileSync(filePath, "utf-8").split("\n");
26560
27056
  } catch {
26561
27057
  continue;
26562
27058
  }
@@ -26642,10 +27138,10 @@ function buildCodexSessions(days, allAuditEntries) {
26642
27138
  return summaries;
26643
27139
  }
26644
27140
  function buildSessions(days, historyPath) {
26645
- const hPath = historyPath ?? path54.join(os48.homedir(), ".claude", "history.jsonl");
27141
+ const hPath = historyPath ?? path56.join(os49.homedir(), ".claude", "history.jsonl");
26646
27142
  let historyRaw = "";
26647
27143
  try {
26648
- historyRaw = fs56.readFileSync(hPath, "utf-8");
27144
+ historyRaw = fs59.readFileSync(hPath, "utf-8");
26649
27145
  } catch {
26650
27146
  }
26651
27147
  const cutoff = days !== null ? (() => {
@@ -26669,7 +27165,7 @@ function buildSessions(days, historyPath) {
26669
27165
  const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
26670
27166
  let sessionLines = [];
26671
27167
  try {
26672
- sessionLines = fs56.readFileSync(jsonlFile, "utf-8").split("\n");
27168
+ sessionLines = fs59.readFileSync(jsonlFile, "utf-8").split("\n");
26673
27169
  } catch {
26674
27170
  }
26675
27171
  const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
@@ -26755,11 +27251,11 @@ function toolInputSummary(tool, input) {
26755
27251
  }
26756
27252
  function toolColor(tool) {
26757
27253
  const t = tool.toLowerCase();
26758
- if (t === "bash" || t === "execute_bash") return chalk28.red;
26759
- if (t === "write") return chalk28.green;
26760
- if (t === "edit" || t === "notebookedit") return chalk28.yellow;
26761
- if (t === "read") return chalk28.cyan;
26762
- return chalk28.gray;
27254
+ if (t === "bash" || t === "execute_bash") return chalk29.red;
27255
+ if (t === "write") return chalk29.green;
27256
+ if (t === "edit" || t === "notebookedit") return chalk29.yellow;
27257
+ if (t === "read") return chalk29.cyan;
27258
+ return chalk29.gray;
26763
27259
  }
26764
27260
  function barStr2(value, max, width) {
26765
27261
  if (max === 0 || width <= 0) return "\u2591".repeat(width);
@@ -26769,7 +27265,7 @@ function barStr2(value, max, width) {
26769
27265
  function colorBar2(value, max, width) {
26770
27266
  const s = barStr2(value, max, width);
26771
27267
  const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
26772
- return chalk28.cyan(s.slice(0, filled)) + chalk28.dim(s.slice(filled));
27268
+ return chalk29.cyan(s.slice(0, filled)) + chalk29.dim(s.slice(filled));
26773
27269
  }
26774
27270
  function renderSummary(summaries) {
26775
27271
  const totalTools = summaries.reduce((n, s) => n + s.toolCalls.length, 0);
@@ -26799,45 +27295,45 @@ function renderSummary(summaries) {
26799
27295
  }
26800
27296
  const topProjects = [...projCosts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
26801
27297
  const W = 20;
26802
- console.log(chalk28.dim(" " + "\u2500".repeat(70)));
27298
+ console.log(chalk29.dim(" " + "\u2500".repeat(70)));
26803
27299
  console.log(
26804
- " " + chalk28.bold.white(String(summaries.length).padEnd(4)) + chalk28.dim("sessions ") + chalk28.bold.yellow(fmtCost3(totalCost).padEnd(10)) + chalk28.dim("total ") + chalk28.bold.white(String(totalTools).padEnd(6)) + chalk28.dim("tool calls ") + chalk28.bold.white(String(totalFiles)) + chalk28.dim(" files modified") + (totalBlocked > 0 ? chalk28.dim(" ") + chalk28.red.bold(String(totalBlocked)) + chalk28.dim(" blocked by node9") : "")
27300
+ " " + chalk29.bold.white(String(summaries.length).padEnd(4)) + chalk29.dim("sessions ") + chalk29.bold.yellow(fmtCost3(totalCost).padEnd(10)) + chalk29.dim("total ") + chalk29.bold.white(String(totalTools).padEnd(6)) + chalk29.dim("tool calls ") + chalk29.bold.white(String(totalFiles)) + chalk29.dim(" files modified") + (totalBlocked > 0 ? chalk29.dim(" ") + chalk29.red.bold(String(totalBlocked)) + chalk29.dim(" blocked by node9") : "")
26805
27301
  );
26806
27302
  console.log(
26807
- " " + chalk28.dim("avg ") + chalk28.white(fmtCost3(avgCost).padEnd(10)) + chalk28.dim("/session ") + chalk28.green(String(snapshots)) + chalk28.dim(` of ${summaries.length} sessions had snapshots`)
27303
+ " " + chalk29.dim("avg ") + chalk29.white(fmtCost3(avgCost).padEnd(10)) + chalk29.dim("/session ") + chalk29.green(String(snapshots)) + chalk29.dim(` of ${summaries.length} sessions had snapshots`)
26808
27304
  );
26809
27305
  console.log("");
26810
- console.log(" " + chalk28.dim("Tool breakdown:"));
27306
+ console.log(" " + chalk29.dim("Tool breakdown:"));
26811
27307
  const maxGroup = Math.max(...Object.values(groups));
26812
27308
  for (const [label2, count] of Object.entries(groups)) {
26813
27309
  if (count === 0) continue;
26814
27310
  const pct = totalTools > 0 ? Math.round(count / totalTools * 100) : 0;
26815
27311
  console.log(
26816
- " " + label2.padEnd(6) + " " + colorBar2(count, maxGroup, W) + " " + chalk28.white(String(count).padStart(4)) + chalk28.dim(` (${String(pct)}%)`)
27312
+ " " + label2.padEnd(6) + " " + colorBar2(count, maxGroup, W) + " " + chalk29.white(String(count).padStart(4)) + chalk29.dim(` (${String(pct)}%)`)
26817
27313
  );
26818
27314
  }
26819
27315
  console.log("");
26820
27316
  if (topProjects.length > 1) {
26821
- console.log(" " + chalk28.dim("Cost by project:"));
27317
+ console.log(" " + chalk29.dim("Cost by project:"));
26822
27318
  const maxProjCost = topProjects[0][1];
26823
27319
  for (const [proj, cost] of topProjects) {
26824
27320
  console.log(
26825
- " " + proj.slice(0, 28).padEnd(28) + " " + colorBar2(cost, maxProjCost, W) + " " + chalk28.yellow(fmtCost3(cost))
27321
+ " " + proj.slice(0, 28).padEnd(28) + " " + colorBar2(cost, maxProjCost, W) + " " + chalk29.yellow(fmtCost3(cost))
26826
27322
  );
26827
27323
  }
26828
27324
  console.log("");
26829
27325
  }
26830
- console.log(chalk28.dim(" " + "\u2500".repeat(70)));
27326
+ console.log(chalk29.dim(" " + "\u2500".repeat(70)));
26831
27327
  console.log("");
26832
27328
  }
26833
27329
  function renderList(summaries, totalCost) {
26834
27330
  if (summaries.length === 0) {
26835
- console.log(chalk28.yellow(" No sessions found in the requested range.\n"));
27331
+ console.log(chalk29.yellow(" No sessions found in the requested range.\n"));
26836
27332
  return;
26837
27333
  }
26838
- const totalLabel = totalCost > 0 ? chalk28.dim(" ~" + fmtCost3(totalCost) + " total") : "";
27334
+ const totalLabel = totalCost > 0 ? chalk29.dim(" ~" + fmtCost3(totalCost) + " total") : "";
26839
27335
  console.log(
26840
- " " + chalk28.white(String(summaries.length)) + chalk28.dim(` session${summaries.length !== 1 ? "s" : ""}`) + totalLabel
27336
+ " " + chalk29.white(String(summaries.length)) + chalk29.dim(` session${summaries.length !== 1 ? "s" : ""}`) + totalLabel
26841
27337
  );
26842
27338
  console.log("");
26843
27339
  let lastGroup = "";
@@ -26845,51 +27341,51 @@ function renderList(summaries, totalCost) {
26845
27341
  const activeDate = fmtDate2(s.lastActiveTime);
26846
27342
  const group = activeDate + " " + s.projectLabel;
26847
27343
  if (group !== lastGroup) {
26848
- console.log(chalk28.dim(" \u2500\u2500\u2500 ") + chalk28.bold(activeDate) + chalk28.dim(" " + s.projectLabel));
27344
+ console.log(chalk29.dim(" \u2500\u2500\u2500 ") + chalk29.bold(activeDate) + chalk29.dim(" " + s.projectLabel));
26849
27345
  lastGroup = group;
26850
27346
  }
26851
27347
  const startDate = fmtDate2(s.startTime);
26852
- const dateRange = startDate !== activeDate ? chalk28.dim(" (" + startDate + " \u2192 " + activeDate + ")") : "";
26853
- const timeStr = chalk28.dim(fmtTime(s.startTime));
26854
- const prompt = chalk28.white(truncate(s.firstPrompt.replace(/\n/g, " "), 50).padEnd(50));
26855
- const tools = s.toolCalls.length > 0 ? chalk28.dim(String(s.toolCalls.length).padStart(3) + " tools") : chalk28.dim(" 0 tools");
26856
- const cost = s.costUSD > 0 ? chalk28.dim(" " + fmtCost3(s.costUSD).padEnd(8)) : " ";
26857
- const blocked = s.blockedCalls.length > 0 ? chalk28.red(" \u{1F6D1} " + String(s.blockedCalls.length)) : "";
26858
- const snap = s.hasSnapshot ? chalk28.green(" \u{1F4F8}") : "";
26859
- const agentBadge = chalk28[agentColorName(s.agent ?? "claude")](
27348
+ const dateRange = startDate !== activeDate ? chalk29.dim(" (" + startDate + " \u2192 " + activeDate + ")") : "";
27349
+ const timeStr = chalk29.dim(fmtTime(s.startTime));
27350
+ const prompt = chalk29.white(truncate(s.firstPrompt.replace(/\n/g, " "), 50).padEnd(50));
27351
+ const tools = s.toolCalls.length > 0 ? chalk29.dim(String(s.toolCalls.length).padStart(3) + " tools") : chalk29.dim(" 0 tools");
27352
+ const cost = s.costUSD > 0 ? chalk29.dim(" " + fmtCost3(s.costUSD).padEnd(8)) : " ";
27353
+ const blocked = s.blockedCalls.length > 0 ? chalk29.red(" \u{1F6D1} " + String(s.blockedCalls.length)) : "";
27354
+ const snap = s.hasSnapshot ? chalk29.green(" \u{1F4F8}") : "";
27355
+ const agentBadge = chalk29[agentColorName(s.agent ?? "claude")](
26860
27356
  " " + agentBadgeText(s.agent ?? "claude", 0)
26861
27357
  );
26862
- const sid = chalk28.dim(" " + s.sessionId.slice(0, 8));
27358
+ const sid = chalk29.dim(" " + s.sessionId.slice(0, 8));
26863
27359
  console.log(
26864
27360
  ` ${timeStr} ${prompt} ${tools}${cost}${blocked}${snap}${agentBadge}${sid}${dateRange}`
26865
27361
  );
26866
27362
  }
26867
27363
  console.log("");
26868
27364
  console.log(
26869
- chalk28.dim(" Run") + " " + chalk28.cyan("node9 sessions --detail <session-id>") + chalk28.dim(" for full tool trace.")
27365
+ chalk29.dim(" Run") + " " + chalk29.cyan("node9 sessions --detail <session-id>") + chalk29.dim(" for full tool trace.")
26870
27366
  );
26871
27367
  console.log("");
26872
27368
  }
26873
27369
  function renderDetail(s) {
26874
27370
  console.log("");
26875
- console.log(chalk28.bold(" Session ") + chalk28.dim(s.sessionId));
27371
+ console.log(chalk29.bold(" Session ") + chalk29.dim(s.sessionId));
26876
27372
  console.log(
26877
- chalk28.bold(" Prompt ") + chalk28.white(s.firstPrompt.replace(/\n/g, " ").slice(0, 120))
27373
+ chalk29.bold(" Prompt ") + chalk29.white(s.firstPrompt.replace(/\n/g, " ").slice(0, 120))
26878
27374
  );
26879
- console.log(chalk28.bold(" Project ") + chalk28.white(s.projectLabel));
27375
+ console.log(chalk29.bold(" Project ") + chalk29.white(s.projectLabel));
26880
27376
  if (s.agent) {
26881
- const agentLabel2 = chalk28[agentColorName(s.agent)](agentDisplayName(s.agent));
26882
- console.log(chalk28.bold(" Agent ") + agentLabel2);
27377
+ const agentLabel2 = chalk29[agentColorName(s.agent)](agentDisplayName(s.agent));
27378
+ console.log(chalk29.bold(" Agent ") + agentLabel2);
26883
27379
  }
26884
- console.log(chalk28.bold(" When ") + chalk28.white(fmtDateTime(s.startTime)));
27380
+ console.log(chalk29.bold(" When ") + chalk29.white(fmtDateTime(s.startTime)));
26885
27381
  if (s.costUSD > 0)
26886
- console.log(chalk28.bold(" Cost ") + chalk28.yellow("~" + fmtCost3(s.costUSD)));
27382
+ console.log(chalk29.bold(" Cost ") + chalk29.yellow("~" + fmtCost3(s.costUSD)));
26887
27383
  console.log(
26888
- chalk28.bold(" Snapshot ") + (s.hasSnapshot ? chalk28.green("\u2713 taken") : chalk28.dim("none"))
27384
+ chalk29.bold(" Snapshot ") + (s.hasSnapshot ? chalk29.green("\u2713 taken") : chalk29.dim("none"))
26889
27385
  );
26890
27386
  console.log("");
26891
27387
  if (s.toolCalls.length === 0 && s.blockedCalls.length === 0) {
26892
- console.log(chalk28.dim(" No tool calls recorded.\n"));
27388
+ console.log(chalk29.dim(" No tool calls recorded.\n"));
26893
27389
  return;
26894
27390
  }
26895
27391
  const timeline = [
@@ -26902,32 +27398,32 @@ function renderDetail(s) {
26902
27398
  });
26903
27399
  const headerParts = [`Tool calls (${s.toolCalls.length})`];
26904
27400
  if (s.blockedCalls.length > 0)
26905
- headerParts.push(chalk28.red(`${s.blockedCalls.length} blocked by node9`));
26906
- console.log(chalk28.bold(" " + headerParts.join(" \xB7 ")));
27401
+ headerParts.push(chalk29.red(`${s.blockedCalls.length} blocked by node9`));
27402
+ console.log(chalk29.bold(" " + headerParts.join(" \xB7 ")));
26907
27403
  console.log("");
26908
27404
  for (const entry of timeline) {
26909
27405
  if (entry.kind === "tool") {
26910
27406
  const tc = entry.tc;
26911
27407
  const colorFn = toolColor(tc.tool);
26912
27408
  const toolPad = colorFn(tc.tool.padEnd(16));
26913
- const detail = chalk28.gray(truncate(toolInputSummary(tc.tool, tc.input), 70));
26914
- const ts = tc.timestamp ? chalk28.dim(fmtTime(tc.timestamp) + " ") : " ";
27409
+ const detail = chalk29.gray(truncate(toolInputSummary(tc.tool, tc.input), 70));
27410
+ const ts = tc.timestamp ? chalk29.dim(fmtTime(tc.timestamp) + " ") : " ";
26915
27411
  console.log(` ${ts}${toolPad} ${detail}`);
26916
27412
  } else {
26917
27413
  const bc = entry.bc;
26918
- const ts = bc.timestamp ? chalk28.dim(fmtTime(bc.timestamp) + " ") : " ";
26919
- const label2 = chalk28.red("\u{1F6D1} BLOCKED".padEnd(16));
26920
- const toolName = chalk28.red(bc.tool.padEnd(10));
26921
- const argsSummary = bc.args ? chalk28.gray(truncate(toolInputSummary(bc.tool, bc.args), 40)) : chalk28.dim("[args not logged]");
26922
- const reason = bc.checkedBy ? chalk28.dim(" \u2190 " + bc.checkedBy) : "";
27414
+ const ts = bc.timestamp ? chalk29.dim(fmtTime(bc.timestamp) + " ") : " ";
27415
+ const label2 = chalk29.red("\u{1F6D1} BLOCKED".padEnd(16));
27416
+ const toolName = chalk29.red(bc.tool.padEnd(10));
27417
+ const argsSummary = bc.args ? chalk29.gray(truncate(toolInputSummary(bc.tool, bc.args), 40)) : chalk29.dim("[args not logged]");
27418
+ const reason = bc.checkedBy ? chalk29.dim(" \u2190 " + bc.checkedBy) : "";
26923
27419
  console.log(` ${ts}${label2} ${toolName} ${argsSummary}${reason}`);
26924
27420
  }
26925
27421
  }
26926
27422
  console.log("");
26927
27423
  if (s.modifiedFiles.length > 0) {
26928
- console.log(chalk28.bold(` Files modified (${s.modifiedFiles.length}):`));
27424
+ console.log(chalk29.bold(` Files modified (${s.modifiedFiles.length}):`));
26929
27425
  for (const f of s.modifiedFiles) {
26930
- console.log(" " + chalk28.yellow(f));
27426
+ console.log(" " + chalk29.yellow(f));
26931
27427
  }
26932
27428
  console.log("");
26933
27429
  }
@@ -26935,13 +27431,13 @@ function renderDetail(s) {
26935
27431
  function registerSessionsCommand(program2) {
26936
27432
  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) => {
26937
27433
  console.log("");
26938
- console.log(chalk28.cyan.bold("\u{1F4CB} node9 sessions") + chalk28.dim(" \u2014 what your AI agent did"));
27434
+ console.log(chalk29.cyan.bold("\u{1F4CB} node9 sessions") + chalk29.dim(" \u2014 what your AI agent did"));
26939
27435
  console.log("");
26940
27436
  const days = options.detail || options.all ? null : Math.max(1, parseInt(options.days, 10) || 7);
26941
27437
  const rangeLabel = options.detail ? "all time" : options.all ? "all time" : `last ${String(days)} days`;
26942
- console.log(chalk28.dim(" " + rangeLabel));
27438
+ console.log(chalk29.dim(" " + rangeLabel));
26943
27439
  console.log("");
26944
- process.stdout.write(chalk28.dim(" Loading\u2026"));
27440
+ process.stdout.write(chalk29.dim(" Loading\u2026"));
26945
27441
  const summaries = buildSessions(days);
26946
27442
  if (process.stdout.isTTY) {
26947
27443
  process.stdout.clearLine(0);
@@ -26954,8 +27450,8 @@ function registerSessionsCommand(program2) {
26954
27450
  (s) => s.sessionId === options.detail || s.sessionId.startsWith(options.detail)
26955
27451
  );
26956
27452
  if (!target) {
26957
- console.log(chalk28.red(` Session not found: ${options.detail}`));
26958
- console.log(chalk28.dim(" Run `node9 sessions` to list recent sessions.\n"));
27453
+ console.log(chalk29.red(` Session not found: ${options.detail}`));
27454
+ console.log(chalk29.dim(" Run `node9 sessions` to list recent sessions.\n"));
26959
27455
  return;
26960
27456
  }
26961
27457
  renderDetail(target);
@@ -26969,7 +27465,7 @@ function registerSessionsCommand(program2) {
26969
27465
 
26970
27466
  // src/cli/commands/session-taint.ts
26971
27467
  init_daemon();
26972
- import chalk29 from "chalk";
27468
+ import chalk30 from "chalk";
26973
27469
  function resolveSessionId(records, query) {
26974
27470
  const exact = records.find((r) => r.sessionId === query);
26975
27471
  if (exact) return { record: exact };
@@ -26995,22 +27491,22 @@ function registerSessionTaintCommand(program2) {
26995
27491
  const records = await listSessionTaints();
26996
27492
  console.log("");
26997
27493
  if (records.length === 0) {
26998
- console.log(chalk29.dim(" No tainted sessions."));
26999
- console.log(chalk29.dim(" (Taint lives in the daemon \u2014 a stopped daemon has none.)") + "\n");
27494
+ console.log(chalk30.dim(" No tainted sessions."));
27495
+ console.log(chalk30.dim(" (Taint lives in the daemon \u2014 a stopped daemon has none.)") + "\n");
27000
27496
  return;
27001
27497
  }
27002
27498
  console.log(
27003
- " " + chalk29.bold(String(records.length)) + chalk29.dim(` tainted session${records.length !== 1 ? "s" : ""}`)
27499
+ " " + chalk30.bold(String(records.length)) + chalk30.dim(` tainted session${records.length !== 1 ? "s" : ""}`)
27004
27500
  );
27005
27501
  console.log("");
27006
27502
  for (const r of records) {
27007
27503
  console.log(
27008
- " " + chalk29.yellow(r.sessionId.slice(0, 8).padEnd(10)) + chalk29.red(r.source) + sourceGap(r.source) + chalk29.dim("clears in " + fmtRemaining(r.expiresAt))
27504
+ " " + chalk30.yellow(r.sessionId.slice(0, 8).padEnd(10)) + chalk30.red(r.source) + sourceGap(r.source) + chalk30.dim("clears in " + fmtRemaining(r.expiresAt))
27009
27505
  );
27010
27506
  }
27011
27507
  console.log("");
27012
27508
  console.log(
27013
- chalk29.dim(" Run ") + chalk29.cyan("node9 session-taint clear <id>") + chalk29.dim(" to release one, or ") + chalk29.cyan("--all") + chalk29.dim(" for every session.") + "\n"
27509
+ chalk30.dim(" Run ") + chalk30.cyan("node9 session-taint clear <id>") + chalk30.dim(" to release one, or ") + chalk30.cyan("--all") + chalk30.dim(" for every session.") + "\n"
27014
27510
  );
27015
27511
  });
27016
27512
  cmd.command("clear").description("Clear a session's taint so its next network/write action isn't held for review").argument("[sessionId]", "Session id to clear (the 8-char prefix from `list` is accepted)").option("--all", "Clear every session taint").action(async (sessionId, opts) => {
@@ -27018,32 +27514,32 @@ function registerSessionTaintCommand(program2) {
27018
27514
  if (opts.all) {
27019
27515
  const res2 = await clearSessionTaint({ all: true });
27020
27516
  if (res2.daemonUnavailable) {
27021
- console.log(chalk29.dim(" node9 daemon not running \u2014 no active taints to clear.") + "\n");
27517
+ console.log(chalk30.dim(" node9 daemon not running \u2014 no active taints to clear.") + "\n");
27022
27518
  return;
27023
27519
  }
27024
27520
  console.log(
27025
- chalk29.green(" \u2713 ") + `Cleared ${chalk29.bold(String(res2.cleared))} session taint${res2.cleared !== 1 ? "s" : ""}.
27521
+ chalk30.green(" \u2713 ") + `Cleared ${chalk30.bold(String(res2.cleared))} session taint${res2.cleared !== 1 ? "s" : ""}.
27026
27522
  `
27027
27523
  );
27028
27524
  return;
27029
27525
  }
27030
27526
  if (!sessionId) {
27031
- console.log(chalk29.red(" Provide a session id or --all."));
27032
- console.log(chalk29.dim(" Run `node9 session-taint list` to see tainted sessions.") + "\n");
27527
+ console.log(chalk30.red(" Provide a session id or --all."));
27528
+ console.log(chalk30.dim(" Run `node9 session-taint list` to see tainted sessions.") + "\n");
27033
27529
  return;
27034
27530
  }
27035
27531
  const records = await listSessionTaints();
27036
27532
  if (records.length === 0) {
27037
- console.log(chalk29.dim(" No tainted sessions \u2014 nothing to clear.") + "\n");
27533
+ console.log(chalk30.dim(" No tainted sessions \u2014 nothing to clear.") + "\n");
27038
27534
  return;
27039
27535
  }
27040
27536
  const resolved = resolveSessionId(records, sessionId);
27041
27537
  if ("error" in resolved) {
27042
27538
  if (resolved.error === "not-found") {
27043
- console.log(chalk29.red(` No tainted session matches "${sessionId}".`));
27539
+ console.log(chalk30.red(` No tainted session matches "${sessionId}".`));
27044
27540
  } else {
27045
- console.log(chalk29.red(` "${sessionId}" is ambiguous \u2014 matches:`));
27046
- for (const m of resolved.matches) console.log(chalk29.dim(" " + m));
27541
+ console.log(chalk30.red(` "${sessionId}" is ambiguous \u2014 matches:`));
27542
+ for (const m of resolved.matches) console.log(chalk30.dim(" " + m));
27047
27543
  }
27048
27544
  console.log("");
27049
27545
  return;
@@ -27051,24 +27547,24 @@ function registerSessionTaintCommand(program2) {
27051
27547
  const res = await clearSessionTaint({ sessionId: resolved.record.sessionId });
27052
27548
  if (res.cleared > 0) {
27053
27549
  console.log(
27054
- chalk29.green(" \u2713 ") + `Cleared taint for ${chalk29.yellow(resolved.record.sessionId.slice(0, 8))} ` + chalk29.dim(`(was flagged by ${resolved.record.source}).`) + "\n"
27550
+ chalk30.green(" \u2713 ") + `Cleared taint for ${chalk30.yellow(resolved.record.sessionId.slice(0, 8))} ` + chalk30.dim(`(was flagged by ${resolved.record.source}).`) + "\n"
27055
27551
  );
27056
27552
  } else {
27057
27553
  console.log(
27058
- chalk29.dim(` Session ${resolved.record.sessionId.slice(0, 8)} was already clear.`) + "\n"
27554
+ chalk30.dim(` Session ${resolved.record.sessionId.slice(0, 8)} was already clear.`) + "\n"
27059
27555
  );
27060
27556
  }
27061
27557
  });
27062
27558
  }
27063
27559
 
27064
27560
  // src/cli/commands/skill-pin.ts
27065
- import chalk30 from "chalk";
27066
- import fs57 from "fs";
27067
- import os49 from "os";
27068
- import path55 from "path";
27561
+ import chalk31 from "chalk";
27562
+ import fs60 from "fs";
27563
+ import os50 from "os";
27564
+ import path57 from "path";
27069
27565
  function wipeSkillSessions() {
27070
27566
  try {
27071
- fs57.rmSync(path55.join(os49.homedir(), ".node9", "skill-sessions"), {
27567
+ fs60.rmSync(path57.join(os50.homedir(), ".node9", "skill-sessions"), {
27072
27568
  recursive: true,
27073
27569
  force: true
27074
27570
  });
@@ -27082,29 +27578,29 @@ function registerSkillPinCommand(program2) {
27082
27578
  const result = readSkillPinsSafe();
27083
27579
  if (!result.ok) {
27084
27580
  if (result.reason === "missing") {
27085
- console.log(chalk30.gray("\nNo skill roots are pinned yet."));
27581
+ console.log(chalk31.gray("\nNo skill roots are pinned yet."));
27086
27582
  console.log(
27087
- chalk30.gray("Pins are created automatically on the first tool call of each session.\n")
27583
+ chalk31.gray("Pins are created automatically on the first tool call of each session.\n")
27088
27584
  );
27089
27585
  return;
27090
27586
  }
27091
- console.error(chalk30.red(`
27587
+ console.error(chalk31.red(`
27092
27588
  \u274C Pin file is corrupt: ${result.detail}`));
27093
- console.error(chalk30.yellow(" Run: node9 skill pin reset\n"));
27589
+ console.error(chalk31.yellow(" Run: node9 skill pin reset\n"));
27094
27590
  process.exit(1);
27095
27591
  }
27096
27592
  const entries = Object.entries(result.pins.roots);
27097
27593
  if (entries.length === 0) {
27098
- console.log(chalk30.gray("\nNo skill roots are pinned yet.\n"));
27594
+ console.log(chalk31.gray("\nNo skill roots are pinned yet.\n"));
27099
27595
  return;
27100
27596
  }
27101
- console.log(chalk30.bold("\n\u{1F512} Pinned Skill Roots\n"));
27597
+ console.log(chalk31.bold("\n\u{1F512} Pinned Skill Roots\n"));
27102
27598
  for (const [key, entry] of entries) {
27103
- const missing = entry.exists ? "" : chalk30.yellow(" (not present at pin time)");
27104
- console.log(` ${chalk30.cyan(key)} ${chalk30.gray(entry.rootPath)}${missing}`);
27599
+ const missing = entry.exists ? "" : chalk31.yellow(" (not present at pin time)");
27600
+ console.log(` ${chalk31.cyan(key)} ${chalk31.gray(entry.rootPath)}${missing}`);
27105
27601
  console.log(` Files (${entry.fileCount})`);
27106
- console.log(` Hash: ${chalk30.gray(entry.contentHash.slice(0, 16))}...`);
27107
- console.log(` Pinned: ${chalk30.gray(entry.pinnedAt)}
27602
+ console.log(` Hash: ${chalk31.gray(entry.contentHash.slice(0, 16))}...`);
27603
+ console.log(` Pinned: ${chalk31.gray(entry.pinnedAt)}
27108
27604
  `);
27109
27605
  }
27110
27606
  });
@@ -27113,52 +27609,52 @@ function registerSkillPinCommand(program2) {
27113
27609
  try {
27114
27610
  pins = readSkillPins();
27115
27611
  } catch {
27116
- console.error(chalk30.red("\n\u274C Pin file is corrupt."));
27117
- console.error(chalk30.yellow(" Run: node9 skill pin reset\n"));
27612
+ console.error(chalk31.red("\n\u274C Pin file is corrupt."));
27613
+ console.error(chalk31.yellow(" Run: node9 skill pin reset\n"));
27118
27614
  process.exit(1);
27119
27615
  }
27120
27616
  if (!pins.roots[rootKey]) {
27121
- console.error(chalk30.red(`
27617
+ console.error(chalk31.red(`
27122
27618
  \u274C No pin found for root key "${rootKey}"
27123
27619
  `));
27124
- console.error(`Run ${chalk30.cyan("node9 skill pin list")} to see pinned roots.
27620
+ console.error(`Run ${chalk31.cyan("node9 skill pin list")} to see pinned roots.
27125
27621
  `);
27126
27622
  process.exit(1);
27127
27623
  }
27128
27624
  const rootPath = pins.roots[rootKey].rootPath;
27129
27625
  removePin2(rootKey);
27130
27626
  wipeSkillSessions();
27131
- console.log(chalk30.green(`
27132
- \u{1F513} Pin removed for ${chalk30.cyan(rootKey)}`));
27133
- console.log(chalk30.gray(` ${rootPath}`));
27134
- console.log(chalk30.gray(" Next session will re-pin with current state.\n"));
27627
+ console.log(chalk31.green(`
27628
+ \u{1F513} Pin removed for ${chalk31.cyan(rootKey)}`));
27629
+ console.log(chalk31.gray(` ${rootPath}`));
27630
+ console.log(chalk31.gray(" Next session will re-pin with current state.\n"));
27135
27631
  });
27136
27632
  pinSubCmd.command("reset").description("Clear all skill pins and wipe session verification flags").action(() => {
27137
27633
  const result = readSkillPinsSafe();
27138
27634
  if (!result.ok && result.reason === "missing") {
27139
27635
  wipeSkillSessions();
27140
- console.log(chalk30.gray("\nNo pins to clear.\n"));
27636
+ console.log(chalk31.gray("\nNo pins to clear.\n"));
27141
27637
  return;
27142
27638
  }
27143
27639
  const count = result.ok ? Object.keys(result.pins.roots).length : "?";
27144
27640
  clearAllPins2();
27145
27641
  wipeSkillSessions();
27146
- console.log(chalk30.green(`
27642
+ console.log(chalk31.green(`
27147
27643
  \u{1F513} Cleared ${count} skill pin(s).`));
27148
- console.log(chalk30.gray(" Next session will re-pin with current state.\n"));
27644
+ console.log(chalk31.gray(" Next session will re-pin with current state.\n"));
27149
27645
  });
27150
27646
  }
27151
27647
 
27152
27648
  // src/cli/commands/decisions.ts
27153
- import fs58 from "fs";
27154
- import os50 from "os";
27155
- import path56 from "path";
27156
- import chalk31 from "chalk";
27157
- var DECISIONS_FILE2 = path56.join(os50.homedir(), ".node9", "decisions.json");
27649
+ import fs61 from "fs";
27650
+ import os51 from "os";
27651
+ import path58 from "path";
27652
+ import chalk32 from "chalk";
27653
+ var DECISIONS_FILE2 = path58.join(os51.homedir(), ".node9", "decisions.json");
27158
27654
  function readDecisions() {
27159
27655
  try {
27160
- if (!fs58.existsSync(DECISIONS_FILE2)) return {};
27161
- const raw = fs58.readFileSync(DECISIONS_FILE2, "utf-8");
27656
+ if (!fs61.existsSync(DECISIONS_FILE2)) return {};
27657
+ const raw = fs61.readFileSync(DECISIONS_FILE2, "utf-8");
27162
27658
  const parsed = JSON.parse(raw);
27163
27659
  const out = {};
27164
27660
  for (const [k, v] of Object.entries(parsed)) {
@@ -27170,11 +27666,11 @@ function readDecisions() {
27170
27666
  }
27171
27667
  }
27172
27668
  function writeDecisions(d) {
27173
- const dir = path56.dirname(DECISIONS_FILE2);
27174
- if (!fs58.existsSync(dir)) fs58.mkdirSync(dir, { recursive: true });
27669
+ const dir = path58.dirname(DECISIONS_FILE2);
27670
+ if (!fs61.existsSync(dir)) fs61.mkdirSync(dir, { recursive: true });
27175
27671
  const tmp = `${DECISIONS_FILE2}.${process.pid}.tmp`;
27176
- fs58.writeFileSync(tmp, JSON.stringify(d, null, 2));
27177
- fs58.renameSync(tmp, DECISIONS_FILE2);
27672
+ fs61.writeFileSync(tmp, JSON.stringify(d, null, 2));
27673
+ fs61.renameSync(tmp, DECISIONS_FILE2);
27178
27674
  }
27179
27675
  function registerDecisionsCommand(program2) {
27180
27676
  const cmd = program2.command("decisions").description('Manage persistent "Always Allow" / "Always Deny" tool decisions');
@@ -27182,67 +27678,67 @@ function registerDecisionsCommand(program2) {
27182
27678
  const decisions = readDecisions();
27183
27679
  const entries = Object.entries(decisions);
27184
27680
  if (entries.length === 0) {
27185
- console.log(chalk31.gray(" No persistent decisions stored."));
27681
+ console.log(chalk32.gray(" No persistent decisions stored."));
27186
27682
  console.log(
27187
- chalk31.gray(` File: ${DECISIONS_FILE2}
27188
- `) + chalk31.gray(' Decisions are written when you click "Always Allow" or')
27683
+ chalk32.gray(` File: ${DECISIONS_FILE2}
27684
+ `) + chalk32.gray(' Decisions are written when you click "Always Allow" or')
27189
27685
  );
27190
- console.log(chalk31.gray(' "Always Deny" in node9 tail or the native popup.'));
27686
+ console.log(chalk32.gray(' "Always Deny" in node9 tail or the native popup.'));
27191
27687
  return;
27192
27688
  }
27193
- console.log(chalk31.bold(`
27689
+ console.log(chalk32.bold(`
27194
27690
  Persistent decisions (${entries.length})
27195
27691
  `));
27196
27692
  const w = Math.max(...entries.map(([k]) => k.length));
27197
27693
  for (const [tool, verdict] of entries.sort()) {
27198
- const colored = verdict === "allow" ? chalk31.green(verdict) : chalk31.red(verdict);
27694
+ const colored = verdict === "allow" ? chalk32.green(verdict) : chalk32.red(verdict);
27199
27695
  console.log(` ${tool.padEnd(w)} ${colored}`);
27200
27696
  }
27201
27697
  console.log(
27202
- chalk31.gray(`
27698
+ chalk32.gray(`
27203
27699
  Stored in ${DECISIONS_FILE2}
27204
- `) + chalk31.gray(" Run `node9 decisions clear <tool>` to remove an entry.")
27700
+ `) + chalk32.gray(" Run `node9 decisions clear <tool>` to remove an entry.")
27205
27701
  );
27206
27702
  });
27207
27703
  cmd.command("clear <toolName>").description("Remove a persistent decision for one tool").action((toolName) => {
27208
27704
  const decisions = readDecisions();
27209
27705
  if (!(toolName in decisions)) {
27210
- console.log(chalk31.yellow(` No persistent decision for "${toolName}". Nothing to clear.`));
27706
+ console.log(chalk32.yellow(` No persistent decision for "${toolName}". Nothing to clear.`));
27211
27707
  process.exitCode = 1;
27212
27708
  return;
27213
27709
  }
27214
27710
  delete decisions[toolName];
27215
27711
  writeDecisions(decisions);
27216
- console.log(chalk31.green(` \u2713 Cleared persistent decision for "${toolName}".`));
27712
+ console.log(chalk32.green(` \u2713 Cleared persistent decision for "${toolName}".`));
27217
27713
  });
27218
27714
  cmd.command("clear-all").description("Remove every persistent decision (irreversible)").action(() => {
27219
27715
  const decisions = readDecisions();
27220
27716
  const count = Object.keys(decisions).length;
27221
27717
  if (count === 0) {
27222
- console.log(chalk31.gray(" Nothing to clear \u2014 no persistent decisions stored."));
27718
+ console.log(chalk32.gray(" Nothing to clear \u2014 no persistent decisions stored."));
27223
27719
  return;
27224
27720
  }
27225
27721
  writeDecisions({});
27226
27722
  console.log(
27227
- chalk31.green(` \u2713 Cleared ${count} persistent decision${count === 1 ? "" : "s"}.`)
27723
+ chalk32.green(` \u2713 Cleared ${count} persistent decision${count === 1 ? "" : "s"}.`)
27228
27724
  );
27229
27725
  });
27230
27726
  }
27231
27727
 
27232
27728
  // src/cli/commands/dlp.ts
27233
- import chalk32 from "chalk";
27234
- import fs59 from "fs";
27235
- import path57 from "path";
27236
- import os51 from "os";
27237
- var AUDIT_LOG = path57.join(os51.homedir(), ".node9", "audit.log");
27238
- var RESOLVED_FILE = path57.join(os51.homedir(), ".node9", "dlp-resolved.json");
27729
+ import chalk33 from "chalk";
27730
+ import fs62 from "fs";
27731
+ import path59 from "path";
27732
+ import os52 from "os";
27733
+ var AUDIT_LOG = path59.join(os52.homedir(), ".node9", "audit.log");
27734
+ var RESOLVED_FILE = path59.join(os52.homedir(), ".node9", "dlp-resolved.json");
27239
27735
  var ANSI_RE = /\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g;
27240
27736
  function stripAnsi(s) {
27241
27737
  return s.replace(ANSI_RE, "");
27242
27738
  }
27243
27739
  function loadResolved() {
27244
27740
  try {
27245
- const raw = JSON.parse(fs59.readFileSync(RESOLVED_FILE, "utf-8"));
27741
+ const raw = JSON.parse(fs62.readFileSync(RESOLVED_FILE, "utf-8"));
27246
27742
  return new Set(raw);
27247
27743
  } catch {
27248
27744
  return /* @__PURE__ */ new Set();
@@ -27250,13 +27746,13 @@ function loadResolved() {
27250
27746
  }
27251
27747
  function saveResolved(resolved) {
27252
27748
  try {
27253
- fs59.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
27749
+ fs62.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
27254
27750
  } catch {
27255
27751
  }
27256
27752
  }
27257
27753
  function loadDlpFindings() {
27258
- if (!fs59.existsSync(AUDIT_LOG)) return [];
27259
- return fs59.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
27754
+ if (!fs62.existsSync(AUDIT_LOG)) return [];
27755
+ return fs62.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
27260
27756
  if (!line.trim()) return [];
27261
27757
  try {
27262
27758
  const e = JSON.parse(line);
@@ -27285,14 +27781,14 @@ function registerDlpCommand(program2) {
27285
27781
  cmd.command("resolve").description("Mark all current DLP findings as resolved").action(() => {
27286
27782
  const findings = loadDlpFindings();
27287
27783
  if (findings.length === 0) {
27288
- console.log(chalk32.green("\n \u2705 No response-DLP findings to resolve.\n"));
27784
+ console.log(chalk33.green("\n \u2705 No response-DLP findings to resolve.\n"));
27289
27785
  return;
27290
27786
  }
27291
27787
  const resolved = loadResolved();
27292
27788
  for (const e of findings) resolved.add(entryKey2(e));
27293
27789
  saveResolved(resolved);
27294
27790
  console.log(
27295
- chalk32.green(
27791
+ chalk33.green(
27296
27792
  `
27297
27793
  \u2705 ${findings.length} finding${findings.length !== 1 ? "s" : ""} marked as resolved.
27298
27794
  `
@@ -27306,47 +27802,47 @@ function registerDlpCommand(program2) {
27306
27802
  const resolvedCount = findings.length - open.length;
27307
27803
  console.log("");
27308
27804
  console.log(
27309
- chalk32.bold.cyan("\u{1F510} node9 dlp") + chalk32.dim(" \u2014 secrets found in Claude response text")
27805
+ chalk33.bold.cyan("\u{1F510} node9 dlp") + chalk33.dim(" \u2014 secrets found in Claude response text")
27310
27806
  );
27311
27807
  console.log("");
27312
27808
  if (open.length === 0) {
27313
27809
  if (resolvedCount > 0) {
27314
- console.log(chalk32.green(` \u2705 No open findings \xB7 ${resolvedCount} previously resolved`));
27810
+ console.log(chalk33.green(` \u2705 No open findings \xB7 ${resolvedCount} previously resolved`));
27315
27811
  } else {
27316
27812
  console.log(
27317
- chalk32.green(" \u2705 No findings \u2014 Claude has not leaked secrets in response text")
27813
+ chalk33.green(" \u2705 No findings \u2014 Claude has not leaked secrets in response text")
27318
27814
  );
27319
27815
  }
27320
27816
  console.log("");
27321
27817
  return;
27322
27818
  }
27323
27819
  console.log(
27324
- chalk32.bgRed.white.bold(` \u26A0\uFE0F ${open.length} open finding${open.length !== 1 ? "s" : ""} `) + chalk32.dim(resolvedCount > 0 ? ` (${resolvedCount} resolved)` : "")
27820
+ chalk33.bgRed.white.bold(` \u26A0\uFE0F ${open.length} open finding${open.length !== 1 ? "s" : ""} `) + chalk33.dim(resolvedCount > 0 ? ` (${resolvedCount} resolved)` : "")
27325
27821
  );
27326
27822
  console.log("");
27327
27823
  console.log(
27328
- chalk32.dim(" These secrets were included in Claude's response text \u2014 NOT blocked.")
27824
+ chalk33.dim(" These secrets were included in Claude's response text \u2014 NOT blocked.")
27329
27825
  );
27330
- console.log(chalk32.dim(" Rotate each affected key immediately.\n"));
27826
+ console.log(chalk33.dim(" Rotate each affected key immediately.\n"));
27331
27827
  for (const e of open) {
27332
27828
  console.log(
27333
- " " + chalk32.red("\u25CF") + " " + chalk32.white(e.dlpPattern ?? "Secret") + chalk32.dim(" " + fmtDate3(e.ts))
27829
+ " " + chalk33.red("\u25CF") + " " + chalk33.white(e.dlpPattern ?? "Secret") + chalk33.dim(" " + fmtDate3(e.ts))
27334
27830
  );
27335
27831
  if (e.dlpSample) {
27336
- console.log(" " + chalk32.dim("Sample: ") + chalk32.yellow(stripAnsi(e.dlpSample)));
27832
+ console.log(" " + chalk33.dim("Sample: ") + chalk33.yellow(stripAnsi(e.dlpSample)));
27337
27833
  }
27338
27834
  if (e.project) {
27339
- console.log(" " + chalk32.dim("Project: ") + chalk32.dim(stripAnsi(e.project)));
27835
+ console.log(" " + chalk33.dim("Project: ") + chalk33.dim(stripAnsi(e.project)));
27340
27836
  }
27341
27837
  console.log("");
27342
27838
  }
27343
- console.log(" " + chalk32.bold("Next steps:"));
27344
- console.log(" " + chalk32.cyan("1.") + " Rotate any exposed keys shown above");
27839
+ console.log(" " + chalk33.bold("Next steps:"));
27840
+ console.log(" " + chalk33.cyan("1.") + " Rotate any exposed keys shown above");
27345
27841
  console.log(
27346
- " " + chalk32.cyan("2.") + " Run " + chalk32.white("node9 dlp resolve") + " to acknowledge"
27842
+ " " + chalk33.cyan("2.") + " Run " + chalk33.white("node9 dlp resolve") + " to acknowledge"
27347
27843
  );
27348
27844
  console.log(
27349
- " " + chalk32.cyan("3.") + " Run " + chalk32.white("node9 report") + " for full audit history"
27845
+ " " + chalk33.cyan("3.") + " Run " + chalk33.white("node9 report") + " for full audit history"
27350
27846
  );
27351
27847
  console.log("");
27352
27848
  });
@@ -27354,15 +27850,15 @@ function registerDlpCommand(program2) {
27354
27850
 
27355
27851
  // src/cli/commands/mask.ts
27356
27852
  init_dlp();
27357
- import chalk33 from "chalk";
27358
- import fs60 from "fs";
27359
- import path58 from "path";
27360
- import os52 from "os";
27853
+ import chalk34 from "chalk";
27854
+ import fs63 from "fs";
27855
+ import path60 from "path";
27856
+ import os53 from "os";
27361
27857
  function findJsonlFiles(dir) {
27362
27858
  const results = [];
27363
- if (!fs60.existsSync(dir)) return results;
27364
- for (const entry of fs60.readdirSync(dir, { withFileTypes: true })) {
27365
- const full = path58.join(dir, entry.name);
27859
+ if (!fs63.existsSync(dir)) return results;
27860
+ for (const entry of fs63.readdirSync(dir, { withFileTypes: true })) {
27861
+ const full = path60.join(dir, entry.name);
27366
27862
  if (entry.isDirectory()) results.push(...findJsonlFiles(full));
27367
27863
  else if (entry.isFile() && entry.name.endsWith(".jsonl")) results.push(full);
27368
27864
  }
@@ -27405,7 +27901,7 @@ function redactJson(obj) {
27405
27901
  function processFile(filePath, dryRun) {
27406
27902
  let raw;
27407
27903
  try {
27408
- raw = fs60.readFileSync(filePath, "utf-8");
27904
+ raw = fs63.readFileSync(filePath, "utf-8");
27409
27905
  } catch {
27410
27906
  return { redactedLines: 0, patterns: [] };
27411
27907
  }
@@ -27437,14 +27933,14 @@ function processFile(filePath, dryRun) {
27437
27933
  }
27438
27934
  }
27439
27935
  if (!dryRun && redactedLines > 0) {
27440
- fs60.writeFileSync(filePath, newLines.join("\n"), "utf-8");
27936
+ fs63.writeFileSync(filePath, newLines.join("\n"), "utf-8");
27441
27937
  }
27442
27938
  return { redactedLines, patterns };
27443
27939
  }
27444
27940
  function processJsonFile(filePath, dryRun) {
27445
27941
  let raw;
27446
27942
  try {
27447
- raw = fs60.readFileSync(filePath, "utf-8");
27943
+ raw = fs63.readFileSync(filePath, "utf-8");
27448
27944
  } catch {
27449
27945
  return { redactedLines: 0, patterns: [] };
27450
27946
  }
@@ -27457,15 +27953,15 @@ function processJsonFile(filePath, dryRun) {
27457
27953
  const { value, modified, found } = redactJson(parsed);
27458
27954
  if (!modified) return { redactedLines: 0, patterns: [] };
27459
27955
  if (!dryRun) {
27460
- fs60.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
27956
+ fs63.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
27461
27957
  }
27462
27958
  return { redactedLines: 1, patterns: found };
27463
27959
  }
27464
27960
  function findJsonFiles(dir) {
27465
27961
  const results = [];
27466
- if (!fs60.existsSync(dir)) return results;
27467
- for (const entry of fs60.readdirSync(dir, { withFileTypes: true })) {
27468
- const full = path58.join(dir, entry.name);
27962
+ if (!fs63.existsSync(dir)) return results;
27963
+ for (const entry of fs63.readdirSync(dir, { withFileTypes: true })) {
27964
+ const full = path60.join(dir, entry.name);
27469
27965
  if (entry.isDirectory()) results.push(...findJsonFiles(full));
27470
27966
  else if (entry.isFile() && entry.name.endsWith(".json")) results.push(full);
27471
27967
  }
@@ -27474,9 +27970,9 @@ function findJsonFiles(dir) {
27474
27970
  function registerMaskCommand(program2) {
27475
27971
  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) => {
27476
27972
  const dryRun = !!options.dryRun;
27477
- const home = os52.homedir();
27478
- const claudeDir = path58.join(home, ".claude", "projects");
27479
- const geminiDir = path58.join(home, ".gemini", "tmp");
27973
+ const home = os53.homedir();
27974
+ const claudeDir = path60.join(home, ".claude", "projects");
27975
+ const geminiDir = path60.join(home, ".gemini", "tmp");
27480
27976
  const allFiles = [
27481
27977
  ...findJsonlFiles(claudeDir).map((p) => ({ path: p, type: "jsonl" })),
27482
27978
  ...findJsonFiles(geminiDir).map((p) => ({ path: p, type: "json" }))
@@ -27484,18 +27980,18 @@ function registerMaskCommand(program2) {
27484
27980
  const cutoff = options.all ? null : new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3);
27485
27981
  const filtered = cutoff ? allFiles.filter((f) => {
27486
27982
  try {
27487
- return fs60.statSync(f.path).mtime >= cutoff;
27983
+ return fs63.statSync(f.path).mtime >= cutoff;
27488
27984
  } catch {
27489
27985
  return false;
27490
27986
  }
27491
27987
  }) : allFiles;
27492
27988
  if (filtered.length === 0) {
27493
- console.log(chalk33.yellow(" No session files found."));
27989
+ console.log(chalk34.yellow(" No session files found."));
27494
27990
  return;
27495
27991
  }
27496
27992
  console.log("");
27497
27993
  if (dryRun) {
27498
- console.log(chalk33.dim(" Dry run \u2014 no files will be modified.\n"));
27994
+ console.log(chalk34.dim(" Dry run \u2014 no files will be modified.\n"));
27499
27995
  }
27500
27996
  let totalFiles = 0;
27501
27997
  let totalLines = 0;
@@ -27511,23 +28007,23 @@ function registerMaskCommand(program2) {
27511
28007
  });
27512
28008
  const verb = dryRun ? "Would redact" : "Redacted";
27513
28009
  console.log(
27514
- " " + chalk33.dim(shortPath.slice(0, 60).padEnd(62)) + chalk33.red(`${verb}: `) + chalk33.yellow(patterns.join(", ")) + chalk33.dim(` (${redactedLines} line${redactedLines !== 1 ? "s" : ""})`)
28010
+ " " + chalk34.dim(shortPath.slice(0, 60).padEnd(62)) + chalk34.red(`${verb}: `) + chalk34.yellow(patterns.join(", ")) + chalk34.dim(` (${redactedLines} line${redactedLines !== 1 ? "s" : ""})`)
27515
28011
  );
27516
28012
  }
27517
28013
  }
27518
28014
  console.log("");
27519
28015
  if (totalFiles === 0) {
27520
- console.log(chalk33.green(" No secrets found in session history."));
28016
+ console.log(chalk34.green(" No secrets found in session history."));
27521
28017
  } else {
27522
28018
  const verb = dryRun ? "would be modified" : "modified";
27523
28019
  console.log(
27524
- chalk33.bold(` ${totalFiles} file${totalFiles !== 1 ? "s" : ""} ${verb}`) + chalk33.dim(`, ${totalLines} line${totalLines !== 1 ? "s" : ""} redacted`)
28020
+ chalk34.bold(` ${totalFiles} file${totalFiles !== 1 ? "s" : ""} ${verb}`) + chalk34.dim(`, ${totalLines} line${totalLines !== 1 ? "s" : ""} redacted`)
27525
28021
  );
27526
- console.log(" Patterns: " + chalk33.yellow(totalPatterns.join(", ")));
28022
+ console.log(" Patterns: " + chalk34.yellow(totalPatterns.join(", ")));
27527
28023
  if (!dryRun) {
27528
28024
  console.log("");
27529
28025
  console.log(
27530
- chalk33.dim(
28026
+ chalk34.dim(
27531
28027
  " Note: secrets were already sent to the AI provider during the active session.\n This cleans your local disk only. Rotate any exposed keys."
27532
28028
  )
27533
28029
  );
@@ -27540,20 +28036,20 @@ function registerMaskCommand(program2) {
27540
28036
  // src/cli.ts
27541
28037
  init_blast();
27542
28038
  var { version } = JSON.parse(
27543
- fs63.readFileSync(path61.join(__dirname, "../package.json"), "utf-8")
28039
+ fs66.readFileSync(path63.join(__dirname, "../package.json"), "utf-8")
27544
28040
  );
27545
28041
  var program = new Command();
27546
28042
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
27547
28043
  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) => {
27548
28044
  const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
27549
- const credPath = path61.join(os55.homedir(), ".node9", "credentials.json");
27550
- if (!fs63.existsSync(path61.dirname(credPath)))
27551
- fs63.mkdirSync(path61.dirname(credPath), { recursive: true });
28045
+ const credPath = path63.join(os56.homedir(), ".node9", "credentials.json");
28046
+ if (!fs66.existsSync(path63.dirname(credPath)))
28047
+ fs66.mkdirSync(path63.dirname(credPath), { recursive: true });
27552
28048
  const profileName = options.profile || "default";
27553
28049
  let existingCreds = {};
27554
28050
  try {
27555
- if (fs63.existsSync(credPath)) {
27556
- const raw = JSON.parse(fs63.readFileSync(credPath, "utf-8"));
28051
+ if (fs66.existsSync(credPath)) {
28052
+ const raw = JSON.parse(fs66.readFileSync(credPath, "utf-8"));
27557
28053
  if (raw.apiKey) {
27558
28054
  existingCreds = {
27559
28055
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
@@ -27565,14 +28061,14 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
27565
28061
  } catch {
27566
28062
  }
27567
28063
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
27568
- fs63.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
28064
+ fs66.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
27569
28065
  let effectiveCloud = null;
27570
28066
  if (profileName === "default") {
27571
- const configPath2 = path61.join(os55.homedir(), ".node9", "config.json");
28067
+ const configPath = path63.join(os56.homedir(), ".node9", "config.json");
27572
28068
  let config = {};
27573
28069
  try {
27574
- if (fs63.existsSync(configPath2))
27575
- config = JSON.parse(fs63.readFileSync(configPath2, "utf-8"));
28070
+ if (fs66.existsSync(configPath))
28071
+ config = JSON.parse(fs66.readFileSync(configPath, "utf-8"));
27576
28072
  } catch {
27577
28073
  }
27578
28074
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -27587,35 +28083,35 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
27587
28083
  approvers.cloud = false;
27588
28084
  }
27589
28085
  s.approvers = approvers;
27590
- if (!fs63.existsSync(path61.dirname(configPath2)))
27591
- fs63.mkdirSync(path61.dirname(configPath2), { recursive: true });
27592
- fs63.writeFileSync(configPath2, JSON.stringify(config, null, 2), { mode: 384 });
28086
+ if (!fs66.existsSync(path63.dirname(configPath)))
28087
+ fs66.mkdirSync(path63.dirname(configPath), { recursive: true });
28088
+ fs66.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
27593
28089
  effectiveCloud = approvers.cloud === true;
27594
28090
  }
27595
28091
  if (options.profile && profileName !== "default") {
27596
- console.log(chalk35.green(`\u2705 Profile "${profileName}" saved`));
27597
- console.log(chalk35.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
28092
+ console.log(chalk36.green(`\u2705 Profile "${profileName}" saved`));
28093
+ console.log(chalk36.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
27598
28094
  } else if (options.local || effectiveCloud === false) {
27599
- console.log(chalk35.green(`\u2705 Key saved \u2014 Privacy mode \u{1F6E1}\uFE0F`));
27600
- console.log(chalk35.gray(` All decisions stay on this machine. Nothing syncs to the cloud.`));
28095
+ console.log(chalk36.green(`\u2705 Key saved \u2014 Privacy mode \u{1F6E1}\uFE0F`));
28096
+ console.log(chalk36.gray(` All decisions stay on this machine. Nothing syncs to the cloud.`));
27601
28097
  if (!options.local) {
27602
28098
  console.log(
27603
- chalk35.yellow(` Your config has cloud approvals OFF (settings.approvers.cloud).`)
28099
+ chalk36.yellow(` Your config has cloud approvals OFF (settings.approvers.cloud).`)
27604
28100
  );
27605
28101
  console.log(
27606
- chalk35.gray(` To enable team policy + dashboard sync: set it to true, or re-init.`)
28102
+ chalk36.gray(` To enable team policy + dashboard sync: set it to true, or re-init.`)
27607
28103
  );
27608
28104
  }
27609
28105
  } else {
27610
- console.log(chalk35.green(`\u2705 Logged in \u2014 agent mode`));
27611
- console.log(chalk35.gray(` Team policy enforced for all calls via Node9 cloud.`));
28106
+ console.log(chalk36.green(`\u2705 Logged in \u2014 agent mode`));
28107
+ console.log(chalk36.gray(` Team policy enforced for all calls via Node9 cloud.`));
27612
28108
  }
27613
28109
  });
27614
28110
  program.command("signup").description("Create your node9 account / open the dashboard in your browser").option("--login", "Open the login page instead of signup").action((options) => {
27615
28111
  const route = options.login ? "auth/login" : "auth/signup";
27616
28112
  const url = `https://node9.ai/${route}?ref=cli_cmd`;
27617
28113
  console.log("");
27618
- console.log(" " + chalk35.dim("Opening ") + chalk35.cyan.underline(url));
28114
+ console.log(" " + chalk36.dim("Opening ") + chalk36.cyan.underline(url));
27619
28115
  const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
27620
28116
  try {
27621
28117
  const child = spawn9(opener, [url], {
@@ -27648,7 +28144,7 @@ program.command("addto", { hidden: true }).description("Integrate Node9 with an
27648
28144
  if (target === "hermes") return setupHermes();
27649
28145
  if (target === "hud") return setupHud();
27650
28146
  console.error(
27651
- chalk35.red(
28147
+ chalk36.red(
27652
28148
  `Unknown target: "${target}". Supported: claude, antigravity, copilot, gemini, cursor, codex, windsurf, vscode, hermes, hud`
27653
28149
  )
27654
28150
  );
@@ -27662,20 +28158,20 @@ program.command("setup", { hidden: true }).description('Alias for "addto" \u2014
27662
28158
  "The agent to protect: claude | antigravity | copilot | gemini | cursor | codex | windsurf | vscode | hud"
27663
28159
  ).action(async (target) => {
27664
28160
  if (!target) {
27665
- console.log(chalk35.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
27666
- console.log(" Usage: " + chalk35.white("node9 setup <target>") + "\n");
28161
+ console.log(chalk36.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
28162
+ console.log(" Usage: " + chalk36.white("node9 setup <target>") + "\n");
27667
28163
  console.log(" Targets:");
27668
- console.log(" " + chalk35.green("claude") + " \u2014 Claude Code (hook mode)");
27669
- console.log(" " + chalk35.green("gemini") + " \u2014 Gemini CLI (hook mode)");
27670
- console.log(" " + chalk35.green("antigravity") + " \u2014 Antigravity / agy (hook mode)");
27671
- console.log(" " + chalk35.green("copilot") + " \u2014 GitHub Copilot CLI (hook mode)");
27672
- console.log(" " + chalk35.green("cursor") + " \u2014 Cursor (MCP proxy)");
27673
- console.log(" " + chalk35.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
27674
- console.log(" " + chalk35.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
27675
- console.log(" " + chalk35.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
27676
- console.log(" " + chalk35.green("hermes") + " \u2014 Hermes Agent (hook mode)");
28164
+ console.log(" " + chalk36.green("claude") + " \u2014 Claude Code (hook mode)");
28165
+ console.log(" " + chalk36.green("gemini") + " \u2014 Gemini CLI (hook mode)");
28166
+ console.log(" " + chalk36.green("antigravity") + " \u2014 Antigravity / agy (hook mode)");
28167
+ console.log(" " + chalk36.green("copilot") + " \u2014 GitHub Copilot CLI (hook mode)");
28168
+ console.log(" " + chalk36.green("cursor") + " \u2014 Cursor (MCP proxy)");
28169
+ console.log(" " + chalk36.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
28170
+ console.log(" " + chalk36.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
28171
+ console.log(" " + chalk36.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
28172
+ console.log(" " + chalk36.green("hermes") + " \u2014 Hermes Agent (hook mode)");
27677
28173
  process.stdout.write(
27678
- " " + chalk35.green("hud") + " \u2014 Claude Code security statusline\n"
28174
+ " " + chalk36.green("hud") + " \u2014 Claude Code security statusline\n"
27679
28175
  );
27680
28176
  console.log("");
27681
28177
  return;
@@ -27692,7 +28188,7 @@ program.command("setup", { hidden: true }).description('Alias for "addto" \u2014
27692
28188
  if (t === "hermes") return setupHermes();
27693
28189
  if (t === "hud") return setupHud();
27694
28190
  console.error(
27695
- chalk35.red(
28191
+ chalk36.red(
27696
28192
  `Unknown target: "${target}". Supported: claude, antigravity, copilot, gemini, cursor, codex, windsurf, vscode, hermes, hud`
27697
28193
  )
27698
28194
  );
@@ -27718,33 +28214,33 @@ program.command("removefrom", { hidden: true }).description("Remove Node9 hooks
27718
28214
  else if (target === "hud") fn = teardownHud;
27719
28215
  else {
27720
28216
  console.error(
27721
- chalk35.red(
28217
+ chalk36.red(
27722
28218
  `Unknown target: "${target}". Supported: claude, antigravity, copilot, gemini, cursor, codex, windsurf, vscode, hermes, hud`
27723
28219
  )
27724
28220
  );
27725
28221
  process.exit(1);
27726
28222
  }
27727
- console.log(chalk35.cyan(`
28223
+ console.log(chalk36.cyan(`
27728
28224
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
27729
28225
  `));
27730
28226
  try {
27731
28227
  fn();
27732
28228
  } catch (err2) {
27733
- console.error(chalk35.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
28229
+ console.error(chalk36.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
27734
28230
  process.exit(1);
27735
28231
  }
27736
- console.log(chalk35.gray("\n Restart the agent for changes to take effect."));
28232
+ console.log(chalk36.gray("\n Restart the agent for changes to take effect."));
27737
28233
  });
27738
28234
  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) => {
27739
- console.log(chalk35.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
27740
- console.log(chalk35.bold("Stopping daemon..."));
28235
+ console.log(chalk36.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
28236
+ console.log(chalk36.bold("Stopping daemon..."));
27741
28237
  try {
27742
28238
  stopDaemon();
27743
- console.log(chalk35.green(" \u2705 Daemon stopped"));
28239
+ console.log(chalk36.green(" \u2705 Daemon stopped"));
27744
28240
  } catch {
27745
- console.log(chalk35.blue(" \u2139\uFE0F Daemon was not running"));
28241
+ console.log(chalk36.blue(" \u2139\uFE0F Daemon was not running"));
27746
28242
  }
27747
- console.log(chalk35.bold("\nRemoving hooks..."));
28243
+ console.log(chalk36.bold("\nRemoving hooks..."));
27748
28244
  let teardownFailed = false;
27749
28245
  for (const [label2, fn] of [
27750
28246
  ["Claude", teardownClaude],
@@ -27760,45 +28256,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
27760
28256
  } catch (err2) {
27761
28257
  teardownFailed = true;
27762
28258
  console.error(
27763
- chalk35.red(
28259
+ chalk36.red(
27764
28260
  ` \u26A0\uFE0F Failed to remove ${label2} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
27765
28261
  )
27766
28262
  );
27767
28263
  }
27768
28264
  }
27769
28265
  if (options.purge) {
27770
- const node9Dir = path61.join(os55.homedir(), ".node9");
27771
- if (fs63.existsSync(node9Dir)) {
28266
+ const node9Dir = path63.join(os56.homedir(), ".node9");
28267
+ if (fs66.existsSync(node9Dir)) {
27772
28268
  const confirmed = await confirm2({
27773
28269
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
27774
28270
  default: false
27775
28271
  });
27776
28272
  if (confirmed) {
27777
- fs63.rmSync(node9Dir, { recursive: true });
27778
- if (fs63.existsSync(node9Dir)) {
28273
+ fs66.rmSync(node9Dir, { recursive: true });
28274
+ if (fs66.existsSync(node9Dir)) {
27779
28275
  console.error(
27780
- chalk35.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
28276
+ chalk36.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
27781
28277
  );
27782
28278
  } else {
27783
- console.log(chalk35.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
28279
+ console.log(chalk36.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
27784
28280
  }
27785
28281
  } else {
27786
- console.log(chalk35.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
28282
+ console.log(chalk36.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
27787
28283
  }
27788
28284
  } else {
27789
- console.log(chalk35.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
28285
+ console.log(chalk36.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
27790
28286
  }
27791
28287
  } else {
27792
28288
  console.log(
27793
- chalk35.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
28289
+ chalk36.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
27794
28290
  );
27795
28291
  }
27796
28292
  if (teardownFailed) {
27797
- console.error(chalk35.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
28293
+ console.error(chalk36.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
27798
28294
  process.exit(1);
27799
28295
  }
27800
- console.log(chalk35.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g node9-ai"));
27801
- console.log(chalk35.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
28296
+ console.log(chalk36.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g node9-ai"));
28297
+ console.log(chalk36.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
27802
28298
  });
27803
28299
  registerDoctorCommand(program, version);
27804
28300
  program.command("explain").description(
@@ -27811,7 +28307,7 @@ program.command("explain").description(
27811
28307
  try {
27812
28308
  args = JSON.parse(trimmed);
27813
28309
  } catch {
27814
- console.error(chalk35.red(`
28310
+ console.error(chalk36.red(`
27815
28311
  \u274C Invalid JSON: ${trimmed}
27816
28312
  `));
27817
28313
  process.exit(1);
@@ -27822,54 +28318,62 @@ program.command("explain").description(
27822
28318
  }
27823
28319
  const result = await explainPolicy(tool, args);
27824
28320
  console.log("");
27825
- console.log(chalk35.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
28321
+ console.log(chalk36.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
27826
28322
  console.log("");
27827
- console.log(` ${chalk35.bold("Tool:")} ${chalk35.white(result.tool)}`);
28323
+ console.log(` ${chalk36.bold("Tool:")} ${chalk36.white(result.tool)}`);
27828
28324
  if (argsRaw) {
27829
28325
  const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
27830
- console.log(` ${chalk35.bold("Input:")} ${chalk35.gray(preview2)}`);
28326
+ console.log(` ${chalk36.bold("Input:")} ${chalk36.gray(preview2)}`);
27831
28327
  }
27832
28328
  console.log("");
27833
- console.log(chalk35.bold("Config Sources (Waterfall):"));
28329
+ console.log(chalk36.bold("Config Sources (Waterfall):"));
27834
28330
  for (const tier of result.waterfall) {
27835
- const num3 = chalk35.gray(` ${tier.tier}.`);
28331
+ const num3 = chalk36.gray(` ${tier.tier}.`);
27836
28332
  const label2 = tier.label.padEnd(16);
27837
28333
  let statusStr;
27838
28334
  if (tier.tier === 1) {
27839
- statusStr = chalk35.gray(tier.note ?? "");
28335
+ statusStr = chalk36.gray(tier.note ?? "");
27840
28336
  } else if (tier.status === "active") {
27841
- const loc = tier.path ? chalk35.gray(tier.path) : "";
27842
- const note = tier.note ? chalk35.gray(`(${tier.note})`) : "";
27843
- statusStr = chalk35.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
28337
+ const loc = tier.path ? chalk36.gray(tier.path) : "";
28338
+ const note = tier.note ? chalk36.gray(`(${tier.note})`) : "";
28339
+ statusStr = chalk36.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
27844
28340
  } else {
27845
- statusStr = chalk35.gray("\u25CB " + (tier.note ?? "not found"));
28341
+ statusStr = chalk36.gray("\u25CB " + (tier.note ?? "not found"));
27846
28342
  }
27847
- console.log(`${num3} ${chalk35.white(label2)} ${statusStr}`);
28343
+ console.log(`${num3} ${chalk36.white(label2)} ${statusStr}`);
27848
28344
  }
27849
28345
  console.log("");
27850
- console.log(chalk35.bold("Policy Evaluation:"));
28346
+ console.log(chalk36.bold("Policy Evaluation:"));
27851
28347
  for (const step of result.steps) {
27852
28348
  const isFinal = step.isFinal;
27853
28349
  let icon;
27854
- if (step.outcome === "allow") icon = chalk35.green(" \u2705");
27855
- else if (step.outcome === "review") icon = chalk35.red(" \u{1F534}");
27856
- else if (step.outcome === "skip") icon = chalk35.gray(" \u2500 ");
27857
- else icon = chalk35.gray(" \u25CB ");
28350
+ if (step.outcome === "allow") icon = chalk36.green(" \u2705");
28351
+ else if (step.outcome === "block") icon = chalk36.red(" \u{1F6D1}");
28352
+ else if (step.outcome === "review") icon = chalk36.red(" \u{1F534}");
28353
+ else if (step.outcome === "skip") icon = chalk36.gray(" \u2500 ");
28354
+ else icon = chalk36.gray(" \u25CB ");
27858
28355
  const name = step.name.padEnd(18);
27859
- const nameStr = isFinal ? chalk35.white.bold(name) : chalk35.white(name);
27860
- const detail = isFinal ? chalk35.white(step.detail) : chalk35.gray(step.detail);
27861
- const arrow = isFinal ? chalk35.yellow(" \u2190 STOP") : "";
28356
+ const nameStr = isFinal ? chalk36.white.bold(name) : chalk36.white(name);
28357
+ const detail = isFinal ? chalk36.white(step.detail) : chalk36.gray(step.detail);
28358
+ const arrow = isFinal ? chalk36.yellow(" \u2190 STOP") : "";
27862
28359
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
27863
28360
  }
27864
28361
  console.log("");
27865
28362
  if (result.decision === "allow") {
27866
- console.log(chalk35.green.bold(" Decision: \u2705 ALLOW") + chalk35.gray(" \u2014 no approval needed"));
28363
+ console.log(chalk36.green.bold(" Decision: \u2705 ALLOW") + chalk36.gray(" \u2014 no approval needed"));
28364
+ } else if (result.decision === "block") {
28365
+ console.log(
28366
+ chalk36.red.bold(" Decision: \u{1F6D1} BLOCK") + chalk36.gray(" \u2014 this action is blocked")
28367
+ );
28368
+ if (result.blockedByLabel) {
28369
+ console.log(chalk36.gray(` Reason: ${result.blockedByLabel}`));
28370
+ }
27867
28371
  } else {
27868
28372
  console.log(
27869
- chalk35.red.bold(" Decision: \u{1F534} REVIEW") + chalk35.gray(" \u2014 human approval required")
28373
+ chalk36.red.bold(" Decision: \u{1F534} REVIEW") + chalk36.gray(" \u2014 human approval required")
27870
28374
  );
27871
28375
  if (result.blockedByLabel) {
27872
- console.log(chalk35.gray(` Reason: ${result.blockedByLabel}`));
28376
+ console.log(chalk36.gray(` Reason: ${result.blockedByLabel}`));
27873
28377
  }
27874
28378
  }
27875
28379
  console.log("");
@@ -27884,18 +28388,18 @@ program.command("tail").description("Stream live agent activity to the terminal"
27884
28388
  try {
27885
28389
  await startTail2(options);
27886
28390
  } catch (err2) {
27887
- console.error(chalk35.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
28391
+ console.error(chalk36.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
27888
28392
  process.exit(1);
27889
28393
  }
27890
28394
  });
27891
28395
  program.command("monitor").description("Live interactive dashboard \u2014 activity feed, approvals, security signals").action(async () => {
27892
28396
  try {
27893
- const dashboardPath = path61.join(__dirname, "dashboard.mjs");
28397
+ const dashboardPath = path63.join(__dirname, "dashboard.mjs");
27894
28398
  const dynamicImport = new Function("id", "return import(id)");
27895
28399
  const mod = await dynamicImport(`file://${dashboardPath}`);
27896
28400
  await mod.startMonitor();
27897
28401
  } catch (err2) {
27898
- console.error(chalk35.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
28402
+ console.error(chalk36.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
27899
28403
  process.exit(1);
27900
28404
  }
27901
28405
  });
@@ -27928,14 +28432,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
27928
28432
  Run "node9 addto claude" to register it as the statusLine.`
27929
28433
  ).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
27930
28434
  if (subcommand === "debug") {
27931
- const flagFile = path61.join(os55.homedir(), ".node9", "hud-debug");
28435
+ const flagFile = path63.join(os56.homedir(), ".node9", "hud-debug");
27932
28436
  if (state === "on") {
27933
- fs63.mkdirSync(path61.dirname(flagFile), { recursive: true });
27934
- fs63.writeFileSync(flagFile, "");
28437
+ fs66.mkdirSync(path63.dirname(flagFile), { recursive: true });
28438
+ fs66.writeFileSync(flagFile, "");
27935
28439
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
27936
28440
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
27937
28441
  } else if (state === "off") {
27938
- if (fs63.existsSync(flagFile)) fs63.unlinkSync(flagFile);
28442
+ if (fs66.existsSync(flagFile)) fs66.unlinkSync(flagFile);
27939
28443
  console.log("HUD debug logging disabled.");
27940
28444
  } else {
27941
28445
  console.error("Usage: node9 hud debug on|off");
@@ -27950,7 +28454,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
27950
28454
  const ms = parseDuration(options.duration);
27951
28455
  if (ms === null) {
27952
28456
  console.error(
27953
- chalk35.red(`
28457
+ chalk36.red(`
27954
28458
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
27955
28459
  `)
27956
28460
  );
@@ -27958,20 +28462,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
27958
28462
  }
27959
28463
  pauseNode9(ms, options.duration);
27960
28464
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
27961
- console.log(chalk35.yellow(`
28465
+ console.log(chalk36.yellow(`
27962
28466
  \u23F8 Node9 paused until ${expiresAt}`));
27963
- console.log(chalk35.gray(` All tool calls will be allowed without review.`));
27964
- console.log(chalk35.gray(` Run "node9 resume" to re-enable early.
28467
+ console.log(chalk36.gray(` All tool calls will be allowed without review.`));
28468
+ console.log(chalk36.gray(` Run "node9 resume" to re-enable early.
27965
28469
  `));
27966
28470
  });
27967
28471
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
27968
28472
  const { paused } = checkPause();
27969
28473
  if (!paused) {
27970
- console.log(chalk35.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
28474
+ console.log(chalk36.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
27971
28475
  return;
27972
28476
  }
27973
28477
  resumeNode9();
27974
- console.log(chalk35.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
28478
+ console.log(chalk36.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
27975
28479
  });
27976
28480
  var HOOK_BASED_AGENTS = {
27977
28481
  claude: "claude",
@@ -27987,15 +28491,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
27987
28491
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
27988
28492
  const target = HOOK_BASED_AGENTS[firstArg2];
27989
28493
  console.error(
27990
- chalk35.yellow(`
28494
+ chalk36.yellow(`
27991
28495
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
27992
28496
  );
27993
- console.error(chalk35.white(`
28497
+ console.error(chalk36.white(`
27994
28498
  "${target}" uses its own hook system. Use:`));
27995
28499
  console.error(
27996
- chalk35.green(` node9 addto ${target} `) + chalk35.gray("# one-time setup")
28500
+ chalk36.green(` node9 addto ${target} `) + chalk36.gray("# one-time setup")
27997
28501
  );
27998
- console.error(chalk35.green(` ${target} `) + chalk35.gray("# run normally"));
28502
+ console.error(chalk36.green(` ${target} `) + chalk36.gray("# run normally"));
27999
28503
  process.exit(1);
28000
28504
  }
28001
28505
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -28012,7 +28516,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
28012
28516
  }
28013
28517
  );
28014
28518
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
28015
- console.error(chalk35.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
28519
+ console.error(chalk36.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
28016
28520
  const daemonReady = await autoStartDaemonAndWait();
28017
28521
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
28018
28522
  }
@@ -28025,12 +28529,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
28025
28529
  }
28026
28530
  if (!result.approved) {
28027
28531
  console.error(
28028
- chalk35.red(`
28532
+ chalk36.red(`
28029
28533
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
28030
28534
  );
28031
28535
  process.exit(1);
28032
28536
  }
28033
- console.error(chalk35.green("\n\u2705 Approved \u2014 running command...\n"));
28537
+ console.error(chalk36.green("\n\u2705 Approved \u2014 running command...\n"));
28034
28538
  await runProxy(fullCommand);
28035
28539
  } else {
28036
28540
  program.help();
@@ -28045,6 +28549,7 @@ registerAgentsCommand(program);
28045
28549
  registerScanCommand(program);
28046
28550
  registerPostureCommand(program);
28047
28551
  registerEgressCommand(program);
28552
+ registerJailCommand(program);
28048
28553
  registerSandboxCommand(program, version);
28049
28554
  registerSessionsCommand(program);
28050
28555
  registerSessionTaintCommand(program);
@@ -28056,9 +28561,9 @@ if (process.argv[2] !== "daemon") {
28056
28561
  const isCheckHook = process.argv[2] === "check";
28057
28562
  if (isCheckHook) {
28058
28563
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
28059
- const logPath = path61.join(os55.homedir(), ".node9", "hook-debug.log");
28564
+ const logPath = path63.join(os56.homedir(), ".node9", "hook-debug.log");
28060
28565
  const msg = reason instanceof Error ? reason.message : String(reason);
28061
- fs63.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
28566
+ fs66.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
28062
28567
  `);
28063
28568
  }
28064
28569
  process.exit(0);