@node9/proxy 1.39.0 → 1.41.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.
package/dist/cli.js CHANGED
@@ -206,8 +206,8 @@ function sanitizeConfig(raw) {
206
206
  }
207
207
  }
208
208
  const lines = result.error.issues.map((issue) => {
209
- const path58 = issue.path.length > 0 ? issue.path.join(".") : "root";
210
- return ` \u2022 ${path58}: ${issue.message}`;
209
+ const path62 = issue.path.length > 0 ? issue.path.join(".") : "root";
210
+ return ` \u2022 ${path62}: ${issue.message}`;
211
211
  });
212
212
  return {
213
213
  sanitized,
@@ -293,6 +293,11 @@ var init_config_schema = __esm({
293
293
  allowGlobalPause: import_zod.z.boolean().optional(),
294
294
  auditHashArgs: import_zod.z.boolean().optional(),
295
295
  agentPolicy: import_zod.z.enum(["require_approval", "block_on_rules"]).optional(),
296
+ // Where a `review` verdict's prompt is rendered: 'ask' = the agent's own
297
+ // inline approve/deny prompt (Claude Code / GitHub Copilot); 'approver' =
298
+ // node9's own approver (terminal/native/cloud). Unset → smart default
299
+ // (ask for ask-capable agents unless a cloud approver is configured).
300
+ reviewChannel: import_zod.z.enum(["ask", "approver"]).optional(),
296
301
  cloudSyncIntervalHours: import_zod.z.number().positive().optional(),
297
302
  // Outbox shipper (audit.log → SaaS batch ingest). enabled defaults
298
303
  // to true; set false to fall back to local-only auditing.
@@ -1274,9 +1279,9 @@ function matchesPattern(text, patterns) {
1274
1279
  const withoutDotSlash = text.replace(/^\.\//, "");
1275
1280
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1276
1281
  }
1277
- function getNestedValue(obj, path58) {
1282
+ function getNestedValue(obj, path62) {
1278
1283
  if (!obj || typeof obj !== "object") return null;
1279
- const segments = path58.split(".");
1284
+ const segments = path62.split(".");
1280
1285
  for (const seg of segments) {
1281
1286
  if (FORBIDDEN_PATH_SEGMENTS.has(seg)) return null;
1282
1287
  }
@@ -4197,6 +4202,7 @@ function getConfig(cwd) {
4197
4202
  if (s.approvalTimeoutSeconds !== void 0 && s.approvalTimeoutMs === void 0)
4198
4203
  mergedSettings.approvalTimeoutMs = s.approvalTimeoutSeconds * 1e3;
4199
4204
  if (s.environment !== void 0) mergedSettings.environment = s.environment;
4205
+ if (s.reviewChannel !== void 0) mergedSettings.reviewChannel = s.reviewChannel;
4200
4206
  if (s.cloudSyncIntervalHours !== void 0)
4201
4207
  mergedSettings.cloudSyncIntervalHours = s.cloudSyncIntervalHours;
4202
4208
  if (s.hud !== void 0) mergedSettings.hud = { ...mergedSettings.hud, ...s.hud };
@@ -6353,7 +6359,7 @@ async function authorizeHeadless(toolName, args, meta, options) {
6353
6359
  tool: toolName,
6354
6360
  args,
6355
6361
  ts: actTs,
6356
- status: result.approved ? "allow" : result.blockedByLabel?.includes("DLP") ? "dlp" : result.blockedByLabel?.includes("Taint") ? "taint" : "block",
6362
+ status: result.review ? "review" : result.approved ? "allow" : result.blockedByLabel?.includes("DLP") ? "dlp" : result.blockedByLabel?.includes("Taint") ? "taint" : "block",
6357
6363
  label: result.blockedByLabel,
6358
6364
  ruleHit: result.ruleHit,
6359
6365
  observeWouldBlock: result.observeWouldBlock,
@@ -6716,6 +6722,16 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
6716
6722
  taintWarning
6717
6723
  );
6718
6724
  }
6725
+ const cloudEnforcedForDefer = approvers.cloud && !!creds?.apiKey;
6726
+ if (options?.deferReview && !taintWarning && !cloudEnforcedForDefer) {
6727
+ return {
6728
+ approved: false,
6729
+ review: true,
6730
+ reason: explainableLabel || "Node9 flagged this action for review.",
6731
+ ruleDescription: policyRuleDescription,
6732
+ blockedByLabel: explainableLabel
6733
+ };
6734
+ }
6719
6735
  let cloudRequestId = null;
6720
6736
  const cloudEnforced = approvers.cloud && !!creds?.apiKey;
6721
6737
  const forceReview = localSmartRuleMatched === true || options?.localSmartRuleMatched === true || void 0;
@@ -7522,6 +7538,18 @@ function removeNode9McpServer(servers) {
7522
7538
  function printDaemonTip() {
7523
7539
  console.log(import_chalk.default.cyan("\n \u{1F4A1} Node9 will protect you automatically using Native OS popups."));
7524
7540
  }
7541
+ function printInlineAskNotice() {
7542
+ console.log(
7543
+ import_chalk.default.cyan(
7544
+ " \u{1F4AC} Review prompts appear inline in your agent (approve/deny in the chat) by default."
7545
+ )
7546
+ );
7547
+ console.log(
7548
+ import_chalk.default.gray(
7549
+ ' Prefer node9\u2019s own approver? Set "reviewChannel": "approver" in config, or add --no-ask to the hook.\n (Inline prompts are auto-disabled when a cloud approver is configured.)'
7550
+ )
7551
+ );
7552
+ }
7525
7553
  function fullPathCommand(subcommand) {
7526
7554
  if (process.env.NODE9_TESTING === "1") return `node9 ${subcommand}`;
7527
7555
  const nodeExec = toForwardSlashes(process.execPath);
@@ -7846,6 +7874,7 @@ async function setupClaude() {
7846
7874
  console.log(import_chalk.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Claude Code!"));
7847
7875
  console.log(import_chalk.default.gray(" Restart Claude Code for changes to take effect."));
7848
7876
  printDaemonTip();
7877
+ printInlineAskNotice();
7849
7878
  }
7850
7879
  }
7851
7880
  async function setupGemini() {
@@ -8242,6 +8271,7 @@ async function setupCopilot() {
8242
8271
  console.log(import_chalk.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting GitHub Copilot CLI!"));
8243
8272
  console.log(import_chalk.default.gray(" Restart Copilot CLI for changes to take effect."));
8244
8273
  printDaemonTip();
8274
+ printInlineAskNotice();
8245
8275
  }
8246
8276
  function teardownCopilot() {
8247
8277
  const homeDir2 = import_os12.default.homedir();
@@ -15532,8 +15562,8 @@ function fileSignature(filePath) {
15532
15562
  const fd = import_fs27.default.openSync(filePath, "r");
15533
15563
  try {
15534
15564
  const buf = Buffer.alloc(512);
15535
- const read = import_fs27.default.readSync(fd, buf, 0, 512, 0);
15536
- const slice = buf.subarray(0, read);
15565
+ const read2 = import_fs27.default.readSync(fd, buf, 0, 512, 0);
15566
+ const slice = buf.subarray(0, read2);
15537
15567
  const nl = slice.indexOf(10);
15538
15568
  const firstLine = nl === -1 ? slice : slice.subarray(0, nl);
15539
15569
  return import_crypto9.default.createHash("sha256").update(firstLine).digest("hex").slice(0, 16);
@@ -15637,13 +15667,13 @@ async function shipOnce(deps = {}) {
15637
15667
  const toRead = Math.min(size - offset, MAX_CHUNK_BYTES);
15638
15668
  const buf = Buffer.alloc(toRead);
15639
15669
  const fd = import_fs27.default.openSync(auditLogPath, "r");
15640
- let read;
15670
+ let read2;
15641
15671
  try {
15642
- read = import_fs27.default.readSync(fd, buf, 0, toRead, offset);
15672
+ read2 = import_fs27.default.readSync(fd, buf, 0, toRead, offset);
15643
15673
  } finally {
15644
15674
  import_fs27.default.closeSync(fd);
15645
15675
  }
15646
- const { rows, consumed } = buildWireRows(buf.subarray(0, read));
15676
+ const { rows, consumed } = buildWireRows(buf.subarray(0, read2));
15647
15677
  if (consumed === 0) break;
15648
15678
  for (let i = 0; i < rows.length; i += MAX_BATCH) {
15649
15679
  const batch = rows.slice(i, i + MAX_BATCH);
@@ -17246,20 +17276,20 @@ function getModelContextLimit(model) {
17246
17276
  return 2e5;
17247
17277
  }
17248
17278
  function readSessionUsage() {
17249
- const projectsDir = import_path55.default.join(import_os51.default.homedir(), ".claude", "projects");
17250
- if (!import_fs56.default.existsSync(projectsDir)) return null;
17279
+ const projectsDir = import_path59.default.join(import_os53.default.homedir(), ".claude", "projects");
17280
+ if (!import_fs61.default.existsSync(projectsDir)) return null;
17251
17281
  let latestFile = null;
17252
17282
  let latestMtime = 0;
17253
17283
  try {
17254
- for (const dir of import_fs56.default.readdirSync(projectsDir)) {
17255
- const dirPath = import_path55.default.join(projectsDir, dir);
17284
+ for (const dir of import_fs61.default.readdirSync(projectsDir)) {
17285
+ const dirPath = import_path59.default.join(projectsDir, dir);
17256
17286
  try {
17257
- if (!import_fs56.default.statSync(dirPath).isDirectory()) continue;
17258
- for (const file of import_fs56.default.readdirSync(dirPath)) {
17287
+ if (!import_fs61.default.statSync(dirPath).isDirectory()) continue;
17288
+ for (const file of import_fs61.default.readdirSync(dirPath)) {
17259
17289
  if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
17260
- const filePath = import_path55.default.join(dirPath, file);
17290
+ const filePath = import_path59.default.join(dirPath, file);
17261
17291
  try {
17262
- const mtime = import_fs56.default.statSync(filePath).mtimeMs;
17292
+ const mtime = import_fs61.default.statSync(filePath).mtimeMs;
17263
17293
  if (mtime > latestMtime) {
17264
17294
  latestMtime = mtime;
17265
17295
  latestFile = filePath;
@@ -17274,7 +17304,7 @@ function readSessionUsage() {
17274
17304
  }
17275
17305
  if (!latestFile) return null;
17276
17306
  try {
17277
- const lines = import_fs56.default.readFileSync(latestFile, "utf-8").split("\n");
17307
+ const lines = import_fs61.default.readFileSync(latestFile, "utf-8").split("\n");
17278
17308
  let lastModel = "";
17279
17309
  let lastInput = 0;
17280
17310
  let lastOutput = 0;
@@ -17299,10 +17329,10 @@ function readSessionUsage() {
17299
17329
  }
17300
17330
  }
17301
17331
  function formatContextStat(stat) {
17302
- const pctColor = stat.fillPct >= 80 ? import_chalk33.default.red : stat.fillPct >= 50 ? import_chalk33.default.yellow : import_chalk33.default.cyan;
17332
+ const pctColor = stat.fillPct >= 80 ? import_chalk34.default.red : stat.fillPct >= 50 ? import_chalk34.default.yellow : import_chalk34.default.cyan;
17303
17333
  const k = (n) => `${Math.round(n / 1e3)}k`;
17304
17334
  const modelShort = stat.model.replace(/@.*$/, "").replace(/-\d{8}$/, "").replace(/^claude-/, "");
17305
- return import_chalk33.default.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + import_chalk33.default.dim(
17335
+ return import_chalk34.default.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + import_chalk34.default.dim(
17306
17336
  ` (${k(stat.inputTokens)}/${k(getModelContextLimit(stat.model))} out ${k(stat.outputTokens)} \xB7 ${modelShort})`
17307
17337
  );
17308
17338
  }
@@ -17325,32 +17355,32 @@ function agentLabel(agent, mcpServer, sessionId) {
17325
17355
  const tag = sessionTag(sessionId);
17326
17356
  const tagSuffix = tag ? `\xB7${tag}` : "";
17327
17357
  if (!agent || agent === "Terminal") {
17328
- return mcpServer ? import_chalk33.default.dim(`[\u2192 ${mcpServer}] `) : "";
17358
+ return mcpServer ? import_chalk34.default.dim(`[\u2192 ${mcpServer}] `) : "";
17329
17359
  }
17330
17360
  const short = agent === "Claude Code" ? "Claude" : agent === "Gemini CLI" ? "Gemini" : agent === "Unknown Agent" ? "" : agent.split(" ")[0];
17331
- if (!short) return mcpServer ? import_chalk33.default.dim(`[\u2192 ${mcpServer}] `) : "";
17332
- return mcpServer ? import_chalk33.default.dim(`[${short}${tagSuffix} \u2192 ${mcpServer}] `) : import_chalk33.default.dim(`[${short}${tagSuffix}] `);
17361
+ if (!short) return mcpServer ? import_chalk34.default.dim(`[\u2192 ${mcpServer}] `) : "";
17362
+ return mcpServer ? import_chalk34.default.dim(`[${short}${tagSuffix} \u2192 ${mcpServer}] `) : import_chalk34.default.dim(`[${short}${tagSuffix}] `);
17333
17363
  }
17334
17364
  function formatBase(activity) {
17335
17365
  const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
17336
17366
  const icon = getIcon(activity.tool);
17337
17367
  const toolName = activity.tool.slice(0, 16).padEnd(16);
17338
- const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os51.default.homedir(), "~");
17368
+ const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os53.default.homedir(), "~");
17339
17369
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
17340
- return `${import_chalk33.default.gray(time)} ${icon} ${agentLabel(activity.agent, activity.mcpServer, activity.sessionId)}${import_chalk33.default.white.bold(toolName)} ${import_chalk33.default.dim(argsPreview)}`;
17370
+ return `${import_chalk34.default.gray(time)} ${icon} ${agentLabel(activity.agent, activity.mcpServer, activity.sessionId)}${import_chalk34.default.white.bold(toolName)} ${import_chalk34.default.dim(argsPreview)}`;
17341
17371
  }
17342
17372
  function renderResult(activity, result) {
17343
17373
  const base = formatBase(activity);
17344
17374
  let status;
17345
17375
  if (result.status === "allow") {
17346
- status = import_chalk33.default.green("\u2713 ALLOW");
17376
+ status = import_chalk34.default.green("\u2713 ALLOW");
17347
17377
  } else if (result.status === "dlp") {
17348
- status = import_chalk33.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
17378
+ status = import_chalk34.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
17349
17379
  } else {
17350
- status = import_chalk33.default.red("\u2717 BLOCK");
17380
+ status = import_chalk34.default.red("\u2717 BLOCK");
17351
17381
  }
17352
17382
  const cost = result.costEstimate ?? activity.costEstimate;
17353
- const costSuffix = cost == null ? "" : import_chalk33.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
17383
+ const costSuffix = cost == null ? "" : import_chalk34.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
17354
17384
  if (process.stdout.isTTY) {
17355
17385
  if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
17356
17386
  import_readline6.default.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
@@ -17367,19 +17397,19 @@ function renderResult(activity, result) {
17367
17397
  }
17368
17398
  function renderPending(activity) {
17369
17399
  if (!process.stdout.isTTY) return;
17370
- const line = `${formatBase(activity)} ${import_chalk33.default.yellow("\u25CF \u2026")}`;
17400
+ const line = `${formatBase(activity)} ${import_chalk34.default.yellow("\u25CF \u2026")}`;
17371
17401
  pendingShownForId = activity.id;
17372
17402
  pendingWrappedLines = wrappedLineCount(line);
17373
17403
  process.stdout.write(`${line}\r`);
17374
17404
  }
17375
17405
  async function ensureDaemon() {
17376
17406
  let pidPort = null;
17377
- if (import_fs56.default.existsSync(PID_FILE)) {
17407
+ if (import_fs61.default.existsSync(PID_FILE)) {
17378
17408
  try {
17379
- const { port } = JSON.parse(import_fs56.default.readFileSync(PID_FILE, "utf-8"));
17409
+ const { port } = JSON.parse(import_fs61.default.readFileSync(PID_FILE, "utf-8"));
17380
17410
  pidPort = port;
17381
17411
  } catch {
17382
- console.error(import_chalk33.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
17412
+ console.error(import_chalk34.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
17383
17413
  }
17384
17414
  }
17385
17415
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -17390,8 +17420,8 @@ async function ensureDaemon() {
17390
17420
  if (res.ok) return checkPort;
17391
17421
  } catch {
17392
17422
  }
17393
- console.log(import_chalk33.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
17394
- const child = (0, import_child_process12.spawn)(process.execPath, [process.argv[1], "daemon"], {
17423
+ console.log(import_chalk34.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
17424
+ const child = (0, import_child_process14.spawn)(process.execPath, [process.argv[1], "daemon"], {
17395
17425
  detached: true,
17396
17426
  stdio: "ignore",
17397
17427
  env: { ...process.env, NODE9_AUTO_STARTED: "1" }
@@ -17407,7 +17437,7 @@ async function ensureDaemon() {
17407
17437
  } catch {
17408
17438
  }
17409
17439
  }
17410
- console.error(import_chalk33.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
17440
+ console.error(import_chalk34.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
17411
17441
  process.exit(1);
17412
17442
  }
17413
17443
  function postDecisionHttp(id, decision, authToken, port, opts) {
@@ -17476,7 +17506,7 @@ function buildCardLines(req, localCount = 0) {
17476
17506
  const severityIcon = isBlock ? `${RED}\u{1F6D1}` : `${YELLOW}\u26A0 `;
17477
17507
  const rawDesc = req.riskMetadata?.ruleDescription ?? "";
17478
17508
  const description = rawDesc ? cleanReason(rawDesc) : "";
17479
- const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${import_chalk33.default.dim(`(${req.agent})`)}` : "";
17509
+ const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${import_chalk34.default.dim(`(${req.agent})`)}` : "";
17480
17510
  const lines = [
17481
17511
  ``,
17482
17512
  `${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
@@ -17532,9 +17562,9 @@ function buildRecoveryCardLines(req) {
17532
17562
  ];
17533
17563
  }
17534
17564
  function readApproversFromDisk() {
17535
- const configPath2 = import_path55.default.join(import_os51.default.homedir(), ".node9", "config.json");
17565
+ const configPath2 = import_path59.default.join(import_os53.default.homedir(), ".node9", "config.json");
17536
17566
  try {
17537
- const raw = JSON.parse(import_fs56.default.readFileSync(configPath2, "utf-8"));
17567
+ const raw = JSON.parse(import_fs61.default.readFileSync(configPath2, "utf-8"));
17538
17568
  const settings = raw.settings ?? {};
17539
17569
  return settings.approvers ?? {};
17540
17570
  } catch {
@@ -17545,20 +17575,20 @@ function approverStatusLine() {
17545
17575
  const a = readApproversFromDisk();
17546
17576
  const fmt = (label2, key) => {
17547
17577
  const on = a[key] !== false;
17548
- return `[${key[0]}]${label2.slice(1)} ${on ? import_chalk33.default.green("\u2713") : import_chalk33.default.dim("\u2717")}`;
17578
+ return `[${key[0]}]${label2.slice(1)} ${on ? import_chalk34.default.green("\u2713") : import_chalk34.default.dim("\u2717")}`;
17549
17579
  };
17550
17580
  return `${fmt("native", "native")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
17551
17581
  }
17552
17582
  function toggleApprover(channel) {
17553
- const configPath2 = import_path55.default.join(import_os51.default.homedir(), ".node9", "config.json");
17583
+ const configPath2 = import_path59.default.join(import_os53.default.homedir(), ".node9", "config.json");
17554
17584
  try {
17555
- const raw = JSON.parse(import_fs56.default.readFileSync(configPath2, "utf-8"));
17585
+ const raw = JSON.parse(import_fs61.default.readFileSync(configPath2, "utf-8"));
17556
17586
  const settings = raw.settings ?? {};
17557
17587
  const approvers = settings.approvers ?? {};
17558
17588
  approvers[channel] = approvers[channel] === false;
17559
17589
  settings.approvers = approvers;
17560
17590
  raw.settings = settings;
17561
- import_fs56.default.writeFileSync(configPath2, JSON.stringify(raw, null, 2) + "\n");
17591
+ import_fs61.default.writeFileSync(configPath2, JSON.stringify(raw, null, 2) + "\n");
17562
17592
  } catch (err2) {
17563
17593
  process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
17564
17594
  `);
@@ -17590,7 +17620,7 @@ async function startTail(options = {}) {
17590
17620
  req2.end();
17591
17621
  });
17592
17622
  if (result.ok) {
17593
- console.log(import_chalk33.default.green("\u2713 Flight Recorder buffer cleared."));
17623
+ console.log(import_chalk34.default.green("\u2713 Flight Recorder buffer cleared."));
17594
17624
  } else if (result.code === "ECONNREFUSED") {
17595
17625
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
17596
17626
  } else if (result.code === "ETIMEDOUT") {
@@ -17636,7 +17666,7 @@ async function startTail(options = {}) {
17636
17666
  const channel = name === "n" ? "native" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
17637
17667
  if (channel) {
17638
17668
  toggleApprover(channel);
17639
- console.log(import_chalk33.default.dim(` Approvers: ${approverStatusLine()}`));
17669
+ console.log(import_chalk34.default.dim(` Approvers: ${approverStatusLine()}`));
17640
17670
  }
17641
17671
  };
17642
17672
  process.stdin.on("keypress", idleKeypressHandler);
@@ -17702,7 +17732,7 @@ async function startTail(options = {}) {
17702
17732
  localAllowCounts.get(req2.toolName) ?? 0
17703
17733
  )
17704
17734
  );
17705
- const decisionStamp = action === "always-allow" ? import_chalk33.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk33.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk33.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk33.default.yellow("\u21A9 REDIRECT AI") : import_chalk33.default.red("\u2717 DENIED");
17735
+ const decisionStamp = action === "always-allow" ? import_chalk34.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk34.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk34.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk34.default.yellow("\u21A9 REDIRECT AI") : import_chalk34.default.red("\u2717 DENIED");
17706
17736
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
17707
17737
  for (const line of stampedLines) process.stdout.write(line + "\n");
17708
17738
  process.stdout.write(SHOW_CURSOR);
@@ -17730,8 +17760,8 @@ async function startTail(options = {}) {
17730
17760
  }
17731
17761
  postDecisionHttp(req2.id, httpDecision, authToken, port, httpOpts).catch((err2) => {
17732
17762
  try {
17733
- import_fs56.default.appendFileSync(
17734
- import_path55.default.join(import_os51.default.homedir(), ".node9", "hook-debug.log"),
17763
+ import_fs61.default.appendFileSync(
17764
+ import_path59.default.join(import_os53.default.homedir(), ".node9", "hook-debug.log"),
17735
17765
  `[tail] POST /decision failed: ${String(err2)}
17736
17766
  `
17737
17767
  );
@@ -17753,7 +17783,7 @@ async function startTail(options = {}) {
17753
17783
  );
17754
17784
  const stampedLines = buildCardLines(req2, priorCount);
17755
17785
  if (externalDecision) {
17756
- const source = externalDecision === "allow" ? import_chalk33.default.green("\u2713 ALLOWED") : import_chalk33.default.red("\u2717 DENIED");
17786
+ const source = externalDecision === "allow" ? import_chalk34.default.green("\u2713 ALLOWED") : import_chalk34.default.red("\u2717 DENIED");
17757
17787
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
17758
17788
  }
17759
17789
  for (const line of stampedLines) process.stdout.write(line + "\n");
@@ -17795,31 +17825,31 @@ async function startTail(options = {}) {
17795
17825
  };
17796
17826
  process.stdin.on("keypress", onKeypress);
17797
17827
  }
17798
- const auditLog = import_path55.default.join(import_os51.default.homedir(), ".node9", "audit.log");
17828
+ const auditLog = import_path59.default.join(import_os53.default.homedir(), ".node9", "audit.log");
17799
17829
  try {
17800
- const unackedDlp = import_fs56.default.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
17830
+ const unackedDlp = import_fs61.default.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
17801
17831
  if (unackedDlp > 0) {
17802
17832
  console.log("");
17803
17833
  console.log(
17804
- import_chalk33.default.bgRed.white.bold(
17834
+ import_chalk34.default.bgRed.white.bold(
17805
17835
  ` \u26A0\uFE0F DLP ALERT: ${unackedDlp} secret${unackedDlp !== 1 ? "s" : ""} found in Claude response text \u2014 run: node9 dlp `
17806
17836
  )
17807
17837
  );
17808
17838
  }
17809
17839
  } catch {
17810
17840
  }
17811
- console.log(import_chalk33.default.cyan.bold(`
17841
+ console.log(import_chalk34.default.cyan.bold(`
17812
17842
  \u{1F6F0}\uFE0F Node9 tail`));
17813
17843
  if (canApprove) {
17814
- console.log(import_chalk33.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
17815
- console.log(import_chalk33.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
17844
+ console.log(import_chalk34.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
17845
+ console.log(import_chalk34.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
17816
17846
  }
17817
17847
  const ctxStat = readSessionUsage();
17818
17848
  if (ctxStat) console.log(" " + formatContextStat(ctxStat));
17819
17849
  if (options.history) {
17820
- console.log(import_chalk33.default.dim("Showing history + live events.\n"));
17850
+ console.log(import_chalk34.default.dim("Showing history + live events.\n"));
17821
17851
  } else {
17822
- console.log(import_chalk33.default.dim("Showing live events only. Use --history to include past.\n"));
17852
+ console.log(import_chalk34.default.dim("Showing live events only. Use --history to include past.\n"));
17823
17853
  }
17824
17854
  process.on("SIGINT", () => {
17825
17855
  exitIdleMode();
@@ -17829,7 +17859,7 @@ async function startTail(options = {}) {
17829
17859
  import_readline6.default.clearLine(process.stdout, 0);
17830
17860
  import_readline6.default.cursorTo(process.stdout, 0);
17831
17861
  }
17832
- console.log(import_chalk33.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
17862
+ console.log(import_chalk34.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
17833
17863
  process.exit(0);
17834
17864
  });
17835
17865
  const STALL_THRESHOLD_MS = 6e4;
@@ -17837,11 +17867,11 @@ async function startTail(options = {}) {
17837
17867
  if (stallWarned) return;
17838
17868
  if (Date.now() - lastActivityFromDaemon < STALL_THRESHOLD_MS) return;
17839
17869
  try {
17840
- const auditMtime = import_fs56.default.statSync(auditLog).mtimeMs;
17870
+ const auditMtime = import_fs61.default.statSync(auditLog).mtimeMs;
17841
17871
  if (Date.now() - auditMtime >= STALL_THRESHOLD_MS) return;
17842
17872
  console.log("");
17843
17873
  console.log(
17844
- import_chalk33.default.yellow(
17874
+ import_chalk34.default.yellow(
17845
17875
  "\u26A0\uFE0F Tail appears stalled \u2014 hooks are firing but no events are arriving. Try: node9 daemon restart"
17846
17876
  )
17847
17877
  );
@@ -17858,7 +17888,7 @@ async function startTail(options = {}) {
17858
17888
  },
17859
17889
  (res) => {
17860
17890
  if (res.statusCode !== 200) {
17861
- console.error(import_chalk33.default.red(`Failed to connect: HTTP ${res.statusCode}`));
17891
+ console.error(import_chalk34.default.red(`Failed to connect: HTTP ${res.statusCode}`));
17862
17892
  process.exit(1);
17863
17893
  }
17864
17894
  if (canApprove) enterIdleMode();
@@ -17889,7 +17919,7 @@ async function startTail(options = {}) {
17889
17919
  import_readline6.default.clearLine(process.stdout, 0);
17890
17920
  import_readline6.default.cursorTo(process.stdout, 0);
17891
17921
  }
17892
- console.log(import_chalk33.default.red("\n\u274C Daemon disconnected."));
17922
+ console.log(import_chalk34.default.red("\n\u274C Daemon disconnected."));
17893
17923
  process.exit(1);
17894
17924
  });
17895
17925
  }
@@ -17902,7 +17932,7 @@ async function startTail(options = {}) {
17902
17932
  const parsed = JSON.parse(rawData);
17903
17933
  const msg = parsed.message ?? "Flight recorder is down \u2014 run: node9 daemon restart";
17904
17934
  console.log("");
17905
- console.log(import_chalk33.default.bgRed.white.bold(` \u26A0\uFE0F ${msg} `));
17935
+ console.log(import_chalk34.default.bgRed.white.bold(` \u26A0\uFE0F ${msg} `));
17906
17936
  } catch {
17907
17937
  }
17908
17938
  return;
@@ -17987,9 +18017,9 @@ async function startTail(options = {}) {
17987
18017
  const rawSummary = data.argsSummary ?? data.tool;
17988
18018
  const summary = shortenPathSummary(rawSummary);
17989
18019
  const fileCount = data.fileCount ?? 0;
17990
- const files = fileCount > 0 ? import_chalk33.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
18020
+ const files = fileCount > 0 ? import_chalk34.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
17991
18021
  process.stdout.write(
17992
- `${import_chalk33.default.dim(time)} ${import_chalk33.default.cyan("\u{1F4F8} snapshot")} ${import_chalk33.default.dim(hash)} ${summary}${files}
18022
+ `${import_chalk34.default.dim(time)} ${import_chalk34.default.cyan("\u{1F4F8} snapshot")} ${import_chalk34.default.dim(hash)} ${summary}${files}
17993
18023
  `
17994
18024
  );
17995
18025
  return;
@@ -18006,36 +18036,36 @@ async function startTail(options = {}) {
18006
18036
  if (event === "execution-result") {
18007
18037
  const exec = data;
18008
18038
  const time = new Date(Date.now()).toLocaleTimeString([], { hour12: false });
18009
- const arrow = exec.isError ? import_chalk33.default.red(" \u21B3 \u2717") : import_chalk33.default.green(" \u21B3 \u2713");
18039
+ const arrow = exec.isError ? import_chalk34.default.red(" \u21B3 \u2717") : import_chalk34.default.green(" \u21B3 \u2713");
18010
18040
  const label2 = agentLabel(exec.agent, exec.mcpServer);
18011
18041
  const tool = (exec.tool ?? "").slice(0, 16);
18012
- const duration = typeof exec.durationMs === "number" ? import_chalk33.default.dim(` (${exec.durationMs}ms)`) : "";
18042
+ const duration = typeof exec.durationMs === "number" ? import_chalk34.default.dim(` (${exec.durationMs}ms)`) : "";
18013
18043
  console.log(
18014
- `${import_chalk33.default.gray(time)} ${arrow} ${label2}${import_chalk33.default.dim(tool)}${import_chalk33.default.dim(" completed")}${duration}`
18044
+ `${import_chalk34.default.gray(time)} ${arrow} ${label2}${import_chalk34.default.dim(tool)}${import_chalk34.default.dim(" completed")}${duration}`
18015
18045
  );
18016
18046
  }
18017
18047
  }
18018
18048
  req.on("error", (err2) => {
18019
18049
  const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
18020
- console.error(import_chalk33.default.red(`
18050
+ console.error(import_chalk34.default.red(`
18021
18051
  \u274C ${msg}`));
18022
18052
  process.exit(1);
18023
18053
  });
18024
18054
  }
18025
- var import_http3, import_chalk33, import_fs56, import_os51, import_path55, import_readline6, import_child_process12, PID_FILE, ICONS, MODEL_CONTEXT_LIMITS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
18055
+ var import_http3, import_chalk34, import_fs61, import_os53, import_path59, import_readline6, import_child_process14, PID_FILE, ICONS, MODEL_CONTEXT_LIMITS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
18026
18056
  var init_tail = __esm({
18027
18057
  "src/tui/tail.ts"() {
18028
18058
  "use strict";
18029
18059
  import_http3 = __toESM(require("http"));
18030
- import_chalk33 = __toESM(require("chalk"));
18031
- import_fs56 = __toESM(require("fs"));
18032
- import_os51 = __toESM(require("os"));
18033
- import_path55 = __toESM(require("path"));
18060
+ import_chalk34 = __toESM(require("chalk"));
18061
+ import_fs61 = __toESM(require("fs"));
18062
+ import_os53 = __toESM(require("os"));
18063
+ import_path59 = __toESM(require("path"));
18034
18064
  import_readline6 = __toESM(require("readline"));
18035
- import_child_process12 = require("child_process");
18065
+ import_child_process14 = require("child_process");
18036
18066
  init_daemon2();
18037
18067
  init_daemon();
18038
- PID_FILE = import_path55.default.join(import_os51.default.homedir(), ".node9", "daemon.pid");
18068
+ PID_FILE = import_path59.default.join(import_os53.default.homedir(), ".node9", "daemon.pid");
18039
18069
  ICONS = {
18040
18070
  bash: "\u{1F4BB}",
18041
18071
  shell: "\u{1F4BB}",
@@ -18157,9 +18187,9 @@ function formatTimeLeft(resetsAt) {
18157
18187
  return ` (${m}m left)`;
18158
18188
  }
18159
18189
  function safeReadJson(filePath) {
18160
- if (!import_fs57.default.existsSync(filePath)) return null;
18190
+ if (!import_fs62.default.existsSync(filePath)) return null;
18161
18191
  try {
18162
- return JSON.parse(import_fs57.default.readFileSync(filePath, "utf-8"));
18192
+ return JSON.parse(import_fs62.default.readFileSync(filePath, "utf-8"));
18163
18193
  } catch {
18164
18194
  return null;
18165
18195
  }
@@ -18180,12 +18210,12 @@ function countHooksInFile(filePath) {
18180
18210
  return Object.keys(cfg.hooks).length;
18181
18211
  }
18182
18212
  function countRulesInDir(rulesDir) {
18183
- if (!import_fs57.default.existsSync(rulesDir)) return 0;
18213
+ if (!import_fs62.default.existsSync(rulesDir)) return 0;
18184
18214
  let count = 0;
18185
18215
  try {
18186
- for (const entry of import_fs57.default.readdirSync(rulesDir, { withFileTypes: true })) {
18216
+ for (const entry of import_fs62.default.readdirSync(rulesDir, { withFileTypes: true })) {
18187
18217
  if (entry.isDirectory()) {
18188
- count += countRulesInDir(import_path56.default.join(rulesDir, entry.name));
18218
+ count += countRulesInDir(import_path60.default.join(rulesDir, entry.name));
18189
18219
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
18190
18220
  count++;
18191
18221
  }
@@ -18196,46 +18226,46 @@ function countRulesInDir(rulesDir) {
18196
18226
  }
18197
18227
  function isSamePath(a, b) {
18198
18228
  try {
18199
- return import_path56.default.resolve(a) === import_path56.default.resolve(b);
18229
+ return import_path60.default.resolve(a) === import_path60.default.resolve(b);
18200
18230
  } catch {
18201
18231
  return false;
18202
18232
  }
18203
18233
  }
18204
18234
  function countConfigs(cwd) {
18205
- const homeDir2 = import_os52.default.homedir();
18206
- const claudeDir = import_path56.default.join(homeDir2, ".claude");
18235
+ const homeDir2 = import_os54.default.homedir();
18236
+ const claudeDir = import_path60.default.join(homeDir2, ".claude");
18207
18237
  let claudeMdCount = 0;
18208
18238
  let rulesCount = 0;
18209
18239
  let hooksCount = 0;
18210
18240
  const userMcpServers = /* @__PURE__ */ new Set();
18211
18241
  const projectMcpServers = /* @__PURE__ */ new Set();
18212
- if (import_fs57.default.existsSync(import_path56.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
18213
- rulesCount += countRulesInDir(import_path56.default.join(claudeDir, "rules"));
18214
- const userSettings = import_path56.default.join(claudeDir, "settings.json");
18242
+ if (import_fs62.default.existsSync(import_path60.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
18243
+ rulesCount += countRulesInDir(import_path60.default.join(claudeDir, "rules"));
18244
+ const userSettings = import_path60.default.join(claudeDir, "settings.json");
18215
18245
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
18216
18246
  hooksCount += countHooksInFile(userSettings);
18217
- const userClaudeJson = import_path56.default.join(homeDir2, ".claude.json");
18247
+ const userClaudeJson = import_path60.default.join(homeDir2, ".claude.json");
18218
18248
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
18219
18249
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
18220
18250
  userMcpServers.delete(name);
18221
18251
  }
18222
18252
  if (cwd) {
18223
- if (import_fs57.default.existsSync(import_path56.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
18224
- if (import_fs57.default.existsSync(import_path56.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
18225
- const projectClaudeDir = import_path56.default.join(cwd, ".claude");
18253
+ if (import_fs62.default.existsSync(import_path60.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
18254
+ if (import_fs62.default.existsSync(import_path60.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
18255
+ const projectClaudeDir = import_path60.default.join(cwd, ".claude");
18226
18256
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
18227
18257
  if (!overlapsUserScope) {
18228
- if (import_fs57.default.existsSync(import_path56.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
18229
- rulesCount += countRulesInDir(import_path56.default.join(projectClaudeDir, "rules"));
18230
- const projSettings = import_path56.default.join(projectClaudeDir, "settings.json");
18258
+ if (import_fs62.default.existsSync(import_path60.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
18259
+ rulesCount += countRulesInDir(import_path60.default.join(projectClaudeDir, "rules"));
18260
+ const projSettings = import_path60.default.join(projectClaudeDir, "settings.json");
18231
18261
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
18232
18262
  hooksCount += countHooksInFile(projSettings);
18233
18263
  }
18234
- if (import_fs57.default.existsSync(import_path56.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
18235
- const localSettings = import_path56.default.join(projectClaudeDir, "settings.local.json");
18264
+ if (import_fs62.default.existsSync(import_path60.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
18265
+ const localSettings = import_path60.default.join(projectClaudeDir, "settings.local.json");
18236
18266
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
18237
18267
  hooksCount += countHooksInFile(localSettings);
18238
- const mcpJsonServers = getMcpServerNames(import_path56.default.join(cwd, ".mcp.json"));
18268
+ const mcpJsonServers = getMcpServerNames(import_path60.default.join(cwd, ".mcp.json"));
18239
18269
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
18240
18270
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
18241
18271
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -18268,12 +18298,12 @@ function readActiveShieldsHud() {
18268
18298
  return shieldsCache.value;
18269
18299
  }
18270
18300
  try {
18271
- const shieldsPath = import_path56.default.join(import_os52.default.homedir(), ".node9", "shields.json");
18272
- if (!import_fs57.default.existsSync(shieldsPath)) {
18301
+ const shieldsPath = import_path60.default.join(import_os54.default.homedir(), ".node9", "shields.json");
18302
+ if (!import_fs62.default.existsSync(shieldsPath)) {
18273
18303
  shieldsCache = { value: [], ts: now };
18274
18304
  return [];
18275
18305
  }
18276
- const parsed = JSON.parse(import_fs57.default.readFileSync(shieldsPath, "utf-8"));
18306
+ const parsed = JSON.parse(import_fs62.default.readFileSync(shieldsPath, "utf-8"));
18277
18307
  if (!Array.isArray(parsed.active)) {
18278
18308
  shieldsCache = { value: [], ts: now };
18279
18309
  return [];
@@ -18375,17 +18405,17 @@ function renderContextLine(stdin) {
18375
18405
  async function main() {
18376
18406
  try {
18377
18407
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
18378
- if (import_fs57.default.existsSync(import_path56.default.join(import_os52.default.homedir(), ".node9", "hud-debug"))) {
18408
+ if (import_fs62.default.existsSync(import_path60.default.join(import_os54.default.homedir(), ".node9", "hud-debug"))) {
18379
18409
  try {
18380
- const logPath = import_path56.default.join(import_os52.default.homedir(), ".node9", "hud-debug.log");
18410
+ const logPath = import_path60.default.join(import_os54.default.homedir(), ".node9", "hud-debug.log");
18381
18411
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
18382
18412
  let size = 0;
18383
18413
  try {
18384
- size = import_fs57.default.statSync(logPath).size;
18414
+ size = import_fs62.default.statSync(logPath).size;
18385
18415
  } catch {
18386
18416
  }
18387
18417
  if (size < MAX_LOG_SIZE) {
18388
- import_fs57.default.appendFileSync(
18418
+ import_fs62.default.appendFileSync(
18389
18419
  logPath,
18390
18420
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
18391
18421
  );
@@ -18406,11 +18436,11 @@ async function main() {
18406
18436
  try {
18407
18437
  const cwd = stdin.cwd ?? process.cwd();
18408
18438
  for (const configPath2 of [
18409
- import_path56.default.join(cwd, "node9.config.json"),
18410
- import_path56.default.join(import_os52.default.homedir(), ".node9", "config.json")
18439
+ import_path60.default.join(cwd, "node9.config.json"),
18440
+ import_path60.default.join(import_os54.default.homedir(), ".node9", "config.json")
18411
18441
  ]) {
18412
- if (!import_fs57.default.existsSync(configPath2)) continue;
18413
- const cfg = JSON.parse(import_fs57.default.readFileSync(configPath2, "utf-8"));
18442
+ if (!import_fs62.default.existsSync(configPath2)) continue;
18443
+ const cfg = JSON.parse(import_fs62.default.readFileSync(configPath2, "utf-8"));
18414
18444
  const hud = cfg.settings?.hud;
18415
18445
  if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
18416
18446
  }
@@ -18428,13 +18458,13 @@ async function main() {
18428
18458
  renderOffline();
18429
18459
  }
18430
18460
  }
18431
- var import_fs57, import_path56, import_os52, import_http4, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
18461
+ var import_fs62, import_path60, import_os54, import_http4, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
18432
18462
  var init_hud = __esm({
18433
18463
  "src/cli/hud.ts"() {
18434
18464
  "use strict";
18435
- import_fs57 = __toESM(require("fs"));
18436
- import_path56 = __toESM(require("path"));
18437
- import_os52 = __toESM(require("os"));
18465
+ import_fs62 = __toESM(require("fs"));
18466
+ import_path60 = __toESM(require("path"));
18467
+ import_os54 = __toESM(require("os"));
18438
18468
  import_http4 = __toESM(require("http"));
18439
18469
  init_daemon();
18440
18470
  RESET3 = "\x1B[0m";
@@ -18460,11 +18490,11 @@ var import_commander = require("commander");
18460
18490
  init_core();
18461
18491
  init_setup();
18462
18492
  init_daemon2();
18463
- var import_chalk34 = __toESM(require("chalk"));
18464
- var import_fs58 = __toESM(require("fs"));
18465
- var import_path57 = __toESM(require("path"));
18466
- var import_os53 = __toESM(require("os"));
18467
- var import_child_process13 = require("child_process");
18493
+ var import_chalk35 = __toESM(require("chalk"));
18494
+ var import_fs63 = __toESM(require("fs"));
18495
+ var import_path61 = __toESM(require("path"));
18496
+ var import_os55 = __toESM(require("os"));
18497
+ var import_child_process15 = require("child_process");
18468
18498
  var import_prompts2 = require("@inquirer/prompts");
18469
18499
 
18470
18500
  // src/utils/duration.ts
@@ -18558,6 +18588,10 @@ INSTRUCTIONS:
18558
18588
  - Do NOT retry this exact command or attempt to bypass the rule.${recovery}
18559
18589
  - Inform the user which security rule was triggered and ask how to proceed.`;
18560
18590
  }
18591
+ function buildReviewMessage(blockedByLabel, ruleDescription) {
18592
+ const why = ruleDescription || blockedByLabel || "this action needs your review";
18593
+ return `Node9 flagged this for your review: ${why}. Approve to proceed, or deny to cancel.`;
18594
+ }
18561
18595
 
18562
18596
  // src/proxy/index.ts
18563
18597
  function sanitize(value) {
@@ -18684,10 +18718,10 @@ async function autoStartDaemonAndWait() {
18684
18718
 
18685
18719
  // src/cli/commands/check.ts
18686
18720
  var import_chalk9 = __toESM(require("chalk"));
18687
- var import_fs36 = __toESM(require("fs"));
18721
+ var import_fs37 = __toESM(require("fs"));
18688
18722
  var import_child_process7 = require("child_process");
18689
- var import_path37 = __toESM(require("path"));
18690
- var import_os32 = __toESM(require("os"));
18723
+ var import_path38 = __toESM(require("path"));
18724
+ var import_os33 = __toESM(require("os"));
18691
18725
  init_orchestrator();
18692
18726
  init_daemon();
18693
18727
  init_config();
@@ -19229,6 +19263,78 @@ function resolveUserSkillRoot(entry, cwd) {
19229
19263
  // src/cli/commands/check.ts
19230
19264
  init_dlp();
19231
19265
  init_audit();
19266
+
19267
+ // src/review-pending.ts
19268
+ var import_fs36 = __toESM(require("fs"));
19269
+ var import_os32 = __toESM(require("os"));
19270
+ var import_path37 = __toESM(require("path"));
19271
+ init_hasher();
19272
+ function storePath() {
19273
+ return process.env.NODE9_PENDING_STORE || import_path37.default.join(import_os32.default.homedir(), ".node9", "pending-reviews.json");
19274
+ }
19275
+ var TTL_MS2 = 6 * 60 * 60 * 1e3;
19276
+ var MAX_ENTRIES = 500;
19277
+ function reviewCorrelationKey(payload) {
19278
+ if (typeof payload.tool_use_id === "string" && payload.tool_use_id) {
19279
+ return `tuid:${payload.tool_use_id}`;
19280
+ }
19281
+ const sid = payload.session_id ?? payload.conversationId;
19282
+ const tool = payload.tool_name;
19283
+ if (typeof sid === "string" && sid && typeof tool === "string" && tool) {
19284
+ return `h:${sid}|${tool}|${hashArgs(payload.tool_input)}`;
19285
+ }
19286
+ return null;
19287
+ }
19288
+ function read() {
19289
+ try {
19290
+ const parsed = JSON.parse(import_fs36.default.readFileSync(storePath(), "utf-8"));
19291
+ if (parsed && Array.isArray(parsed.entries)) return parsed;
19292
+ } catch {
19293
+ }
19294
+ return { entries: [] };
19295
+ }
19296
+ function write(store) {
19297
+ try {
19298
+ const p = storePath();
19299
+ const dir = import_path37.default.dirname(p);
19300
+ if (!import_fs36.default.existsSync(dir)) import_fs36.default.mkdirSync(dir, { recursive: true });
19301
+ const tmp = `${p}.${process.pid}.tmp`;
19302
+ import_fs36.default.writeFileSync(tmp, JSON.stringify(store));
19303
+ import_fs36.default.renameSync(tmp, p);
19304
+ } catch {
19305
+ }
19306
+ }
19307
+ function prune(entries, now) {
19308
+ const fresh = entries.filter((e) => now - e.ts < TTL_MS2);
19309
+ return fresh.length > MAX_ENTRIES ? fresh.slice(fresh.length - MAX_ENTRIES) : fresh;
19310
+ }
19311
+ function recordPendingReview(entry) {
19312
+ try {
19313
+ const store = read();
19314
+ store.entries = prune(store.entries, entry.ts);
19315
+ store.entries.push(entry);
19316
+ write(store);
19317
+ } catch {
19318
+ }
19319
+ }
19320
+ function resolvePendingReview(key, now = Date.now()) {
19321
+ try {
19322
+ const store = read();
19323
+ const idx = store.entries.findIndex((e) => e.key === key);
19324
+ if (idx === -1) {
19325
+ const pruned = prune(store.entries, now);
19326
+ if (pruned.length !== store.entries.length) write({ entries: pruned });
19327
+ return null;
19328
+ }
19329
+ const [match] = store.entries.splice(idx, 1);
19330
+ write({ entries: prune(store.entries, now) });
19331
+ return match;
19332
+ } catch {
19333
+ return null;
19334
+ }
19335
+ }
19336
+
19337
+ // src/cli/commands/check.ts
19232
19338
  init_hook_payload();
19233
19339
  function sanitize2(value) {
19234
19340
  return value.replace(/[\x00-\x1F\x7F]/g, "");
@@ -19277,11 +19383,26 @@ function detectAiAgent(payload) {
19277
19383
  }
19278
19384
  return "Terminal";
19279
19385
  }
19386
+ function agentSupportsAsk(agent) {
19387
+ return agent === "Claude Code" || agent === "GitHub Copilot";
19388
+ }
19389
+ function resolveAskMode(agent, opts, config) {
19390
+ if (!agentSupportsAsk(agent)) return false;
19391
+ if (config.settings.approvers.cloud === true) return false;
19392
+ if (opts.ask === true) return true;
19393
+ if (opts.ask === false) return false;
19394
+ if (config.settings.reviewChannel === "ask") return true;
19395
+ if (config.settings.reviewChannel === "approver") return false;
19396
+ return true;
19397
+ }
19280
19398
  function registerCheckCommand(program2) {
19281
19399
  program2.command("check", { hidden: true }).description("Hook handler \u2014 evaluates a tool call before execution").argument("[data]", "JSON string of the tool call").option(
19282
19400
  "--agent <name>",
19283
19401
  "Agent identity override, set by node9-authored hook registrations (e.g. antigravity)"
19284
- ).action(async (data, opts) => {
19402
+ ).option(
19403
+ "--ask",
19404
+ "Route review verdicts to the agent\u2019s native inline approve/deny prompt (Claude Code / GitHub Copilot only)"
19405
+ ).option("--no-ask", "Force node9\u2019s own approver for review verdicts (override default-on)").action(async (data, opts) => {
19285
19406
  const agentOverride = agentLabelFromFlag(opts?.agent);
19286
19407
  const processPayload = async (raw) => {
19287
19408
  try {
@@ -19292,9 +19413,9 @@ function registerCheckCommand(program2) {
19292
19413
  } catch (err2) {
19293
19414
  const tempConfig = getConfig();
19294
19415
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
19295
- const logPath = import_path37.default.join(import_os32.default.homedir(), ".node9", "hook-debug.log");
19416
+ const logPath = import_path38.default.join(import_os33.default.homedir(), ".node9", "hook-debug.log");
19296
19417
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
19297
- import_fs36.default.appendFileSync(
19418
+ import_fs37.default.appendFileSync(
19298
19419
  logPath,
19299
19420
  `[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
19300
19421
  RAW: ${raw}
@@ -19307,14 +19428,14 @@ RAW: ${raw}
19307
19428
  const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
19308
19429
  if (process.env.NODE9_DEBUG === "1") {
19309
19430
  try {
19310
- const logPath = import_path37.default.join(import_os32.default.homedir(), ".node9", "hook-debug.log");
19311
- if (!import_fs36.default.existsSync(import_path37.default.dirname(logPath)))
19312
- import_fs36.default.mkdirSync(import_path37.default.dirname(logPath), { recursive: true });
19431
+ const logPath = import_path38.default.join(import_os33.default.homedir(), ".node9", "hook-debug.log");
19432
+ if (!import_fs37.default.existsSync(import_path38.default.dirname(logPath)))
19433
+ import_fs37.default.mkdirSync(import_path38.default.dirname(logPath), { recursive: true });
19313
19434
  const sanitized = JSON.stringify({
19314
19435
  ...payload,
19315
19436
  prompt: `<redacted, ${prompt.length} bytes>`
19316
19437
  });
19317
- import_fs36.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${sanitized}
19438
+ import_fs37.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${sanitized}
19318
19439
  `);
19319
19440
  } catch {
19320
19441
  }
@@ -19334,8 +19455,8 @@ RAW: ${raw}
19334
19455
  );
19335
19456
  const reason = `\u{1F6A8} Node9 DLP: ${dlpMatch.patternName} detected in prompt (${dlpMatch.redactedSample}). Prompt was not submitted \u2014 remove the credential and try again.`;
19336
19457
  try {
19337
- const ttyFd = import_fs36.default.openSync("/dev/tty", "w");
19338
- import_fs36.default.writeSync(
19458
+ const ttyFd = import_fs37.default.openSync("/dev/tty", "w");
19459
+ import_fs37.default.writeSync(
19339
19460
  ttyFd,
19340
19461
  import_chalk9.default.bgRed.white.bold(`
19341
19462
  \u{1F6A8} NODE9 DLP \u2014 PROMPT BLOCKED
@@ -19345,7 +19466,7 @@ RAW: ${raw}
19345
19466
 
19346
19467
  `)
19347
19468
  );
19348
- import_fs36.default.closeSync(ttyFd);
19469
+ import_fs37.default.closeSync(ttyFd);
19349
19470
  } catch {
19350
19471
  }
19351
19472
  const isCodex = agent2 === "Codex";
@@ -19364,16 +19485,16 @@ RAW: ${raw}
19364
19485
  process.exit(2);
19365
19486
  }
19366
19487
  const payloadCwd = typeof payload.cwd === "string" ? payload.cwd : Array.isArray(payload.workspacePaths) && typeof payload.workspacePaths[0] === "string" ? payload.workspacePaths[0] : void 0;
19367
- const safeCwdForConfig = typeof payloadCwd === "string" && import_path37.default.isAbsolute(payloadCwd) ? payloadCwd : void 0;
19488
+ const safeCwdForConfig = typeof payloadCwd === "string" && import_path38.default.isAbsolute(payloadCwd) ? payloadCwd : void 0;
19368
19489
  const config = getConfig(safeCwdForConfig);
19369
19490
  if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
19370
19491
  try {
19371
19492
  const scriptPath = process.argv[1];
19372
- if (typeof scriptPath !== "string" || !import_path37.default.isAbsolute(scriptPath))
19493
+ if (typeof scriptPath !== "string" || !import_path38.default.isAbsolute(scriptPath))
19373
19494
  throw new Error("node9: argv[1] is not an absolute path");
19374
- const resolvedScript = import_fs36.default.realpathSync(scriptPath);
19375
- const packageDist = import_fs36.default.realpathSync(import_path37.default.resolve(__dirname, "../.."));
19376
- if (!resolvedScript.startsWith(packageDist + import_path37.default.sep) && resolvedScript !== packageDist)
19495
+ const resolvedScript = import_fs37.default.realpathSync(scriptPath);
19496
+ const packageDist = import_fs37.default.realpathSync(import_path38.default.resolve(__dirname, "../.."));
19497
+ if (!resolvedScript.startsWith(packageDist + import_path38.default.sep) && resolvedScript !== packageDist)
19377
19498
  throw new Error(
19378
19499
  `node9: daemon spawn aborted \u2014 argv[1] (${resolvedScript}) is outside package dist (${packageDist})`
19379
19500
  );
@@ -19395,10 +19516,10 @@ RAW: ${raw}
19395
19516
  });
19396
19517
  d.unref();
19397
19518
  } catch (spawnErr) {
19398
- const logPath = import_path37.default.join(import_os32.default.homedir(), ".node9", "hook-debug.log");
19519
+ const logPath = import_path38.default.join(import_os33.default.homedir(), ".node9", "hook-debug.log");
19399
19520
  const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
19400
19521
  try {
19401
- import_fs36.default.appendFileSync(
19522
+ import_fs37.default.appendFileSync(
19402
19523
  logPath,
19403
19524
  `[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-failed: ${msg}
19404
19525
  `
@@ -19408,10 +19529,10 @@ RAW: ${raw}
19408
19529
  }
19409
19530
  }
19410
19531
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
19411
- const logPath = import_path37.default.join(import_os32.default.homedir(), ".node9", "hook-debug.log");
19412
- if (!import_fs36.default.existsSync(import_path37.default.dirname(logPath)))
19413
- import_fs36.default.mkdirSync(import_path37.default.dirname(logPath), { recursive: true });
19414
- import_fs36.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
19532
+ const logPath = import_path38.default.join(import_os33.default.homedir(), ".node9", "hook-debug.log");
19533
+ if (!import_fs37.default.existsSync(import_path38.default.dirname(logPath)))
19534
+ import_fs37.default.mkdirSync(import_path38.default.dirname(logPath), { recursive: true });
19535
+ import_fs37.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
19415
19536
  `);
19416
19537
  }
19417
19538
  const rawToolName = sanitize2(extractToolName(payload));
@@ -19425,8 +19546,8 @@ RAW: ${raw}
19425
19546
  const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
19426
19547
  let ttyFd = null;
19427
19548
  try {
19428
- ttyFd = import_fs36.default.openSync("/dev/tty", "w");
19429
- const writeTty = (line) => import_fs36.default.writeSync(ttyFd, line + "\n");
19549
+ ttyFd = import_fs37.default.openSync("/dev/tty", "w");
19550
+ const writeTty = (line) => import_fs37.default.writeSync(ttyFd, line + "\n");
19430
19551
  if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
19431
19552
  writeTty(import_chalk9.default.bgRed.white.bold(`
19432
19553
  \u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
@@ -19445,7 +19566,7 @@ RAW: ${raw}
19445
19566
  } finally {
19446
19567
  if (ttyFd !== null)
19447
19568
  try {
19448
- import_fs36.default.closeSync(ttyFd);
19569
+ import_fs37.default.closeSync(ttyFd);
19449
19570
  } catch {
19450
19571
  }
19451
19572
  }
@@ -19484,6 +19605,53 @@ RAW: ${raw}
19484
19605
  );
19485
19606
  process.exit(2);
19486
19607
  };
19608
+ const sendAsk = (result2) => {
19609
+ const msg = buildReviewMessage(result2.blockedByLabel, result2.ruleDescription);
19610
+ try {
19611
+ const key = reviewCorrelationKey(payload);
19612
+ if (key) {
19613
+ const sid = typeof payload.session_id === "string" ? payload.session_id : typeof payload.conversationId === "string" ? payload.conversationId : void 0;
19614
+ recordPendingReview({
19615
+ key,
19616
+ agent,
19617
+ tool: toolName,
19618
+ sessionId: sid,
19619
+ ts: Date.now(),
19620
+ label: result2.blockedByLabel
19621
+ });
19622
+ }
19623
+ } catch {
19624
+ }
19625
+ try {
19626
+ const ttyFd = import_fs37.default.openSync("/dev/tty", "w");
19627
+ import_fs37.default.writeSync(
19628
+ ttyFd,
19629
+ import_chalk9.default.yellow(
19630
+ `
19631
+ \u26A0\uFE0F Node9: review requested for "${toolName}" \u2014 answer in the prompt.
19632
+ `
19633
+ )
19634
+ );
19635
+ import_fs37.default.closeSync(ttyFd);
19636
+ } catch {
19637
+ }
19638
+ if (agent === "GitHub Copilot") {
19639
+ process.stdout.write(
19640
+ JSON.stringify({ permissionDecision: "ask", permissionDecisionReason: msg }) + "\n"
19641
+ );
19642
+ } else {
19643
+ process.stdout.write(
19644
+ JSON.stringify({
19645
+ hookSpecificOutput: {
19646
+ hookEventName: "PreToolUse",
19647
+ permissionDecision: "ask",
19648
+ permissionDecisionReason: msg
19649
+ }
19650
+ }) + "\n"
19651
+ );
19652
+ }
19653
+ process.exit(0);
19654
+ };
19487
19655
  if (!toolName) {
19488
19656
  sendBlock("Node9: unrecognised hook payload \u2014 tool name missing.");
19489
19657
  return;
@@ -19496,17 +19664,17 @@ RAW: ${raw}
19496
19664
  const safeSessionId = /^[A-Za-z0-9_\-]{1,128}$/.test(rawSessionId) ? rawSessionId : "";
19497
19665
  if (skillPinCfg.enabled && safeSessionId) {
19498
19666
  try {
19499
- const sessionsDir = import_path37.default.join(import_os32.default.homedir(), ".node9", "skill-sessions");
19500
- const flagPath = import_path37.default.join(sessionsDir, `${safeSessionId}.json`);
19667
+ const sessionsDir = import_path38.default.join(import_os33.default.homedir(), ".node9", "skill-sessions");
19668
+ const flagPath = import_path38.default.join(sessionsDir, `${safeSessionId}.json`);
19501
19669
  let flag = null;
19502
19670
  try {
19503
- flag = JSON.parse(import_fs36.default.readFileSync(flagPath, "utf-8"));
19671
+ flag = JSON.parse(import_fs37.default.readFileSync(flagPath, "utf-8"));
19504
19672
  } catch {
19505
19673
  }
19506
19674
  const writeFlag = (data2) => {
19507
19675
  try {
19508
- import_fs36.default.mkdirSync(sessionsDir, { recursive: true });
19509
- import_fs36.default.writeFileSync(
19676
+ import_fs37.default.mkdirSync(sessionsDir, { recursive: true });
19677
+ import_fs37.default.writeFileSync(
19510
19678
  flagPath,
19511
19679
  JSON.stringify({ ...data2, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
19512
19680
  { mode: 384 }
@@ -19517,8 +19685,8 @@ RAW: ${raw}
19517
19685
  const sendSkillWarn = (detail, recoveryCmd) => {
19518
19686
  let ttyFd = null;
19519
19687
  try {
19520
- ttyFd = import_fs36.default.openSync("/dev/tty", "w");
19521
- const w = (line) => import_fs36.default.writeSync(ttyFd, line + "\n");
19688
+ ttyFd = import_fs37.default.openSync("/dev/tty", "w");
19689
+ const w = (line) => import_fs37.default.writeSync(ttyFd, line + "\n");
19522
19690
  w(import_chalk9.default.yellow(`
19523
19691
  \u26A0\uFE0F Node9: installed skill drift detected`));
19524
19692
  w(import_chalk9.default.gray(` ${detail}`));
@@ -19533,7 +19701,7 @@ RAW: ${raw}
19533
19701
  } finally {
19534
19702
  if (ttyFd !== null)
19535
19703
  try {
19536
- import_fs36.default.closeSync(ttyFd);
19704
+ import_fs37.default.closeSync(ttyFd);
19537
19705
  } catch {
19538
19706
  }
19539
19707
  }
@@ -19549,7 +19717,7 @@ RAW: ${raw}
19549
19717
  return;
19550
19718
  }
19551
19719
  if (!flag || flag.state !== "verified" && flag.state !== "warned") {
19552
- const absoluteCwd = typeof payloadCwd === "string" && import_path37.default.isAbsolute(payloadCwd) ? payloadCwd : void 0;
19720
+ const absoluteCwd = typeof payloadCwd === "string" && import_path38.default.isAbsolute(payloadCwd) ? payloadCwd : void 0;
19553
19721
  const extraRoots = skillPinCfg.roots;
19554
19722
  const resolvedExtra = extraRoots.map((r) => resolveUserSkillRoot(r, absoluteCwd)).filter((r) => typeof r === "string");
19555
19723
  const roots = [...defaultSkillRoots(absoluteCwd), ...resolvedExtra];
@@ -19590,10 +19758,10 @@ RAW: ${raw}
19590
19758
  }
19591
19759
  try {
19592
19760
  const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
19593
- for (const name of import_fs36.default.readdirSync(sessionsDir)) {
19594
- const p = import_path37.default.join(sessionsDir, name);
19761
+ for (const name of import_fs37.default.readdirSync(sessionsDir)) {
19762
+ const p = import_path38.default.join(sessionsDir, name);
19595
19763
  try {
19596
- if (import_fs36.default.statSync(p).mtimeMs < cutoff) import_fs36.default.unlinkSync(p);
19764
+ if (import_fs37.default.statSync(p).mtimeMs < cutoff) import_fs37.default.unlinkSync(p);
19597
19765
  } catch {
19598
19766
  }
19599
19767
  }
@@ -19603,9 +19771,9 @@ RAW: ${raw}
19603
19771
  } catch (err2) {
19604
19772
  if (process.env.NODE9_DEBUG === "1") {
19605
19773
  try {
19606
- const dbg = import_path37.default.join(import_os32.default.homedir(), ".node9", "hook-debug.log");
19774
+ const dbg = import_path38.default.join(import_os33.default.homedir(), ".node9", "hook-debug.log");
19607
19775
  const msg = err2 instanceof Error ? err2.message : String(err2);
19608
- import_fs36.default.appendFileSync(dbg, `[${(/* @__PURE__ */ new Date()).toISOString()}] SKILL_PIN_ERROR: ${msg}
19776
+ import_fs37.default.appendFileSync(dbg, `[${(/* @__PURE__ */ new Date()).toISOString()}] SKILL_PIN_ERROR: ${msg}
19609
19777
  `);
19610
19778
  } catch {
19611
19779
  }
@@ -19615,9 +19783,11 @@ RAW: ${raw}
19615
19783
  if (shouldSnapshot(toolName, toolInput, config)) {
19616
19784
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
19617
19785
  }
19618
- const safeCwdForAuth = typeof payloadCwd === "string" && import_path37.default.isAbsolute(payloadCwd) ? payloadCwd : void 0;
19786
+ const safeCwdForAuth = typeof payloadCwd === "string" && import_path38.default.isAbsolute(payloadCwd) ? payloadCwd : void 0;
19787
+ const askMode = resolveAskMode(agent, opts, config);
19619
19788
  const result = await authorizeHeadless(toolName, toolInput, meta, {
19620
- cwd: safeCwdForAuth
19789
+ cwd: safeCwdForAuth,
19790
+ deferReview: askMode
19621
19791
  });
19622
19792
  if (result.approved) {
19623
19793
  if (result.checkedBy && process.env.NODE9_DEBUG === "1")
@@ -19625,14 +19795,18 @@ RAW: ${raw}
19625
19795
  `);
19626
19796
  process.exit(0);
19627
19797
  }
19798
+ if (result.review) {
19799
+ sendAsk(result);
19800
+ return;
19801
+ }
19628
19802
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
19629
19803
  try {
19630
- const tty = import_fs36.default.openSync("/dev/tty", "w");
19631
- import_fs36.default.writeSync(
19804
+ const tty = import_fs37.default.openSync("/dev/tty", "w");
19805
+ import_fs37.default.writeSync(
19632
19806
  tty,
19633
19807
  import_chalk9.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
19634
19808
  );
19635
- import_fs36.default.closeSync(tty);
19809
+ import_fs37.default.closeSync(tty);
19636
19810
  } catch {
19637
19811
  }
19638
19812
  const daemonReady = await autoStartDaemonAndWait();
@@ -19659,9 +19833,9 @@ RAW: ${raw}
19659
19833
  });
19660
19834
  } catch (err2) {
19661
19835
  if (process.env.NODE9_DEBUG === "1") {
19662
- const logPath = import_path37.default.join(import_os32.default.homedir(), ".node9", "hook-debug.log");
19836
+ const logPath = import_path38.default.join(import_os33.default.homedir(), ".node9", "hook-debug.log");
19663
19837
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
19664
- import_fs36.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
19838
+ import_fs37.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
19665
19839
  `);
19666
19840
  }
19667
19841
  process.exit(0);
@@ -19695,9 +19869,9 @@ RAW: ${raw}
19695
19869
  }
19696
19870
 
19697
19871
  // src/cli/commands/log.ts
19698
- var import_fs37 = __toESM(require("fs"));
19699
- var import_path38 = __toESM(require("path"));
19700
- var import_os33 = __toESM(require("os"));
19872
+ var import_fs38 = __toESM(require("fs"));
19873
+ var import_path39 = __toESM(require("path"));
19874
+ var import_os34 = __toESM(require("os"));
19701
19875
  init_audit();
19702
19876
  init_config();
19703
19877
  init_daemon();
@@ -19790,21 +19964,27 @@ function registerLogCommand(program2) {
19790
19964
  return void 0;
19791
19965
  })();
19792
19966
  const agent = agentOverride !== void 0 ? agentOverride : metaTag !== void 0 ? metaTag : payload.turn_id !== void 0 ? "Codex" : payload.toolCall !== void 0 || payload.conversationId !== void 0 ? "Antigravity" : payload.hook_event_name === "pre_tool_call" || payload.hook_event_name === "post_tool_call" ? "Hermes" : payload.hook_event_name === "PreToolUse" || payload.hook_event_name === "PostToolUse" || payload.tool_use_id !== void 0 || payload.permission_mode !== void 0 ? "Claude Code" : payload.hook_event_name === "BeforeTool" || payload.hook_event_name === "AfterTool" || payload.timestamp !== void 0 ? "Gemini CLI" : process.env.HERMES_SESSION_ID || process.env.HERMES_HOME || process.env.HERMES_INTERACTIVE ? "Hermes" : process.env.ANTIGRAVITY_CONVERSATION_ID ? "Antigravity" : void 0;
19967
+ let reviewApproved = false;
19968
+ try {
19969
+ const key = reviewCorrelationKey(payload);
19970
+ if (key && resolvePendingReview(key)) reviewApproved = true;
19971
+ } catch {
19972
+ }
19793
19973
  const entry = {
19794
19974
  ts: (/* @__PURE__ */ new Date()).toISOString(),
19795
19975
  tool,
19796
19976
  args: JSON.parse(redactSecrets(JSON.stringify(rawInput))),
19797
19977
  decision: "allowed",
19798
- source: "post-hook"
19978
+ source: reviewApproved ? "inline-review-approved" : "post-hook"
19799
19979
  };
19800
19980
  if (agent) entry.agent = agent;
19801
19981
  if (rawToolName !== tool) entry.agentToolName = rawToolName;
19802
19982
  const payloadSessionId = payload.session_id ?? payload.conversationId;
19803
19983
  if (payloadSessionId) entry.sessionId = payloadSessionId;
19804
- const logPath = import_path38.default.join(import_os33.default.homedir(), ".node9", "audit.log");
19805
- if (!import_fs37.default.existsSync(import_path38.default.dirname(logPath)))
19806
- import_fs37.default.mkdirSync(import_path38.default.dirname(logPath), { recursive: true });
19807
- import_fs37.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
19984
+ const logPath = import_path39.default.join(import_os34.default.homedir(), ".node9", "audit.log");
19985
+ if (!import_fs38.default.existsSync(import_path39.default.dirname(logPath)))
19986
+ import_fs38.default.mkdirSync(import_path39.default.dirname(logPath), { recursive: true });
19987
+ import_fs38.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
19808
19988
  if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
19809
19989
  const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
19810
19990
  if (command) {
@@ -19838,7 +20018,7 @@ function registerLogCommand(program2) {
19838
20018
  }
19839
20019
  }
19840
20020
  const payloadCwd = typeof payload.cwd === "string" ? payload.cwd : Array.isArray(payload.workspacePaths) && typeof payload.workspacePaths[0] === "string" ? payload.workspacePaths[0] : void 0;
19841
- const safeCwd = typeof payloadCwd === "string" && import_path38.default.isAbsolute(payloadCwd) ? payloadCwd : void 0;
20021
+ const safeCwd = typeof payloadCwd === "string" && import_path39.default.isAbsolute(payloadCwd) ? payloadCwd : void 0;
19842
20022
  const config = getConfig(safeCwd);
19843
20023
  {
19844
20024
  const toolOutput = payload.tool_response?.output;
@@ -19915,9 +20095,9 @@ function registerLogCommand(program2) {
19915
20095
  const msg = err2 instanceof Error ? err2.message : String(err2);
19916
20096
  process.stderr.write(`[Node9] audit log error: ${msg}
19917
20097
  `);
19918
- const debugPath = import_path38.default.join(import_os33.default.homedir(), ".node9", "hook-debug.log");
20098
+ const debugPath = import_path39.default.join(import_os34.default.homedir(), ".node9", "hook-debug.log");
19919
20099
  try {
19920
- import_fs37.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
20100
+ import_fs38.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
19921
20101
  `);
19922
20102
  } catch {
19923
20103
  }
@@ -20317,24 +20497,24 @@ function registerConfigShowCommand(program2) {
20317
20497
 
20318
20498
  // src/cli/commands/doctor.ts
20319
20499
  var import_chalk11 = __toESM(require("chalk"));
20320
- var import_fs39 = __toESM(require("fs"));
20321
- var import_path40 = __toESM(require("path"));
20322
- var import_os35 = __toESM(require("os"));
20500
+ var import_fs40 = __toESM(require("fs"));
20501
+ var import_path41 = __toESM(require("path"));
20502
+ var import_os36 = __toESM(require("os"));
20323
20503
  var import_child_process8 = require("child_process");
20324
20504
  init_daemon();
20325
20505
  init_config();
20326
20506
 
20327
20507
  // src/agent-wiring.ts
20328
- var import_fs38 = __toESM(require("fs"));
20329
- var import_path39 = __toESM(require("path"));
20330
- var import_os34 = __toESM(require("os"));
20508
+ var import_fs39 = __toESM(require("fs"));
20509
+ var import_path40 = __toESM(require("path"));
20510
+ var import_os35 = __toESM(require("os"));
20331
20511
  var yaml2 = __toESM(require("yaml"));
20332
20512
  var import_smol_toml2 = require("smol-toml");
20333
20513
  init_setup();
20334
20514
  function readJson2(filePath) {
20335
- if (!import_fs38.default.existsSync(filePath)) return null;
20515
+ if (!import_fs39.default.existsSync(filePath)) return null;
20336
20516
  try {
20337
- return JSON.parse(import_fs38.default.readFileSync(filePath, "utf-8"));
20517
+ return JSON.parse(import_fs39.default.readFileSync(filePath, "utf-8"));
20338
20518
  } catch {
20339
20519
  return "invalid";
20340
20520
  }
@@ -20346,10 +20526,10 @@ function flatHaveNode9Hook(entries) {
20346
20526
  return (Array.isArray(entries) ? entries : []).some((h) => isNode9Hook(h.command));
20347
20527
  }
20348
20528
  function readHookRoot(filePath, format) {
20349
- if (!import_fs38.default.existsSync(filePath)) return "absent";
20529
+ if (!import_fs39.default.existsSync(filePath)) return "absent";
20350
20530
  let raw;
20351
20531
  try {
20352
- raw = import_fs38.default.readFileSync(filePath, "utf-8");
20532
+ raw = import_fs39.default.readFileSync(filePath, "utf-8");
20353
20533
  } catch {
20354
20534
  return "absent";
20355
20535
  }
@@ -20372,10 +20552,10 @@ function detectMcp(servers) {
20372
20552
  return { wrapped, present };
20373
20553
  }
20374
20554
  function readMcp(filePath, format) {
20375
- if (!import_fs38.default.existsSync(filePath)) return { wrapped: [], present: false };
20555
+ if (!import_fs39.default.existsSync(filePath)) return { wrapped: [], present: false };
20376
20556
  try {
20377
20557
  if (format === "toml") {
20378
- const parsed2 = (0, import_smol_toml2.parse)(import_fs38.default.readFileSync(filePath, "utf-8"));
20558
+ const parsed2 = (0, import_smol_toml2.parse)(import_fs39.default.readFileSync(filePath, "utf-8"));
20379
20559
  return detectMcp(parsed2?.mcp_servers);
20380
20560
  }
20381
20561
  const parsed = readJson2(filePath);
@@ -20387,7 +20567,7 @@ function readMcp(filePath, format) {
20387
20567
  }
20388
20568
  var exists = (p) => {
20389
20569
  try {
20390
- return import_fs38.default.existsSync(p);
20570
+ return import_fs39.default.existsSync(p);
20391
20571
  } catch {
20392
20572
  return false;
20393
20573
  }
@@ -20401,52 +20581,52 @@ var AGENT_SPECS = [
20401
20581
  id: "claude",
20402
20582
  label: "Claude Code",
20403
20583
  setupCommand: "node9 agents add claude",
20404
- hookFile: (h) => import_path39.default.join(h, ".claude", "settings.json"),
20584
+ hookFile: (h) => import_path40.default.join(h, ".claude", "settings.json"),
20405
20585
  hookFormat: "matcher",
20406
20586
  hookEvents: [ck("PreToolUse"), lg("PostToolUse")],
20407
- mcpFile: (h) => import_path39.default.join(h, ".claude.json"),
20408
- present: (h) => exists(import_path39.default.join(h, ".claude", "settings.json")) || exists(import_path39.default.join(h, ".claude.json"))
20587
+ mcpFile: (h) => import_path40.default.join(h, ".claude.json"),
20588
+ present: (h) => exists(import_path40.default.join(h, ".claude", "settings.json")) || exists(import_path40.default.join(h, ".claude.json"))
20409
20589
  },
20410
20590
  {
20411
20591
  id: "gemini",
20412
20592
  label: "Gemini CLI",
20413
20593
  setupCommand: "node9 agents add gemini",
20414
- hookFile: (h) => import_path39.default.join(h, ".gemini", "settings.json"),
20594
+ hookFile: (h) => import_path40.default.join(h, ".gemini", "settings.json"),
20415
20595
  hookFormat: "matcher",
20416
20596
  hookEvents: [ck("BeforeTool"), lg("AfterTool")],
20417
- mcpFile: (h) => import_path39.default.join(h, ".gemini", "settings.json"),
20418
- present: (h) => exists(import_path39.default.join(h, ".gemini", "settings.json"))
20597
+ mcpFile: (h) => import_path40.default.join(h, ".gemini", "settings.json"),
20598
+ present: (h) => exists(import_path40.default.join(h, ".gemini", "settings.json"))
20419
20599
  },
20420
20600
  {
20421
20601
  id: "codex",
20422
20602
  label: "Codex",
20423
20603
  setupCommand: "node9 agents add codex",
20424
- hookFile: (h) => import_path39.default.join(h, ".codex", "hooks.json"),
20604
+ hookFile: (h) => import_path40.default.join(h, ".codex", "hooks.json"),
20425
20605
  hookFormat: "matcher",
20426
20606
  hookEvents: [ck("PreToolUse"), ck("UserPromptSubmit")],
20427
- mcpFile: (h) => import_path39.default.join(h, ".codex", "config.toml"),
20607
+ mcpFile: (h) => import_path40.default.join(h, ".codex", "config.toml"),
20428
20608
  mcpFormat: "toml",
20429
- present: (h) => exists(import_path39.default.join(h, ".codex"))
20609
+ present: (h) => exists(import_path40.default.join(h, ".codex"))
20430
20610
  },
20431
20611
  {
20432
20612
  id: "antigravity",
20433
20613
  label: "Antigravity",
20434
20614
  setupCommand: "node9 agents add antigravity",
20435
- hookFile: (h) => import_path39.default.join(h, ".gemini", "config", "hooks.json"),
20615
+ hookFile: (h) => import_path40.default.join(h, ".gemini", "config", "hooks.json"),
20436
20616
  hookFormat: "matcher",
20437
20617
  hookEvents: [ck("PreToolUse"), lg("PostToolUse")],
20438
- mcpFile: (h) => import_path39.default.join(h, ".gemini", "config", "mcp_config.json"),
20439
- present: (h) => exists(import_path39.default.join(h, ".gemini", "config", "hooks.json")) || exists(import_path39.default.join(h, ".gemini", "antigravity-cli")) || exists(import_path39.default.join(h, ".gemini", "antigravity-ide"))
20618
+ mcpFile: (h) => import_path40.default.join(h, ".gemini", "config", "mcp_config.json"),
20619
+ present: (h) => exists(import_path40.default.join(h, ".gemini", "config", "hooks.json")) || exists(import_path40.default.join(h, ".gemini", "antigravity-cli")) || exists(import_path40.default.join(h, ".gemini", "antigravity-ide"))
20440
20620
  },
20441
20621
  {
20442
20622
  id: "copilot",
20443
20623
  label: "GitHub Copilot",
20444
20624
  setupCommand: "node9 agents add copilot",
20445
- hookFile: (h) => import_path39.default.join(h, ".copilot", "hooks", "node9.json"),
20625
+ hookFile: (h) => import_path40.default.join(h, ".copilot", "hooks", "node9.json"),
20446
20626
  hookFormat: "flat",
20447
20627
  hookEvents: [ck("PreToolUse"), lg("PostToolUse"), ck("UserPromptSubmit")],
20448
- mcpFile: (h) => import_path39.default.join(h, ".copilot", "mcp-config.json"),
20449
- present: (h) => exists(import_path39.default.join(h, ".copilot"))
20628
+ mcpFile: (h) => import_path40.default.join(h, ".copilot", "mcp-config.json"),
20629
+ present: (h) => exists(import_path40.default.join(h, ".copilot"))
20450
20630
  },
20451
20631
  {
20452
20632
  id: "cursor",
@@ -20455,8 +20635,8 @@ var AGENT_SPECS = [
20455
20635
  // MCP-only — no hook file (see note above).
20456
20636
  hookFormat: "flat",
20457
20637
  hookEvents: [],
20458
- mcpFile: (h) => import_path39.default.join(h, ".cursor", "mcp.json"),
20459
- present: (h) => exists(import_path39.default.join(h, ".cursor", "mcp.json"))
20638
+ mcpFile: (h) => import_path40.default.join(h, ".cursor", "mcp.json"),
20639
+ present: (h) => exists(import_path40.default.join(h, ".cursor", "mcp.json"))
20460
20640
  },
20461
20641
  {
20462
20642
  id: "hermes",
@@ -20477,8 +20657,8 @@ var AGENT_SPECS = [
20477
20657
  setupCommand: "node9 agents add opencode",
20478
20658
  hookFormat: "flat",
20479
20659
  hookEvents: [],
20480
- shimFile: (h) => import_path39.default.join(h, ".config", "opencode", "plugins", "node9.js"),
20481
- present: (h) => exists(import_path39.default.join(h, ".config", "opencode")) || exists(import_path39.default.join(h, ".config", "opencode", "plugins", "node9.js"))
20660
+ shimFile: (h) => import_path40.default.join(h, ".config", "opencode", "plugins", "node9.js"),
20661
+ present: (h) => exists(import_path40.default.join(h, ".config", "opencode")) || exists(import_path40.default.join(h, ".config", "opencode", "plugins", "node9.js"))
20482
20662
  },
20483
20663
  {
20484
20664
  id: "pi",
@@ -20486,11 +20666,11 @@ var AGENT_SPECS = [
20486
20666
  setupCommand: "node9 agents add pi",
20487
20667
  hookFormat: "flat",
20488
20668
  hookEvents: [],
20489
- shimFile: (h) => import_path39.default.join(h, ".pi", "agent", "extensions", "node9.js"),
20490
- present: (h) => exists(import_path39.default.join(h, ".pi", "agent")) || exists(import_path39.default.join(h, ".pi", "agent", "extensions", "node9.js"))
20669
+ shimFile: (h) => import_path40.default.join(h, ".pi", "agent", "extensions", "node9.js"),
20670
+ present: (h) => exists(import_path40.default.join(h, ".pi", "agent")) || exists(import_path40.default.join(h, ".pi", "agent", "extensions", "node9.js"))
20491
20671
  }
20492
20672
  ];
20493
- function getAgentWiring(home = import_os34.default.homedir()) {
20673
+ function getAgentWiring(home = import_os35.default.homedir()) {
20494
20674
  const detected = detectAgents(home);
20495
20675
  return AGENT_SPECS.map((spec) => {
20496
20676
  const present = spec.present(home);
@@ -20542,7 +20722,7 @@ function getAgentWiring(home = import_os34.default.homedir()) {
20542
20722
  // src/cli/commands/doctor.ts
20543
20723
  function registerDoctorCommand(program2, version2) {
20544
20724
  program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(async () => {
20545
- const homeDir2 = import_os35.default.homedir();
20725
+ const homeDir2 = import_os36.default.homedir();
20546
20726
  let failures = 0;
20547
20727
  function pass(msg) {
20548
20728
  console.log(import_chalk11.default.green(" \u2705 ") + msg);
@@ -20588,10 +20768,10 @@ function registerDoctorCommand(program2, version2) {
20588
20768
  );
20589
20769
  }
20590
20770
  section("Configuration");
20591
- const globalConfigPath = import_path40.default.join(homeDir2, ".node9", "config.json");
20592
- if (import_fs39.default.existsSync(globalConfigPath)) {
20771
+ const globalConfigPath = import_path41.default.join(homeDir2, ".node9", "config.json");
20772
+ if (import_fs40.default.existsSync(globalConfigPath)) {
20593
20773
  try {
20594
- JSON.parse(import_fs39.default.readFileSync(globalConfigPath, "utf-8"));
20774
+ JSON.parse(import_fs40.default.readFileSync(globalConfigPath, "utf-8"));
20595
20775
  pass("~/.node9/config.json found and valid");
20596
20776
  } catch {
20597
20777
  fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
@@ -20599,10 +20779,10 @@ function registerDoctorCommand(program2, version2) {
20599
20779
  } else {
20600
20780
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
20601
20781
  }
20602
- const projectConfigPath = import_path40.default.join(process.cwd(), "node9.config.json");
20603
- if (import_fs39.default.existsSync(projectConfigPath)) {
20782
+ const projectConfigPath = import_path41.default.join(process.cwd(), "node9.config.json");
20783
+ if (import_fs40.default.existsSync(projectConfigPath)) {
20604
20784
  try {
20605
- JSON.parse(import_fs39.default.readFileSync(projectConfigPath, "utf-8"));
20785
+ JSON.parse(import_fs40.default.readFileSync(projectConfigPath, "utf-8"));
20606
20786
  pass("node9.config.json found and valid (project)");
20607
20787
  } catch {
20608
20788
  fail(
@@ -20611,8 +20791,8 @@ function registerDoctorCommand(program2, version2) {
20611
20791
  );
20612
20792
  }
20613
20793
  }
20614
- const credsPath = import_path40.default.join(homeDir2, ".node9", "credentials.json");
20615
- if (import_fs39.default.existsSync(credsPath)) {
20794
+ const credsPath = import_path41.default.join(homeDir2, ".node9", "credentials.json");
20795
+ if (import_fs40.default.existsSync(credsPath)) {
20616
20796
  pass("Cloud credentials found (~/.node9/credentials.json)");
20617
20797
  } else {
20618
20798
  warn(
@@ -20656,7 +20836,7 @@ function registerDoctorCommand(program2, version2) {
20656
20836
  try {
20657
20837
  const { shipLagBytes: shipLagBytes2, readWatermark: readWatermark2, AUDIT_SHIP_WATERMARK: AUDIT_SHIP_WATERMARK2 } = await Promise.resolve().then(() => (init_audit_shipper(), audit_shipper_exports));
20658
20838
  const cfg = getConfig();
20659
- const creds = import_fs39.default.existsSync(import_path40.default.join(import_os35.default.homedir(), ".node9", "credentials.json"));
20839
+ const creds = import_fs40.default.existsSync(import_path41.default.join(import_os36.default.homedir(), ".node9", "credentials.json"));
20660
20840
  if (!creds) {
20661
20841
  warn("Not logged in \u2014 audit rows stay local", "Run: node9 login <api-key>");
20662
20842
  } else if (!cfg.settings.approvers.cloud) {
@@ -20706,9 +20886,9 @@ function registerDoctorCommand(program2, version2) {
20706
20886
 
20707
20887
  // src/cli/commands/audit.ts
20708
20888
  var import_chalk12 = __toESM(require("chalk"));
20709
- var import_fs40 = __toESM(require("fs"));
20710
- var import_path41 = __toESM(require("path"));
20711
- var import_os36 = __toESM(require("os"));
20889
+ var import_fs41 = __toESM(require("fs"));
20890
+ var import_path42 = __toESM(require("path"));
20891
+ var import_os37 = __toESM(require("os"));
20712
20892
  function formatRelativeTime(timestamp) {
20713
20893
  const diff = Date.now() - new Date(timestamp).getTime();
20714
20894
  const sec = Math.floor(diff / 1e3);
@@ -20721,14 +20901,14 @@ function formatRelativeTime(timestamp) {
20721
20901
  }
20722
20902
  function registerAuditCommand(program2) {
20723
20903
  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) => {
20724
- const logPath = import_path41.default.join(import_os36.default.homedir(), ".node9", "audit.log");
20725
- if (!import_fs40.default.existsSync(logPath)) {
20904
+ const logPath = import_path42.default.join(import_os37.default.homedir(), ".node9", "audit.log");
20905
+ if (!import_fs41.default.existsSync(logPath)) {
20726
20906
  console.log(
20727
20907
  import_chalk12.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
20728
20908
  );
20729
20909
  return;
20730
20910
  }
20731
- const raw = import_fs40.default.readFileSync(logPath, "utf-8");
20911
+ const raw = import_fs41.default.readFileSync(logPath, "utf-8");
20732
20912
  const lines = raw.split("\n").filter((l) => l.trim() !== "");
20733
20913
  let entries = lines.flatMap((line) => {
20734
20914
  try {
@@ -20784,9 +20964,9 @@ function registerAuditCommand(program2) {
20784
20964
  var import_chalk13 = __toESM(require("chalk"));
20785
20965
 
20786
20966
  // src/cli/aggregate/report-audit.ts
20787
- var import_fs41 = __toESM(require("fs"));
20788
- var import_os37 = __toESM(require("os"));
20789
- var import_path42 = __toESM(require("path"));
20967
+ var import_fs42 = __toESM(require("fs"));
20968
+ var import_os38 = __toESM(require("os"));
20969
+ var import_path43 = __toESM(require("path"));
20790
20970
  init_costSync();
20791
20971
  init_litellm();
20792
20972
  init_cost_codex();
@@ -20869,8 +21049,8 @@ function getDateRange(period, now) {
20869
21049
  }
20870
21050
  }
20871
21051
  function parseAuditLog(logPath) {
20872
- if (!import_fs41.default.existsSync(logPath)) return [];
20873
- const raw = import_fs41.default.readFileSync(logPath, "utf-8");
21052
+ if (!import_fs42.default.existsSync(logPath)) return [];
21053
+ const raw = import_fs42.default.readFileSync(logPath, "utf-8");
20874
21054
  return raw.split("\n").flatMap((line) => {
20875
21055
  if (!line.trim()) return [];
20876
21056
  try {
@@ -20917,25 +21097,25 @@ function freezeClaudeCost(acc) {
20917
21097
  };
20918
21098
  }
20919
21099
  function processClaudeCostProject(proj, projectsDir, start, end, acc) {
20920
- const projPath = import_path42.default.join(projectsDir, proj);
21100
+ const projPath = import_path43.default.join(projectsDir, proj);
20921
21101
  let files;
20922
21102
  try {
20923
- const stat = import_fs41.default.statSync(projPath);
21103
+ const stat = import_fs42.default.statSync(projPath);
20924
21104
  if (!stat.isDirectory()) return;
20925
- files = import_fs41.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
21105
+ files = import_fs42.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
20926
21106
  } catch {
20927
21107
  return;
20928
21108
  }
20929
21109
  const startMs = start.getTime();
20930
21110
  for (const file of files) {
20931
- const filePath = import_path42.default.join(projPath, file);
21111
+ const filePath = import_path43.default.join(projPath, file);
20932
21112
  try {
20933
- if (import_fs41.default.statSync(filePath).mtimeMs < startMs) continue;
21113
+ if (import_fs42.default.statSync(filePath).mtimeMs < startMs) continue;
20934
21114
  } catch {
20935
21115
  continue;
20936
21116
  }
20937
21117
  try {
20938
- const raw = import_fs41.default.readFileSync(filePath, "utf-8");
21118
+ const raw = import_fs42.default.readFileSync(filePath, "utf-8");
20939
21119
  for (const line of raw.split("\n")) {
20940
21120
  if (!line.trim()) continue;
20941
21121
  let entry;
@@ -20985,10 +21165,10 @@ function processClaudeCostProject(proj, projectsDir, start, end, acc) {
20985
21165
  }
20986
21166
  function loadClaudeCost(start, end, projectsDir) {
20987
21167
  const acc = emptyClaudeCostAccumulator();
20988
- if (!import_fs41.default.existsSync(projectsDir)) return freezeClaudeCost(acc);
21168
+ if (!import_fs42.default.existsSync(projectsDir)) return freezeClaudeCost(acc);
20989
21169
  let dirs;
20990
21170
  try {
20991
- dirs = import_fs41.default.readdirSync(projectsDir);
21171
+ dirs = import_fs42.default.readdirSync(projectsDir);
20992
21172
  } catch {
20993
21173
  return freezeClaudeCost(acc);
20994
21174
  }
@@ -21000,7 +21180,7 @@ function loadClaudeCost(start, end, projectsDir) {
21000
21180
  function processCodexCostFile(filePath, start, end, acc) {
21001
21181
  let lines;
21002
21182
  try {
21003
- lines = import_fs41.default.readFileSync(filePath, "utf-8").split("\n");
21183
+ lines = import_fs42.default.readFileSync(filePath, "utf-8").split("\n");
21004
21184
  } catch {
21005
21185
  return;
21006
21186
  }
@@ -21055,31 +21235,31 @@ function processCodexCostFile(filePath, start, end, acc) {
21055
21235
  }
21056
21236
  function listCodexSessionFiles2(sessionsBase) {
21057
21237
  const jsonlFiles = [];
21058
- if (!import_fs41.default.existsSync(sessionsBase)) return jsonlFiles;
21238
+ if (!import_fs42.default.existsSync(sessionsBase)) return jsonlFiles;
21059
21239
  try {
21060
- for (const year of import_fs41.default.readdirSync(sessionsBase)) {
21061
- const yearPath = import_path42.default.join(sessionsBase, year);
21240
+ for (const year of import_fs42.default.readdirSync(sessionsBase)) {
21241
+ const yearPath = import_path43.default.join(sessionsBase, year);
21062
21242
  try {
21063
- if (!import_fs41.default.statSync(yearPath).isDirectory()) continue;
21243
+ if (!import_fs42.default.statSync(yearPath).isDirectory()) continue;
21064
21244
  } catch {
21065
21245
  continue;
21066
21246
  }
21067
- for (const month of import_fs41.default.readdirSync(yearPath)) {
21068
- const monthPath = import_path42.default.join(yearPath, month);
21247
+ for (const month of import_fs42.default.readdirSync(yearPath)) {
21248
+ const monthPath = import_path43.default.join(yearPath, month);
21069
21249
  try {
21070
- if (!import_fs41.default.statSync(monthPath).isDirectory()) continue;
21250
+ if (!import_fs42.default.statSync(monthPath).isDirectory()) continue;
21071
21251
  } catch {
21072
21252
  continue;
21073
21253
  }
21074
- for (const day of import_fs41.default.readdirSync(monthPath)) {
21075
- const dayPath = import_path42.default.join(monthPath, day);
21254
+ for (const day of import_fs42.default.readdirSync(monthPath)) {
21255
+ const dayPath = import_path43.default.join(monthPath, day);
21076
21256
  try {
21077
- if (!import_fs41.default.statSync(dayPath).isDirectory()) continue;
21257
+ if (!import_fs42.default.statSync(dayPath).isDirectory()) continue;
21078
21258
  } catch {
21079
21259
  continue;
21080
21260
  }
21081
- for (const file of import_fs41.default.readdirSync(dayPath)) {
21082
- if (file.endsWith(".jsonl")) jsonlFiles.push(import_path42.default.join(dayPath, file));
21261
+ for (const file of import_fs42.default.readdirSync(dayPath)) {
21262
+ if (file.endsWith(".jsonl")) jsonlFiles.push(import_path43.default.join(dayPath, file));
21083
21263
  }
21084
21264
  }
21085
21265
  }
@@ -21144,13 +21324,13 @@ function freezeGeminiCost(acc) {
21144
21324
  function processGeminiCostFile(filePath, projectKey, start, end, acc) {
21145
21325
  const startMs = start.getTime();
21146
21326
  try {
21147
- if (import_fs41.default.statSync(filePath).mtimeMs < startMs) return;
21327
+ if (import_fs42.default.statSync(filePath).mtimeMs < startMs) return;
21148
21328
  } catch {
21149
21329
  return;
21150
21330
  }
21151
21331
  let raw;
21152
21332
  try {
21153
- raw = import_fs41.default.readFileSync(filePath, "utf-8");
21333
+ raw = import_fs42.default.readFileSync(filePath, "utf-8");
21154
21334
  } catch {
21155
21335
  return;
21156
21336
  }
@@ -21199,30 +21379,30 @@ function listGeminiSessionFiles2(geminiTmpDir2) {
21199
21379
  const out = [];
21200
21380
  let dirs;
21201
21381
  try {
21202
- if (!import_fs41.default.statSync(geminiTmpDir2).isDirectory()) return out;
21203
- dirs = import_fs41.default.readdirSync(geminiTmpDir2);
21382
+ if (!import_fs42.default.statSync(geminiTmpDir2).isDirectory()) return out;
21383
+ dirs = import_fs42.default.readdirSync(geminiTmpDir2);
21204
21384
  } catch {
21205
21385
  return out;
21206
21386
  }
21207
21387
  for (const proj of dirs) {
21208
- const chatsDir = import_path42.default.join(geminiTmpDir2, proj, "chats");
21388
+ const chatsDir = import_path43.default.join(geminiTmpDir2, proj, "chats");
21209
21389
  let files;
21210
21390
  try {
21211
- if (!import_fs41.default.statSync(chatsDir).isDirectory()) continue;
21212
- files = import_fs41.default.readdirSync(chatsDir);
21391
+ if (!import_fs42.default.statSync(chatsDir).isDirectory()) continue;
21392
+ files = import_fs42.default.readdirSync(chatsDir);
21213
21393
  } catch {
21214
21394
  continue;
21215
21395
  }
21216
21396
  for (const f of files) {
21217
21397
  if (!f.endsWith(".jsonl")) continue;
21218
- out.push({ projectKey: proj, file: import_path42.default.join(chatsDir, f) });
21398
+ out.push({ projectKey: proj, file: import_path43.default.join(chatsDir, f) });
21219
21399
  }
21220
21400
  }
21221
21401
  return out;
21222
21402
  }
21223
21403
  function loadGeminiCost(start, end, geminiTmpDir2) {
21224
21404
  const acc = emptyGeminiAccumulator();
21225
- if (!import_fs41.default.existsSync(geminiTmpDir2)) return freezeGeminiCost(acc);
21405
+ if (!import_fs42.default.existsSync(geminiTmpDir2)) return freezeGeminiCost(acc);
21226
21406
  for (const { projectKey, file } of listGeminiSessionFiles2(geminiTmpDir2)) {
21227
21407
  processGeminiCostFile(file, projectKey, start, end, acc);
21228
21408
  }
@@ -21230,11 +21410,11 @@ function loadGeminiCost(start, end, geminiTmpDir2) {
21230
21410
  }
21231
21411
  function aggregateReportFromAudit(period, opts = {}) {
21232
21412
  const now = opts.now ?? /* @__PURE__ */ new Date();
21233
- const auditLogPath = opts.auditLogPath ?? import_path42.default.join(import_os37.default.homedir(), ".node9", "audit.log");
21234
- const claudeProjectsDir = opts.claudeProjectsDir ?? import_path42.default.join(import_os37.default.homedir(), ".claude", "projects");
21235
- const codexSessionsDir2 = opts.codexSessionsDir ?? import_path42.default.join(import_os37.default.homedir(), ".codex", "sessions");
21236
- const geminiTmpDir2 = opts.geminiTmpDir ?? import_path42.default.join(import_os37.default.homedir(), ".gemini", "tmp");
21237
- const hasAuditFile = import_fs41.default.existsSync(auditLogPath);
21413
+ const auditLogPath = opts.auditLogPath ?? import_path43.default.join(import_os38.default.homedir(), ".node9", "audit.log");
21414
+ const claudeProjectsDir = opts.claudeProjectsDir ?? import_path43.default.join(import_os38.default.homedir(), ".claude", "projects");
21415
+ const codexSessionsDir2 = opts.codexSessionsDir ?? import_path43.default.join(import_os38.default.homedir(), ".codex", "sessions");
21416
+ const geminiTmpDir2 = opts.geminiTmpDir ?? import_path43.default.join(import_os38.default.homedir(), ".gemini", "tmp");
21417
+ const hasAuditFile = import_fs42.default.existsSync(auditLogPath);
21238
21418
  const allEntries = opts.preloadedAuditEntries ?? parseAuditLog(auditLogPath);
21239
21419
  const unackedDlp = allEntries.filter((e) => e.source === "response-dlp");
21240
21420
  const { start, end } = getDateRange(period, now);
@@ -21930,9 +22110,9 @@ function registerDaemonCommand(program2) {
21930
22110
 
21931
22111
  // src/cli/commands/status.ts
21932
22112
  var import_chalk15 = __toESM(require("chalk"));
21933
- var import_fs42 = __toESM(require("fs"));
21934
- var import_path43 = __toESM(require("path"));
21935
- var import_os38 = __toESM(require("os"));
22113
+ var import_fs43 = __toESM(require("fs"));
22114
+ var import_path44 = __toESM(require("path"));
22115
+ var import_os39 = __toESM(require("os"));
21936
22116
  init_core();
21937
22117
  init_daemon();
21938
22118
  function printAgentSection(label2, hookPairs, wrapped) {
@@ -21988,20 +22168,20 @@ function registerStatusCommand(program2) {
21988
22168
  console.log("");
21989
22169
  const modeLabel = settings.mode === "audit" ? import_chalk15.default.blue("audit") : settings.mode === "strict" ? import_chalk15.default.red("strict") : import_chalk15.default.white("standard");
21990
22170
  console.log(` Mode: ${modeLabel}`);
21991
- const projectConfig = import_path43.default.join(process.cwd(), "node9.config.json");
21992
- const globalConfig = import_path43.default.join(import_os38.default.homedir(), ".node9", "config.json");
22171
+ const projectConfig = import_path44.default.join(process.cwd(), "node9.config.json");
22172
+ const globalConfig = import_path44.default.join(import_os39.default.homedir(), ".node9", "config.json");
21993
22173
  console.log(
21994
- ` Local: ${import_fs42.default.existsSync(projectConfig) ? import_chalk15.default.green("Active (node9.config.json)") : import_chalk15.default.gray("Not present")}`
22174
+ ` Local: ${import_fs43.default.existsSync(projectConfig) ? import_chalk15.default.green("Active (node9.config.json)") : import_chalk15.default.gray("Not present")}`
21995
22175
  );
21996
22176
  console.log(
21997
- ` Global: ${import_fs42.default.existsSync(globalConfig) ? import_chalk15.default.green("Active (~/.node9/config.json)") : import_chalk15.default.gray("Not present")}`
22177
+ ` Global: ${import_fs43.default.existsSync(globalConfig) ? import_chalk15.default.green("Active (~/.node9/config.json)") : import_chalk15.default.gray("Not present")}`
21998
22178
  );
21999
22179
  if (mergedConfig.policy.sandboxPaths.length > 0) {
22000
22180
  console.log(
22001
22181
  ` Sandbox: ${import_chalk15.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
22002
22182
  );
22003
22183
  }
22004
- const wiring = getAgentWiring(import_os38.default.homedir()).filter((a) => a.present);
22184
+ const wiring = getAgentWiring(import_os39.default.homedir()).filter((a) => a.present);
22005
22185
  if (wiring.length > 0) {
22006
22186
  console.log("");
22007
22187
  console.log(import_chalk15.default.bold(" Agent Wiring:"));
@@ -22036,9 +22216,9 @@ function registerStatusCommand(program2) {
22036
22216
 
22037
22217
  // src/cli/commands/init.ts
22038
22218
  var import_chalk16 = __toESM(require("chalk"));
22039
- var import_fs43 = __toESM(require("fs"));
22040
- var import_path44 = __toESM(require("path"));
22041
- var import_os39 = __toESM(require("os"));
22219
+ var import_fs44 = __toESM(require("fs"));
22220
+ var import_path45 = __toESM(require("path"));
22221
+ var import_os40 = __toESM(require("os"));
22042
22222
  var import_https4 = __toESM(require("https"));
22043
22223
  init_core();
22044
22224
  init_setup();
@@ -22128,16 +22308,16 @@ function registerInitCommand(program2) {
22128
22308
  }
22129
22309
  console.log("");
22130
22310
  }
22131
- const configPath2 = import_path44.default.join(import_os39.default.homedir(), ".node9", "config.json");
22132
- const isFirstInstall = !import_fs43.default.existsSync(configPath2);
22133
- if (import_fs43.default.existsSync(configPath2) && !options.force) {
22311
+ const configPath2 = import_path45.default.join(import_os40.default.homedir(), ".node9", "config.json");
22312
+ const isFirstInstall = !import_fs44.default.existsSync(configPath2);
22313
+ if (import_fs44.default.existsSync(configPath2) && !options.force) {
22134
22314
  try {
22135
- const existing = JSON.parse(import_fs43.default.readFileSync(configPath2, "utf-8"));
22315
+ const existing = JSON.parse(import_fs44.default.readFileSync(configPath2, "utf-8"));
22136
22316
  const settings = existing.settings ?? {};
22137
22317
  if (settings.mode !== chosenMode) {
22138
22318
  settings.mode = chosenMode;
22139
22319
  existing.settings = settings;
22140
- import_fs43.default.writeFileSync(configPath2, JSON.stringify(existing, null, 2) + "\n");
22320
+ import_fs44.default.writeFileSync(configPath2, JSON.stringify(existing, null, 2) + "\n");
22141
22321
  console.log(import_chalk16.default.green(`\u2705 Mode updated: ${chosenMode}`));
22142
22322
  } else {
22143
22323
  console.log(import_chalk16.default.blue(`\u2139\uFE0F Config already exists: ${configPath2}`));
@@ -22150,9 +22330,9 @@ function registerInitCommand(program2) {
22150
22330
  ...DEFAULT_CONFIG,
22151
22331
  settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
22152
22332
  };
22153
- const dir = import_path44.default.dirname(configPath2);
22154
- if (!import_fs43.default.existsSync(dir)) import_fs43.default.mkdirSync(dir, { recursive: true });
22155
- import_fs43.default.writeFileSync(configPath2, JSON.stringify(configToSave, null, 2) + "\n");
22333
+ const dir = import_path45.default.dirname(configPath2);
22334
+ if (!import_fs44.default.existsSync(dir)) import_fs44.default.mkdirSync(dir, { recursive: true });
22335
+ import_fs44.default.writeFileSync(configPath2, JSON.stringify(configToSave, null, 2) + "\n");
22156
22336
  console.log(import_chalk16.default.green(`\u2705 Config created: ${configPath2}`));
22157
22337
  console.log(import_chalk16.default.gray(` Mode: ${chosenMode}`));
22158
22338
  }
@@ -22257,7 +22437,7 @@ function registerInitCommand(program2) {
22257
22437
  }
22258
22438
 
22259
22439
  // src/cli/commands/undo.ts
22260
- var import_path45 = __toESM(require("path"));
22440
+ var import_path46 = __toESM(require("path"));
22261
22441
  var import_chalk18 = __toESM(require("chalk"));
22262
22442
 
22263
22443
  // src/tui/undo-navigator.ts
@@ -22416,7 +22596,7 @@ function findMatchingCwd(startDir, history) {
22416
22596
  let dir = startDir;
22417
22597
  while (true) {
22418
22598
  if (cwds.has(dir)) return dir;
22419
- const parent = import_path45.default.dirname(dir);
22599
+ const parent = import_path46.default.dirname(dir);
22420
22600
  if (parent === dir) return null;
22421
22601
  dir = parent;
22422
22602
  }
@@ -23051,9 +23231,9 @@ function registerMcpGatewayCommand(program2) {
23051
23231
 
23052
23232
  // src/mcp-server/index.ts
23053
23233
  var import_readline5 = __toESM(require("readline"));
23054
- var import_fs44 = __toESM(require("fs"));
23055
- var import_os40 = __toESM(require("os"));
23056
- var import_path46 = __toESM(require("path"));
23234
+ var import_fs45 = __toESM(require("fs"));
23235
+ var import_os41 = __toESM(require("os"));
23236
+ var import_path47 = __toESM(require("path"));
23057
23237
  var import_child_process11 = require("child_process");
23058
23238
  init_core();
23059
23239
  init_daemon();
@@ -23304,13 +23484,13 @@ function handleStatus() {
23304
23484
  lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
23305
23485
  lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
23306
23486
  lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
23307
- const projectConfig = import_path46.default.join(process.cwd(), "node9.config.json");
23308
- const globalConfig = import_path46.default.join(import_os40.default.homedir(), ".node9", "config.json");
23487
+ const projectConfig = import_path47.default.join(process.cwd(), "node9.config.json");
23488
+ const globalConfig = import_path47.default.join(import_os41.default.homedir(), ".node9", "config.json");
23309
23489
  lines.push(
23310
- `Project config (node9.config.json): ${import_fs44.default.existsSync(projectConfig) ? "present" : "not found"}`
23490
+ `Project config (node9.config.json): ${import_fs45.default.existsSync(projectConfig) ? "present" : "not found"}`
23311
23491
  );
23312
23492
  lines.push(
23313
- `Global config (~/.node9/config.json): ${import_fs44.default.existsSync(globalConfig) ? "present" : "not found"}`
23493
+ `Global config (~/.node9/config.json): ${import_fs45.default.existsSync(globalConfig) ? "present" : "not found"}`
23314
23494
  );
23315
23495
  return lines.join("\n");
23316
23496
  }
@@ -23384,21 +23564,21 @@ function handleShieldDisable(args) {
23384
23564
  writeActiveShields(active.filter((s) => s !== name));
23385
23565
  return `Shield "${name}" disabled.`;
23386
23566
  }
23387
- var GLOBAL_CONFIG_PATH = import_path46.default.join(import_os40.default.homedir(), ".node9", "config.json");
23567
+ var GLOBAL_CONFIG_PATH = import_path47.default.join(import_os41.default.homedir(), ".node9", "config.json");
23388
23568
  var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
23389
23569
  function readGlobalConfigRaw() {
23390
23570
  try {
23391
- if (import_fs44.default.existsSync(GLOBAL_CONFIG_PATH)) {
23392
- return JSON.parse(import_fs44.default.readFileSync(GLOBAL_CONFIG_PATH, "utf-8"));
23571
+ if (import_fs45.default.existsSync(GLOBAL_CONFIG_PATH)) {
23572
+ return JSON.parse(import_fs45.default.readFileSync(GLOBAL_CONFIG_PATH, "utf-8"));
23393
23573
  }
23394
23574
  } catch {
23395
23575
  }
23396
23576
  return {};
23397
23577
  }
23398
23578
  function writeGlobalConfigRaw(data) {
23399
- const dir = import_path46.default.dirname(GLOBAL_CONFIG_PATH);
23400
- if (!import_fs44.default.existsSync(dir)) import_fs44.default.mkdirSync(dir, { recursive: true });
23401
- import_fs44.default.writeFileSync(GLOBAL_CONFIG_PATH, JSON.stringify(data, null, 2) + "\n");
23579
+ const dir = import_path47.default.dirname(GLOBAL_CONFIG_PATH);
23580
+ if (!import_fs45.default.existsSync(dir)) import_fs45.default.mkdirSync(dir, { recursive: true });
23581
+ import_fs45.default.writeFileSync(GLOBAL_CONFIG_PATH, JSON.stringify(data, null, 2) + "\n");
23402
23582
  }
23403
23583
  function handleApproverList() {
23404
23584
  const config = getConfig();
@@ -23442,9 +23622,9 @@ function handleApproverSet(args) {
23442
23622
  function handleAuditGet(args) {
23443
23623
  const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
23444
23624
  const filter = typeof args.filter === "string" && args.filter !== "all" ? args.filter : null;
23445
- const auditPath = import_path46.default.join(import_os40.default.homedir(), ".node9", "audit.log");
23446
- if (!import_fs44.default.existsSync(auditPath)) return "No audit log found.";
23447
- const rawLines = import_fs44.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
23625
+ const auditPath = import_path47.default.join(import_os41.default.homedir(), ".node9", "audit.log");
23626
+ if (!import_fs45.default.existsSync(auditPath)) return "No audit log found.";
23627
+ const rawLines = import_fs45.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
23448
23628
  const parsed = [];
23449
23629
  for (const line of rawLines) {
23450
23630
  try {
@@ -23779,7 +23959,7 @@ function registerTrustCommand(program2) {
23779
23959
  // src/cli/commands/mcp-pin.ts
23780
23960
  var import_chalk21 = __toESM(require("chalk"));
23781
23961
  init_mcp_pin();
23782
- var import_fs45 = __toESM(require("fs"));
23962
+ var import_fs46 = __toESM(require("fs"));
23783
23963
  function registerMcpPinCommand(program2) {
23784
23964
  const pinCmd = program2.command("mcp").description("Manage MCP server tool definition pinning (rug pull defense)");
23785
23965
  const pinSubCmd = pinCmd.command("pin").description("Manage pinned MCP server tool definitions");
@@ -23790,7 +23970,7 @@ function registerMcpPinCommand(program2) {
23790
23970
  let repoCorrupt = false;
23791
23971
  if (found.source === "repo") {
23792
23972
  try {
23793
- const raw = import_fs45.default.readFileSync(found.path, "utf-8");
23973
+ const raw = import_fs46.default.readFileSync(found.path, "utf-8");
23794
23974
  const parsed = JSON.parse(raw);
23795
23975
  repoEntries = parsed.servers ?? {};
23796
23976
  } catch {
@@ -24105,25 +24285,25 @@ init_scan();
24105
24285
  var import_chalk25 = __toESM(require("chalk"));
24106
24286
 
24107
24287
  // src/posture/index.ts
24108
- var import_os44 = __toESM(require("os"));
24288
+ var import_os45 = __toESM(require("os"));
24109
24289
 
24110
24290
  // src/posture/secrets.ts
24111
- var import_fs46 = __toESM(require("fs"));
24112
- var import_path47 = __toESM(require("path"));
24113
- var import_os41 = __toESM(require("os"));
24291
+ var import_fs47 = __toESM(require("fs"));
24292
+ var import_path48 = __toESM(require("path"));
24293
+ var import_os42 = __toESM(require("os"));
24114
24294
  init_dist();
24115
24295
  var MAX_FILE_BYTES = 256 * 1024;
24116
24296
  function displayPath(p, home) {
24117
24297
  if (p === home) return "~";
24118
- const prefix = home.endsWith(import_path47.default.sep) ? home : home + import_path47.default.sep;
24119
- if (p.startsWith(prefix)) return "~" + import_path47.default.sep + p.slice(prefix.length);
24298
+ const prefix = home.endsWith(import_path48.default.sep) ? home : home + import_path48.default.sep;
24299
+ if (p.startsWith(prefix)) return "~" + import_path48.default.sep + p.slice(prefix.length);
24120
24300
  return p;
24121
24301
  }
24122
24302
  function safeRead(file) {
24123
24303
  try {
24124
- const stat = import_fs46.default.statSync(file);
24304
+ const stat = import_fs47.default.statSync(file);
24125
24305
  if (!stat.isFile() || stat.size === 0 || stat.size > MAX_FILE_BYTES) return null;
24126
- return import_fs46.default.readFileSync(file, "utf8");
24306
+ return import_fs47.default.readFileSync(file, "utf8");
24127
24307
  } catch {
24128
24308
  return null;
24129
24309
  }
@@ -24131,8 +24311,8 @@ function safeRead(file) {
24131
24311
  function candidateFiles(home, cwd) {
24132
24312
  const files = /* @__PURE__ */ new Set();
24133
24313
  try {
24134
- for (const name of import_fs46.default.readdirSync(cwd)) {
24135
- if (name === ".env" || name.startsWith(".env.")) files.add(import_path47.default.join(cwd, name));
24314
+ for (const name of import_fs47.default.readdirSync(cwd)) {
24315
+ if (name === ".env" || name.startsWith(".env.")) files.add(import_path48.default.join(cwd, name));
24136
24316
  }
24137
24317
  } catch {
24138
24318
  }
@@ -24140,21 +24320,21 @@ function candidateFiles(home, cwd) {
24140
24320
  if (spec.hookFile) files.add(spec.hookFile(home));
24141
24321
  if (spec.mcpFile) files.add(spec.mcpFile(home));
24142
24322
  }
24143
- files.add(import_path47.default.join(home, ".env"));
24323
+ files.add(import_path48.default.join(home, ".env"));
24144
24324
  return [...files];
24145
24325
  }
24146
24326
  function credentialMaterial(home) {
24147
24327
  return [
24148
- import_path47.default.join(home, ".ssh", "id_rsa"),
24149
- import_path47.default.join(home, ".ssh", "id_dsa"),
24150
- import_path47.default.join(home, ".ssh", "id_ecdsa"),
24151
- import_path47.default.join(home, ".ssh", "id_ed25519"),
24152
- import_path47.default.join(home, ".aws", "credentials"),
24153
- import_path47.default.join(home, ".config", "gcloud", "application_default_credentials.json")
24328
+ import_path48.default.join(home, ".ssh", "id_rsa"),
24329
+ import_path48.default.join(home, ".ssh", "id_dsa"),
24330
+ import_path48.default.join(home, ".ssh", "id_ecdsa"),
24331
+ import_path48.default.join(home, ".ssh", "id_ed25519"),
24332
+ import_path48.default.join(home, ".aws", "credentials"),
24333
+ import_path48.default.join(home, ".config", "gcloud", "application_default_credentials.json")
24154
24334
  ];
24155
24335
  }
24156
24336
  function checkSecrets(ctx) {
24157
- const home = ctx.home || import_os41.default.homedir();
24337
+ const home = ctx.home || import_os42.default.homedir();
24158
24338
  const findings = [];
24159
24339
  const plaintext = [];
24160
24340
  const plaintextPaths = [];
@@ -24187,7 +24367,7 @@ function checkSecrets(ctx) {
24187
24367
  const credPaths = [];
24188
24368
  for (const file of credentialMaterial(home)) {
24189
24369
  try {
24190
- if (import_fs46.default.statSync(file).isFile()) {
24370
+ if (import_fs47.default.statSync(file).isFile()) {
24191
24371
  creds.push(displayPath(file, home));
24192
24372
  credPaths.push(file);
24193
24373
  }
@@ -24212,7 +24392,133 @@ function checkSecrets(ctx) {
24212
24392
  }
24213
24393
 
24214
24394
  // src/posture/egress.ts
24395
+ var import_fs48 = __toESM(require("fs"));
24215
24396
  init_config();
24397
+
24398
+ // src/sandbox/templates.ts
24399
+ var AGENT_NPM_PACKAGE = {
24400
+ claude: "@anthropic-ai/claude-code",
24401
+ codex: "@openai/codex"
24402
+ };
24403
+ function pinnedNode9Version(hostVersion) {
24404
+ return hostVersion && /^\d+\.\d+\.\d+$/.test(hostVersion) ? hostVersion : "latest";
24405
+ }
24406
+ var AGENT_BIN = {
24407
+ claude: "claude",
24408
+ codex: "codex"
24409
+ };
24410
+ var RUN_AS_USER = "agent";
24411
+ var ALLOWED_DOMAINS_PATH = "/etc/node9-sandbox/allowed-domains.txt";
24412
+ function renderDockerfile(config, node9Version2) {
24413
+ const agentPkg = AGENT_NPM_PACKAGE[config.agent];
24414
+ return `# Auto-generated by node9 sandbox. Do not edit by hand.
24415
+ FROM node:22-bookworm
24416
+
24417
+ ENV DEBIAN_FRONTEND=noninteractive
24418
+
24419
+ # Wall + base tooling (iptables/ipset/dig/gosu) \u2014 ported from Isag.
24420
+ RUN apt-get update && apt-get install -y --no-install-recommends \\
24421
+ ca-certificates curl git gosu iproute2 ipset iptables dnsutils jq \\
24422
+ && rm -rf /var/lib/apt/lists/*
24423
+
24424
+ # The worker (agent CLI).
24425
+ RUN npm install -g ${agentPkg}
24426
+
24427
+ # The guard (node9), pinned to the host version.
24428
+ RUN npm install -g node9-ai@${node9Version2}
24429
+
24430
+ # Non-root runtime user at uid 1000 (matches the typical single-user host so the
24431
+ # mounted ~/.claude / ~/.codex / project are read/writable). The node base image
24432
+ # already claims uid 1000 for the 'node' user \u2014 free it first (cf. Isag/ubuntu).
24433
+ RUN userdel -r node 2>/dev/null || true; \\
24434
+ userdel -r ubuntu 2>/dev/null || true; \\
24435
+ useradd --create-home --uid 1000 --shell /bin/bash ${RUN_AS_USER}
24436
+
24437
+ # Wire the agent's node9 hooks into the runtime user's home (build-time, static).
24438
+ RUN gosu ${RUN_AS_USER} node9 agents add ${config.agent} || true
24439
+
24440
+ RUN mkdir -p /workspace /etc/node9-sandbox \\
24441
+ && chown ${RUN_AS_USER}:${RUN_AS_USER} /workspace
24442
+
24443
+ COPY entrypoint.sh /usr/local/bin/entrypoint.sh
24444
+ RUN chmod +x /usr/local/bin/entrypoint.sh
24445
+ ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
24446
+ `;
24447
+ }
24448
+ function renderEntrypoint(config) {
24449
+ const agentBin = AGENT_BIN[config.agent];
24450
+ return `#!/usr/bin/env bash
24451
+ # Auto-generated by node9 sandbox. Seals the egress wall (root), then drops to the
24452
+ # non-root agent which starts the node9 daemon + execs the agent.
24453
+ set -Eeuo pipefail
24454
+
24455
+ DOMAINS_FILE="${ALLOWED_DOMAINS_PATH}"
24456
+ RUN_AS_USER="${RUN_AS_USER}"
24457
+
24458
+ [[ -s "$DOMAINS_FILE" ]] || { echo "entrypoint: missing/empty $DOMAINS_FILE" >&2; exit 1; }
24459
+
24460
+ # Own the mounted node9 data dir so the agent user can write audit there.
24461
+ mkdir -p "/home/$RUN_AS_USER/.node9"
24462
+ chown -R "$RUN_AS_USER:$RUN_AS_USER" "/home/$RUN_AS_USER/.node9" || true
24463
+
24464
+ # \u2500\u2500 Resolve the allowlist \u2192 ipset (union of every resolver, like Isag) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
24465
+ mapfile -t LOCAL_DNS < <(awk '/^nameserver / {print $2}' /etc/resolv.conf)
24466
+ [[ \${#LOCAL_DNS[@]} -gt 0 ]] || { echo "entrypoint: no resolvers in /etc/resolv.conf" >&2; exit 1; }
24467
+
24468
+ ipset create node9_allowed hash:ip family inet -exist
24469
+ ipset flush node9_allowed
24470
+
24471
+ while IFS= read -r domain; do
24472
+ [[ -n "$domain" ]] || continue
24473
+ found=0
24474
+ # union the local resolver + each upstream so CDN/anycast IP rotation is covered
24475
+ for ip in $(getent ahostsv4 "$domain" 2>/dev/null | awk '{print $1}' | sort -u); do
24476
+ ipset add node9_allowed "$ip" -exist; found=1
24477
+ done
24478
+ for r in "\${LOCAL_DNS[@]}"; do
24479
+ for ip in $(dig +short +time=2 +tries=1 @"$r" A "$domain" 2>/dev/null | awk '/^[0-9.]+$/'); do
24480
+ ipset add node9_allowed "$ip" -exist; found=1
24481
+ done
24482
+ done
24483
+ [[ $found -eq 1 ]] || { echo "entrypoint: failed to resolve $domain" >&2; exit 1; }
24484
+ echo "entrypoint: allowed $domain"
24485
+ done < "$DOMAINS_FILE"
24486
+
24487
+ # \u2500\u2500 Seal iptables: deny-by-default except lo, established, DNS, the allowlist \u2500\u2500\u2500\u2500
24488
+ echo "entrypoint: sealing firewall..."
24489
+ iptables -F; iptables -X
24490
+ iptables -P INPUT DROP
24491
+ iptables -P FORWARD DROP
24492
+ iptables -P OUTPUT DROP
24493
+ iptables -A INPUT -i lo -j ACCEPT
24494
+ iptables -A OUTPUT -o lo -j ACCEPT
24495
+ iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
24496
+ iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
24497
+ for r in "\${LOCAL_DNS[@]}"; do
24498
+ iptables -A OUTPUT -p udp -d "$r" --dport 53 -j ACCEPT
24499
+ iptables -A OUTPUT -p tcp -d "$r" --dport 53 -j ACCEPT
24500
+ done
24501
+ iptables -A OUTPUT -m set --match-set node9_allowed dst -j ACCEPT
24502
+
24503
+ # \u2500\u2500 Drop to the agent: start the node9 daemon (as the user), then exec the agent \u2500
24504
+ echo "entrypoint: starting node9 + ${agentBin} as $RUN_AS_USER"
24505
+ exec gosu "$RUN_AS_USER" bash -lc '
24506
+ set -e
24507
+ node9 daemon --background >/dev/null 2>&1 || true
24508
+ cd /workspace
24509
+ exec ${agentBin} "$@"
24510
+ ' -- "$@"
24511
+ `;
24512
+ }
24513
+
24514
+ // src/posture/egress.ts
24515
+ function sandboxEgressWallActive() {
24516
+ try {
24517
+ return import_fs48.default.existsSync(ALLOWED_DOMAINS_PATH);
24518
+ } catch {
24519
+ return false;
24520
+ }
24521
+ }
24216
24522
  function evaluateEgressConfig(egress) {
24217
24523
  if (egress.enabled && egress.mode === "block") {
24218
24524
  return {
@@ -24262,6 +24568,21 @@ function evaluateEgressConfig(egress) {
24262
24568
  };
24263
24569
  }
24264
24570
  function checkEgress(ctx) {
24571
+ if (sandboxEgressWallActive()) {
24572
+ return [
24573
+ {
24574
+ category: "Egress",
24575
+ severity: "advisory",
24576
+ title: "Egress is hard-blocked by the sandbox kernel wall",
24577
+ what: "Outbound is deny-by-default at the kernel; only the allowlist is reachable.",
24578
+ why: "The sandbox seals egress with an ipset/iptables wall before the agent starts.",
24579
+ who: "Even a compromised agent can only reach the allowlisted hosts.",
24580
+ owner: "node9",
24581
+ detail: [],
24582
+ coverage: { state: "covered", level: "block", via: "sandbox egress wall" }
24583
+ }
24584
+ ];
24585
+ }
24265
24586
  const config = getConfig(ctx.cwd);
24266
24587
  const egress = config.policy.egress;
24267
24588
  return [evaluateEgressConfig({ enabled: egress.enabled, mode: egress.mode })];
@@ -24302,26 +24623,26 @@ async function checkGate(ctx) {
24302
24623
  }
24303
24624
 
24304
24625
  // src/posture/supply-chain.ts
24305
- var import_fs47 = __toESM(require("fs"));
24306
- var import_os42 = __toESM(require("os"));
24307
- var import_path48 = __toESM(require("path"));
24626
+ var import_fs49 = __toESM(require("fs"));
24627
+ var import_os43 = __toESM(require("os"));
24628
+ var import_path49 = __toESM(require("path"));
24308
24629
  var import_smol_toml3 = require("smol-toml");
24309
24630
  init_provenance();
24310
24631
  var PACKAGE_RUNNERS = /* @__PURE__ */ new Set(["npx", "pnpx", "bunx", "dlx", "yarn", "pnpm", "bun"]);
24311
24632
  function isNode9Managed(command, args = []) {
24312
24633
  if (!command) return false;
24313
- if (import_path48.default.basename(command).toLowerCase() === "node9") return true;
24314
- if (PACKAGE_RUNNERS.has(import_path48.default.basename(command).toLowerCase())) {
24315
- return args.some((a) => a === "node9" || import_path48.default.basename(a).toLowerCase() === "node9");
24634
+ if (import_path49.default.basename(command).toLowerCase() === "node9") return true;
24635
+ if (PACKAGE_RUNNERS.has(import_path49.default.basename(command).toLowerCase())) {
24636
+ return args.some((a) => a === "node9" || import_path49.default.basename(a).toLowerCase() === "node9");
24316
24637
  }
24317
24638
  return false;
24318
24639
  }
24319
24640
  var MAX_CONFIG_BYTES = 10 * 1024 * 1024;
24320
24641
  function readServers(file, format, agent) {
24321
24642
  try {
24322
- const stat = import_fs47.default.statSync(file);
24643
+ const stat = import_fs49.default.statSync(file);
24323
24644
  if (!stat.isFile() || stat.size > MAX_CONFIG_BYTES) return [];
24324
- const text = import_fs47.default.readFileSync(file, "utf8");
24645
+ const text = import_fs49.default.readFileSync(file, "utf8");
24325
24646
  const map = format === "toml" ? (0, import_smol_toml3.parse)(text)?.mcp_servers : JSON.parse(text)?.mcpServers;
24326
24647
  if (!map || typeof map !== "object") return [];
24327
24648
  return Object.entries(map).map(([name, v]) => ({
@@ -24335,7 +24656,7 @@ function readServers(file, format, agent) {
24335
24656
  }
24336
24657
  }
24337
24658
  function checkSupplyChain(ctx) {
24338
- const home = ctx.home || import_os42.default.homedir();
24659
+ const home = ctx.home || import_os43.default.homedir();
24339
24660
  const servers = [];
24340
24661
  for (const spec of AGENT_SPECS) {
24341
24662
  if (!spec.mcpFile) continue;
@@ -24417,11 +24738,12 @@ async function checkPrivilege(ctx) {
24417
24738
  }
24418
24739
 
24419
24740
  // src/posture/containment.ts
24420
- var import_fs48 = __toESM(require("fs"));
24741
+ var import_fs50 = __toESM(require("fs"));
24742
+ var ISOLATION_WEIGHT = 12;
24421
24743
  function inContainer() {
24422
- if (import_fs48.default.existsSync("/.dockerenv") || import_fs48.default.existsSync("/run/.containerenv")) return true;
24744
+ if (import_fs50.default.existsSync("/.dockerenv") || import_fs50.default.existsSync("/run/.containerenv")) return true;
24423
24745
  try {
24424
- const cgroup = import_fs48.default.readFileSync("/proc/1/cgroup", "utf8");
24746
+ const cgroup = import_fs50.default.readFileSync("/proc/1/cgroup", "utf8");
24425
24747
  if (/docker|kubepods|containerd|lxc|libpod/.test(cgroup)) return true;
24426
24748
  } catch {
24427
24749
  }
@@ -24440,14 +24762,28 @@ function checkContainment(_ctx) {
24440
24762
  detail: [],
24441
24763
  owner: "os",
24442
24764
  node9Reduces: true,
24443
- fix: "node9 can shrink the blast radius without a container \u2014 you keep every tool:\n \u2022 node9 shield enable project-jail \u2014 block credential reads\n \u2022 node9 egress lock \u2014 block data exfil\nA container/VM adds full isolation, but you lose host access.",
24444
- coverageProbe: { kind: "cantFix" }
24765
+ // The single biggest hardening gap, and node9 now fully remedies it
24766
+ // (`node9 sandbox run`). Deducts while open; closing it is the headline
24767
+ // payoff. No coverageProbe → stays OPEN (scored) until adopted; live
24768
+ // partial-credit for the lighter shield path is a fast-follow.
24769
+ scoreWeight: ISOLATION_WEIGHT,
24770
+ gain: "jailed container \xB7 kernel egress wall \xB7 scoped mounts \xB7 governed inside",
24771
+ cost: "the agent works inside /workspace, not your live host",
24772
+ fix: `Two ways to shrink the blast radius \u2014 pick by how much flexibility you need:
24773
+ Strongest \u2014 jail it (closes this gap, +${ISOLATION_WEIGHT}):
24774
+ \u2022 node9 sandbox run <agent>
24775
+ Lighter \u2014 harden in place, keep full host access (about +${Math.round(
24776
+ ISOLATION_WEIGHT / 2
24777
+ )}):
24778
+ \u2022 node9 shield enable project-jail \u2014 block stray credential reads
24779
+ \u2022 node9 egress lock \u2014 block data exfil`
24445
24780
  }
24446
24781
  ];
24447
24782
  }
24448
24783
 
24449
24784
  // src/posture/inbound.ts
24450
- var import_fs49 = __toESM(require("fs"));
24785
+ var import_fs51 = __toESM(require("fs"));
24786
+ var DB_EXPOSURE_WEIGHT = 4;
24451
24787
  var KNOWN_SERVICE_PORTS = {
24452
24788
  5432: "PostgreSQL",
24453
24789
  6379: "Redis",
@@ -24533,7 +24869,7 @@ function collectListeners() {
24533
24869
  const byPort = /* @__PURE__ */ new Map();
24534
24870
  for (const file of ["/proc/net/tcp", "/proc/net/tcp6"]) {
24535
24871
  try {
24536
- for (const l of parseListeners(import_fs49.default.readFileSync(file, "utf8"))) {
24872
+ for (const l of parseListeners(import_fs51.default.readFileSync(file, "utf8"))) {
24537
24873
  if (!byPort.has(l.port)) byPort.set(l.port, l);
24538
24874
  }
24539
24875
  } catch {
@@ -24545,11 +24881,11 @@ function readProc(pid) {
24545
24881
  let comm = "unknown";
24546
24882
  let cmdline = "";
24547
24883
  try {
24548
- comm = import_fs49.default.readFileSync(`/proc/${pid}/comm`, "utf8").trim().replace(/-MainThread$/, "") || "unknown";
24884
+ comm = import_fs51.default.readFileSync(`/proc/${pid}/comm`, "utf8").trim().replace(/-MainThread$/, "") || "unknown";
24549
24885
  } catch {
24550
24886
  }
24551
24887
  try {
24552
- cmdline = import_fs49.default.readFileSync(`/proc/${pid}/cmdline`).toString().replace(/\0/g, " ").trim();
24888
+ cmdline = import_fs51.default.readFileSync(`/proc/${pid}/cmdline`).toString().replace(/\0/g, " ").trim();
24553
24889
  } catch {
24554
24890
  }
24555
24891
  return { comm, cmdline };
@@ -24559,21 +24895,21 @@ function resolveProcesses(inodes) {
24559
24895
  if (inodes.size === 0) return map;
24560
24896
  let pids;
24561
24897
  try {
24562
- pids = import_fs49.default.readdirSync("/proc").filter((d) => /^\d+$/.test(d));
24898
+ pids = import_fs51.default.readdirSync("/proc").filter((d) => /^\d+$/.test(d));
24563
24899
  } catch {
24564
24900
  return map;
24565
24901
  }
24566
24902
  for (const pid of pids) {
24567
24903
  let fds;
24568
24904
  try {
24569
- fds = import_fs49.default.readdirSync(`/proc/${pid}/fd`);
24905
+ fds = import_fs51.default.readdirSync(`/proc/${pid}/fd`);
24570
24906
  } catch {
24571
24907
  continue;
24572
24908
  }
24573
24909
  for (const fd of fds) {
24574
24910
  let link;
24575
24911
  try {
24576
- link = import_fs49.default.readlinkSync(`/proc/${pid}/fd/${fd}`);
24912
+ link = import_fs51.default.readlinkSync(`/proc/${pid}/fd/${fd}`);
24577
24913
  } catch {
24578
24914
  continue;
24579
24915
  }
@@ -24628,18 +24964,25 @@ function checkInbound(ctx) {
24628
24964
  // Only when node9 actually has a shield for an exposed service — otherwise
24629
24965
  // (bare dev servers) it stays purely the user's to rebind.
24630
24966
  node9Reduces: reduces,
24631
- fix,
24632
- coverageProbe: { kind: "cantFix" }
24967
+ // When a db-shield applies this is real, node9-addressable hardening → it
24968
+ // scores (and stays OPEN, no cantFix probe). Bare dev servers node9 can't
24969
+ // touch stay can't-fix / your-part / unscored.
24970
+ ...reduces ? {
24971
+ scoreWeight: DB_EXPOSURE_WEIGHT,
24972
+ gain: "blocks DROP TABLE / TRUNCATE / FLUSHALL on the exposed DB",
24973
+ cost: "you confirm legit destructive migrations"
24974
+ } : { coverageProbe: { kind: "cantFix" } },
24975
+ fix
24633
24976
  });
24634
24977
  }
24635
24978
  return findings;
24636
24979
  }
24637
24980
 
24638
24981
  // src/posture/coverage.ts
24639
- var import_os43 = __toESM(require("os"));
24982
+ var import_os44 = __toESM(require("os"));
24640
24983
  init_config();
24641
24984
  function checkCoverage(ctx) {
24642
- const home = ctx.home || import_os43.default.homedir();
24985
+ const home = ctx.home || import_os44.default.homedir();
24643
24986
  const findings = [];
24644
24987
  const protectedAgents = getAgentWiring(home).filter((r) => r.isProtected);
24645
24988
  if (protectedAgents.length === 0) {
@@ -24679,16 +25022,20 @@ function scorePosture(findings, checksRun) {
24679
25022
  const open = findings.filter(
24680
25023
  (f) => f.coverage?.state !== "covered" && f.coverage?.state !== "cant-fix"
24681
25024
  );
24682
- const count = (sev) => open.filter((f) => f.severity === sev).length;
24683
- return computeSecurityScore({
25025
+ const count = (sev) => open.filter((f) => f.severity === sev && !f.scoreWeight).length;
25026
+ const base = computeSecurityScore({
24684
25027
  critical: count("critical"),
24685
25028
  high: count("high"),
24686
25029
  medium: count("medium"),
24687
- // Denominator = number of checks evaluated. With computeSecurityScore's
24688
- // caps this makes any critical → critical tier, any high → at-risk, and a
24689
- // fully clean run (0 findings, checksRun > 0) → 100/good.
24690
25030
  total: Math.max(checksRun, 1)
24691
25031
  });
25032
+ const headroom = open.reduce((sum, f) => sum + (f.scoreWeight ?? 0), 0);
25033
+ const score = Math.max(0, base.score - headroom);
25034
+ const tier = score >= 80 ? "good" : score >= 50 ? "at-risk" : "critical";
25035
+ return { score, tier };
25036
+ }
25037
+ function openHeadroom(findings) {
25038
+ return findings.filter((f) => f.coverage?.state !== "covered" && f.coverage?.state !== "cant-fix").reduce((sum, f) => sum + (f.scoreWeight ?? 0), 0);
24692
25039
  }
24693
25040
 
24694
25041
  // src/posture/headline.ts
@@ -24843,7 +25190,7 @@ async function runChecks(checks, ctx) {
24843
25190
  }
24844
25191
  async function runPosture(opts = {}) {
24845
25192
  const ctx = {
24846
- home: opts.home ?? import_os44.default.homedir(),
25193
+ home: opts.home ?? import_os45.default.homedir(),
24847
25194
  cwd: opts.cwd ?? process.cwd(),
24848
25195
  agent: opts.agent
24849
25196
  };
@@ -24898,9 +25245,10 @@ var LABEL_WIDTH = 14;
24898
25245
  function label(category) {
24899
25246
  return import_chalk24.default.bold(category.padEnd(Math.max(LABEL_WIDTH, category.length + 1)));
24900
25247
  }
24901
- function renderFinding(f) {
25248
+ function renderFinding(f, showWeight = false) {
24902
25249
  const lines = [];
24903
- lines.push(` ${ICON[f.severity]} ${label(f.category)}${f.title}`);
25250
+ const wt = showWeight && f.scoreWeight ? import_chalk24.default.cyan.bold(`+${f.scoreWeight} `) : "";
25251
+ lines.push(` ${ICON[f.severity]} ${label(f.category)}${wt}${f.title}`);
24904
25252
  const indent = " ".repeat(2 + 3 + LABEL_WIDTH);
24905
25253
  const width = 80 - indent.length;
24906
25254
  for (const s of [f.what, f.why, f.who]) {
@@ -24916,6 +25264,16 @@ function renderFinding(f) {
24916
25264
  }
24917
25265
  }
24918
25266
  }
25267
+ const tradeoff = [
25268
+ [f.gain, "gain: ", import_chalk24.default.green],
25269
+ [f.cost, "cost: ", import_chalk24.default.yellow]
25270
+ ];
25271
+ for (const [text, lbl, color2] of tradeoff) {
25272
+ if (!text) continue;
25273
+ wrap(text, width - 6).forEach((l, i) => {
25274
+ lines.push(indent + (i === 0 ? color2(lbl) : " ") + import_chalk24.default.gray(l));
25275
+ });
25276
+ }
24919
25277
  return lines;
24920
25278
  }
24921
25279
  function renderPosture(result) {
@@ -24925,15 +25283,11 @@ function renderPosture(result) {
24925
25283
  lines.push(
24926
25284
  import_chalk24.default.cyan.bold(`\u{1F6E1}\uFE0F Node9 Posture`) + import_chalk24.default.gray(` \u2014 ${result.agent}`) + ` ${import_chalk24.default.bold(`Score: ${result.score}/100`)} (${tier})`
24927
25285
  );
24928
- const advisories = result.findings.filter(
24929
- (f) => f.severity === "advisory" && f.coverage?.state !== "covered"
24930
- ).length;
24931
- if (advisories > 0) {
24932
- const word = advisories === 1 ? "advisory" : "advisories";
24933
- const verb = advisories === 1 ? "doesn't" : "don't";
25286
+ const headroom = openHeadroom(result.findings);
25287
+ if (headroom > 0) {
24934
25288
  lines.push(
24935
25289
  " " + import_chalk24.default.gray(
24936
- `${advisories} ${word} below ${verb} affect the score \u2014 OS-level exposure node9 can't enforce, yours to weigh.`
25290
+ `${headroom} pts of headroom below \u2014 each layer adds security at some cost to flexibility. You choose which to close.`
24937
25291
  )
24938
25292
  );
24939
25293
  }
@@ -24949,7 +25303,7 @@ function renderPosture(result) {
24949
25303
  const covered = result.findings.filter((f) => f.coverage?.state === "covered");
24950
25304
  const open = result.findings.filter((f) => f.coverage?.state !== "covered");
24951
25305
  if (covered.length > 0) {
24952
- lines.push(" " + import_chalk24.default.green("\u{1F7E2} node9 is already protecting you"));
25306
+ lines.push(" " + import_chalk24.default.green("\u{1F7E2} ON NOW \u2014 node9 is enforcing these (your floor)"));
24953
25307
  for (const f of covered) {
24954
25308
  const gated = f.coverage?.level === "review" ? "approval-gating" : "blocking";
24955
25309
  const via = f.coverage?.via ?? "node9";
@@ -24964,18 +25318,16 @@ function renderPosture(result) {
24964
25318
  const osOpen = open.filter((f) => f.owner !== "node9" && !f.node9Reduces);
24965
25319
  if (node9Open.length > 0) {
24966
25320
  lines.push(" " + import_chalk24.default.cyan.bold("\u{1F527} node9 can fix these \u2014 run the command"));
24967
- for (const f of node9Open) lines.push(...renderFinding(f));
25321
+ for (const f of node9Open) lines.push(...renderFinding(f, true));
24968
25322
  }
24969
25323
  if (reduceOpen.length > 0) {
24970
25324
  if (node9Open.length > 0) lines.push("");
24971
- lines.push(
24972
- " " + import_chalk24.default.yellow.bold("\u{1F512} node9 reduces these \u2014 run the command, the rest is yours")
24973
- );
24974
- for (const f of reduceOpen) lines.push(...renderFinding(f));
25325
+ lines.push(" " + import_chalk24.default.yellow.bold("\u{1F512} AVAILABLE \u2014 turn on to harden (each has a tradeoff)"));
25326
+ for (const f of reduceOpen) lines.push(...renderFinding(f, true));
24975
25327
  }
24976
25328
  if (osOpen.length > 0) {
24977
25329
  if (node9Open.length > 0 || reduceOpen.length > 0) lines.push("");
24978
- lines.push(" " + import_chalk24.default.bold("\u{1F9F1} Only you can fix these \u2014 node9 can't"));
25330
+ lines.push(" " + import_chalk24.default.bold("\u{1F9F1} YOUR PART \u2014 node9 can't fix these (OS-level)"));
24979
25331
  for (const f of osOpen) lines.push(...renderFinding(f));
24980
25332
  }
24981
25333
  for (const cat of result.passedCategories) {
@@ -25030,7 +25382,12 @@ function buildShipBody(result) {
25030
25382
  // The runnable fix / OS action — commands + advice, never a path.
25031
25383
  fix: f.fix,
25032
25384
  // Whose job it is. Default 'os' so the SaaS never falsely claims node9 can fix it.
25033
- owner: f.owner ?? "os"
25385
+ owner: f.owner ?? "os",
25386
+ // Hardening weight + the flexibility tradeoff (generic prose / a number —
25387
+ // no values or paths), so the fleet view can show the same headroom story.
25388
+ scoreWeight: f.scoreWeight,
25389
+ gain: f.gain,
25390
+ cost: f.cost
25034
25391
  }))
25035
25392
  };
25036
25393
  }
@@ -25101,9 +25458,9 @@ function registerPostureCommand(program2) {
25101
25458
 
25102
25459
  // src/cli/commands/egress.ts
25103
25460
  var import_chalk26 = __toESM(require("chalk"));
25104
- var import_fs50 = __toESM(require("fs"));
25105
- var import_os45 = __toESM(require("os"));
25106
- var import_path49 = __toESM(require("path"));
25461
+ var import_fs52 = __toESM(require("fs"));
25462
+ var import_os46 = __toESM(require("os"));
25463
+ var import_path50 = __toESM(require("path"));
25107
25464
  init_config();
25108
25465
  init_dist();
25109
25466
  var DEFAULT_EGRESS = {
@@ -25114,12 +25471,12 @@ var DEFAULT_EGRESS = {
25114
25471
  allowPrivate: true
25115
25472
  };
25116
25473
  function configPath() {
25117
- return import_path49.default.join(import_os45.default.homedir(), ".node9", "config.json");
25474
+ return import_path50.default.join(import_os46.default.homedir(), ".node9", "config.json");
25118
25475
  }
25119
25476
  function readRawConfig() {
25120
25477
  let text;
25121
25478
  try {
25122
- text = import_fs50.default.readFileSync(configPath(), "utf8");
25479
+ text = import_fs52.default.readFileSync(configPath(), "utf8");
25123
25480
  } catch (err2) {
25124
25481
  if (err2.code === "ENOENT") return {};
25125
25482
  throw err2;
@@ -25134,8 +25491,8 @@ function readRawConfig() {
25134
25491
  }
25135
25492
  function writeRawConfig(config) {
25136
25493
  const p = configPath();
25137
- import_fs50.default.mkdirSync(import_path49.default.dirname(p), { recursive: true });
25138
- import_fs50.default.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
25494
+ import_fs52.default.mkdirSync(import_path50.default.dirname(p), { recursive: true });
25495
+ import_fs52.default.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
25139
25496
  }
25140
25497
  function applyEgress(config, change) {
25141
25498
  const policy = config.policy = config.policy ?? {};
@@ -25226,11 +25583,365 @@ function registerEgressCommand(program2) {
25226
25583
  egress.action(showStatus);
25227
25584
  }
25228
25585
 
25229
- // src/cli/commands/sessions.ts
25586
+ // src/cli/commands/sandbox.ts
25230
25587
  var import_chalk27 = __toESM(require("chalk"));
25231
- var import_fs51 = __toESM(require("fs"));
25232
- var import_path50 = __toESM(require("path"));
25233
- var import_os46 = __toESM(require("os"));
25588
+ var import_fs55 = __toESM(require("fs"));
25589
+ var import_path53 = __toESM(require("path"));
25590
+ var import_child_process13 = require("child_process");
25591
+ init_config();
25592
+
25593
+ // src/sandbox/config.ts
25594
+ var import_fs53 = __toESM(require("fs"));
25595
+ var import_path51 = __toESM(require("path"));
25596
+ var import_yaml = require("yaml");
25597
+ var SANDBOX_CONFIG_FILE = "node9.sandbox.yaml";
25598
+ var FORBIDDEN_ENV = /* @__PURE__ */ new Set(["NODE9_API_KEY", "NODE9_API_URL"]);
25599
+ function defaultSandboxConfig(agent) {
25600
+ return {
25601
+ agent,
25602
+ workspace: { mount: ".", target: "/workspace", mode: "rw" },
25603
+ runtime: { engine: "docker", image: "node9-sandbox:local", rebuild: "auto" },
25604
+ outbound: {
25605
+ mode: "block",
25606
+ allow: agent === "codex" ? ["api.openai.com", "api.github.com", "github.com", "registry.npmjs.org"] : ["api.anthropic.com", "api.github.com", "github.com", "registry.npmjs.org"]
25607
+ },
25608
+ inbound: { expose: [] },
25609
+ // Provider key only — NODE9_API_KEY intentionally absent (fix #1).
25610
+ env: { pass: [agent === "codex" ? "OPENAI_API_KEY" : "ANTHROPIC_API_KEY"] },
25611
+ // Terminal-only approval in the MVP; cloud/native/browser off (fix #1).
25612
+ node9: {
25613
+ approvals: { terminal: true, native: false, browser: false, cloud: false },
25614
+ // Mount the agent's OAuth/creds dir so it can authenticate in the box.
25615
+ mountAgentCredentials: true
25616
+ }
25617
+ };
25618
+ }
25619
+ function asStringArray(v) {
25620
+ return Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
25621
+ }
25622
+ function mergeSandboxConfig(raw, fallbackAgent) {
25623
+ const r = raw && typeof raw === "object" ? raw : {};
25624
+ const agent = typeof r.agent === "string" ? r.agent : fallbackAgent;
25625
+ if (agent !== "claude" && agent !== "codex") {
25626
+ throw new Error(`sandbox: unsupported agent "${String(agent)}" (use claude or codex)`);
25627
+ }
25628
+ const d = defaultSandboxConfig(agent);
25629
+ const ws = r.workspace ?? {};
25630
+ const rt = r.runtime ?? {};
25631
+ const out = r.outbound ?? {};
25632
+ const inb = r.inbound ?? {};
25633
+ const env = r.env ?? {};
25634
+ const n9 = r.node9 ?? {};
25635
+ const appr = n9.approvals ?? {};
25636
+ const pass = asStringArray(env.pass).filter((k) => !FORBIDDEN_ENV.has(k));
25637
+ return {
25638
+ agent,
25639
+ workspace: {
25640
+ mount: typeof ws.mount === "string" ? ws.mount : d.workspace.mount,
25641
+ target: typeof ws.target === "string" ? ws.target : d.workspace.target,
25642
+ mode: ws.mode === "ro" ? "ro" : "rw"
25643
+ },
25644
+ runtime: {
25645
+ engine: rt.engine === "podman" ? "podman" : "docker",
25646
+ image: typeof rt.image === "string" ? rt.image : d.runtime.image,
25647
+ rebuild: rt.rebuild === "never" || rt.rebuild === "always" ? rt.rebuild : d.runtime.rebuild
25648
+ },
25649
+ outbound: { mode: "block", allow: out.allow ? asStringArray(out.allow) : d.outbound.allow },
25650
+ inbound: { expose: inb.expose ? asStringArray(inb.expose) : d.inbound.expose },
25651
+ env: { pass: env.pass ? pass : d.env.pass },
25652
+ node9: {
25653
+ approvals: {
25654
+ terminal: appr.terminal !== false,
25655
+ native: appr.native === true,
25656
+ browser: appr.browser === true,
25657
+ cloud: appr.cloud === true
25658
+ },
25659
+ mountAgentCredentials: n9.mountAgentCredentials !== false
25660
+ }
25661
+ };
25662
+ }
25663
+ function scaffoldSandboxYaml(agent) {
25664
+ const header = "# node9.sandbox.yaml \u2014 sandbox TOPOLOGY (what the agent may touch).\n# Security policy (shields / egress rules / approvers) lives in ~/.node9/config.json\n# and applies to both native and sandbox. NODE9_API_KEY is never passed into the box.\n\n";
25665
+ return header + (0, import_yaml.stringify)(defaultSandboxConfig(agent));
25666
+ }
25667
+ function sandboxConfigPath(cwd = process.cwd()) {
25668
+ return import_path51.default.join(cwd, SANDBOX_CONFIG_FILE);
25669
+ }
25670
+ function loadSandboxConfig(cwd = process.cwd(), fallbackAgent = "claude") {
25671
+ const p = sandboxConfigPath(cwd);
25672
+ if (!import_fs53.default.existsSync(p)) {
25673
+ throw new Error(`sandbox: ${SANDBOX_CONFIG_FILE} not found \u2014 run \`node9 sandbox new\` first.`);
25674
+ }
25675
+ let raw;
25676
+ try {
25677
+ raw = (0, import_yaml.parse)(import_fs53.default.readFileSync(p, "utf-8"));
25678
+ } catch (err2) {
25679
+ throw new Error(
25680
+ `sandbox: ${SANDBOX_CONFIG_FILE} is not valid YAML \u2014 ${err2.message}`
25681
+ );
25682
+ }
25683
+ return mergeSandboxConfig(raw, fallbackAgent);
25684
+ }
25685
+
25686
+ // src/sandbox/firewall.ts
25687
+ var AGENT_PROVIDER_HOST = {
25688
+ claude: ["api.anthropic.com"],
25689
+ codex: ["api.openai.com"]
25690
+ };
25691
+ var NODE9_SAAS_HOSTS = ["api.node9.ai", "app.node9.ai", "node9.ai"];
25692
+ function isValidHost2(host) {
25693
+ if (typeof host !== "string") return false;
25694
+ const h = host.trim().toLowerCase();
25695
+ if (!h || h.length > 253) return false;
25696
+ if (/[\s/:@?#\\]/.test(h)) return false;
25697
+ return /^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(
25698
+ h
25699
+ );
25700
+ }
25701
+ function compileAllowlist(input) {
25702
+ const norm = (h) => h.trim().toLowerCase();
25703
+ const denySet = /* @__PURE__ */ new Set([...input.configDeny.map(norm), ...NODE9_SAAS_HOSTS.map(norm)]);
25704
+ const candidates = [
25705
+ ...AGENT_PROVIDER_HOST[input.agent],
25706
+ ...input.sandboxAllow,
25707
+ ...input.configAllow
25708
+ ].map(norm);
25709
+ const allow = /* @__PURE__ */ new Set();
25710
+ const rejected = [];
25711
+ const denied = [];
25712
+ for (const host of candidates) {
25713
+ if (!host) continue;
25714
+ if (!isValidHost2(host)) {
25715
+ if (!rejected.includes(host)) rejected.push(host);
25716
+ continue;
25717
+ }
25718
+ if (denySet.has(host)) {
25719
+ if (!denied.includes(host)) denied.push(host);
25720
+ continue;
25721
+ }
25722
+ allow.add(host);
25723
+ }
25724
+ return {
25725
+ allow: [...allow].sort(),
25726
+ rejected: rejected.sort(),
25727
+ denied: denied.sort()
25728
+ };
25729
+ }
25730
+
25731
+ // src/sandbox/runtime.ts
25732
+ var import_fs54 = __toESM(require("fs"));
25733
+ var import_os47 = __toESM(require("os"));
25734
+ var import_path52 = __toESM(require("path"));
25735
+ var import_crypto13 = __toESM(require("crypto"));
25736
+ var import_child_process12 = require("child_process");
25737
+ function sandboxDataDir(cwd = process.cwd()) {
25738
+ return import_path52.default.join(cwd, ".node9", "sandbox", "data");
25739
+ }
25740
+ function detectEngine(engine) {
25741
+ const r = (0, import_child_process12.spawnSync)(engine, ["--version"], { encoding: "utf-8" });
25742
+ if (r.status === 0 && typeof r.stdout === "string") {
25743
+ return { available: true, version: r.stdout.trim() };
25744
+ }
25745
+ return { available: false };
25746
+ }
25747
+ function agentCredentialsMount(agent) {
25748
+ const rel = agent === "codex" ? ".codex/auth.json" : ".claude/.credentials.json";
25749
+ return { hostPath: import_path52.default.join(import_os47.default.homedir(), rel), target: `/home/${RUN_AS_USER}/${rel}` };
25750
+ }
25751
+ function buildRunArgs(opts) {
25752
+ const { config, workspaceHostPath, dataHostPath, allowlistHostPath, agentArgs } = opts;
25753
+ const args = ["run", "--rm", "-it", "--cap-add=NET_ADMIN"];
25754
+ args.push("-v", `${workspaceHostPath}:${config.workspace.target}:${config.workspace.mode}`);
25755
+ args.push("-v", `${dataHostPath}:/home/${RUN_AS_USER}/.node9`);
25756
+ args.push("-v", `${allowlistHostPath}:${ALLOWED_DOMAINS_PATH}:ro`);
25757
+ if (config.node9.mountAgentCredentials) {
25758
+ const creds = agentCredentialsMount(config.agent);
25759
+ if (import_fs54.default.existsSync(creds.hostPath)) {
25760
+ args.push("-v", `${creds.hostPath}:${creds.target}`);
25761
+ }
25762
+ }
25763
+ for (const key of config.env.pass) {
25764
+ if (process.env[key] !== void 0) args.push("-e", key);
25765
+ }
25766
+ for (const port of config.inbound.expose) {
25767
+ args.push("-p", port);
25768
+ }
25769
+ args.push(config.runtime.image);
25770
+ if (agentArgs.length) args.push(...agentArgs);
25771
+ return args;
25772
+ }
25773
+ function imageContentHash(dockerfile, entrypoint) {
25774
+ return import_crypto13.default.createHash("sha256").update(dockerfile).update("\0").update(entrypoint).digest("hex").slice(0, 16);
25775
+ }
25776
+ function sandboxBuildDir(cwd = process.cwd()) {
25777
+ return import_path52.default.join(cwd, ".node9", "sandbox", "build");
25778
+ }
25779
+ function writeBuildContext(cwd, dockerfile, entrypoint) {
25780
+ const dir = sandboxBuildDir(cwd);
25781
+ import_fs54.default.mkdirSync(dir, { recursive: true });
25782
+ import_fs54.default.writeFileSync(import_path52.default.join(dir, "Dockerfile"), dockerfile);
25783
+ import_fs54.default.writeFileSync(import_path52.default.join(dir, "entrypoint.sh"), entrypoint);
25784
+ return dir;
25785
+ }
25786
+ function writeAllowlist(cwd, hosts) {
25787
+ const dir = import_path52.default.join(cwd, ".node9", "sandbox");
25788
+ import_fs54.default.mkdirSync(dir, { recursive: true });
25789
+ const p = import_path52.default.join(dir, "allowed-domains.txt");
25790
+ import_fs54.default.writeFileSync(p, hosts.join("\n") + "\n");
25791
+ return p;
25792
+ }
25793
+ function resolveHomePath(p) {
25794
+ return p.startsWith("~") ? import_path52.default.join(import_os47.default.homedir(), p.slice(1)) : import_path52.default.resolve(p);
25795
+ }
25796
+
25797
+ // src/cli/commands/sandbox.ts
25798
+ function seedDataDirConfig(dataDir, sandbox) {
25799
+ import_fs55.default.mkdirSync(dataDir, { recursive: true });
25800
+ const configPath2 = import_path53.default.join(dataDir, "config.json");
25801
+ const seed = {
25802
+ settings: {
25803
+ approvers: {
25804
+ terminal: sandbox.node9.approvals.terminal,
25805
+ native: false,
25806
+ browser: false,
25807
+ cloud: false
25808
+ }
25809
+ }
25810
+ };
25811
+ import_fs55.default.writeFileSync(configPath2, JSON.stringify(seed, null, 2), { mode: 384 });
25812
+ }
25813
+ function registerSandboxCommand(program2, version2) {
25814
+ const node9Version2 = pinnedNode9Version(version2);
25815
+ const cmd = program2.command("sandbox").description("Run an agent in a disposable, jailed container \u2014 governed + audited inside");
25816
+ cmd.command("new").description(`Scaffold ${SANDBOX_CONFIG_FILE} in this project`).option("--agent <agent>", "claude (default) or codex", "claude").action((opts) => {
25817
+ const agent = opts.agent === "codex" ? "codex" : "claude";
25818
+ const p = sandboxConfigPath();
25819
+ if (import_fs55.default.existsSync(p)) {
25820
+ console.log(
25821
+ import_chalk27.default.yellow(` ${SANDBOX_CONFIG_FILE} already exists \u2014 leaving it untouched.`)
25822
+ );
25823
+ return;
25824
+ }
25825
+ import_fs55.default.writeFileSync(p, scaffoldSandboxYaml(agent));
25826
+ console.log(
25827
+ import_chalk27.default.green(` \u2713 wrote ${SANDBOX_CONFIG_FILE}`) + import_chalk27.default.dim(` (agent: ${agent})`)
25828
+ );
25829
+ console.log(
25830
+ import_chalk27.default.dim(" Edit it (mounts / allow / expose), then: ") + import_chalk27.default.cyan("node9 sandbox run")
25831
+ );
25832
+ });
25833
+ 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) => {
25834
+ const cwd = process.cwd();
25835
+ const sandbox = loadSandboxConfig(cwd, agentArg || "claude");
25836
+ if (agentArg === "claude" || agentArg === "codex") sandbox.agent = agentArg;
25837
+ const engine = detectEngine(sandbox.runtime.engine);
25838
+ if (!engine.available) {
25839
+ console.error(
25840
+ import_chalk27.default.red(` ${sandbox.runtime.engine} not found.`) + import_chalk27.default.dim(` Install it first \u2014 node9 sandbox needs a container runtime.`)
25841
+ );
25842
+ process.exit(1);
25843
+ }
25844
+ const node9Config = getConfig(cwd);
25845
+ const compiled = compileAllowlist({
25846
+ agent: sandbox.agent,
25847
+ sandboxAllow: sandbox.outbound.allow,
25848
+ configAllow: node9Config.policy.egress.allow,
25849
+ configDeny: node9Config.policy.egress.deny
25850
+ });
25851
+ if (compiled.rejected.length) {
25852
+ console.log(
25853
+ import_chalk27.default.yellow(` \u26A0 ignoring invalid allow hosts: ${compiled.rejected.join(", ")}`)
25854
+ );
25855
+ }
25856
+ if (compiled.denied.length) {
25857
+ console.log(import_chalk27.default.dim(` (denied: ${compiled.denied.join(", ")})`));
25858
+ }
25859
+ const allowlistPath = writeAllowlist(cwd, compiled.allow);
25860
+ const dockerfile = renderDockerfile(sandbox, node9Version2);
25861
+ const entrypoint = renderEntrypoint(sandbox);
25862
+ const buildDir = writeBuildContext(cwd, dockerfile, entrypoint);
25863
+ const hash = imageContentHash(dockerfile, entrypoint);
25864
+ const image = sandbox.runtime.image;
25865
+ const hashFile = import_path53.default.join(sandboxBuildDir(cwd), ".image-hash");
25866
+ const lastHash = import_fs55.default.existsSync(hashFile) ? import_fs55.default.readFileSync(hashFile, "utf-8").trim() : "";
25867
+ const imageExists = (0, import_child_process13.spawnSync)(sandbox.runtime.engine, ["image", "inspect", image], { stdio: "ignore" }).status === 0;
25868
+ const needBuild = sandbox.runtime.rebuild === "always" || !imageExists || sandbox.runtime.rebuild !== "never" && lastHash !== hash;
25869
+ if (needBuild) {
25870
+ console.log(import_chalk27.default.dim(` building ${image} \u2026`));
25871
+ const b = (0, import_child_process13.spawnSync)(sandbox.runtime.engine, ["build", "-t", image, buildDir], {
25872
+ stdio: "inherit"
25873
+ });
25874
+ if (b.status !== 0) {
25875
+ console.error(import_chalk27.default.red(" build failed."));
25876
+ process.exit(b.status ?? 1);
25877
+ }
25878
+ import_fs55.default.writeFileSync(hashFile, hash);
25879
+ }
25880
+ const dataDir = sandboxDataDir(cwd);
25881
+ seedDataDirConfig(dataDir, sandbox);
25882
+ const passthru = command.args.slice(agentArg ? 1 : 0);
25883
+ const runArgs = buildRunArgs({
25884
+ config: sandbox,
25885
+ workspaceHostPath: resolveHomePath(sandbox.workspace.mount),
25886
+ dataHostPath: dataDir,
25887
+ allowlistHostPath: allowlistPath,
25888
+ agentArgs: passthru
25889
+ });
25890
+ if (sandbox.node9.mountAgentCredentials) {
25891
+ const creds = agentCredentialsMount(sandbox.agent);
25892
+ if (import_fs55.default.existsSync(creds.hostPath)) {
25893
+ console.log(import_chalk27.default.dim(` mounting ${creds.hostPath} (agent credentials, rw)`));
25894
+ } else {
25895
+ console.log(
25896
+ import_chalk27.default.yellow(` \u26A0 ${creds.hostPath} not found \u2014 `) + import_chalk27.default.dim(`the agent must auth via an env key in env.pass.`)
25897
+ );
25898
+ }
25899
+ }
25900
+ console.log(
25901
+ import_chalk27.default.green(` \u{1F6E1}\uFE0F ${sandbox.agent} jailed \u2014 ${compiled.allow.length} hosts allowed
25902
+ `)
25903
+ );
25904
+ const r = (0, import_child_process13.spawnSync)(sandbox.runtime.engine, runArgs, { stdio: "inherit" });
25905
+ process.exit(r.status ?? 0);
25906
+ });
25907
+ cmd.command("tail").description("Stream the sandbox's audit log (host-side)").action(() => {
25908
+ const auditPath = import_path53.default.join(sandboxDataDir(), "audit.log");
25909
+ if (!import_fs55.default.existsSync(auditPath)) {
25910
+ console.log(import_chalk27.default.dim(" no sandbox audit yet."));
25911
+ return;
25912
+ }
25913
+ (0, import_child_process13.spawnSync)("tail", ["-f", auditPath], { stdio: "inherit" });
25914
+ });
25915
+ cmd.command("logs").description("Dump the sandbox's audit log").action(() => {
25916
+ const auditPath = import_path53.default.join(sandboxDataDir(), "audit.log");
25917
+ if (!import_fs55.default.existsSync(auditPath)) {
25918
+ console.log(import_chalk27.default.dim(" no sandbox audit yet."));
25919
+ return;
25920
+ }
25921
+ process.stdout.write(import_fs55.default.readFileSync(auditPath, "utf-8"));
25922
+ });
25923
+ cmd.command("clean").description("Remove the sandbox image, build context, and data").action(() => {
25924
+ const cwd = process.cwd();
25925
+ let sandbox = null;
25926
+ try {
25927
+ sandbox = loadSandboxConfig(cwd);
25928
+ } catch {
25929
+ }
25930
+ if (sandbox) {
25931
+ (0, import_child_process13.spawnSync)(sandbox.runtime.engine, ["image", "rm", "-f", sandbox.runtime.image], {
25932
+ stdio: "ignore"
25933
+ });
25934
+ }
25935
+ import_fs55.default.rmSync(import_path53.default.join(cwd, ".node9", "sandbox"), { recursive: true, force: true });
25936
+ console.log(import_chalk27.default.green(" \u2713 sandbox image + build + data removed."));
25937
+ });
25938
+ }
25939
+
25940
+ // src/cli/commands/sessions.ts
25941
+ var import_chalk28 = __toESM(require("chalk"));
25942
+ var import_fs56 = __toESM(require("fs"));
25943
+ var import_path54 = __toESM(require("path"));
25944
+ var import_os48 = __toESM(require("os"));
25234
25945
  init_scan_summary();
25235
25946
  init_litellm();
25236
25947
  init_cost_gemini();
@@ -25251,10 +25962,10 @@ function encodeProjectPath(projectPath) {
25251
25962
  }
25252
25963
  function sessionJsonlPath(projectPath, sessionId) {
25253
25964
  const encoded = encodeProjectPath(projectPath);
25254
- return import_path50.default.join(import_os46.default.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
25965
+ return import_path54.default.join(import_os48.default.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
25255
25966
  }
25256
25967
  function projectLabel(projectPath) {
25257
- return projectPath.replace(import_os46.default.homedir(), "~");
25968
+ return projectPath.replace(import_os48.default.homedir(), "~");
25258
25969
  }
25259
25970
  function parseHistoryLines(lines) {
25260
25971
  const entries = [];
@@ -25323,10 +26034,10 @@ function parseSessionLines(lines) {
25323
26034
  return { toolCalls, costUSD, hasSnapshot, modifiedFiles };
25324
26035
  }
25325
26036
  function loadAuditEntries(auditPath) {
25326
- const aPath = auditPath ?? import_path50.default.join(import_os46.default.homedir(), ".node9", "audit.log");
26037
+ const aPath = auditPath ?? import_path54.default.join(import_os48.default.homedir(), ".node9", "audit.log");
25327
26038
  let raw;
25328
26039
  try {
25329
- raw = import_fs51.default.readFileSync(aPath, "utf-8");
26040
+ raw = import_fs56.default.readFileSync(aPath, "utf-8");
25330
26041
  } catch {
25331
26042
  return [];
25332
26043
  }
@@ -25362,8 +26073,8 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
25362
26073
  return result;
25363
26074
  }
25364
26075
  function buildGeminiSessions(days, allAuditEntries) {
25365
- const tmpDir = import_path50.default.join(import_os46.default.homedir(), ".gemini", "tmp");
25366
- if (!import_fs51.default.existsSync(tmpDir)) return [];
26076
+ const tmpDir = import_path54.default.join(import_os48.default.homedir(), ".gemini", "tmp");
26077
+ if (!import_fs56.default.existsSync(tmpDir)) return [];
25367
26078
  const cutoff = days !== null ? (() => {
25368
26079
  const d = /* @__PURE__ */ new Date();
25369
26080
  d.setDate(d.getDate() - days);
@@ -25372,35 +26083,35 @@ function buildGeminiSessions(days, allAuditEntries) {
25372
26083
  })() : null;
25373
26084
  let slugDirs;
25374
26085
  try {
25375
- slugDirs = import_fs51.default.readdirSync(tmpDir);
26086
+ slugDirs = import_fs56.default.readdirSync(tmpDir);
25376
26087
  } catch {
25377
26088
  return [];
25378
26089
  }
25379
26090
  const summaries = [];
25380
26091
  for (const slug of slugDirs) {
25381
- const slugPath = import_path50.default.join(tmpDir, slug);
26092
+ const slugPath = import_path54.default.join(tmpDir, slug);
25382
26093
  try {
25383
- if (!import_fs51.default.statSync(slugPath).isDirectory()) continue;
26094
+ if (!import_fs56.default.statSync(slugPath).isDirectory()) continue;
25384
26095
  } catch {
25385
26096
  continue;
25386
26097
  }
25387
- let projectRoot = import_path50.default.join(import_os46.default.homedir(), slug);
26098
+ let projectRoot = import_path54.default.join(import_os48.default.homedir(), slug);
25388
26099
  try {
25389
- projectRoot = import_fs51.default.readFileSync(import_path50.default.join(slugPath, ".project_root"), "utf-8").trim();
26100
+ projectRoot = import_fs56.default.readFileSync(import_path54.default.join(slugPath, ".project_root"), "utf-8").trim();
25390
26101
  } catch {
25391
26102
  }
25392
- const chatsDir = import_path50.default.join(slugPath, "chats");
25393
- if (!import_fs51.default.existsSync(chatsDir)) continue;
26103
+ const chatsDir = import_path54.default.join(slugPath, "chats");
26104
+ if (!import_fs56.default.existsSync(chatsDir)) continue;
25394
26105
  let chatFiles;
25395
26106
  try {
25396
- chatFiles = import_fs51.default.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
26107
+ chatFiles = import_fs56.default.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
25397
26108
  } catch {
25398
26109
  continue;
25399
26110
  }
25400
26111
  for (const chatFile of chatFiles) {
25401
26112
  let raw;
25402
26113
  try {
25403
- raw = import_fs51.default.readFileSync(import_path50.default.join(chatsDir, chatFile), "utf-8");
26114
+ raw = import_fs56.default.readFileSync(import_path54.default.join(chatsDir, chatFile), "utf-8");
25404
26115
  } catch {
25405
26116
  continue;
25406
26117
  }
@@ -25480,8 +26191,8 @@ function buildGeminiSessions(days, allAuditEntries) {
25480
26191
  return summaries;
25481
26192
  }
25482
26193
  function buildCodexSessions(days, allAuditEntries) {
25483
- const sessionsBase = import_path50.default.join(import_os46.default.homedir(), ".codex", "sessions");
25484
- if (!import_fs51.default.existsSync(sessionsBase)) return [];
26194
+ const sessionsBase = import_path54.default.join(import_os48.default.homedir(), ".codex", "sessions");
26195
+ if (!import_fs56.default.existsSync(sessionsBase)) return [];
25485
26196
  const cutoff = days !== null ? (() => {
25486
26197
  const d = /* @__PURE__ */ new Date();
25487
26198
  d.setDate(d.getDate() - days);
@@ -25490,29 +26201,29 @@ function buildCodexSessions(days, allAuditEntries) {
25490
26201
  })() : null;
25491
26202
  const jsonlFiles = [];
25492
26203
  try {
25493
- for (const year of import_fs51.default.readdirSync(sessionsBase)) {
25494
- const yearPath = import_path50.default.join(sessionsBase, year);
26204
+ for (const year of import_fs56.default.readdirSync(sessionsBase)) {
26205
+ const yearPath = import_path54.default.join(sessionsBase, year);
25495
26206
  try {
25496
- if (!import_fs51.default.statSync(yearPath).isDirectory()) continue;
26207
+ if (!import_fs56.default.statSync(yearPath).isDirectory()) continue;
25497
26208
  } catch {
25498
26209
  continue;
25499
26210
  }
25500
- for (const month of import_fs51.default.readdirSync(yearPath)) {
25501
- const monthPath = import_path50.default.join(yearPath, month);
26211
+ for (const month of import_fs56.default.readdirSync(yearPath)) {
26212
+ const monthPath = import_path54.default.join(yearPath, month);
25502
26213
  try {
25503
- if (!import_fs51.default.statSync(monthPath).isDirectory()) continue;
26214
+ if (!import_fs56.default.statSync(monthPath).isDirectory()) continue;
25504
26215
  } catch {
25505
26216
  continue;
25506
26217
  }
25507
- for (const day of import_fs51.default.readdirSync(monthPath)) {
25508
- const dayPath = import_path50.default.join(monthPath, day);
26218
+ for (const day of import_fs56.default.readdirSync(monthPath)) {
26219
+ const dayPath = import_path54.default.join(monthPath, day);
25509
26220
  try {
25510
- if (!import_fs51.default.statSync(dayPath).isDirectory()) continue;
26221
+ if (!import_fs56.default.statSync(dayPath).isDirectory()) continue;
25511
26222
  } catch {
25512
26223
  continue;
25513
26224
  }
25514
- for (const file of import_fs51.default.readdirSync(dayPath)) {
25515
- if (file.endsWith(".jsonl")) jsonlFiles.push(import_path50.default.join(dayPath, file));
26225
+ for (const file of import_fs56.default.readdirSync(dayPath)) {
26226
+ if (file.endsWith(".jsonl")) jsonlFiles.push(import_path54.default.join(dayPath, file));
25516
26227
  }
25517
26228
  }
25518
26229
  }
@@ -25524,7 +26235,7 @@ function buildCodexSessions(days, allAuditEntries) {
25524
26235
  for (const filePath of jsonlFiles) {
25525
26236
  let lines;
25526
26237
  try {
25527
- lines = import_fs51.default.readFileSync(filePath, "utf-8").split("\n");
26238
+ lines = import_fs56.default.readFileSync(filePath, "utf-8").split("\n");
25528
26239
  } catch {
25529
26240
  continue;
25530
26241
  }
@@ -25610,10 +26321,10 @@ function buildCodexSessions(days, allAuditEntries) {
25610
26321
  return summaries;
25611
26322
  }
25612
26323
  function buildSessions(days, historyPath) {
25613
- const hPath = historyPath ?? import_path50.default.join(import_os46.default.homedir(), ".claude", "history.jsonl");
26324
+ const hPath = historyPath ?? import_path54.default.join(import_os48.default.homedir(), ".claude", "history.jsonl");
25614
26325
  let historyRaw = "";
25615
26326
  try {
25616
- historyRaw = import_fs51.default.readFileSync(hPath, "utf-8");
26327
+ historyRaw = import_fs56.default.readFileSync(hPath, "utf-8");
25617
26328
  } catch {
25618
26329
  }
25619
26330
  const cutoff = days !== null ? (() => {
@@ -25637,7 +26348,7 @@ function buildSessions(days, historyPath) {
25637
26348
  const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
25638
26349
  let sessionLines = [];
25639
26350
  try {
25640
- sessionLines = import_fs51.default.readFileSync(jsonlFile, "utf-8").split("\n");
26351
+ sessionLines = import_fs56.default.readFileSync(jsonlFile, "utf-8").split("\n");
25641
26352
  } catch {
25642
26353
  }
25643
26354
  const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
@@ -25723,11 +26434,11 @@ function toolInputSummary(tool, input) {
25723
26434
  }
25724
26435
  function toolColor(tool) {
25725
26436
  const t = tool.toLowerCase();
25726
- if (t === "bash" || t === "execute_bash") return import_chalk27.default.red;
25727
- if (t === "write") return import_chalk27.default.green;
25728
- if (t === "edit" || t === "notebookedit") return import_chalk27.default.yellow;
25729
- if (t === "read") return import_chalk27.default.cyan;
25730
- return import_chalk27.default.gray;
26437
+ if (t === "bash" || t === "execute_bash") return import_chalk28.default.red;
26438
+ if (t === "write") return import_chalk28.default.green;
26439
+ if (t === "edit" || t === "notebookedit") return import_chalk28.default.yellow;
26440
+ if (t === "read") return import_chalk28.default.cyan;
26441
+ return import_chalk28.default.gray;
25731
26442
  }
25732
26443
  function barStr2(value, max, width) {
25733
26444
  if (max === 0 || width <= 0) return "\u2591".repeat(width);
@@ -25737,7 +26448,7 @@ function barStr2(value, max, width) {
25737
26448
  function colorBar2(value, max, width) {
25738
26449
  const s = barStr2(value, max, width);
25739
26450
  const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
25740
- return import_chalk27.default.cyan(s.slice(0, filled)) + import_chalk27.default.dim(s.slice(filled));
26451
+ return import_chalk28.default.cyan(s.slice(0, filled)) + import_chalk28.default.dim(s.slice(filled));
25741
26452
  }
25742
26453
  function renderSummary(summaries) {
25743
26454
  const totalTools = summaries.reduce((n, s) => n + s.toolCalls.length, 0);
@@ -25767,45 +26478,45 @@ function renderSummary(summaries) {
25767
26478
  }
25768
26479
  const topProjects = [...projCosts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
25769
26480
  const W = 20;
25770
- console.log(import_chalk27.default.dim(" " + "\u2500".repeat(70)));
26481
+ console.log(import_chalk28.default.dim(" " + "\u2500".repeat(70)));
25771
26482
  console.log(
25772
- " " + import_chalk27.default.bold.white(String(summaries.length).padEnd(4)) + import_chalk27.default.dim("sessions ") + import_chalk27.default.bold.yellow(fmtCost3(totalCost).padEnd(10)) + import_chalk27.default.dim("total ") + import_chalk27.default.bold.white(String(totalTools).padEnd(6)) + import_chalk27.default.dim("tool calls ") + import_chalk27.default.bold.white(String(totalFiles)) + import_chalk27.default.dim(" files modified") + (totalBlocked > 0 ? import_chalk27.default.dim(" ") + import_chalk27.default.red.bold(String(totalBlocked)) + import_chalk27.default.dim(" blocked by node9") : "")
26483
+ " " + import_chalk28.default.bold.white(String(summaries.length).padEnd(4)) + import_chalk28.default.dim("sessions ") + import_chalk28.default.bold.yellow(fmtCost3(totalCost).padEnd(10)) + import_chalk28.default.dim("total ") + import_chalk28.default.bold.white(String(totalTools).padEnd(6)) + import_chalk28.default.dim("tool calls ") + import_chalk28.default.bold.white(String(totalFiles)) + import_chalk28.default.dim(" files modified") + (totalBlocked > 0 ? import_chalk28.default.dim(" ") + import_chalk28.default.red.bold(String(totalBlocked)) + import_chalk28.default.dim(" blocked by node9") : "")
25773
26484
  );
25774
26485
  console.log(
25775
- " " + import_chalk27.default.dim("avg ") + import_chalk27.default.white(fmtCost3(avgCost).padEnd(10)) + import_chalk27.default.dim("/session ") + import_chalk27.default.green(String(snapshots)) + import_chalk27.default.dim(` of ${summaries.length} sessions had snapshots`)
26486
+ " " + import_chalk28.default.dim("avg ") + import_chalk28.default.white(fmtCost3(avgCost).padEnd(10)) + import_chalk28.default.dim("/session ") + import_chalk28.default.green(String(snapshots)) + import_chalk28.default.dim(` of ${summaries.length} sessions had snapshots`)
25776
26487
  );
25777
26488
  console.log("");
25778
- console.log(" " + import_chalk27.default.dim("Tool breakdown:"));
26489
+ console.log(" " + import_chalk28.default.dim("Tool breakdown:"));
25779
26490
  const maxGroup = Math.max(...Object.values(groups));
25780
26491
  for (const [label2, count] of Object.entries(groups)) {
25781
26492
  if (count === 0) continue;
25782
26493
  const pct = totalTools > 0 ? Math.round(count / totalTools * 100) : 0;
25783
26494
  console.log(
25784
- " " + label2.padEnd(6) + " " + colorBar2(count, maxGroup, W) + " " + import_chalk27.default.white(String(count).padStart(4)) + import_chalk27.default.dim(` (${String(pct)}%)`)
26495
+ " " + label2.padEnd(6) + " " + colorBar2(count, maxGroup, W) + " " + import_chalk28.default.white(String(count).padStart(4)) + import_chalk28.default.dim(` (${String(pct)}%)`)
25785
26496
  );
25786
26497
  }
25787
26498
  console.log("");
25788
26499
  if (topProjects.length > 1) {
25789
- console.log(" " + import_chalk27.default.dim("Cost by project:"));
26500
+ console.log(" " + import_chalk28.default.dim("Cost by project:"));
25790
26501
  const maxProjCost = topProjects[0][1];
25791
26502
  for (const [proj, cost] of topProjects) {
25792
26503
  console.log(
25793
- " " + proj.slice(0, 28).padEnd(28) + " " + colorBar2(cost, maxProjCost, W) + " " + import_chalk27.default.yellow(fmtCost3(cost))
26504
+ " " + proj.slice(0, 28).padEnd(28) + " " + colorBar2(cost, maxProjCost, W) + " " + import_chalk28.default.yellow(fmtCost3(cost))
25794
26505
  );
25795
26506
  }
25796
26507
  console.log("");
25797
26508
  }
25798
- console.log(import_chalk27.default.dim(" " + "\u2500".repeat(70)));
26509
+ console.log(import_chalk28.default.dim(" " + "\u2500".repeat(70)));
25799
26510
  console.log("");
25800
26511
  }
25801
26512
  function renderList(summaries, totalCost) {
25802
26513
  if (summaries.length === 0) {
25803
- console.log(import_chalk27.default.yellow(" No sessions found in the requested range.\n"));
26514
+ console.log(import_chalk28.default.yellow(" No sessions found in the requested range.\n"));
25804
26515
  return;
25805
26516
  }
25806
- const totalLabel = totalCost > 0 ? import_chalk27.default.dim(" ~" + fmtCost3(totalCost) + " total") : "";
26517
+ const totalLabel = totalCost > 0 ? import_chalk28.default.dim(" ~" + fmtCost3(totalCost) + " total") : "";
25807
26518
  console.log(
25808
- " " + import_chalk27.default.white(String(summaries.length)) + import_chalk27.default.dim(` session${summaries.length !== 1 ? "s" : ""}`) + totalLabel
26519
+ " " + import_chalk28.default.white(String(summaries.length)) + import_chalk28.default.dim(` session${summaries.length !== 1 ? "s" : ""}`) + totalLabel
25809
26520
  );
25810
26521
  console.log("");
25811
26522
  let lastGroup = "";
@@ -25813,51 +26524,51 @@ function renderList(summaries, totalCost) {
25813
26524
  const activeDate = fmtDate2(s.lastActiveTime);
25814
26525
  const group = activeDate + " " + s.projectLabel;
25815
26526
  if (group !== lastGroup) {
25816
- console.log(import_chalk27.default.dim(" \u2500\u2500\u2500 ") + import_chalk27.default.bold(activeDate) + import_chalk27.default.dim(" " + s.projectLabel));
26527
+ console.log(import_chalk28.default.dim(" \u2500\u2500\u2500 ") + import_chalk28.default.bold(activeDate) + import_chalk28.default.dim(" " + s.projectLabel));
25817
26528
  lastGroup = group;
25818
26529
  }
25819
26530
  const startDate = fmtDate2(s.startTime);
25820
- const dateRange = startDate !== activeDate ? import_chalk27.default.dim(" (" + startDate + " \u2192 " + activeDate + ")") : "";
25821
- const timeStr = import_chalk27.default.dim(fmtTime(s.startTime));
25822
- const prompt = import_chalk27.default.white(truncate(s.firstPrompt.replace(/\n/g, " "), 50).padEnd(50));
25823
- const tools = s.toolCalls.length > 0 ? import_chalk27.default.dim(String(s.toolCalls.length).padStart(3) + " tools") : import_chalk27.default.dim(" 0 tools");
25824
- const cost = s.costUSD > 0 ? import_chalk27.default.dim(" " + fmtCost3(s.costUSD).padEnd(8)) : " ";
25825
- const blocked = s.blockedCalls.length > 0 ? import_chalk27.default.red(" \u{1F6D1} " + String(s.blockedCalls.length)) : "";
25826
- const snap = s.hasSnapshot ? import_chalk27.default.green(" \u{1F4F8}") : "";
25827
- const agentBadge = import_chalk27.default[agentColorName(s.agent ?? "claude")](
26531
+ const dateRange = startDate !== activeDate ? import_chalk28.default.dim(" (" + startDate + " \u2192 " + activeDate + ")") : "";
26532
+ const timeStr = import_chalk28.default.dim(fmtTime(s.startTime));
26533
+ const prompt = import_chalk28.default.white(truncate(s.firstPrompt.replace(/\n/g, " "), 50).padEnd(50));
26534
+ const tools = s.toolCalls.length > 0 ? import_chalk28.default.dim(String(s.toolCalls.length).padStart(3) + " tools") : import_chalk28.default.dim(" 0 tools");
26535
+ const cost = s.costUSD > 0 ? import_chalk28.default.dim(" " + fmtCost3(s.costUSD).padEnd(8)) : " ";
26536
+ const blocked = s.blockedCalls.length > 0 ? import_chalk28.default.red(" \u{1F6D1} " + String(s.blockedCalls.length)) : "";
26537
+ const snap = s.hasSnapshot ? import_chalk28.default.green(" \u{1F4F8}") : "";
26538
+ const agentBadge = import_chalk28.default[agentColorName(s.agent ?? "claude")](
25828
26539
  " " + agentBadgeText(s.agent ?? "claude", 0)
25829
26540
  );
25830
- const sid = import_chalk27.default.dim(" " + s.sessionId.slice(0, 8));
26541
+ const sid = import_chalk28.default.dim(" " + s.sessionId.slice(0, 8));
25831
26542
  console.log(
25832
26543
  ` ${timeStr} ${prompt} ${tools}${cost}${blocked}${snap}${agentBadge}${sid}${dateRange}`
25833
26544
  );
25834
26545
  }
25835
26546
  console.log("");
25836
26547
  console.log(
25837
- import_chalk27.default.dim(" Run") + " " + import_chalk27.default.cyan("node9 sessions --detail <session-id>") + import_chalk27.default.dim(" for full tool trace.")
26548
+ import_chalk28.default.dim(" Run") + " " + import_chalk28.default.cyan("node9 sessions --detail <session-id>") + import_chalk28.default.dim(" for full tool trace.")
25838
26549
  );
25839
26550
  console.log("");
25840
26551
  }
25841
26552
  function renderDetail(s) {
25842
26553
  console.log("");
25843
- console.log(import_chalk27.default.bold(" Session ") + import_chalk27.default.dim(s.sessionId));
26554
+ console.log(import_chalk28.default.bold(" Session ") + import_chalk28.default.dim(s.sessionId));
25844
26555
  console.log(
25845
- import_chalk27.default.bold(" Prompt ") + import_chalk27.default.white(s.firstPrompt.replace(/\n/g, " ").slice(0, 120))
26556
+ import_chalk28.default.bold(" Prompt ") + import_chalk28.default.white(s.firstPrompt.replace(/\n/g, " ").slice(0, 120))
25846
26557
  );
25847
- console.log(import_chalk27.default.bold(" Project ") + import_chalk27.default.white(s.projectLabel));
26558
+ console.log(import_chalk28.default.bold(" Project ") + import_chalk28.default.white(s.projectLabel));
25848
26559
  if (s.agent) {
25849
- const agentLabel2 = import_chalk27.default[agentColorName(s.agent)](agentDisplayName(s.agent));
25850
- console.log(import_chalk27.default.bold(" Agent ") + agentLabel2);
26560
+ const agentLabel2 = import_chalk28.default[agentColorName(s.agent)](agentDisplayName(s.agent));
26561
+ console.log(import_chalk28.default.bold(" Agent ") + agentLabel2);
25851
26562
  }
25852
- console.log(import_chalk27.default.bold(" When ") + import_chalk27.default.white(fmtDateTime(s.startTime)));
26563
+ console.log(import_chalk28.default.bold(" When ") + import_chalk28.default.white(fmtDateTime(s.startTime)));
25853
26564
  if (s.costUSD > 0)
25854
- console.log(import_chalk27.default.bold(" Cost ") + import_chalk27.default.yellow("~" + fmtCost3(s.costUSD)));
26565
+ console.log(import_chalk28.default.bold(" Cost ") + import_chalk28.default.yellow("~" + fmtCost3(s.costUSD)));
25855
26566
  console.log(
25856
- import_chalk27.default.bold(" Snapshot ") + (s.hasSnapshot ? import_chalk27.default.green("\u2713 taken") : import_chalk27.default.dim("none"))
26567
+ import_chalk28.default.bold(" Snapshot ") + (s.hasSnapshot ? import_chalk28.default.green("\u2713 taken") : import_chalk28.default.dim("none"))
25857
26568
  );
25858
26569
  console.log("");
25859
26570
  if (s.toolCalls.length === 0 && s.blockedCalls.length === 0) {
25860
- console.log(import_chalk27.default.dim(" No tool calls recorded.\n"));
26571
+ console.log(import_chalk28.default.dim(" No tool calls recorded.\n"));
25861
26572
  return;
25862
26573
  }
25863
26574
  const timeline = [
@@ -25870,32 +26581,32 @@ function renderDetail(s) {
25870
26581
  });
25871
26582
  const headerParts = [`Tool calls (${s.toolCalls.length})`];
25872
26583
  if (s.blockedCalls.length > 0)
25873
- headerParts.push(import_chalk27.default.red(`${s.blockedCalls.length} blocked by node9`));
25874
- console.log(import_chalk27.default.bold(" " + headerParts.join(" \xB7 ")));
26584
+ headerParts.push(import_chalk28.default.red(`${s.blockedCalls.length} blocked by node9`));
26585
+ console.log(import_chalk28.default.bold(" " + headerParts.join(" \xB7 ")));
25875
26586
  console.log("");
25876
26587
  for (const entry of timeline) {
25877
26588
  if (entry.kind === "tool") {
25878
26589
  const tc = entry.tc;
25879
26590
  const colorFn = toolColor(tc.tool);
25880
26591
  const toolPad = colorFn(tc.tool.padEnd(16));
25881
- const detail = import_chalk27.default.gray(truncate(toolInputSummary(tc.tool, tc.input), 70));
25882
- const ts = tc.timestamp ? import_chalk27.default.dim(fmtTime(tc.timestamp) + " ") : " ";
26592
+ const detail = import_chalk28.default.gray(truncate(toolInputSummary(tc.tool, tc.input), 70));
26593
+ const ts = tc.timestamp ? import_chalk28.default.dim(fmtTime(tc.timestamp) + " ") : " ";
25883
26594
  console.log(` ${ts}${toolPad} ${detail}`);
25884
26595
  } else {
25885
26596
  const bc = entry.bc;
25886
- const ts = bc.timestamp ? import_chalk27.default.dim(fmtTime(bc.timestamp) + " ") : " ";
25887
- const label2 = import_chalk27.default.red("\u{1F6D1} BLOCKED".padEnd(16));
25888
- const toolName = import_chalk27.default.red(bc.tool.padEnd(10));
25889
- const argsSummary = bc.args ? import_chalk27.default.gray(truncate(toolInputSummary(bc.tool, bc.args), 40)) : import_chalk27.default.dim("[args not logged]");
25890
- const reason = bc.checkedBy ? import_chalk27.default.dim(" \u2190 " + bc.checkedBy) : "";
26597
+ const ts = bc.timestamp ? import_chalk28.default.dim(fmtTime(bc.timestamp) + " ") : " ";
26598
+ const label2 = import_chalk28.default.red("\u{1F6D1} BLOCKED".padEnd(16));
26599
+ const toolName = import_chalk28.default.red(bc.tool.padEnd(10));
26600
+ const argsSummary = bc.args ? import_chalk28.default.gray(truncate(toolInputSummary(bc.tool, bc.args), 40)) : import_chalk28.default.dim("[args not logged]");
26601
+ const reason = bc.checkedBy ? import_chalk28.default.dim(" \u2190 " + bc.checkedBy) : "";
25891
26602
  console.log(` ${ts}${label2} ${toolName} ${argsSummary}${reason}`);
25892
26603
  }
25893
26604
  }
25894
26605
  console.log("");
25895
26606
  if (s.modifiedFiles.length > 0) {
25896
- console.log(import_chalk27.default.bold(` Files modified (${s.modifiedFiles.length}):`));
26607
+ console.log(import_chalk28.default.bold(` Files modified (${s.modifiedFiles.length}):`));
25897
26608
  for (const f of s.modifiedFiles) {
25898
- console.log(" " + import_chalk27.default.yellow(f));
26609
+ console.log(" " + import_chalk28.default.yellow(f));
25899
26610
  }
25900
26611
  console.log("");
25901
26612
  }
@@ -25903,13 +26614,13 @@ function renderDetail(s) {
25903
26614
  function registerSessionsCommand(program2) {
25904
26615
  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) => {
25905
26616
  console.log("");
25906
- console.log(import_chalk27.default.cyan.bold("\u{1F4CB} node9 sessions") + import_chalk27.default.dim(" \u2014 what your AI agent did"));
26617
+ console.log(import_chalk28.default.cyan.bold("\u{1F4CB} node9 sessions") + import_chalk28.default.dim(" \u2014 what your AI agent did"));
25907
26618
  console.log("");
25908
26619
  const days = options.detail || options.all ? null : Math.max(1, parseInt(options.days, 10) || 7);
25909
26620
  const rangeLabel = options.detail ? "all time" : options.all ? "all time" : `last ${String(days)} days`;
25910
- console.log(import_chalk27.default.dim(" " + rangeLabel));
26621
+ console.log(import_chalk28.default.dim(" " + rangeLabel));
25911
26622
  console.log("");
25912
- process.stdout.write(import_chalk27.default.dim(" Loading\u2026"));
26623
+ process.stdout.write(import_chalk28.default.dim(" Loading\u2026"));
25913
26624
  const summaries = buildSessions(days);
25914
26625
  if (process.stdout.isTTY) {
25915
26626
  process.stdout.clearLine(0);
@@ -25922,8 +26633,8 @@ function registerSessionsCommand(program2) {
25922
26633
  (s) => s.sessionId === options.detail || s.sessionId.startsWith(options.detail)
25923
26634
  );
25924
26635
  if (!target) {
25925
- console.log(import_chalk27.default.red(` Session not found: ${options.detail}`));
25926
- console.log(import_chalk27.default.dim(" Run `node9 sessions` to list recent sessions.\n"));
26636
+ console.log(import_chalk28.default.red(` Session not found: ${options.detail}`));
26637
+ console.log(import_chalk28.default.dim(" Run `node9 sessions` to list recent sessions.\n"));
25927
26638
  return;
25928
26639
  }
25929
26640
  renderDetail(target);
@@ -25936,7 +26647,7 @@ function registerSessionsCommand(program2) {
25936
26647
  }
25937
26648
 
25938
26649
  // src/cli/commands/session-taint.ts
25939
- var import_chalk28 = __toESM(require("chalk"));
26650
+ var import_chalk29 = __toESM(require("chalk"));
25940
26651
  init_daemon();
25941
26652
  function resolveSessionId(records, query) {
25942
26653
  const exact = records.find((r) => r.sessionId === query);
@@ -25963,22 +26674,22 @@ function registerSessionTaintCommand(program2) {
25963
26674
  const records = await listSessionTaints();
25964
26675
  console.log("");
25965
26676
  if (records.length === 0) {
25966
- console.log(import_chalk28.default.dim(" No tainted sessions."));
25967
- console.log(import_chalk28.default.dim(" (Taint lives in the daemon \u2014 a stopped daemon has none.)") + "\n");
26677
+ console.log(import_chalk29.default.dim(" No tainted sessions."));
26678
+ console.log(import_chalk29.default.dim(" (Taint lives in the daemon \u2014 a stopped daemon has none.)") + "\n");
25968
26679
  return;
25969
26680
  }
25970
26681
  console.log(
25971
- " " + import_chalk28.default.bold(String(records.length)) + import_chalk28.default.dim(` tainted session${records.length !== 1 ? "s" : ""}`)
26682
+ " " + import_chalk29.default.bold(String(records.length)) + import_chalk29.default.dim(` tainted session${records.length !== 1 ? "s" : ""}`)
25972
26683
  );
25973
26684
  console.log("");
25974
26685
  for (const r of records) {
25975
26686
  console.log(
25976
- " " + import_chalk28.default.yellow(r.sessionId.slice(0, 8).padEnd(10)) + import_chalk28.default.red(r.source) + sourceGap(r.source) + import_chalk28.default.dim("clears in " + fmtRemaining(r.expiresAt))
26687
+ " " + import_chalk29.default.yellow(r.sessionId.slice(0, 8).padEnd(10)) + import_chalk29.default.red(r.source) + sourceGap(r.source) + import_chalk29.default.dim("clears in " + fmtRemaining(r.expiresAt))
25977
26688
  );
25978
26689
  }
25979
26690
  console.log("");
25980
26691
  console.log(
25981
- import_chalk28.default.dim(" Run ") + import_chalk28.default.cyan("node9 session-taint clear <id>") + import_chalk28.default.dim(" to release one, or ") + import_chalk28.default.cyan("--all") + import_chalk28.default.dim(" for every session.") + "\n"
26692
+ import_chalk29.default.dim(" Run ") + import_chalk29.default.cyan("node9 session-taint clear <id>") + import_chalk29.default.dim(" to release one, or ") + import_chalk29.default.cyan("--all") + import_chalk29.default.dim(" for every session.") + "\n"
25982
26693
  );
25983
26694
  });
25984
26695
  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) => {
@@ -25986,32 +26697,32 @@ function registerSessionTaintCommand(program2) {
25986
26697
  if (opts.all) {
25987
26698
  const res2 = await clearSessionTaint({ all: true });
25988
26699
  if (res2.daemonUnavailable) {
25989
- console.log(import_chalk28.default.dim(" node9 daemon not running \u2014 no active taints to clear.") + "\n");
26700
+ console.log(import_chalk29.default.dim(" node9 daemon not running \u2014 no active taints to clear.") + "\n");
25990
26701
  return;
25991
26702
  }
25992
26703
  console.log(
25993
- import_chalk28.default.green(" \u2713 ") + `Cleared ${import_chalk28.default.bold(String(res2.cleared))} session taint${res2.cleared !== 1 ? "s" : ""}.
26704
+ import_chalk29.default.green(" \u2713 ") + `Cleared ${import_chalk29.default.bold(String(res2.cleared))} session taint${res2.cleared !== 1 ? "s" : ""}.
25994
26705
  `
25995
26706
  );
25996
26707
  return;
25997
26708
  }
25998
26709
  if (!sessionId) {
25999
- console.log(import_chalk28.default.red(" Provide a session id or --all."));
26000
- console.log(import_chalk28.default.dim(" Run `node9 session-taint list` to see tainted sessions.") + "\n");
26710
+ console.log(import_chalk29.default.red(" Provide a session id or --all."));
26711
+ console.log(import_chalk29.default.dim(" Run `node9 session-taint list` to see tainted sessions.") + "\n");
26001
26712
  return;
26002
26713
  }
26003
26714
  const records = await listSessionTaints();
26004
26715
  if (records.length === 0) {
26005
- console.log(import_chalk28.default.dim(" No tainted sessions \u2014 nothing to clear.") + "\n");
26716
+ console.log(import_chalk29.default.dim(" No tainted sessions \u2014 nothing to clear.") + "\n");
26006
26717
  return;
26007
26718
  }
26008
26719
  const resolved = resolveSessionId(records, sessionId);
26009
26720
  if ("error" in resolved) {
26010
26721
  if (resolved.error === "not-found") {
26011
- console.log(import_chalk28.default.red(` No tainted session matches "${sessionId}".`));
26722
+ console.log(import_chalk29.default.red(` No tainted session matches "${sessionId}".`));
26012
26723
  } else {
26013
- console.log(import_chalk28.default.red(` "${sessionId}" is ambiguous \u2014 matches:`));
26014
- for (const m of resolved.matches) console.log(import_chalk28.default.dim(" " + m));
26724
+ console.log(import_chalk29.default.red(` "${sessionId}" is ambiguous \u2014 matches:`));
26725
+ for (const m of resolved.matches) console.log(import_chalk29.default.dim(" " + m));
26015
26726
  }
26016
26727
  console.log("");
26017
26728
  return;
@@ -26019,24 +26730,24 @@ function registerSessionTaintCommand(program2) {
26019
26730
  const res = await clearSessionTaint({ sessionId: resolved.record.sessionId });
26020
26731
  if (res.cleared > 0) {
26021
26732
  console.log(
26022
- import_chalk28.default.green(" \u2713 ") + `Cleared taint for ${import_chalk28.default.yellow(resolved.record.sessionId.slice(0, 8))} ` + import_chalk28.default.dim(`(was flagged by ${resolved.record.source}).`) + "\n"
26733
+ import_chalk29.default.green(" \u2713 ") + `Cleared taint for ${import_chalk29.default.yellow(resolved.record.sessionId.slice(0, 8))} ` + import_chalk29.default.dim(`(was flagged by ${resolved.record.source}).`) + "\n"
26023
26734
  );
26024
26735
  } else {
26025
26736
  console.log(
26026
- import_chalk28.default.dim(` Session ${resolved.record.sessionId.slice(0, 8)} was already clear.`) + "\n"
26737
+ import_chalk29.default.dim(` Session ${resolved.record.sessionId.slice(0, 8)} was already clear.`) + "\n"
26027
26738
  );
26028
26739
  }
26029
26740
  });
26030
26741
  }
26031
26742
 
26032
26743
  // src/cli/commands/skill-pin.ts
26033
- var import_chalk29 = __toESM(require("chalk"));
26034
- var import_fs52 = __toESM(require("fs"));
26035
- var import_os47 = __toESM(require("os"));
26036
- var import_path51 = __toESM(require("path"));
26744
+ var import_chalk30 = __toESM(require("chalk"));
26745
+ var import_fs57 = __toESM(require("fs"));
26746
+ var import_os49 = __toESM(require("os"));
26747
+ var import_path55 = __toESM(require("path"));
26037
26748
  function wipeSkillSessions() {
26038
26749
  try {
26039
- import_fs52.default.rmSync(import_path51.default.join(import_os47.default.homedir(), ".node9", "skill-sessions"), {
26750
+ import_fs57.default.rmSync(import_path55.default.join(import_os49.default.homedir(), ".node9", "skill-sessions"), {
26040
26751
  recursive: true,
26041
26752
  force: true
26042
26753
  });
@@ -26050,29 +26761,29 @@ function registerSkillPinCommand(program2) {
26050
26761
  const result = readSkillPinsSafe();
26051
26762
  if (!result.ok) {
26052
26763
  if (result.reason === "missing") {
26053
- console.log(import_chalk29.default.gray("\nNo skill roots are pinned yet."));
26764
+ console.log(import_chalk30.default.gray("\nNo skill roots are pinned yet."));
26054
26765
  console.log(
26055
- import_chalk29.default.gray("Pins are created automatically on the first tool call of each session.\n")
26766
+ import_chalk30.default.gray("Pins are created automatically on the first tool call of each session.\n")
26056
26767
  );
26057
26768
  return;
26058
26769
  }
26059
- console.error(import_chalk29.default.red(`
26770
+ console.error(import_chalk30.default.red(`
26060
26771
  \u274C Pin file is corrupt: ${result.detail}`));
26061
- console.error(import_chalk29.default.yellow(" Run: node9 skill pin reset\n"));
26772
+ console.error(import_chalk30.default.yellow(" Run: node9 skill pin reset\n"));
26062
26773
  process.exit(1);
26063
26774
  }
26064
26775
  const entries = Object.entries(result.pins.roots);
26065
26776
  if (entries.length === 0) {
26066
- console.log(import_chalk29.default.gray("\nNo skill roots are pinned yet.\n"));
26777
+ console.log(import_chalk30.default.gray("\nNo skill roots are pinned yet.\n"));
26067
26778
  return;
26068
26779
  }
26069
- console.log(import_chalk29.default.bold("\n\u{1F512} Pinned Skill Roots\n"));
26780
+ console.log(import_chalk30.default.bold("\n\u{1F512} Pinned Skill Roots\n"));
26070
26781
  for (const [key, entry] of entries) {
26071
- const missing = entry.exists ? "" : import_chalk29.default.yellow(" (not present at pin time)");
26072
- console.log(` ${import_chalk29.default.cyan(key)} ${import_chalk29.default.gray(entry.rootPath)}${missing}`);
26782
+ const missing = entry.exists ? "" : import_chalk30.default.yellow(" (not present at pin time)");
26783
+ console.log(` ${import_chalk30.default.cyan(key)} ${import_chalk30.default.gray(entry.rootPath)}${missing}`);
26073
26784
  console.log(` Files (${entry.fileCount})`);
26074
- console.log(` Hash: ${import_chalk29.default.gray(entry.contentHash.slice(0, 16))}...`);
26075
- console.log(` Pinned: ${import_chalk29.default.gray(entry.pinnedAt)}
26785
+ console.log(` Hash: ${import_chalk30.default.gray(entry.contentHash.slice(0, 16))}...`);
26786
+ console.log(` Pinned: ${import_chalk30.default.gray(entry.pinnedAt)}
26076
26787
  `);
26077
26788
  }
26078
26789
  });
@@ -26081,52 +26792,52 @@ function registerSkillPinCommand(program2) {
26081
26792
  try {
26082
26793
  pins = readSkillPins();
26083
26794
  } catch {
26084
- console.error(import_chalk29.default.red("\n\u274C Pin file is corrupt."));
26085
- console.error(import_chalk29.default.yellow(" Run: node9 skill pin reset\n"));
26795
+ console.error(import_chalk30.default.red("\n\u274C Pin file is corrupt."));
26796
+ console.error(import_chalk30.default.yellow(" Run: node9 skill pin reset\n"));
26086
26797
  process.exit(1);
26087
26798
  }
26088
26799
  if (!pins.roots[rootKey]) {
26089
- console.error(import_chalk29.default.red(`
26800
+ console.error(import_chalk30.default.red(`
26090
26801
  \u274C No pin found for root key "${rootKey}"
26091
26802
  `));
26092
- console.error(`Run ${import_chalk29.default.cyan("node9 skill pin list")} to see pinned roots.
26803
+ console.error(`Run ${import_chalk30.default.cyan("node9 skill pin list")} to see pinned roots.
26093
26804
  `);
26094
26805
  process.exit(1);
26095
26806
  }
26096
26807
  const rootPath = pins.roots[rootKey].rootPath;
26097
26808
  removePin2(rootKey);
26098
26809
  wipeSkillSessions();
26099
- console.log(import_chalk29.default.green(`
26100
- \u{1F513} Pin removed for ${import_chalk29.default.cyan(rootKey)}`));
26101
- console.log(import_chalk29.default.gray(` ${rootPath}`));
26102
- console.log(import_chalk29.default.gray(" Next session will re-pin with current state.\n"));
26810
+ console.log(import_chalk30.default.green(`
26811
+ \u{1F513} Pin removed for ${import_chalk30.default.cyan(rootKey)}`));
26812
+ console.log(import_chalk30.default.gray(` ${rootPath}`));
26813
+ console.log(import_chalk30.default.gray(" Next session will re-pin with current state.\n"));
26103
26814
  });
26104
26815
  pinSubCmd.command("reset").description("Clear all skill pins and wipe session verification flags").action(() => {
26105
26816
  const result = readSkillPinsSafe();
26106
26817
  if (!result.ok && result.reason === "missing") {
26107
26818
  wipeSkillSessions();
26108
- console.log(import_chalk29.default.gray("\nNo pins to clear.\n"));
26819
+ console.log(import_chalk30.default.gray("\nNo pins to clear.\n"));
26109
26820
  return;
26110
26821
  }
26111
26822
  const count = result.ok ? Object.keys(result.pins.roots).length : "?";
26112
26823
  clearAllPins2();
26113
26824
  wipeSkillSessions();
26114
- console.log(import_chalk29.default.green(`
26825
+ console.log(import_chalk30.default.green(`
26115
26826
  \u{1F513} Cleared ${count} skill pin(s).`));
26116
- console.log(import_chalk29.default.gray(" Next session will re-pin with current state.\n"));
26827
+ console.log(import_chalk30.default.gray(" Next session will re-pin with current state.\n"));
26117
26828
  });
26118
26829
  }
26119
26830
 
26120
26831
  // src/cli/commands/decisions.ts
26121
- var import_fs53 = __toESM(require("fs"));
26122
- var import_os48 = __toESM(require("os"));
26123
- var import_path52 = __toESM(require("path"));
26124
- var import_chalk30 = __toESM(require("chalk"));
26125
- var DECISIONS_FILE2 = import_path52.default.join(import_os48.default.homedir(), ".node9", "decisions.json");
26832
+ var import_fs58 = __toESM(require("fs"));
26833
+ var import_os50 = __toESM(require("os"));
26834
+ var import_path56 = __toESM(require("path"));
26835
+ var import_chalk31 = __toESM(require("chalk"));
26836
+ var DECISIONS_FILE2 = import_path56.default.join(import_os50.default.homedir(), ".node9", "decisions.json");
26126
26837
  function readDecisions() {
26127
26838
  try {
26128
- if (!import_fs53.default.existsSync(DECISIONS_FILE2)) return {};
26129
- const raw = import_fs53.default.readFileSync(DECISIONS_FILE2, "utf-8");
26839
+ if (!import_fs58.default.existsSync(DECISIONS_FILE2)) return {};
26840
+ const raw = import_fs58.default.readFileSync(DECISIONS_FILE2, "utf-8");
26130
26841
  const parsed = JSON.parse(raw);
26131
26842
  const out = {};
26132
26843
  for (const [k, v] of Object.entries(parsed)) {
@@ -26138,11 +26849,11 @@ function readDecisions() {
26138
26849
  }
26139
26850
  }
26140
26851
  function writeDecisions(d) {
26141
- const dir = import_path52.default.dirname(DECISIONS_FILE2);
26142
- if (!import_fs53.default.existsSync(dir)) import_fs53.default.mkdirSync(dir, { recursive: true });
26852
+ const dir = import_path56.default.dirname(DECISIONS_FILE2);
26853
+ if (!import_fs58.default.existsSync(dir)) import_fs58.default.mkdirSync(dir, { recursive: true });
26143
26854
  const tmp = `${DECISIONS_FILE2}.${process.pid}.tmp`;
26144
- import_fs53.default.writeFileSync(tmp, JSON.stringify(d, null, 2));
26145
- import_fs53.default.renameSync(tmp, DECISIONS_FILE2);
26855
+ import_fs58.default.writeFileSync(tmp, JSON.stringify(d, null, 2));
26856
+ import_fs58.default.renameSync(tmp, DECISIONS_FILE2);
26146
26857
  }
26147
26858
  function registerDecisionsCommand(program2) {
26148
26859
  const cmd = program2.command("decisions").description('Manage persistent "Always Allow" / "Always Deny" tool decisions');
@@ -26150,67 +26861,67 @@ function registerDecisionsCommand(program2) {
26150
26861
  const decisions = readDecisions();
26151
26862
  const entries = Object.entries(decisions);
26152
26863
  if (entries.length === 0) {
26153
- console.log(import_chalk30.default.gray(" No persistent decisions stored."));
26864
+ console.log(import_chalk31.default.gray(" No persistent decisions stored."));
26154
26865
  console.log(
26155
- import_chalk30.default.gray(` File: ${DECISIONS_FILE2}
26156
- `) + import_chalk30.default.gray(' Decisions are written when you click "Always Allow" or')
26866
+ import_chalk31.default.gray(` File: ${DECISIONS_FILE2}
26867
+ `) + import_chalk31.default.gray(' Decisions are written when you click "Always Allow" or')
26157
26868
  );
26158
- console.log(import_chalk30.default.gray(' "Always Deny" in node9 tail or the native popup.'));
26869
+ console.log(import_chalk31.default.gray(' "Always Deny" in node9 tail or the native popup.'));
26159
26870
  return;
26160
26871
  }
26161
- console.log(import_chalk30.default.bold(`
26872
+ console.log(import_chalk31.default.bold(`
26162
26873
  Persistent decisions (${entries.length})
26163
26874
  `));
26164
26875
  const w = Math.max(...entries.map(([k]) => k.length));
26165
26876
  for (const [tool, verdict] of entries.sort()) {
26166
- const colored = verdict === "allow" ? import_chalk30.default.green(verdict) : import_chalk30.default.red(verdict);
26877
+ const colored = verdict === "allow" ? import_chalk31.default.green(verdict) : import_chalk31.default.red(verdict);
26167
26878
  console.log(` ${tool.padEnd(w)} ${colored}`);
26168
26879
  }
26169
26880
  console.log(
26170
- import_chalk30.default.gray(`
26881
+ import_chalk31.default.gray(`
26171
26882
  Stored in ${DECISIONS_FILE2}
26172
- `) + import_chalk30.default.gray(" Run `node9 decisions clear <tool>` to remove an entry.")
26883
+ `) + import_chalk31.default.gray(" Run `node9 decisions clear <tool>` to remove an entry.")
26173
26884
  );
26174
26885
  });
26175
26886
  cmd.command("clear <toolName>").description("Remove a persistent decision for one tool").action((toolName) => {
26176
26887
  const decisions = readDecisions();
26177
26888
  if (!(toolName in decisions)) {
26178
- console.log(import_chalk30.default.yellow(` No persistent decision for "${toolName}". Nothing to clear.`));
26889
+ console.log(import_chalk31.default.yellow(` No persistent decision for "${toolName}". Nothing to clear.`));
26179
26890
  process.exitCode = 1;
26180
26891
  return;
26181
26892
  }
26182
26893
  delete decisions[toolName];
26183
26894
  writeDecisions(decisions);
26184
- console.log(import_chalk30.default.green(` \u2713 Cleared persistent decision for "${toolName}".`));
26895
+ console.log(import_chalk31.default.green(` \u2713 Cleared persistent decision for "${toolName}".`));
26185
26896
  });
26186
26897
  cmd.command("clear-all").description("Remove every persistent decision (irreversible)").action(() => {
26187
26898
  const decisions = readDecisions();
26188
26899
  const count = Object.keys(decisions).length;
26189
26900
  if (count === 0) {
26190
- console.log(import_chalk30.default.gray(" Nothing to clear \u2014 no persistent decisions stored."));
26901
+ console.log(import_chalk31.default.gray(" Nothing to clear \u2014 no persistent decisions stored."));
26191
26902
  return;
26192
26903
  }
26193
26904
  writeDecisions({});
26194
26905
  console.log(
26195
- import_chalk30.default.green(` \u2713 Cleared ${count} persistent decision${count === 1 ? "" : "s"}.`)
26906
+ import_chalk31.default.green(` \u2713 Cleared ${count} persistent decision${count === 1 ? "" : "s"}.`)
26196
26907
  );
26197
26908
  });
26198
26909
  }
26199
26910
 
26200
26911
  // src/cli/commands/dlp.ts
26201
- var import_chalk31 = __toESM(require("chalk"));
26202
- var import_fs54 = __toESM(require("fs"));
26203
- var import_path53 = __toESM(require("path"));
26204
- var import_os49 = __toESM(require("os"));
26205
- var AUDIT_LOG = import_path53.default.join(import_os49.default.homedir(), ".node9", "audit.log");
26206
- var RESOLVED_FILE = import_path53.default.join(import_os49.default.homedir(), ".node9", "dlp-resolved.json");
26912
+ var import_chalk32 = __toESM(require("chalk"));
26913
+ var import_fs59 = __toESM(require("fs"));
26914
+ var import_path57 = __toESM(require("path"));
26915
+ var import_os51 = __toESM(require("os"));
26916
+ var AUDIT_LOG = import_path57.default.join(import_os51.default.homedir(), ".node9", "audit.log");
26917
+ var RESOLVED_FILE = import_path57.default.join(import_os51.default.homedir(), ".node9", "dlp-resolved.json");
26207
26918
  var ANSI_RE = /\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g;
26208
26919
  function stripAnsi(s) {
26209
26920
  return s.replace(ANSI_RE, "");
26210
26921
  }
26211
26922
  function loadResolved() {
26212
26923
  try {
26213
- const raw = JSON.parse(import_fs54.default.readFileSync(RESOLVED_FILE, "utf-8"));
26924
+ const raw = JSON.parse(import_fs59.default.readFileSync(RESOLVED_FILE, "utf-8"));
26214
26925
  return new Set(raw);
26215
26926
  } catch {
26216
26927
  return /* @__PURE__ */ new Set();
@@ -26218,13 +26929,13 @@ function loadResolved() {
26218
26929
  }
26219
26930
  function saveResolved(resolved) {
26220
26931
  try {
26221
- import_fs54.default.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
26932
+ import_fs59.default.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
26222
26933
  } catch {
26223
26934
  }
26224
26935
  }
26225
26936
  function loadDlpFindings() {
26226
- if (!import_fs54.default.existsSync(AUDIT_LOG)) return [];
26227
- return import_fs54.default.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
26937
+ if (!import_fs59.default.existsSync(AUDIT_LOG)) return [];
26938
+ return import_fs59.default.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
26228
26939
  if (!line.trim()) return [];
26229
26940
  try {
26230
26941
  const e = JSON.parse(line);
@@ -26253,14 +26964,14 @@ function registerDlpCommand(program2) {
26253
26964
  cmd.command("resolve").description("Mark all current DLP findings as resolved").action(() => {
26254
26965
  const findings = loadDlpFindings();
26255
26966
  if (findings.length === 0) {
26256
- console.log(import_chalk31.default.green("\n \u2705 No response-DLP findings to resolve.\n"));
26967
+ console.log(import_chalk32.default.green("\n \u2705 No response-DLP findings to resolve.\n"));
26257
26968
  return;
26258
26969
  }
26259
26970
  const resolved = loadResolved();
26260
26971
  for (const e of findings) resolved.add(entryKey2(e));
26261
26972
  saveResolved(resolved);
26262
26973
  console.log(
26263
- import_chalk31.default.green(
26974
+ import_chalk32.default.green(
26264
26975
  `
26265
26976
  \u2705 ${findings.length} finding${findings.length !== 1 ? "s" : ""} marked as resolved.
26266
26977
  `
@@ -26274,63 +26985,63 @@ function registerDlpCommand(program2) {
26274
26985
  const resolvedCount = findings.length - open.length;
26275
26986
  console.log("");
26276
26987
  console.log(
26277
- import_chalk31.default.bold.cyan("\u{1F510} node9 dlp") + import_chalk31.default.dim(" \u2014 secrets found in Claude response text")
26988
+ import_chalk32.default.bold.cyan("\u{1F510} node9 dlp") + import_chalk32.default.dim(" \u2014 secrets found in Claude response text")
26278
26989
  );
26279
26990
  console.log("");
26280
26991
  if (open.length === 0) {
26281
26992
  if (resolvedCount > 0) {
26282
- console.log(import_chalk31.default.green(` \u2705 No open findings \xB7 ${resolvedCount} previously resolved`));
26993
+ console.log(import_chalk32.default.green(` \u2705 No open findings \xB7 ${resolvedCount} previously resolved`));
26283
26994
  } else {
26284
26995
  console.log(
26285
- import_chalk31.default.green(" \u2705 No findings \u2014 Claude has not leaked secrets in response text")
26996
+ import_chalk32.default.green(" \u2705 No findings \u2014 Claude has not leaked secrets in response text")
26286
26997
  );
26287
26998
  }
26288
26999
  console.log("");
26289
27000
  return;
26290
27001
  }
26291
27002
  console.log(
26292
- import_chalk31.default.bgRed.white.bold(` \u26A0\uFE0F ${open.length} open finding${open.length !== 1 ? "s" : ""} `) + import_chalk31.default.dim(resolvedCount > 0 ? ` (${resolvedCount} resolved)` : "")
27003
+ import_chalk32.default.bgRed.white.bold(` \u26A0\uFE0F ${open.length} open finding${open.length !== 1 ? "s" : ""} `) + import_chalk32.default.dim(resolvedCount > 0 ? ` (${resolvedCount} resolved)` : "")
26293
27004
  );
26294
27005
  console.log("");
26295
27006
  console.log(
26296
- import_chalk31.default.dim(" These secrets were included in Claude's response text \u2014 NOT blocked.")
27007
+ import_chalk32.default.dim(" These secrets were included in Claude's response text \u2014 NOT blocked.")
26297
27008
  );
26298
- console.log(import_chalk31.default.dim(" Rotate each affected key immediately.\n"));
27009
+ console.log(import_chalk32.default.dim(" Rotate each affected key immediately.\n"));
26299
27010
  for (const e of open) {
26300
27011
  console.log(
26301
- " " + import_chalk31.default.red("\u25CF") + " " + import_chalk31.default.white(e.dlpPattern ?? "Secret") + import_chalk31.default.dim(" " + fmtDate3(e.ts))
27012
+ " " + import_chalk32.default.red("\u25CF") + " " + import_chalk32.default.white(e.dlpPattern ?? "Secret") + import_chalk32.default.dim(" " + fmtDate3(e.ts))
26302
27013
  );
26303
27014
  if (e.dlpSample) {
26304
- console.log(" " + import_chalk31.default.dim("Sample: ") + import_chalk31.default.yellow(stripAnsi(e.dlpSample)));
27015
+ console.log(" " + import_chalk32.default.dim("Sample: ") + import_chalk32.default.yellow(stripAnsi(e.dlpSample)));
26305
27016
  }
26306
27017
  if (e.project) {
26307
- console.log(" " + import_chalk31.default.dim("Project: ") + import_chalk31.default.dim(stripAnsi(e.project)));
27018
+ console.log(" " + import_chalk32.default.dim("Project: ") + import_chalk32.default.dim(stripAnsi(e.project)));
26308
27019
  }
26309
27020
  console.log("");
26310
27021
  }
26311
- console.log(" " + import_chalk31.default.bold("Next steps:"));
26312
- console.log(" " + import_chalk31.default.cyan("1.") + " Rotate any exposed keys shown above");
27022
+ console.log(" " + import_chalk32.default.bold("Next steps:"));
27023
+ console.log(" " + import_chalk32.default.cyan("1.") + " Rotate any exposed keys shown above");
26313
27024
  console.log(
26314
- " " + import_chalk31.default.cyan("2.") + " Run " + import_chalk31.default.white("node9 dlp resolve") + " to acknowledge"
27025
+ " " + import_chalk32.default.cyan("2.") + " Run " + import_chalk32.default.white("node9 dlp resolve") + " to acknowledge"
26315
27026
  );
26316
27027
  console.log(
26317
- " " + import_chalk31.default.cyan("3.") + " Run " + import_chalk31.default.white("node9 report") + " for full audit history"
27028
+ " " + import_chalk32.default.cyan("3.") + " Run " + import_chalk32.default.white("node9 report") + " for full audit history"
26318
27029
  );
26319
27030
  console.log("");
26320
27031
  });
26321
27032
  }
26322
27033
 
26323
27034
  // src/cli/commands/mask.ts
26324
- var import_chalk32 = __toESM(require("chalk"));
26325
- var import_fs55 = __toESM(require("fs"));
26326
- var import_path54 = __toESM(require("path"));
26327
- var import_os50 = __toESM(require("os"));
27035
+ var import_chalk33 = __toESM(require("chalk"));
27036
+ var import_fs60 = __toESM(require("fs"));
27037
+ var import_path58 = __toESM(require("path"));
27038
+ var import_os52 = __toESM(require("os"));
26328
27039
  init_dlp();
26329
27040
  function findJsonlFiles(dir) {
26330
27041
  const results = [];
26331
- if (!import_fs55.default.existsSync(dir)) return results;
26332
- for (const entry of import_fs55.default.readdirSync(dir, { withFileTypes: true })) {
26333
- const full = import_path54.default.join(dir, entry.name);
27042
+ if (!import_fs60.default.existsSync(dir)) return results;
27043
+ for (const entry of import_fs60.default.readdirSync(dir, { withFileTypes: true })) {
27044
+ const full = import_path58.default.join(dir, entry.name);
26334
27045
  if (entry.isDirectory()) results.push(...findJsonlFiles(full));
26335
27046
  else if (entry.isFile() && entry.name.endsWith(".jsonl")) results.push(full);
26336
27047
  }
@@ -26373,7 +27084,7 @@ function redactJson(obj) {
26373
27084
  function processFile(filePath, dryRun) {
26374
27085
  let raw;
26375
27086
  try {
26376
- raw = import_fs55.default.readFileSync(filePath, "utf-8");
27087
+ raw = import_fs60.default.readFileSync(filePath, "utf-8");
26377
27088
  } catch {
26378
27089
  return { redactedLines: 0, patterns: [] };
26379
27090
  }
@@ -26405,14 +27116,14 @@ function processFile(filePath, dryRun) {
26405
27116
  }
26406
27117
  }
26407
27118
  if (!dryRun && redactedLines > 0) {
26408
- import_fs55.default.writeFileSync(filePath, newLines.join("\n"), "utf-8");
27119
+ import_fs60.default.writeFileSync(filePath, newLines.join("\n"), "utf-8");
26409
27120
  }
26410
27121
  return { redactedLines, patterns };
26411
27122
  }
26412
27123
  function processJsonFile(filePath, dryRun) {
26413
27124
  let raw;
26414
27125
  try {
26415
- raw = import_fs55.default.readFileSync(filePath, "utf-8");
27126
+ raw = import_fs60.default.readFileSync(filePath, "utf-8");
26416
27127
  } catch {
26417
27128
  return { redactedLines: 0, patterns: [] };
26418
27129
  }
@@ -26425,15 +27136,15 @@ function processJsonFile(filePath, dryRun) {
26425
27136
  const { value, modified, found } = redactJson(parsed);
26426
27137
  if (!modified) return { redactedLines: 0, patterns: [] };
26427
27138
  if (!dryRun) {
26428
- import_fs55.default.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
27139
+ import_fs60.default.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
26429
27140
  }
26430
27141
  return { redactedLines: 1, patterns: found };
26431
27142
  }
26432
27143
  function findJsonFiles(dir) {
26433
27144
  const results = [];
26434
- if (!import_fs55.default.existsSync(dir)) return results;
26435
- for (const entry of import_fs55.default.readdirSync(dir, { withFileTypes: true })) {
26436
- const full = import_path54.default.join(dir, entry.name);
27145
+ if (!import_fs60.default.existsSync(dir)) return results;
27146
+ for (const entry of import_fs60.default.readdirSync(dir, { withFileTypes: true })) {
27147
+ const full = import_path58.default.join(dir, entry.name);
26437
27148
  if (entry.isDirectory()) results.push(...findJsonFiles(full));
26438
27149
  else if (entry.isFile() && entry.name.endsWith(".json")) results.push(full);
26439
27150
  }
@@ -26442,9 +27153,9 @@ function findJsonFiles(dir) {
26442
27153
  function registerMaskCommand(program2) {
26443
27154
  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) => {
26444
27155
  const dryRun = !!options.dryRun;
26445
- const home = import_os50.default.homedir();
26446
- const claudeDir = import_path54.default.join(home, ".claude", "projects");
26447
- const geminiDir = import_path54.default.join(home, ".gemini", "tmp");
27156
+ const home = import_os52.default.homedir();
27157
+ const claudeDir = import_path58.default.join(home, ".claude", "projects");
27158
+ const geminiDir = import_path58.default.join(home, ".gemini", "tmp");
26448
27159
  const allFiles = [
26449
27160
  ...findJsonlFiles(claudeDir).map((p) => ({ path: p, type: "jsonl" })),
26450
27161
  ...findJsonFiles(geminiDir).map((p) => ({ path: p, type: "json" }))
@@ -26452,18 +27163,18 @@ function registerMaskCommand(program2) {
26452
27163
  const cutoff = options.all ? null : new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3);
26453
27164
  const filtered = cutoff ? allFiles.filter((f) => {
26454
27165
  try {
26455
- return import_fs55.default.statSync(f.path).mtime >= cutoff;
27166
+ return import_fs60.default.statSync(f.path).mtime >= cutoff;
26456
27167
  } catch {
26457
27168
  return false;
26458
27169
  }
26459
27170
  }) : allFiles;
26460
27171
  if (filtered.length === 0) {
26461
- console.log(import_chalk32.default.yellow(" No session files found."));
27172
+ console.log(import_chalk33.default.yellow(" No session files found."));
26462
27173
  return;
26463
27174
  }
26464
27175
  console.log("");
26465
27176
  if (dryRun) {
26466
- console.log(import_chalk32.default.dim(" Dry run \u2014 no files will be modified.\n"));
27177
+ console.log(import_chalk33.default.dim(" Dry run \u2014 no files will be modified.\n"));
26467
27178
  }
26468
27179
  let totalFiles = 0;
26469
27180
  let totalLines = 0;
@@ -26479,23 +27190,23 @@ function registerMaskCommand(program2) {
26479
27190
  });
26480
27191
  const verb = dryRun ? "Would redact" : "Redacted";
26481
27192
  console.log(
26482
- " " + import_chalk32.default.dim(shortPath.slice(0, 60).padEnd(62)) + import_chalk32.default.red(`${verb}: `) + import_chalk32.default.yellow(patterns.join(", ")) + import_chalk32.default.dim(` (${redactedLines} line${redactedLines !== 1 ? "s" : ""})`)
27193
+ " " + import_chalk33.default.dim(shortPath.slice(0, 60).padEnd(62)) + import_chalk33.default.red(`${verb}: `) + import_chalk33.default.yellow(patterns.join(", ")) + import_chalk33.default.dim(` (${redactedLines} line${redactedLines !== 1 ? "s" : ""})`)
26483
27194
  );
26484
27195
  }
26485
27196
  }
26486
27197
  console.log("");
26487
27198
  if (totalFiles === 0) {
26488
- console.log(import_chalk32.default.green(" No secrets found in session history."));
27199
+ console.log(import_chalk33.default.green(" No secrets found in session history."));
26489
27200
  } else {
26490
27201
  const verb = dryRun ? "would be modified" : "modified";
26491
27202
  console.log(
26492
- import_chalk32.default.bold(` ${totalFiles} file${totalFiles !== 1 ? "s" : ""} ${verb}`) + import_chalk32.default.dim(`, ${totalLines} line${totalLines !== 1 ? "s" : ""} redacted`)
27203
+ import_chalk33.default.bold(` ${totalFiles} file${totalFiles !== 1 ? "s" : ""} ${verb}`) + import_chalk33.default.dim(`, ${totalLines} line${totalLines !== 1 ? "s" : ""} redacted`)
26493
27204
  );
26494
- console.log(" Patterns: " + import_chalk32.default.yellow(totalPatterns.join(", ")));
27205
+ console.log(" Patterns: " + import_chalk33.default.yellow(totalPatterns.join(", ")));
26495
27206
  if (!dryRun) {
26496
27207
  console.log("");
26497
27208
  console.log(
26498
- import_chalk32.default.dim(
27209
+ import_chalk33.default.dim(
26499
27210
  " Note: secrets were already sent to the AI provider during the active session.\n This cleans your local disk only. Rotate any exposed keys."
26500
27211
  )
26501
27212
  );
@@ -26508,20 +27219,20 @@ function registerMaskCommand(program2) {
26508
27219
  // src/cli.ts
26509
27220
  init_blast();
26510
27221
  var { version } = JSON.parse(
26511
- import_fs58.default.readFileSync(import_path57.default.join(__dirname, "../package.json"), "utf-8")
27222
+ import_fs63.default.readFileSync(import_path61.default.join(__dirname, "../package.json"), "utf-8")
26512
27223
  );
26513
27224
  var program = new import_commander.Command();
26514
27225
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
26515
27226
  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) => {
26516
27227
  const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
26517
- const credPath = import_path57.default.join(import_os53.default.homedir(), ".node9", "credentials.json");
26518
- if (!import_fs58.default.existsSync(import_path57.default.dirname(credPath)))
26519
- import_fs58.default.mkdirSync(import_path57.default.dirname(credPath), { recursive: true });
27228
+ const credPath = import_path61.default.join(import_os55.default.homedir(), ".node9", "credentials.json");
27229
+ if (!import_fs63.default.existsSync(import_path61.default.dirname(credPath)))
27230
+ import_fs63.default.mkdirSync(import_path61.default.dirname(credPath), { recursive: true });
26520
27231
  const profileName = options.profile || "default";
26521
27232
  let existingCreds = {};
26522
27233
  try {
26523
- if (import_fs58.default.existsSync(credPath)) {
26524
- const raw = JSON.parse(import_fs58.default.readFileSync(credPath, "utf-8"));
27234
+ if (import_fs63.default.existsSync(credPath)) {
27235
+ const raw = JSON.parse(import_fs63.default.readFileSync(credPath, "utf-8"));
26525
27236
  if (raw.apiKey) {
26526
27237
  existingCreds = {
26527
27238
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
@@ -26533,14 +27244,14 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
26533
27244
  } catch {
26534
27245
  }
26535
27246
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
26536
- import_fs58.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
27247
+ import_fs63.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
26537
27248
  let effectiveCloud = null;
26538
27249
  if (profileName === "default") {
26539
- const configPath2 = import_path57.default.join(import_os53.default.homedir(), ".node9", "config.json");
27250
+ const configPath2 = import_path61.default.join(import_os55.default.homedir(), ".node9", "config.json");
26540
27251
  let config = {};
26541
27252
  try {
26542
- if (import_fs58.default.existsSync(configPath2))
26543
- config = JSON.parse(import_fs58.default.readFileSync(configPath2, "utf-8"));
27253
+ if (import_fs63.default.existsSync(configPath2))
27254
+ config = JSON.parse(import_fs63.default.readFileSync(configPath2, "utf-8"));
26544
27255
  } catch {
26545
27256
  }
26546
27257
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -26555,38 +27266,38 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
26555
27266
  approvers.cloud = false;
26556
27267
  }
26557
27268
  s.approvers = approvers;
26558
- if (!import_fs58.default.existsSync(import_path57.default.dirname(configPath2)))
26559
- import_fs58.default.mkdirSync(import_path57.default.dirname(configPath2), { recursive: true });
26560
- import_fs58.default.writeFileSync(configPath2, JSON.stringify(config, null, 2), { mode: 384 });
27269
+ if (!import_fs63.default.existsSync(import_path61.default.dirname(configPath2)))
27270
+ import_fs63.default.mkdirSync(import_path61.default.dirname(configPath2), { recursive: true });
27271
+ import_fs63.default.writeFileSync(configPath2, JSON.stringify(config, null, 2), { mode: 384 });
26561
27272
  effectiveCloud = approvers.cloud === true;
26562
27273
  }
26563
27274
  if (options.profile && profileName !== "default") {
26564
- console.log(import_chalk34.default.green(`\u2705 Profile "${profileName}" saved`));
26565
- console.log(import_chalk34.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
27275
+ console.log(import_chalk35.default.green(`\u2705 Profile "${profileName}" saved`));
27276
+ console.log(import_chalk35.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
26566
27277
  } else if (options.local || effectiveCloud === false) {
26567
- console.log(import_chalk34.default.green(`\u2705 Key saved \u2014 Privacy mode \u{1F6E1}\uFE0F`));
26568
- console.log(import_chalk34.default.gray(` All decisions stay on this machine. Nothing syncs to the cloud.`));
27278
+ console.log(import_chalk35.default.green(`\u2705 Key saved \u2014 Privacy mode \u{1F6E1}\uFE0F`));
27279
+ console.log(import_chalk35.default.gray(` All decisions stay on this machine. Nothing syncs to the cloud.`));
26569
27280
  if (!options.local) {
26570
27281
  console.log(
26571
- import_chalk34.default.yellow(` Your config has cloud approvals OFF (settings.approvers.cloud).`)
27282
+ import_chalk35.default.yellow(` Your config has cloud approvals OFF (settings.approvers.cloud).`)
26572
27283
  );
26573
27284
  console.log(
26574
- import_chalk34.default.gray(` To enable team policy + dashboard sync: set it to true, or re-init.`)
27285
+ import_chalk35.default.gray(` To enable team policy + dashboard sync: set it to true, or re-init.`)
26575
27286
  );
26576
27287
  }
26577
27288
  } else {
26578
- console.log(import_chalk34.default.green(`\u2705 Logged in \u2014 agent mode`));
26579
- console.log(import_chalk34.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
27289
+ console.log(import_chalk35.default.green(`\u2705 Logged in \u2014 agent mode`));
27290
+ console.log(import_chalk35.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
26580
27291
  }
26581
27292
  });
26582
27293
  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) => {
26583
27294
  const route = options.login ? "auth/login" : "auth/signup";
26584
27295
  const url = `https://node9.ai/${route}?ref=cli_cmd`;
26585
27296
  console.log("");
26586
- console.log(" " + import_chalk34.default.dim("Opening ") + import_chalk34.default.cyan.underline(url));
27297
+ console.log(" " + import_chalk35.default.dim("Opening ") + import_chalk35.default.cyan.underline(url));
26587
27298
  const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
26588
27299
  try {
26589
- const child = (0, import_child_process13.spawn)(opener, [url], {
27300
+ const child = (0, import_child_process15.spawn)(opener, [url], {
26590
27301
  stdio: "ignore",
26591
27302
  detached: true,
26592
27303
  shell: process.platform === "win32"
@@ -26616,7 +27327,7 @@ program.command("addto", { hidden: true }).description("Integrate Node9 with an
26616
27327
  if (target === "hermes") return setupHermes();
26617
27328
  if (target === "hud") return setupHud();
26618
27329
  console.error(
26619
- import_chalk34.default.red(
27330
+ import_chalk35.default.red(
26620
27331
  `Unknown target: "${target}". Supported: claude, antigravity, copilot, gemini, cursor, codex, windsurf, vscode, hermes, hud`
26621
27332
  )
26622
27333
  );
@@ -26630,20 +27341,20 @@ program.command("setup", { hidden: true }).description('Alias for "addto" \u2014
26630
27341
  "The agent to protect: claude | antigravity | copilot | gemini | cursor | codex | windsurf | vscode | hud"
26631
27342
  ).action(async (target) => {
26632
27343
  if (!target) {
26633
- console.log(import_chalk34.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
26634
- console.log(" Usage: " + import_chalk34.default.white("node9 setup <target>") + "\n");
27344
+ console.log(import_chalk35.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
27345
+ console.log(" Usage: " + import_chalk35.default.white("node9 setup <target>") + "\n");
26635
27346
  console.log(" Targets:");
26636
- console.log(" " + import_chalk34.default.green("claude") + " \u2014 Claude Code (hook mode)");
26637
- console.log(" " + import_chalk34.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
26638
- console.log(" " + import_chalk34.default.green("antigravity") + " \u2014 Antigravity / agy (hook mode)");
26639
- console.log(" " + import_chalk34.default.green("copilot") + " \u2014 GitHub Copilot CLI (hook mode)");
26640
- console.log(" " + import_chalk34.default.green("cursor") + " \u2014 Cursor (MCP proxy)");
26641
- console.log(" " + import_chalk34.default.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
26642
- console.log(" " + import_chalk34.default.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
26643
- console.log(" " + import_chalk34.default.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
26644
- console.log(" " + import_chalk34.default.green("hermes") + " \u2014 Hermes Agent (hook mode)");
27347
+ console.log(" " + import_chalk35.default.green("claude") + " \u2014 Claude Code (hook mode)");
27348
+ console.log(" " + import_chalk35.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
27349
+ console.log(" " + import_chalk35.default.green("antigravity") + " \u2014 Antigravity / agy (hook mode)");
27350
+ console.log(" " + import_chalk35.default.green("copilot") + " \u2014 GitHub Copilot CLI (hook mode)");
27351
+ console.log(" " + import_chalk35.default.green("cursor") + " \u2014 Cursor (MCP proxy)");
27352
+ console.log(" " + import_chalk35.default.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
27353
+ console.log(" " + import_chalk35.default.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
27354
+ console.log(" " + import_chalk35.default.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
27355
+ console.log(" " + import_chalk35.default.green("hermes") + " \u2014 Hermes Agent (hook mode)");
26645
27356
  process.stdout.write(
26646
- " " + import_chalk34.default.green("hud") + " \u2014 Claude Code security statusline\n"
27357
+ " " + import_chalk35.default.green("hud") + " \u2014 Claude Code security statusline\n"
26647
27358
  );
26648
27359
  console.log("");
26649
27360
  return;
@@ -26660,7 +27371,7 @@ program.command("setup", { hidden: true }).description('Alias for "addto" \u2014
26660
27371
  if (t === "hermes") return setupHermes();
26661
27372
  if (t === "hud") return setupHud();
26662
27373
  console.error(
26663
- import_chalk34.default.red(
27374
+ import_chalk35.default.red(
26664
27375
  `Unknown target: "${target}". Supported: claude, antigravity, copilot, gemini, cursor, codex, windsurf, vscode, hermes, hud`
26665
27376
  )
26666
27377
  );
@@ -26686,33 +27397,33 @@ program.command("removefrom", { hidden: true }).description("Remove Node9 hooks
26686
27397
  else if (target === "hud") fn = teardownHud;
26687
27398
  else {
26688
27399
  console.error(
26689
- import_chalk34.default.red(
27400
+ import_chalk35.default.red(
26690
27401
  `Unknown target: "${target}". Supported: claude, antigravity, copilot, gemini, cursor, codex, windsurf, vscode, hermes, hud`
26691
27402
  )
26692
27403
  );
26693
27404
  process.exit(1);
26694
27405
  }
26695
- console.log(import_chalk34.default.cyan(`
27406
+ console.log(import_chalk35.default.cyan(`
26696
27407
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
26697
27408
  `));
26698
27409
  try {
26699
27410
  fn();
26700
27411
  } catch (err2) {
26701
- console.error(import_chalk34.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
27412
+ console.error(import_chalk35.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
26702
27413
  process.exit(1);
26703
27414
  }
26704
- console.log(import_chalk34.default.gray("\n Restart the agent for changes to take effect."));
27415
+ console.log(import_chalk35.default.gray("\n Restart the agent for changes to take effect."));
26705
27416
  });
26706
27417
  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) => {
26707
- console.log(import_chalk34.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
26708
- console.log(import_chalk34.default.bold("Stopping daemon..."));
27418
+ console.log(import_chalk35.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
27419
+ console.log(import_chalk35.default.bold("Stopping daemon..."));
26709
27420
  try {
26710
27421
  stopDaemon();
26711
- console.log(import_chalk34.default.green(" \u2705 Daemon stopped"));
27422
+ console.log(import_chalk35.default.green(" \u2705 Daemon stopped"));
26712
27423
  } catch {
26713
- console.log(import_chalk34.default.blue(" \u2139\uFE0F Daemon was not running"));
27424
+ console.log(import_chalk35.default.blue(" \u2139\uFE0F Daemon was not running"));
26714
27425
  }
26715
- console.log(import_chalk34.default.bold("\nRemoving hooks..."));
27426
+ console.log(import_chalk35.default.bold("\nRemoving hooks..."));
26716
27427
  let teardownFailed = false;
26717
27428
  for (const [label2, fn] of [
26718
27429
  ["Claude", teardownClaude],
@@ -26728,45 +27439,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
26728
27439
  } catch (err2) {
26729
27440
  teardownFailed = true;
26730
27441
  console.error(
26731
- import_chalk34.default.red(
27442
+ import_chalk35.default.red(
26732
27443
  ` \u26A0\uFE0F Failed to remove ${label2} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
26733
27444
  )
26734
27445
  );
26735
27446
  }
26736
27447
  }
26737
27448
  if (options.purge) {
26738
- const node9Dir = import_path57.default.join(import_os53.default.homedir(), ".node9");
26739
- if (import_fs58.default.existsSync(node9Dir)) {
27449
+ const node9Dir = import_path61.default.join(import_os55.default.homedir(), ".node9");
27450
+ if (import_fs63.default.existsSync(node9Dir)) {
26740
27451
  const confirmed = await (0, import_prompts2.confirm)({
26741
27452
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
26742
27453
  default: false
26743
27454
  });
26744
27455
  if (confirmed) {
26745
- import_fs58.default.rmSync(node9Dir, { recursive: true });
26746
- if (import_fs58.default.existsSync(node9Dir)) {
27456
+ import_fs63.default.rmSync(node9Dir, { recursive: true });
27457
+ if (import_fs63.default.existsSync(node9Dir)) {
26747
27458
  console.error(
26748
- import_chalk34.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
27459
+ import_chalk35.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
26749
27460
  );
26750
27461
  } else {
26751
- console.log(import_chalk34.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
27462
+ console.log(import_chalk35.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
26752
27463
  }
26753
27464
  } else {
26754
- console.log(import_chalk34.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
27465
+ console.log(import_chalk35.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
26755
27466
  }
26756
27467
  } else {
26757
- console.log(import_chalk34.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
27468
+ console.log(import_chalk35.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
26758
27469
  }
26759
27470
  } else {
26760
27471
  console.log(
26761
- import_chalk34.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
27472
+ import_chalk35.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
26762
27473
  );
26763
27474
  }
26764
27475
  if (teardownFailed) {
26765
- console.error(import_chalk34.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
27476
+ console.error(import_chalk35.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
26766
27477
  process.exit(1);
26767
27478
  }
26768
- console.log(import_chalk34.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g node9-ai"));
26769
- console.log(import_chalk34.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
27479
+ console.log(import_chalk35.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g node9-ai"));
27480
+ console.log(import_chalk35.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
26770
27481
  });
26771
27482
  registerDoctorCommand(program, version);
26772
27483
  program.command("explain").description(
@@ -26779,7 +27490,7 @@ program.command("explain").description(
26779
27490
  try {
26780
27491
  args = JSON.parse(trimmed);
26781
27492
  } catch {
26782
- console.error(import_chalk34.default.red(`
27493
+ console.error(import_chalk35.default.red(`
26783
27494
  \u274C Invalid JSON: ${trimmed}
26784
27495
  `));
26785
27496
  process.exit(1);
@@ -26790,54 +27501,54 @@ program.command("explain").description(
26790
27501
  }
26791
27502
  const result = await explainPolicy(tool, args);
26792
27503
  console.log("");
26793
- console.log(import_chalk34.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
27504
+ console.log(import_chalk35.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
26794
27505
  console.log("");
26795
- console.log(` ${import_chalk34.default.bold("Tool:")} ${import_chalk34.default.white(result.tool)}`);
27506
+ console.log(` ${import_chalk35.default.bold("Tool:")} ${import_chalk35.default.white(result.tool)}`);
26796
27507
  if (argsRaw) {
26797
27508
  const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
26798
- console.log(` ${import_chalk34.default.bold("Input:")} ${import_chalk34.default.gray(preview2)}`);
27509
+ console.log(` ${import_chalk35.default.bold("Input:")} ${import_chalk35.default.gray(preview2)}`);
26799
27510
  }
26800
27511
  console.log("");
26801
- console.log(import_chalk34.default.bold("Config Sources (Waterfall):"));
27512
+ console.log(import_chalk35.default.bold("Config Sources (Waterfall):"));
26802
27513
  for (const tier of result.waterfall) {
26803
- const num3 = import_chalk34.default.gray(` ${tier.tier}.`);
27514
+ const num3 = import_chalk35.default.gray(` ${tier.tier}.`);
26804
27515
  const label2 = tier.label.padEnd(16);
26805
27516
  let statusStr;
26806
27517
  if (tier.tier === 1) {
26807
- statusStr = import_chalk34.default.gray(tier.note ?? "");
27518
+ statusStr = import_chalk35.default.gray(tier.note ?? "");
26808
27519
  } else if (tier.status === "active") {
26809
- const loc = tier.path ? import_chalk34.default.gray(tier.path) : "";
26810
- const note = tier.note ? import_chalk34.default.gray(`(${tier.note})`) : "";
26811
- statusStr = import_chalk34.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
27520
+ const loc = tier.path ? import_chalk35.default.gray(tier.path) : "";
27521
+ const note = tier.note ? import_chalk35.default.gray(`(${tier.note})`) : "";
27522
+ statusStr = import_chalk35.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
26812
27523
  } else {
26813
- statusStr = import_chalk34.default.gray("\u25CB " + (tier.note ?? "not found"));
27524
+ statusStr = import_chalk35.default.gray("\u25CB " + (tier.note ?? "not found"));
26814
27525
  }
26815
- console.log(`${num3} ${import_chalk34.default.white(label2)} ${statusStr}`);
27526
+ console.log(`${num3} ${import_chalk35.default.white(label2)} ${statusStr}`);
26816
27527
  }
26817
27528
  console.log("");
26818
- console.log(import_chalk34.default.bold("Policy Evaluation:"));
27529
+ console.log(import_chalk35.default.bold("Policy Evaluation:"));
26819
27530
  for (const step of result.steps) {
26820
27531
  const isFinal = step.isFinal;
26821
27532
  let icon;
26822
- if (step.outcome === "allow") icon = import_chalk34.default.green(" \u2705");
26823
- else if (step.outcome === "review") icon = import_chalk34.default.red(" \u{1F534}");
26824
- else if (step.outcome === "skip") icon = import_chalk34.default.gray(" \u2500 ");
26825
- else icon = import_chalk34.default.gray(" \u25CB ");
27533
+ if (step.outcome === "allow") icon = import_chalk35.default.green(" \u2705");
27534
+ else if (step.outcome === "review") icon = import_chalk35.default.red(" \u{1F534}");
27535
+ else if (step.outcome === "skip") icon = import_chalk35.default.gray(" \u2500 ");
27536
+ else icon = import_chalk35.default.gray(" \u25CB ");
26826
27537
  const name = step.name.padEnd(18);
26827
- const nameStr = isFinal ? import_chalk34.default.white.bold(name) : import_chalk34.default.white(name);
26828
- const detail = isFinal ? import_chalk34.default.white(step.detail) : import_chalk34.default.gray(step.detail);
26829
- const arrow = isFinal ? import_chalk34.default.yellow(" \u2190 STOP") : "";
27538
+ const nameStr = isFinal ? import_chalk35.default.white.bold(name) : import_chalk35.default.white(name);
27539
+ const detail = isFinal ? import_chalk35.default.white(step.detail) : import_chalk35.default.gray(step.detail);
27540
+ const arrow = isFinal ? import_chalk35.default.yellow(" \u2190 STOP") : "";
26830
27541
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
26831
27542
  }
26832
27543
  console.log("");
26833
27544
  if (result.decision === "allow") {
26834
- console.log(import_chalk34.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk34.default.gray(" \u2014 no approval needed"));
27545
+ console.log(import_chalk35.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk35.default.gray(" \u2014 no approval needed"));
26835
27546
  } else {
26836
27547
  console.log(
26837
- import_chalk34.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk34.default.gray(" \u2014 human approval required")
27548
+ import_chalk35.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk35.default.gray(" \u2014 human approval required")
26838
27549
  );
26839
27550
  if (result.blockedByLabel) {
26840
- console.log(import_chalk34.default.gray(` Reason: ${result.blockedByLabel}`));
27551
+ console.log(import_chalk35.default.gray(` Reason: ${result.blockedByLabel}`));
26841
27552
  }
26842
27553
  }
26843
27554
  console.log("");
@@ -26852,18 +27563,18 @@ program.command("tail").description("Stream live agent activity to the terminal"
26852
27563
  try {
26853
27564
  await startTail2(options);
26854
27565
  } catch (err2) {
26855
- console.error(import_chalk34.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
27566
+ console.error(import_chalk35.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
26856
27567
  process.exit(1);
26857
27568
  }
26858
27569
  });
26859
27570
  program.command("monitor").description("Live interactive dashboard \u2014 activity feed, approvals, security signals").action(async () => {
26860
27571
  try {
26861
- const dashboardPath = import_path57.default.join(__dirname, "dashboard.mjs");
27572
+ const dashboardPath = import_path61.default.join(__dirname, "dashboard.mjs");
26862
27573
  const dynamicImport = new Function("id", "return import(id)");
26863
27574
  const mod = await dynamicImport(`file://${dashboardPath}`);
26864
27575
  await mod.startMonitor();
26865
27576
  } catch (err2) {
26866
- console.error(import_chalk34.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
27577
+ console.error(import_chalk35.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
26867
27578
  process.exit(1);
26868
27579
  }
26869
27580
  });
@@ -26896,14 +27607,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
26896
27607
  Run "node9 addto claude" to register it as the statusLine.`
26897
27608
  ).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
26898
27609
  if (subcommand === "debug") {
26899
- const flagFile = import_path57.default.join(import_os53.default.homedir(), ".node9", "hud-debug");
27610
+ const flagFile = import_path61.default.join(import_os55.default.homedir(), ".node9", "hud-debug");
26900
27611
  if (state === "on") {
26901
- import_fs58.default.mkdirSync(import_path57.default.dirname(flagFile), { recursive: true });
26902
- import_fs58.default.writeFileSync(flagFile, "");
27612
+ import_fs63.default.mkdirSync(import_path61.default.dirname(flagFile), { recursive: true });
27613
+ import_fs63.default.writeFileSync(flagFile, "");
26903
27614
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
26904
27615
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
26905
27616
  } else if (state === "off") {
26906
- if (import_fs58.default.existsSync(flagFile)) import_fs58.default.unlinkSync(flagFile);
27617
+ if (import_fs63.default.existsSync(flagFile)) import_fs63.default.unlinkSync(flagFile);
26907
27618
  console.log("HUD debug logging disabled.");
26908
27619
  } else {
26909
27620
  console.error("Usage: node9 hud debug on|off");
@@ -26918,7 +27629,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
26918
27629
  const ms = parseDuration(options.duration);
26919
27630
  if (ms === null) {
26920
27631
  console.error(
26921
- import_chalk34.default.red(`
27632
+ import_chalk35.default.red(`
26922
27633
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
26923
27634
  `)
26924
27635
  );
@@ -26926,20 +27637,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
26926
27637
  }
26927
27638
  pauseNode9(ms, options.duration);
26928
27639
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
26929
- console.log(import_chalk34.default.yellow(`
27640
+ console.log(import_chalk35.default.yellow(`
26930
27641
  \u23F8 Node9 paused until ${expiresAt}`));
26931
- console.log(import_chalk34.default.gray(` All tool calls will be allowed without review.`));
26932
- console.log(import_chalk34.default.gray(` Run "node9 resume" to re-enable early.
27642
+ console.log(import_chalk35.default.gray(` All tool calls will be allowed without review.`));
27643
+ console.log(import_chalk35.default.gray(` Run "node9 resume" to re-enable early.
26933
27644
  `));
26934
27645
  });
26935
27646
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
26936
27647
  const { paused } = checkPause();
26937
27648
  if (!paused) {
26938
- console.log(import_chalk34.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
27649
+ console.log(import_chalk35.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
26939
27650
  return;
26940
27651
  }
26941
27652
  resumeNode9();
26942
- console.log(import_chalk34.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
27653
+ console.log(import_chalk35.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
26943
27654
  });
26944
27655
  var HOOK_BASED_AGENTS = {
26945
27656
  claude: "claude",
@@ -26955,15 +27666,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
26955
27666
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
26956
27667
  const target = HOOK_BASED_AGENTS[firstArg2];
26957
27668
  console.error(
26958
- import_chalk34.default.yellow(`
27669
+ import_chalk35.default.yellow(`
26959
27670
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
26960
27671
  );
26961
- console.error(import_chalk34.default.white(`
27672
+ console.error(import_chalk35.default.white(`
26962
27673
  "${target}" uses its own hook system. Use:`));
26963
27674
  console.error(
26964
- import_chalk34.default.green(` node9 addto ${target} `) + import_chalk34.default.gray("# one-time setup")
27675
+ import_chalk35.default.green(` node9 addto ${target} `) + import_chalk35.default.gray("# one-time setup")
26965
27676
  );
26966
- console.error(import_chalk34.default.green(` ${target} `) + import_chalk34.default.gray("# run normally"));
27677
+ console.error(import_chalk35.default.green(` ${target} `) + import_chalk35.default.gray("# run normally"));
26967
27678
  process.exit(1);
26968
27679
  }
26969
27680
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -26980,7 +27691,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
26980
27691
  }
26981
27692
  );
26982
27693
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
26983
- console.error(import_chalk34.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
27694
+ console.error(import_chalk35.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
26984
27695
  const daemonReady = await autoStartDaemonAndWait();
26985
27696
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
26986
27697
  }
@@ -26993,12 +27704,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
26993
27704
  }
26994
27705
  if (!result.approved) {
26995
27706
  console.error(
26996
- import_chalk34.default.red(`
27707
+ import_chalk35.default.red(`
26997
27708
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
26998
27709
  );
26999
27710
  process.exit(1);
27000
27711
  }
27001
- console.error(import_chalk34.default.green("\n\u2705 Approved \u2014 running command...\n"));
27712
+ console.error(import_chalk35.default.green("\n\u2705 Approved \u2014 running command...\n"));
27002
27713
  await runProxy(fullCommand);
27003
27714
  } else {
27004
27715
  program.help();
@@ -27013,6 +27724,7 @@ registerAgentsCommand(program);
27013
27724
  registerScanCommand(program);
27014
27725
  registerPostureCommand(program);
27015
27726
  registerEgressCommand(program);
27727
+ registerSandboxCommand(program, version);
27016
27728
  registerSessionsCommand(program);
27017
27729
  registerSessionTaintCommand(program);
27018
27730
  registerDlpCommand(program);
@@ -27023,9 +27735,9 @@ if (process.argv[2] !== "daemon") {
27023
27735
  const isCheckHook = process.argv[2] === "check";
27024
27736
  if (isCheckHook) {
27025
27737
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
27026
- const logPath = import_path57.default.join(import_os53.default.homedir(), ".node9", "hook-debug.log");
27738
+ const logPath = import_path61.default.join(import_os55.default.homedir(), ".node9", "hook-debug.log");
27027
27739
  const msg = reason instanceof Error ? reason.message : String(reason);
27028
- import_fs58.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
27740
+ import_fs63.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
27029
27741
  `);
27030
27742
  }
27031
27743
  process.exit(0);