@node9/proxy 1.40.0 → 1.42.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 path61 = issue.path.length > 0 ? issue.path.join(".") : "root";
210
- return ` \u2022 ${path61}: ${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, path61) {
1282
+ function getNestedValue(obj, path62) {
1278
1283
  if (!obj || typeof obj !== "object") return null;
1279
- const segments = path61.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_path58.default.join(import_os52.default.homedir(), ".claude", "projects");
17250
- if (!import_fs60.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_fs60.default.readdirSync(projectsDir)) {
17255
- const dirPath = import_path58.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_fs60.default.statSync(dirPath).isDirectory()) continue;
17258
- for (const file of import_fs60.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_path58.default.join(dirPath, file);
17290
+ const filePath = import_path59.default.join(dirPath, file);
17261
17291
  try {
17262
- const mtime = import_fs60.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_fs60.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;
@@ -17335,7 +17365,7 @@ 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_os52.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
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
  }
@@ -17374,9 +17404,9 @@ function renderPending(activity) {
17374
17404
  }
17375
17405
  async function ensureDaemon() {
17376
17406
  let pidPort = null;
17377
- if (import_fs60.default.existsSync(PID_FILE)) {
17407
+ if (import_fs61.default.existsSync(PID_FILE)) {
17378
17408
  try {
17379
- const { port } = JSON.parse(import_fs60.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
17412
  console.error(import_chalk34.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
@@ -17532,9 +17562,9 @@ function buildRecoveryCardLines(req) {
17532
17562
  ];
17533
17563
  }
17534
17564
  function readApproversFromDisk() {
17535
- const configPath2 = import_path58.default.join(import_os52.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_fs60.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 {
@@ -17550,15 +17580,15 @@ function approverStatusLine() {
17550
17580
  return `${fmt("native", "native")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
17551
17581
  }
17552
17582
  function toggleApprover(channel) {
17553
- const configPath2 = import_path58.default.join(import_os52.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_fs60.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_fs60.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
  `);
@@ -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_fs60.default.appendFileSync(
17734
- import_path58.default.join(import_os52.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
  );
@@ -17795,9 +17825,9 @@ async function startTail(options = {}) {
17795
17825
  };
17796
17826
  process.stdin.on("keypress", onKeypress);
17797
17827
  }
17798
- const auditLog = import_path58.default.join(import_os52.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_fs60.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(
@@ -17837,7 +17867,7 @@ 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_fs60.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(
@@ -18022,20 +18052,20 @@ async function startTail(options = {}) {
18022
18052
  process.exit(1);
18023
18053
  });
18024
18054
  }
18025
- var import_http3, import_chalk34, import_fs60, import_os52, import_path58, 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;
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
18060
  import_chalk34 = __toESM(require("chalk"));
18031
- import_fs60 = __toESM(require("fs"));
18032
- import_os52 = __toESM(require("os"));
18033
- import_path58 = __toESM(require("path"));
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
18065
  import_child_process14 = require("child_process");
18036
18066
  init_daemon2();
18037
18067
  init_daemon();
18038
- PID_FILE = import_path58.default.join(import_os52.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_fs61.default.existsSync(filePath)) return null;
18190
+ if (!import_fs62.default.existsSync(filePath)) return null;
18161
18191
  try {
18162
- return JSON.parse(import_fs61.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_fs61.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_fs61.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_path59.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_path59.default.resolve(a) === import_path59.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_os53.default.homedir();
18206
- const claudeDir = import_path59.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_fs61.default.existsSync(import_path59.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
18213
- rulesCount += countRulesInDir(import_path59.default.join(claudeDir, "rules"));
18214
- const userSettings = import_path59.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_path59.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_fs61.default.existsSync(import_path59.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
18224
- if (import_fs61.default.existsSync(import_path59.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
18225
- const projectClaudeDir = import_path59.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_fs61.default.existsSync(import_path59.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
18229
- rulesCount += countRulesInDir(import_path59.default.join(projectClaudeDir, "rules"));
18230
- const projSettings = import_path59.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_fs61.default.existsSync(import_path59.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
18235
- const localSettings = import_path59.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_path59.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_path59.default.join(import_os53.default.homedir(), ".node9", "shields.json");
18272
- if (!import_fs61.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_fs61.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_fs61.default.existsSync(import_path59.default.join(import_os53.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_path59.default.join(import_os53.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_fs61.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_fs61.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_path59.default.join(cwd, "node9.config.json"),
18410
- import_path59.default.join(import_os53.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_fs61.default.existsSync(configPath2)) continue;
18413
- const cfg = JSON.parse(import_fs61.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_fs61, import_path59, import_os53, 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_fs61 = __toESM(require("fs"));
18436
- import_path59 = __toESM(require("path"));
18437
- import_os53 = __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";
@@ -18461,9 +18491,9 @@ init_core();
18461
18491
  init_setup();
18462
18492
  init_daemon2();
18463
18493
  var import_chalk35 = __toESM(require("chalk"));
18464
- var import_fs62 = __toESM(require("fs"));
18465
- var import_path60 = __toESM(require("path"));
18466
- var import_os54 = __toESM(require("os"));
18494
+ var import_fs63 = __toESM(require("fs"));
18495
+ var import_path61 = __toESM(require("path"));
18496
+ var import_os55 = __toESM(require("os"));
18467
18497
  var import_child_process15 = require("child_process");
18468
18498
  var import_prompts2 = require("@inquirer/prompts");
18469
18499
 
@@ -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();
@@ -23249,6 +23429,38 @@ var TOOLS = [
23249
23429
  required: []
23250
23430
  }
23251
23431
  },
23432
+ {
23433
+ name: "node9_posture",
23434
+ description: "Run the node9 security posture scorecard for the agent on this host \u2014 grades how exposed the machine is to a compromised agent across isolation, egress, secrets-on-disk, supply chain, and privilege, with the #1 risk and a concrete fix for each finding. Read-only.",
23435
+ inputSchema: {
23436
+ type: "object",
23437
+ properties: {
23438
+ agent: {
23439
+ type: "string",
23440
+ description: "Optional label / policy scope for the agent being graded."
23441
+ }
23442
+ },
23443
+ required: []
23444
+ }
23445
+ },
23446
+ {
23447
+ name: "node9_explain",
23448
+ description: 'Preview exactly how node9 would evaluate a tool call BEFORE running it \u2014 the full allow / review / block waterfall and step-by-step policy trace. Use this to self-check a command (e.g. "git push --force") and see whether it would be allowed, sent for human review, or blocked, and why. Read-only \u2014 nothing executes.',
23449
+ inputSchema: {
23450
+ type: "object",
23451
+ properties: {
23452
+ tool: {
23453
+ type: "string",
23454
+ description: 'Tool name to evaluate. Defaults to "bash" for plain shell commands.'
23455
+ },
23456
+ args: {
23457
+ type: "string",
23458
+ description: 'Tool arguments as JSON, or a plain shell command string (e.g. "git push --force").'
23459
+ }
23460
+ },
23461
+ required: []
23462
+ }
23463
+ },
23252
23464
  {
23253
23465
  name: "node9_rule_add",
23254
23466
  description: 'Add a new protective smart rule to the global node9 config (~/.node9/config.json). Rules can block or send dangerous commands for human review based on regex conditions. IMPORTANT: only "block" and "review" verdicts are permitted \u2014 "allow" rules are never accepted because they would weaken node9 security. Rules can only be added, never removed.',
@@ -23304,13 +23516,13 @@ function handleStatus() {
23304
23516
  lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
23305
23517
  lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
23306
23518
  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");
23519
+ const projectConfig = import_path47.default.join(process.cwd(), "node9.config.json");
23520
+ const globalConfig = import_path47.default.join(import_os41.default.homedir(), ".node9", "config.json");
23309
23521
  lines.push(
23310
- `Project config (node9.config.json): ${import_fs44.default.existsSync(projectConfig) ? "present" : "not found"}`
23522
+ `Project config (node9.config.json): ${import_fs45.default.existsSync(projectConfig) ? "present" : "not found"}`
23311
23523
  );
23312
23524
  lines.push(
23313
- `Global config (~/.node9/config.json): ${import_fs44.default.existsSync(globalConfig) ? "present" : "not found"}`
23525
+ `Global config (~/.node9/config.json): ${import_fs45.default.existsSync(globalConfig) ? "present" : "not found"}`
23314
23526
  );
23315
23527
  return lines.join("\n");
23316
23528
  }
@@ -23384,21 +23596,21 @@ function handleShieldDisable(args) {
23384
23596
  writeActiveShields(active.filter((s) => s !== name));
23385
23597
  return `Shield "${name}" disabled.`;
23386
23598
  }
23387
- var GLOBAL_CONFIG_PATH = import_path46.default.join(import_os40.default.homedir(), ".node9", "config.json");
23599
+ var GLOBAL_CONFIG_PATH = import_path47.default.join(import_os41.default.homedir(), ".node9", "config.json");
23388
23600
  var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
23389
23601
  function readGlobalConfigRaw() {
23390
23602
  try {
23391
- if (import_fs44.default.existsSync(GLOBAL_CONFIG_PATH)) {
23392
- return JSON.parse(import_fs44.default.readFileSync(GLOBAL_CONFIG_PATH, "utf-8"));
23603
+ if (import_fs45.default.existsSync(GLOBAL_CONFIG_PATH)) {
23604
+ return JSON.parse(import_fs45.default.readFileSync(GLOBAL_CONFIG_PATH, "utf-8"));
23393
23605
  }
23394
23606
  } catch {
23395
23607
  }
23396
23608
  return {};
23397
23609
  }
23398
23610
  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");
23611
+ const dir = import_path47.default.dirname(GLOBAL_CONFIG_PATH);
23612
+ if (!import_fs45.default.existsSync(dir)) import_fs45.default.mkdirSync(dir, { recursive: true });
23613
+ import_fs45.default.writeFileSync(GLOBAL_CONFIG_PATH, JSON.stringify(data, null, 2) + "\n");
23402
23614
  }
23403
23615
  function handleApproverList() {
23404
23616
  const config = getConfig();
@@ -23442,9 +23654,9 @@ function handleApproverSet(args) {
23442
23654
  function handleAuditGet(args) {
23443
23655
  const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
23444
23656
  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);
23657
+ const auditPath = import_path47.default.join(import_os41.default.homedir(), ".node9", "audit.log");
23658
+ if (!import_fs45.default.existsSync(auditPath)) return "No audit log found.";
23659
+ const rawLines = import_fs45.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
23448
23660
  const parsed = [];
23449
23661
  for (const line of rawLines) {
23450
23662
  try {
@@ -23555,6 +23767,17 @@ function handleSessionMcp(args) {
23555
23767
  if (typeof args.detail === "string" && args.detail) cliArgs.push("--detail", args.detail);
23556
23768
  return runCliCommand(cliArgs);
23557
23769
  }
23770
+ function handlePostureMcp(args) {
23771
+ const cliArgs = ["posture"];
23772
+ if (typeof args.agent === "string" && args.agent) cliArgs.push("--agent", args.agent);
23773
+ return runCliCommand(cliArgs);
23774
+ }
23775
+ function handleExplainMcp(args) {
23776
+ const tool = typeof args.tool === "string" && args.tool ? args.tool : "bash";
23777
+ const cliArgs = ["explain", tool];
23778
+ if (typeof args.args === "string" && args.args) cliArgs.push(args.args);
23779
+ return runCliCommand(cliArgs);
23780
+ }
23558
23781
  function handleUndoList(args) {
23559
23782
  const cwdFilter = typeof args.cwd === "string" && args.cwd ? args.cwd : null;
23560
23783
  let history = getSnapshotHistory();
@@ -23689,6 +23912,10 @@ function runMcpServer() {
23689
23912
  text = handleReportMcp(toolArgs);
23690
23913
  } else if (toolName === "node9_session") {
23691
23914
  text = handleSessionMcp(toolArgs);
23915
+ } else if (toolName === "node9_posture") {
23916
+ text = handlePostureMcp(toolArgs);
23917
+ } else if (toolName === "node9_explain") {
23918
+ text = handleExplainMcp(toolArgs);
23692
23919
  } else {
23693
23920
  process.stdout.write(err(id, -32601, `Unknown tool: ${toolName}`) + "\n");
23694
23921
  return;
@@ -23779,7 +24006,7 @@ function registerTrustCommand(program2) {
23779
24006
  // src/cli/commands/mcp-pin.ts
23780
24007
  var import_chalk21 = __toESM(require("chalk"));
23781
24008
  init_mcp_pin();
23782
- var import_fs45 = __toESM(require("fs"));
24009
+ var import_fs46 = __toESM(require("fs"));
23783
24010
  function registerMcpPinCommand(program2) {
23784
24011
  const pinCmd = program2.command("mcp").description("Manage MCP server tool definition pinning (rug pull defense)");
23785
24012
  const pinSubCmd = pinCmd.command("pin").description("Manage pinned MCP server tool definitions");
@@ -23790,7 +24017,7 @@ function registerMcpPinCommand(program2) {
23790
24017
  let repoCorrupt = false;
23791
24018
  if (found.source === "repo") {
23792
24019
  try {
23793
- const raw = import_fs45.default.readFileSync(found.path, "utf-8");
24020
+ const raw = import_fs46.default.readFileSync(found.path, "utf-8");
23794
24021
  const parsed = JSON.parse(raw);
23795
24022
  repoEntries = parsed.servers ?? {};
23796
24023
  } catch {
@@ -24105,25 +24332,25 @@ init_scan();
24105
24332
  var import_chalk25 = __toESM(require("chalk"));
24106
24333
 
24107
24334
  // src/posture/index.ts
24108
- var import_os44 = __toESM(require("os"));
24335
+ var import_os45 = __toESM(require("os"));
24109
24336
 
24110
24337
  // 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"));
24338
+ var import_fs47 = __toESM(require("fs"));
24339
+ var import_path48 = __toESM(require("path"));
24340
+ var import_os42 = __toESM(require("os"));
24114
24341
  init_dist();
24115
24342
  var MAX_FILE_BYTES = 256 * 1024;
24116
24343
  function displayPath(p, home) {
24117
24344
  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);
24345
+ const prefix = home.endsWith(import_path48.default.sep) ? home : home + import_path48.default.sep;
24346
+ if (p.startsWith(prefix)) return "~" + import_path48.default.sep + p.slice(prefix.length);
24120
24347
  return p;
24121
24348
  }
24122
24349
  function safeRead(file) {
24123
24350
  try {
24124
- const stat = import_fs46.default.statSync(file);
24351
+ const stat = import_fs47.default.statSync(file);
24125
24352
  if (!stat.isFile() || stat.size === 0 || stat.size > MAX_FILE_BYTES) return null;
24126
- return import_fs46.default.readFileSync(file, "utf8");
24353
+ return import_fs47.default.readFileSync(file, "utf8");
24127
24354
  } catch {
24128
24355
  return null;
24129
24356
  }
@@ -24131,8 +24358,8 @@ function safeRead(file) {
24131
24358
  function candidateFiles(home, cwd) {
24132
24359
  const files = /* @__PURE__ */ new Set();
24133
24360
  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));
24361
+ for (const name of import_fs47.default.readdirSync(cwd)) {
24362
+ if (name === ".env" || name.startsWith(".env.")) files.add(import_path48.default.join(cwd, name));
24136
24363
  }
24137
24364
  } catch {
24138
24365
  }
@@ -24140,21 +24367,21 @@ function candidateFiles(home, cwd) {
24140
24367
  if (spec.hookFile) files.add(spec.hookFile(home));
24141
24368
  if (spec.mcpFile) files.add(spec.mcpFile(home));
24142
24369
  }
24143
- files.add(import_path47.default.join(home, ".env"));
24370
+ files.add(import_path48.default.join(home, ".env"));
24144
24371
  return [...files];
24145
24372
  }
24146
24373
  function credentialMaterial(home) {
24147
24374
  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")
24375
+ import_path48.default.join(home, ".ssh", "id_rsa"),
24376
+ import_path48.default.join(home, ".ssh", "id_dsa"),
24377
+ import_path48.default.join(home, ".ssh", "id_ecdsa"),
24378
+ import_path48.default.join(home, ".ssh", "id_ed25519"),
24379
+ import_path48.default.join(home, ".aws", "credentials"),
24380
+ import_path48.default.join(home, ".config", "gcloud", "application_default_credentials.json")
24154
24381
  ];
24155
24382
  }
24156
24383
  function checkSecrets(ctx) {
24157
- const home = ctx.home || import_os41.default.homedir();
24384
+ const home = ctx.home || import_os42.default.homedir();
24158
24385
  const findings = [];
24159
24386
  const plaintext = [];
24160
24387
  const plaintextPaths = [];
@@ -24187,7 +24414,7 @@ function checkSecrets(ctx) {
24187
24414
  const credPaths = [];
24188
24415
  for (const file of credentialMaterial(home)) {
24189
24416
  try {
24190
- if (import_fs46.default.statSync(file).isFile()) {
24417
+ if (import_fs47.default.statSync(file).isFile()) {
24191
24418
  creds.push(displayPath(file, home));
24192
24419
  credPaths.push(file);
24193
24420
  }
@@ -24212,7 +24439,7 @@ function checkSecrets(ctx) {
24212
24439
  }
24213
24440
 
24214
24441
  // src/posture/egress.ts
24215
- var import_fs47 = __toESM(require("fs"));
24442
+ var import_fs48 = __toESM(require("fs"));
24216
24443
  init_config();
24217
24444
 
24218
24445
  // src/sandbox/templates.ts
@@ -24334,7 +24561,7 @@ exec gosu "$RUN_AS_USER" bash -lc '
24334
24561
  // src/posture/egress.ts
24335
24562
  function sandboxEgressWallActive() {
24336
24563
  try {
24337
- return import_fs47.default.existsSync(ALLOWED_DOMAINS_PATH);
24564
+ return import_fs48.default.existsSync(ALLOWED_DOMAINS_PATH);
24338
24565
  } catch {
24339
24566
  return false;
24340
24567
  }
@@ -24443,26 +24670,26 @@ async function checkGate(ctx) {
24443
24670
  }
24444
24671
 
24445
24672
  // src/posture/supply-chain.ts
24446
- var import_fs48 = __toESM(require("fs"));
24447
- var import_os42 = __toESM(require("os"));
24448
- var import_path48 = __toESM(require("path"));
24673
+ var import_fs49 = __toESM(require("fs"));
24674
+ var import_os43 = __toESM(require("os"));
24675
+ var import_path49 = __toESM(require("path"));
24449
24676
  var import_smol_toml3 = require("smol-toml");
24450
24677
  init_provenance();
24451
24678
  var PACKAGE_RUNNERS = /* @__PURE__ */ new Set(["npx", "pnpx", "bunx", "dlx", "yarn", "pnpm", "bun"]);
24452
24679
  function isNode9Managed(command, args = []) {
24453
24680
  if (!command) return false;
24454
- if (import_path48.default.basename(command).toLowerCase() === "node9") return true;
24455
- if (PACKAGE_RUNNERS.has(import_path48.default.basename(command).toLowerCase())) {
24456
- return args.some((a) => a === "node9" || import_path48.default.basename(a).toLowerCase() === "node9");
24681
+ if (import_path49.default.basename(command).toLowerCase() === "node9") return true;
24682
+ if (PACKAGE_RUNNERS.has(import_path49.default.basename(command).toLowerCase())) {
24683
+ return args.some((a) => a === "node9" || import_path49.default.basename(a).toLowerCase() === "node9");
24457
24684
  }
24458
24685
  return false;
24459
24686
  }
24460
24687
  var MAX_CONFIG_BYTES = 10 * 1024 * 1024;
24461
24688
  function readServers(file, format, agent) {
24462
24689
  try {
24463
- const stat = import_fs48.default.statSync(file);
24690
+ const stat = import_fs49.default.statSync(file);
24464
24691
  if (!stat.isFile() || stat.size > MAX_CONFIG_BYTES) return [];
24465
- const text = import_fs48.default.readFileSync(file, "utf8");
24692
+ const text = import_fs49.default.readFileSync(file, "utf8");
24466
24693
  const map = format === "toml" ? (0, import_smol_toml3.parse)(text)?.mcp_servers : JSON.parse(text)?.mcpServers;
24467
24694
  if (!map || typeof map !== "object") return [];
24468
24695
  return Object.entries(map).map(([name, v]) => ({
@@ -24476,7 +24703,7 @@ function readServers(file, format, agent) {
24476
24703
  }
24477
24704
  }
24478
24705
  function checkSupplyChain(ctx) {
24479
- const home = ctx.home || import_os42.default.homedir();
24706
+ const home = ctx.home || import_os43.default.homedir();
24480
24707
  const servers = [];
24481
24708
  for (const spec of AGENT_SPECS) {
24482
24709
  if (!spec.mcpFile) continue;
@@ -24558,12 +24785,12 @@ async function checkPrivilege(ctx) {
24558
24785
  }
24559
24786
 
24560
24787
  // src/posture/containment.ts
24561
- var import_fs49 = __toESM(require("fs"));
24788
+ var import_fs50 = __toESM(require("fs"));
24562
24789
  var ISOLATION_WEIGHT = 12;
24563
24790
  function inContainer() {
24564
- if (import_fs49.default.existsSync("/.dockerenv") || import_fs49.default.existsSync("/run/.containerenv")) return true;
24791
+ if (import_fs50.default.existsSync("/.dockerenv") || import_fs50.default.existsSync("/run/.containerenv")) return true;
24565
24792
  try {
24566
- const cgroup = import_fs49.default.readFileSync("/proc/1/cgroup", "utf8");
24793
+ const cgroup = import_fs50.default.readFileSync("/proc/1/cgroup", "utf8");
24567
24794
  if (/docker|kubepods|containerd|lxc|libpod/.test(cgroup)) return true;
24568
24795
  } catch {
24569
24796
  }
@@ -24602,7 +24829,7 @@ Lighter \u2014 harden in place, keep full host access (about +${Math.round(
24602
24829
  }
24603
24830
 
24604
24831
  // src/posture/inbound.ts
24605
- var import_fs50 = __toESM(require("fs"));
24832
+ var import_fs51 = __toESM(require("fs"));
24606
24833
  var DB_EXPOSURE_WEIGHT = 4;
24607
24834
  var KNOWN_SERVICE_PORTS = {
24608
24835
  5432: "PostgreSQL",
@@ -24689,7 +24916,7 @@ function collectListeners() {
24689
24916
  const byPort = /* @__PURE__ */ new Map();
24690
24917
  for (const file of ["/proc/net/tcp", "/proc/net/tcp6"]) {
24691
24918
  try {
24692
- for (const l of parseListeners(import_fs50.default.readFileSync(file, "utf8"))) {
24919
+ for (const l of parseListeners(import_fs51.default.readFileSync(file, "utf8"))) {
24693
24920
  if (!byPort.has(l.port)) byPort.set(l.port, l);
24694
24921
  }
24695
24922
  } catch {
@@ -24701,11 +24928,11 @@ function readProc(pid) {
24701
24928
  let comm = "unknown";
24702
24929
  let cmdline = "";
24703
24930
  try {
24704
- comm = import_fs50.default.readFileSync(`/proc/${pid}/comm`, "utf8").trim().replace(/-MainThread$/, "") || "unknown";
24931
+ comm = import_fs51.default.readFileSync(`/proc/${pid}/comm`, "utf8").trim().replace(/-MainThread$/, "") || "unknown";
24705
24932
  } catch {
24706
24933
  }
24707
24934
  try {
24708
- cmdline = import_fs50.default.readFileSync(`/proc/${pid}/cmdline`).toString().replace(/\0/g, " ").trim();
24935
+ cmdline = import_fs51.default.readFileSync(`/proc/${pid}/cmdline`).toString().replace(/\0/g, " ").trim();
24709
24936
  } catch {
24710
24937
  }
24711
24938
  return { comm, cmdline };
@@ -24715,21 +24942,21 @@ function resolveProcesses(inodes) {
24715
24942
  if (inodes.size === 0) return map;
24716
24943
  let pids;
24717
24944
  try {
24718
- pids = import_fs50.default.readdirSync("/proc").filter((d) => /^\d+$/.test(d));
24945
+ pids = import_fs51.default.readdirSync("/proc").filter((d) => /^\d+$/.test(d));
24719
24946
  } catch {
24720
24947
  return map;
24721
24948
  }
24722
24949
  for (const pid of pids) {
24723
24950
  let fds;
24724
24951
  try {
24725
- fds = import_fs50.default.readdirSync(`/proc/${pid}/fd`);
24952
+ fds = import_fs51.default.readdirSync(`/proc/${pid}/fd`);
24726
24953
  } catch {
24727
24954
  continue;
24728
24955
  }
24729
24956
  for (const fd of fds) {
24730
24957
  let link;
24731
24958
  try {
24732
- link = import_fs50.default.readlinkSync(`/proc/${pid}/fd/${fd}`);
24959
+ link = import_fs51.default.readlinkSync(`/proc/${pid}/fd/${fd}`);
24733
24960
  } catch {
24734
24961
  continue;
24735
24962
  }
@@ -24799,10 +25026,10 @@ function checkInbound(ctx) {
24799
25026
  }
24800
25027
 
24801
25028
  // src/posture/coverage.ts
24802
- var import_os43 = __toESM(require("os"));
25029
+ var import_os44 = __toESM(require("os"));
24803
25030
  init_config();
24804
25031
  function checkCoverage(ctx) {
24805
- const home = ctx.home || import_os43.default.homedir();
25032
+ const home = ctx.home || import_os44.default.homedir();
24806
25033
  const findings = [];
24807
25034
  const protectedAgents = getAgentWiring(home).filter((r) => r.isProtected);
24808
25035
  if (protectedAgents.length === 0) {
@@ -25010,7 +25237,7 @@ async function runChecks(checks, ctx) {
25010
25237
  }
25011
25238
  async function runPosture(opts = {}) {
25012
25239
  const ctx = {
25013
- home: opts.home ?? import_os44.default.homedir(),
25240
+ home: opts.home ?? import_os45.default.homedir(),
25014
25241
  cwd: opts.cwd ?? process.cwd(),
25015
25242
  agent: opts.agent
25016
25243
  };
@@ -25278,9 +25505,9 @@ function registerPostureCommand(program2) {
25278
25505
 
25279
25506
  // src/cli/commands/egress.ts
25280
25507
  var import_chalk26 = __toESM(require("chalk"));
25281
- var import_fs51 = __toESM(require("fs"));
25282
- var import_os45 = __toESM(require("os"));
25283
- var import_path49 = __toESM(require("path"));
25508
+ var import_fs52 = __toESM(require("fs"));
25509
+ var import_os46 = __toESM(require("os"));
25510
+ var import_path50 = __toESM(require("path"));
25284
25511
  init_config();
25285
25512
  init_dist();
25286
25513
  var DEFAULT_EGRESS = {
@@ -25291,12 +25518,12 @@ var DEFAULT_EGRESS = {
25291
25518
  allowPrivate: true
25292
25519
  };
25293
25520
  function configPath() {
25294
- return import_path49.default.join(import_os45.default.homedir(), ".node9", "config.json");
25521
+ return import_path50.default.join(import_os46.default.homedir(), ".node9", "config.json");
25295
25522
  }
25296
25523
  function readRawConfig() {
25297
25524
  let text;
25298
25525
  try {
25299
- text = import_fs51.default.readFileSync(configPath(), "utf8");
25526
+ text = import_fs52.default.readFileSync(configPath(), "utf8");
25300
25527
  } catch (err2) {
25301
25528
  if (err2.code === "ENOENT") return {};
25302
25529
  throw err2;
@@ -25311,8 +25538,8 @@ function readRawConfig() {
25311
25538
  }
25312
25539
  function writeRawConfig(config) {
25313
25540
  const p = configPath();
25314
- import_fs51.default.mkdirSync(import_path49.default.dirname(p), { recursive: true });
25315
- import_fs51.default.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
25541
+ import_fs52.default.mkdirSync(import_path50.default.dirname(p), { recursive: true });
25542
+ import_fs52.default.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
25316
25543
  }
25317
25544
  function applyEgress(config, change) {
25318
25545
  const policy = config.policy = config.policy ?? {};
@@ -25405,14 +25632,14 @@ function registerEgressCommand(program2) {
25405
25632
 
25406
25633
  // src/cli/commands/sandbox.ts
25407
25634
  var import_chalk27 = __toESM(require("chalk"));
25408
- var import_fs54 = __toESM(require("fs"));
25409
- var import_path52 = __toESM(require("path"));
25635
+ var import_fs55 = __toESM(require("fs"));
25636
+ var import_path53 = __toESM(require("path"));
25410
25637
  var import_child_process13 = require("child_process");
25411
25638
  init_config();
25412
25639
 
25413
25640
  // src/sandbox/config.ts
25414
- var import_fs52 = __toESM(require("fs"));
25415
- var import_path50 = __toESM(require("path"));
25641
+ var import_fs53 = __toESM(require("fs"));
25642
+ var import_path51 = __toESM(require("path"));
25416
25643
  var import_yaml = require("yaml");
25417
25644
  var SANDBOX_CONFIG_FILE = "node9.sandbox.yaml";
25418
25645
  var FORBIDDEN_ENV = /* @__PURE__ */ new Set(["NODE9_API_KEY", "NODE9_API_URL"]);
@@ -25485,16 +25712,16 @@ function scaffoldSandboxYaml(agent) {
25485
25712
  return header + (0, import_yaml.stringify)(defaultSandboxConfig(agent));
25486
25713
  }
25487
25714
  function sandboxConfigPath(cwd = process.cwd()) {
25488
- return import_path50.default.join(cwd, SANDBOX_CONFIG_FILE);
25715
+ return import_path51.default.join(cwd, SANDBOX_CONFIG_FILE);
25489
25716
  }
25490
25717
  function loadSandboxConfig(cwd = process.cwd(), fallbackAgent = "claude") {
25491
25718
  const p = sandboxConfigPath(cwd);
25492
- if (!import_fs52.default.existsSync(p)) {
25719
+ if (!import_fs53.default.existsSync(p)) {
25493
25720
  throw new Error(`sandbox: ${SANDBOX_CONFIG_FILE} not found \u2014 run \`node9 sandbox new\` first.`);
25494
25721
  }
25495
25722
  let raw;
25496
25723
  try {
25497
- raw = (0, import_yaml.parse)(import_fs52.default.readFileSync(p, "utf-8"));
25724
+ raw = (0, import_yaml.parse)(import_fs53.default.readFileSync(p, "utf-8"));
25498
25725
  } catch (err2) {
25499
25726
  throw new Error(
25500
25727
  `sandbox: ${SANDBOX_CONFIG_FILE} is not valid YAML \u2014 ${err2.message}`
@@ -25549,13 +25776,13 @@ function compileAllowlist(input) {
25549
25776
  }
25550
25777
 
25551
25778
  // src/sandbox/runtime.ts
25552
- var import_fs53 = __toESM(require("fs"));
25553
- var import_os46 = __toESM(require("os"));
25554
- var import_path51 = __toESM(require("path"));
25779
+ var import_fs54 = __toESM(require("fs"));
25780
+ var import_os47 = __toESM(require("os"));
25781
+ var import_path52 = __toESM(require("path"));
25555
25782
  var import_crypto13 = __toESM(require("crypto"));
25556
25783
  var import_child_process12 = require("child_process");
25557
25784
  function sandboxDataDir(cwd = process.cwd()) {
25558
- return import_path51.default.join(cwd, ".node9", "sandbox", "data");
25785
+ return import_path52.default.join(cwd, ".node9", "sandbox", "data");
25559
25786
  }
25560
25787
  function detectEngine(engine) {
25561
25788
  const r = (0, import_child_process12.spawnSync)(engine, ["--version"], { encoding: "utf-8" });
@@ -25566,7 +25793,7 @@ function detectEngine(engine) {
25566
25793
  }
25567
25794
  function agentCredentialsMount(agent) {
25568
25795
  const rel = agent === "codex" ? ".codex/auth.json" : ".claude/.credentials.json";
25569
- return { hostPath: import_path51.default.join(import_os46.default.homedir(), rel), target: `/home/${RUN_AS_USER}/${rel}` };
25796
+ return { hostPath: import_path52.default.join(import_os47.default.homedir(), rel), target: `/home/${RUN_AS_USER}/${rel}` };
25570
25797
  }
25571
25798
  function buildRunArgs(opts) {
25572
25799
  const { config, workspaceHostPath, dataHostPath, allowlistHostPath, agentArgs } = opts;
@@ -25576,7 +25803,7 @@ function buildRunArgs(opts) {
25576
25803
  args.push("-v", `${allowlistHostPath}:${ALLOWED_DOMAINS_PATH}:ro`);
25577
25804
  if (config.node9.mountAgentCredentials) {
25578
25805
  const creds = agentCredentialsMount(config.agent);
25579
- if (import_fs53.default.existsSync(creds.hostPath)) {
25806
+ if (import_fs54.default.existsSync(creds.hostPath)) {
25580
25807
  args.push("-v", `${creds.hostPath}:${creds.target}`);
25581
25808
  }
25582
25809
  }
@@ -25594,30 +25821,30 @@ function imageContentHash(dockerfile, entrypoint) {
25594
25821
  return import_crypto13.default.createHash("sha256").update(dockerfile).update("\0").update(entrypoint).digest("hex").slice(0, 16);
25595
25822
  }
25596
25823
  function sandboxBuildDir(cwd = process.cwd()) {
25597
- return import_path51.default.join(cwd, ".node9", "sandbox", "build");
25824
+ return import_path52.default.join(cwd, ".node9", "sandbox", "build");
25598
25825
  }
25599
25826
  function writeBuildContext(cwd, dockerfile, entrypoint) {
25600
25827
  const dir = sandboxBuildDir(cwd);
25601
- import_fs53.default.mkdirSync(dir, { recursive: true });
25602
- import_fs53.default.writeFileSync(import_path51.default.join(dir, "Dockerfile"), dockerfile);
25603
- import_fs53.default.writeFileSync(import_path51.default.join(dir, "entrypoint.sh"), entrypoint);
25828
+ import_fs54.default.mkdirSync(dir, { recursive: true });
25829
+ import_fs54.default.writeFileSync(import_path52.default.join(dir, "Dockerfile"), dockerfile);
25830
+ import_fs54.default.writeFileSync(import_path52.default.join(dir, "entrypoint.sh"), entrypoint);
25604
25831
  return dir;
25605
25832
  }
25606
25833
  function writeAllowlist(cwd, hosts) {
25607
- const dir = import_path51.default.join(cwd, ".node9", "sandbox");
25608
- import_fs53.default.mkdirSync(dir, { recursive: true });
25609
- const p = import_path51.default.join(dir, "allowed-domains.txt");
25610
- import_fs53.default.writeFileSync(p, hosts.join("\n") + "\n");
25834
+ const dir = import_path52.default.join(cwd, ".node9", "sandbox");
25835
+ import_fs54.default.mkdirSync(dir, { recursive: true });
25836
+ const p = import_path52.default.join(dir, "allowed-domains.txt");
25837
+ import_fs54.default.writeFileSync(p, hosts.join("\n") + "\n");
25611
25838
  return p;
25612
25839
  }
25613
25840
  function resolveHomePath(p) {
25614
- return p.startsWith("~") ? import_path51.default.join(import_os46.default.homedir(), p.slice(1)) : import_path51.default.resolve(p);
25841
+ return p.startsWith("~") ? import_path52.default.join(import_os47.default.homedir(), p.slice(1)) : import_path52.default.resolve(p);
25615
25842
  }
25616
25843
 
25617
25844
  // src/cli/commands/sandbox.ts
25618
25845
  function seedDataDirConfig(dataDir, sandbox) {
25619
- import_fs54.default.mkdirSync(dataDir, { recursive: true });
25620
- const configPath2 = import_path52.default.join(dataDir, "config.json");
25846
+ import_fs55.default.mkdirSync(dataDir, { recursive: true });
25847
+ const configPath2 = import_path53.default.join(dataDir, "config.json");
25621
25848
  const seed = {
25622
25849
  settings: {
25623
25850
  approvers: {
@@ -25628,7 +25855,7 @@ function seedDataDirConfig(dataDir, sandbox) {
25628
25855
  }
25629
25856
  }
25630
25857
  };
25631
- import_fs54.default.writeFileSync(configPath2, JSON.stringify(seed, null, 2), { mode: 384 });
25858
+ import_fs55.default.writeFileSync(configPath2, JSON.stringify(seed, null, 2), { mode: 384 });
25632
25859
  }
25633
25860
  function registerSandboxCommand(program2, version2) {
25634
25861
  const node9Version2 = pinnedNode9Version(version2);
@@ -25636,13 +25863,13 @@ function registerSandboxCommand(program2, version2) {
25636
25863
  cmd.command("new").description(`Scaffold ${SANDBOX_CONFIG_FILE} in this project`).option("--agent <agent>", "claude (default) or codex", "claude").action((opts) => {
25637
25864
  const agent = opts.agent === "codex" ? "codex" : "claude";
25638
25865
  const p = sandboxConfigPath();
25639
- if (import_fs54.default.existsSync(p)) {
25866
+ if (import_fs55.default.existsSync(p)) {
25640
25867
  console.log(
25641
25868
  import_chalk27.default.yellow(` ${SANDBOX_CONFIG_FILE} already exists \u2014 leaving it untouched.`)
25642
25869
  );
25643
25870
  return;
25644
25871
  }
25645
- import_fs54.default.writeFileSync(p, scaffoldSandboxYaml(agent));
25872
+ import_fs55.default.writeFileSync(p, scaffoldSandboxYaml(agent));
25646
25873
  console.log(
25647
25874
  import_chalk27.default.green(` \u2713 wrote ${SANDBOX_CONFIG_FILE}`) + import_chalk27.default.dim(` (agent: ${agent})`)
25648
25875
  );
@@ -25682,8 +25909,8 @@ function registerSandboxCommand(program2, version2) {
25682
25909
  const buildDir = writeBuildContext(cwd, dockerfile, entrypoint);
25683
25910
  const hash = imageContentHash(dockerfile, entrypoint);
25684
25911
  const image = sandbox.runtime.image;
25685
- const hashFile = import_path52.default.join(sandboxBuildDir(cwd), ".image-hash");
25686
- const lastHash = import_fs54.default.existsSync(hashFile) ? import_fs54.default.readFileSync(hashFile, "utf-8").trim() : "";
25912
+ const hashFile = import_path53.default.join(sandboxBuildDir(cwd), ".image-hash");
25913
+ const lastHash = import_fs55.default.existsSync(hashFile) ? import_fs55.default.readFileSync(hashFile, "utf-8").trim() : "";
25687
25914
  const imageExists = (0, import_child_process13.spawnSync)(sandbox.runtime.engine, ["image", "inspect", image], { stdio: "ignore" }).status === 0;
25688
25915
  const needBuild = sandbox.runtime.rebuild === "always" || !imageExists || sandbox.runtime.rebuild !== "never" && lastHash !== hash;
25689
25916
  if (needBuild) {
@@ -25695,7 +25922,7 @@ function registerSandboxCommand(program2, version2) {
25695
25922
  console.error(import_chalk27.default.red(" build failed."));
25696
25923
  process.exit(b.status ?? 1);
25697
25924
  }
25698
- import_fs54.default.writeFileSync(hashFile, hash);
25925
+ import_fs55.default.writeFileSync(hashFile, hash);
25699
25926
  }
25700
25927
  const dataDir = sandboxDataDir(cwd);
25701
25928
  seedDataDirConfig(dataDir, sandbox);
@@ -25709,7 +25936,7 @@ function registerSandboxCommand(program2, version2) {
25709
25936
  });
25710
25937
  if (sandbox.node9.mountAgentCredentials) {
25711
25938
  const creds = agentCredentialsMount(sandbox.agent);
25712
- if (import_fs54.default.existsSync(creds.hostPath)) {
25939
+ if (import_fs55.default.existsSync(creds.hostPath)) {
25713
25940
  console.log(import_chalk27.default.dim(` mounting ${creds.hostPath} (agent credentials, rw)`));
25714
25941
  } else {
25715
25942
  console.log(
@@ -25725,20 +25952,20 @@ function registerSandboxCommand(program2, version2) {
25725
25952
  process.exit(r.status ?? 0);
25726
25953
  });
25727
25954
  cmd.command("tail").description("Stream the sandbox's audit log (host-side)").action(() => {
25728
- const auditPath = import_path52.default.join(sandboxDataDir(), "audit.log");
25729
- if (!import_fs54.default.existsSync(auditPath)) {
25955
+ const auditPath = import_path53.default.join(sandboxDataDir(), "audit.log");
25956
+ if (!import_fs55.default.existsSync(auditPath)) {
25730
25957
  console.log(import_chalk27.default.dim(" no sandbox audit yet."));
25731
25958
  return;
25732
25959
  }
25733
25960
  (0, import_child_process13.spawnSync)("tail", ["-f", auditPath], { stdio: "inherit" });
25734
25961
  });
25735
25962
  cmd.command("logs").description("Dump the sandbox's audit log").action(() => {
25736
- const auditPath = import_path52.default.join(sandboxDataDir(), "audit.log");
25737
- if (!import_fs54.default.existsSync(auditPath)) {
25963
+ const auditPath = import_path53.default.join(sandboxDataDir(), "audit.log");
25964
+ if (!import_fs55.default.existsSync(auditPath)) {
25738
25965
  console.log(import_chalk27.default.dim(" no sandbox audit yet."));
25739
25966
  return;
25740
25967
  }
25741
- process.stdout.write(import_fs54.default.readFileSync(auditPath, "utf-8"));
25968
+ process.stdout.write(import_fs55.default.readFileSync(auditPath, "utf-8"));
25742
25969
  });
25743
25970
  cmd.command("clean").description("Remove the sandbox image, build context, and data").action(() => {
25744
25971
  const cwd = process.cwd();
@@ -25752,16 +25979,16 @@ function registerSandboxCommand(program2, version2) {
25752
25979
  stdio: "ignore"
25753
25980
  });
25754
25981
  }
25755
- import_fs54.default.rmSync(import_path52.default.join(cwd, ".node9", "sandbox"), { recursive: true, force: true });
25982
+ import_fs55.default.rmSync(import_path53.default.join(cwd, ".node9", "sandbox"), { recursive: true, force: true });
25756
25983
  console.log(import_chalk27.default.green(" \u2713 sandbox image + build + data removed."));
25757
25984
  });
25758
25985
  }
25759
25986
 
25760
25987
  // src/cli/commands/sessions.ts
25761
25988
  var import_chalk28 = __toESM(require("chalk"));
25762
- var import_fs55 = __toESM(require("fs"));
25763
- var import_path53 = __toESM(require("path"));
25764
- var import_os47 = __toESM(require("os"));
25989
+ var import_fs56 = __toESM(require("fs"));
25990
+ var import_path54 = __toESM(require("path"));
25991
+ var import_os48 = __toESM(require("os"));
25765
25992
  init_scan_summary();
25766
25993
  init_litellm();
25767
25994
  init_cost_gemini();
@@ -25782,10 +26009,10 @@ function encodeProjectPath(projectPath) {
25782
26009
  }
25783
26010
  function sessionJsonlPath(projectPath, sessionId) {
25784
26011
  const encoded = encodeProjectPath(projectPath);
25785
- return import_path53.default.join(import_os47.default.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
26012
+ return import_path54.default.join(import_os48.default.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
25786
26013
  }
25787
26014
  function projectLabel(projectPath) {
25788
- return projectPath.replace(import_os47.default.homedir(), "~");
26015
+ return projectPath.replace(import_os48.default.homedir(), "~");
25789
26016
  }
25790
26017
  function parseHistoryLines(lines) {
25791
26018
  const entries = [];
@@ -25854,10 +26081,10 @@ function parseSessionLines(lines) {
25854
26081
  return { toolCalls, costUSD, hasSnapshot, modifiedFiles };
25855
26082
  }
25856
26083
  function loadAuditEntries(auditPath) {
25857
- const aPath = auditPath ?? import_path53.default.join(import_os47.default.homedir(), ".node9", "audit.log");
26084
+ const aPath = auditPath ?? import_path54.default.join(import_os48.default.homedir(), ".node9", "audit.log");
25858
26085
  let raw;
25859
26086
  try {
25860
- raw = import_fs55.default.readFileSync(aPath, "utf-8");
26087
+ raw = import_fs56.default.readFileSync(aPath, "utf-8");
25861
26088
  } catch {
25862
26089
  return [];
25863
26090
  }
@@ -25893,8 +26120,8 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
25893
26120
  return result;
25894
26121
  }
25895
26122
  function buildGeminiSessions(days, allAuditEntries) {
25896
- const tmpDir = import_path53.default.join(import_os47.default.homedir(), ".gemini", "tmp");
25897
- if (!import_fs55.default.existsSync(tmpDir)) return [];
26123
+ const tmpDir = import_path54.default.join(import_os48.default.homedir(), ".gemini", "tmp");
26124
+ if (!import_fs56.default.existsSync(tmpDir)) return [];
25898
26125
  const cutoff = days !== null ? (() => {
25899
26126
  const d = /* @__PURE__ */ new Date();
25900
26127
  d.setDate(d.getDate() - days);
@@ -25903,35 +26130,35 @@ function buildGeminiSessions(days, allAuditEntries) {
25903
26130
  })() : null;
25904
26131
  let slugDirs;
25905
26132
  try {
25906
- slugDirs = import_fs55.default.readdirSync(tmpDir);
26133
+ slugDirs = import_fs56.default.readdirSync(tmpDir);
25907
26134
  } catch {
25908
26135
  return [];
25909
26136
  }
25910
26137
  const summaries = [];
25911
26138
  for (const slug of slugDirs) {
25912
- const slugPath = import_path53.default.join(tmpDir, slug);
26139
+ const slugPath = import_path54.default.join(tmpDir, slug);
25913
26140
  try {
25914
- if (!import_fs55.default.statSync(slugPath).isDirectory()) continue;
26141
+ if (!import_fs56.default.statSync(slugPath).isDirectory()) continue;
25915
26142
  } catch {
25916
26143
  continue;
25917
26144
  }
25918
- let projectRoot = import_path53.default.join(import_os47.default.homedir(), slug);
26145
+ let projectRoot = import_path54.default.join(import_os48.default.homedir(), slug);
25919
26146
  try {
25920
- projectRoot = import_fs55.default.readFileSync(import_path53.default.join(slugPath, ".project_root"), "utf-8").trim();
26147
+ projectRoot = import_fs56.default.readFileSync(import_path54.default.join(slugPath, ".project_root"), "utf-8").trim();
25921
26148
  } catch {
25922
26149
  }
25923
- const chatsDir = import_path53.default.join(slugPath, "chats");
25924
- if (!import_fs55.default.existsSync(chatsDir)) continue;
26150
+ const chatsDir = import_path54.default.join(slugPath, "chats");
26151
+ if (!import_fs56.default.existsSync(chatsDir)) continue;
25925
26152
  let chatFiles;
25926
26153
  try {
25927
- chatFiles = import_fs55.default.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
26154
+ chatFiles = import_fs56.default.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
25928
26155
  } catch {
25929
26156
  continue;
25930
26157
  }
25931
26158
  for (const chatFile of chatFiles) {
25932
26159
  let raw;
25933
26160
  try {
25934
- raw = import_fs55.default.readFileSync(import_path53.default.join(chatsDir, chatFile), "utf-8");
26161
+ raw = import_fs56.default.readFileSync(import_path54.default.join(chatsDir, chatFile), "utf-8");
25935
26162
  } catch {
25936
26163
  continue;
25937
26164
  }
@@ -26011,8 +26238,8 @@ function buildGeminiSessions(days, allAuditEntries) {
26011
26238
  return summaries;
26012
26239
  }
26013
26240
  function buildCodexSessions(days, allAuditEntries) {
26014
- const sessionsBase = import_path53.default.join(import_os47.default.homedir(), ".codex", "sessions");
26015
- if (!import_fs55.default.existsSync(sessionsBase)) return [];
26241
+ const sessionsBase = import_path54.default.join(import_os48.default.homedir(), ".codex", "sessions");
26242
+ if (!import_fs56.default.existsSync(sessionsBase)) return [];
26016
26243
  const cutoff = days !== null ? (() => {
26017
26244
  const d = /* @__PURE__ */ new Date();
26018
26245
  d.setDate(d.getDate() - days);
@@ -26021,29 +26248,29 @@ function buildCodexSessions(days, allAuditEntries) {
26021
26248
  })() : null;
26022
26249
  const jsonlFiles = [];
26023
26250
  try {
26024
- for (const year of import_fs55.default.readdirSync(sessionsBase)) {
26025
- const yearPath = import_path53.default.join(sessionsBase, year);
26251
+ for (const year of import_fs56.default.readdirSync(sessionsBase)) {
26252
+ const yearPath = import_path54.default.join(sessionsBase, year);
26026
26253
  try {
26027
- if (!import_fs55.default.statSync(yearPath).isDirectory()) continue;
26254
+ if (!import_fs56.default.statSync(yearPath).isDirectory()) continue;
26028
26255
  } catch {
26029
26256
  continue;
26030
26257
  }
26031
- for (const month of import_fs55.default.readdirSync(yearPath)) {
26032
- const monthPath = import_path53.default.join(yearPath, month);
26258
+ for (const month of import_fs56.default.readdirSync(yearPath)) {
26259
+ const monthPath = import_path54.default.join(yearPath, month);
26033
26260
  try {
26034
- if (!import_fs55.default.statSync(monthPath).isDirectory()) continue;
26261
+ if (!import_fs56.default.statSync(monthPath).isDirectory()) continue;
26035
26262
  } catch {
26036
26263
  continue;
26037
26264
  }
26038
- for (const day of import_fs55.default.readdirSync(monthPath)) {
26039
- const dayPath = import_path53.default.join(monthPath, day);
26265
+ for (const day of import_fs56.default.readdirSync(monthPath)) {
26266
+ const dayPath = import_path54.default.join(monthPath, day);
26040
26267
  try {
26041
- if (!import_fs55.default.statSync(dayPath).isDirectory()) continue;
26268
+ if (!import_fs56.default.statSync(dayPath).isDirectory()) continue;
26042
26269
  } catch {
26043
26270
  continue;
26044
26271
  }
26045
- for (const file of import_fs55.default.readdirSync(dayPath)) {
26046
- if (file.endsWith(".jsonl")) jsonlFiles.push(import_path53.default.join(dayPath, file));
26272
+ for (const file of import_fs56.default.readdirSync(dayPath)) {
26273
+ if (file.endsWith(".jsonl")) jsonlFiles.push(import_path54.default.join(dayPath, file));
26047
26274
  }
26048
26275
  }
26049
26276
  }
@@ -26055,7 +26282,7 @@ function buildCodexSessions(days, allAuditEntries) {
26055
26282
  for (const filePath of jsonlFiles) {
26056
26283
  let lines;
26057
26284
  try {
26058
- lines = import_fs55.default.readFileSync(filePath, "utf-8").split("\n");
26285
+ lines = import_fs56.default.readFileSync(filePath, "utf-8").split("\n");
26059
26286
  } catch {
26060
26287
  continue;
26061
26288
  }
@@ -26141,10 +26368,10 @@ function buildCodexSessions(days, allAuditEntries) {
26141
26368
  return summaries;
26142
26369
  }
26143
26370
  function buildSessions(days, historyPath) {
26144
- const hPath = historyPath ?? import_path53.default.join(import_os47.default.homedir(), ".claude", "history.jsonl");
26371
+ const hPath = historyPath ?? import_path54.default.join(import_os48.default.homedir(), ".claude", "history.jsonl");
26145
26372
  let historyRaw = "";
26146
26373
  try {
26147
- historyRaw = import_fs55.default.readFileSync(hPath, "utf-8");
26374
+ historyRaw = import_fs56.default.readFileSync(hPath, "utf-8");
26148
26375
  } catch {
26149
26376
  }
26150
26377
  const cutoff = days !== null ? (() => {
@@ -26168,7 +26395,7 @@ function buildSessions(days, historyPath) {
26168
26395
  const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
26169
26396
  let sessionLines = [];
26170
26397
  try {
26171
- sessionLines = import_fs55.default.readFileSync(jsonlFile, "utf-8").split("\n");
26398
+ sessionLines = import_fs56.default.readFileSync(jsonlFile, "utf-8").split("\n");
26172
26399
  } catch {
26173
26400
  }
26174
26401
  const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
@@ -26562,12 +26789,12 @@ function registerSessionTaintCommand(program2) {
26562
26789
 
26563
26790
  // src/cli/commands/skill-pin.ts
26564
26791
  var import_chalk30 = __toESM(require("chalk"));
26565
- var import_fs56 = __toESM(require("fs"));
26566
- var import_os48 = __toESM(require("os"));
26567
- var import_path54 = __toESM(require("path"));
26792
+ var import_fs57 = __toESM(require("fs"));
26793
+ var import_os49 = __toESM(require("os"));
26794
+ var import_path55 = __toESM(require("path"));
26568
26795
  function wipeSkillSessions() {
26569
26796
  try {
26570
- import_fs56.default.rmSync(import_path54.default.join(import_os48.default.homedir(), ".node9", "skill-sessions"), {
26797
+ import_fs57.default.rmSync(import_path55.default.join(import_os49.default.homedir(), ".node9", "skill-sessions"), {
26571
26798
  recursive: true,
26572
26799
  force: true
26573
26800
  });
@@ -26649,15 +26876,15 @@ function registerSkillPinCommand(program2) {
26649
26876
  }
26650
26877
 
26651
26878
  // src/cli/commands/decisions.ts
26652
- var import_fs57 = __toESM(require("fs"));
26653
- var import_os49 = __toESM(require("os"));
26654
- var import_path55 = __toESM(require("path"));
26879
+ var import_fs58 = __toESM(require("fs"));
26880
+ var import_os50 = __toESM(require("os"));
26881
+ var import_path56 = __toESM(require("path"));
26655
26882
  var import_chalk31 = __toESM(require("chalk"));
26656
- var DECISIONS_FILE2 = import_path55.default.join(import_os49.default.homedir(), ".node9", "decisions.json");
26883
+ var DECISIONS_FILE2 = import_path56.default.join(import_os50.default.homedir(), ".node9", "decisions.json");
26657
26884
  function readDecisions() {
26658
26885
  try {
26659
- if (!import_fs57.default.existsSync(DECISIONS_FILE2)) return {};
26660
- const raw = import_fs57.default.readFileSync(DECISIONS_FILE2, "utf-8");
26886
+ if (!import_fs58.default.existsSync(DECISIONS_FILE2)) return {};
26887
+ const raw = import_fs58.default.readFileSync(DECISIONS_FILE2, "utf-8");
26661
26888
  const parsed = JSON.parse(raw);
26662
26889
  const out = {};
26663
26890
  for (const [k, v] of Object.entries(parsed)) {
@@ -26669,11 +26896,11 @@ function readDecisions() {
26669
26896
  }
26670
26897
  }
26671
26898
  function writeDecisions(d) {
26672
- const dir = import_path55.default.dirname(DECISIONS_FILE2);
26673
- if (!import_fs57.default.existsSync(dir)) import_fs57.default.mkdirSync(dir, { recursive: true });
26899
+ const dir = import_path56.default.dirname(DECISIONS_FILE2);
26900
+ if (!import_fs58.default.existsSync(dir)) import_fs58.default.mkdirSync(dir, { recursive: true });
26674
26901
  const tmp = `${DECISIONS_FILE2}.${process.pid}.tmp`;
26675
- import_fs57.default.writeFileSync(tmp, JSON.stringify(d, null, 2));
26676
- import_fs57.default.renameSync(tmp, DECISIONS_FILE2);
26902
+ import_fs58.default.writeFileSync(tmp, JSON.stringify(d, null, 2));
26903
+ import_fs58.default.renameSync(tmp, DECISIONS_FILE2);
26677
26904
  }
26678
26905
  function registerDecisionsCommand(program2) {
26679
26906
  const cmd = program2.command("decisions").description('Manage persistent "Always Allow" / "Always Deny" tool decisions');
@@ -26730,18 +26957,18 @@ Persistent decisions (${entries.length})
26730
26957
 
26731
26958
  // src/cli/commands/dlp.ts
26732
26959
  var import_chalk32 = __toESM(require("chalk"));
26733
- var import_fs58 = __toESM(require("fs"));
26734
- var import_path56 = __toESM(require("path"));
26735
- var import_os50 = __toESM(require("os"));
26736
- var AUDIT_LOG = import_path56.default.join(import_os50.default.homedir(), ".node9", "audit.log");
26737
- var RESOLVED_FILE = import_path56.default.join(import_os50.default.homedir(), ".node9", "dlp-resolved.json");
26960
+ var import_fs59 = __toESM(require("fs"));
26961
+ var import_path57 = __toESM(require("path"));
26962
+ var import_os51 = __toESM(require("os"));
26963
+ var AUDIT_LOG = import_path57.default.join(import_os51.default.homedir(), ".node9", "audit.log");
26964
+ var RESOLVED_FILE = import_path57.default.join(import_os51.default.homedir(), ".node9", "dlp-resolved.json");
26738
26965
  var ANSI_RE = /\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g;
26739
26966
  function stripAnsi(s) {
26740
26967
  return s.replace(ANSI_RE, "");
26741
26968
  }
26742
26969
  function loadResolved() {
26743
26970
  try {
26744
- const raw = JSON.parse(import_fs58.default.readFileSync(RESOLVED_FILE, "utf-8"));
26971
+ const raw = JSON.parse(import_fs59.default.readFileSync(RESOLVED_FILE, "utf-8"));
26745
26972
  return new Set(raw);
26746
26973
  } catch {
26747
26974
  return /* @__PURE__ */ new Set();
@@ -26749,13 +26976,13 @@ function loadResolved() {
26749
26976
  }
26750
26977
  function saveResolved(resolved) {
26751
26978
  try {
26752
- import_fs58.default.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
26979
+ import_fs59.default.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
26753
26980
  } catch {
26754
26981
  }
26755
26982
  }
26756
26983
  function loadDlpFindings() {
26757
- if (!import_fs58.default.existsSync(AUDIT_LOG)) return [];
26758
- return import_fs58.default.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
26984
+ if (!import_fs59.default.existsSync(AUDIT_LOG)) return [];
26985
+ return import_fs59.default.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
26759
26986
  if (!line.trim()) return [];
26760
26987
  try {
26761
26988
  const e = JSON.parse(line);
@@ -26853,15 +27080,15 @@ function registerDlpCommand(program2) {
26853
27080
 
26854
27081
  // src/cli/commands/mask.ts
26855
27082
  var import_chalk33 = __toESM(require("chalk"));
26856
- var import_fs59 = __toESM(require("fs"));
26857
- var import_path57 = __toESM(require("path"));
26858
- var import_os51 = __toESM(require("os"));
27083
+ var import_fs60 = __toESM(require("fs"));
27084
+ var import_path58 = __toESM(require("path"));
27085
+ var import_os52 = __toESM(require("os"));
26859
27086
  init_dlp();
26860
27087
  function findJsonlFiles(dir) {
26861
27088
  const results = [];
26862
- if (!import_fs59.default.existsSync(dir)) return results;
26863
- for (const entry of import_fs59.default.readdirSync(dir, { withFileTypes: true })) {
26864
- const full = import_path57.default.join(dir, entry.name);
27089
+ if (!import_fs60.default.existsSync(dir)) return results;
27090
+ for (const entry of import_fs60.default.readdirSync(dir, { withFileTypes: true })) {
27091
+ const full = import_path58.default.join(dir, entry.name);
26865
27092
  if (entry.isDirectory()) results.push(...findJsonlFiles(full));
26866
27093
  else if (entry.isFile() && entry.name.endsWith(".jsonl")) results.push(full);
26867
27094
  }
@@ -26904,7 +27131,7 @@ function redactJson(obj) {
26904
27131
  function processFile(filePath, dryRun) {
26905
27132
  let raw;
26906
27133
  try {
26907
- raw = import_fs59.default.readFileSync(filePath, "utf-8");
27134
+ raw = import_fs60.default.readFileSync(filePath, "utf-8");
26908
27135
  } catch {
26909
27136
  return { redactedLines: 0, patterns: [] };
26910
27137
  }
@@ -26936,14 +27163,14 @@ function processFile(filePath, dryRun) {
26936
27163
  }
26937
27164
  }
26938
27165
  if (!dryRun && redactedLines > 0) {
26939
- import_fs59.default.writeFileSync(filePath, newLines.join("\n"), "utf-8");
27166
+ import_fs60.default.writeFileSync(filePath, newLines.join("\n"), "utf-8");
26940
27167
  }
26941
27168
  return { redactedLines, patterns };
26942
27169
  }
26943
27170
  function processJsonFile(filePath, dryRun) {
26944
27171
  let raw;
26945
27172
  try {
26946
- raw = import_fs59.default.readFileSync(filePath, "utf-8");
27173
+ raw = import_fs60.default.readFileSync(filePath, "utf-8");
26947
27174
  } catch {
26948
27175
  return { redactedLines: 0, patterns: [] };
26949
27176
  }
@@ -26956,15 +27183,15 @@ function processJsonFile(filePath, dryRun) {
26956
27183
  const { value, modified, found } = redactJson(parsed);
26957
27184
  if (!modified) return { redactedLines: 0, patterns: [] };
26958
27185
  if (!dryRun) {
26959
- import_fs59.default.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
27186
+ import_fs60.default.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
26960
27187
  }
26961
27188
  return { redactedLines: 1, patterns: found };
26962
27189
  }
26963
27190
  function findJsonFiles(dir) {
26964
27191
  const results = [];
26965
- if (!import_fs59.default.existsSync(dir)) return results;
26966
- for (const entry of import_fs59.default.readdirSync(dir, { withFileTypes: true })) {
26967
- const full = import_path57.default.join(dir, entry.name);
27192
+ if (!import_fs60.default.existsSync(dir)) return results;
27193
+ for (const entry of import_fs60.default.readdirSync(dir, { withFileTypes: true })) {
27194
+ const full = import_path58.default.join(dir, entry.name);
26968
27195
  if (entry.isDirectory()) results.push(...findJsonFiles(full));
26969
27196
  else if (entry.isFile() && entry.name.endsWith(".json")) results.push(full);
26970
27197
  }
@@ -26973,9 +27200,9 @@ function findJsonFiles(dir) {
26973
27200
  function registerMaskCommand(program2) {
26974
27201
  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) => {
26975
27202
  const dryRun = !!options.dryRun;
26976
- const home = import_os51.default.homedir();
26977
- const claudeDir = import_path57.default.join(home, ".claude", "projects");
26978
- const geminiDir = import_path57.default.join(home, ".gemini", "tmp");
27203
+ const home = import_os52.default.homedir();
27204
+ const claudeDir = import_path58.default.join(home, ".claude", "projects");
27205
+ const geminiDir = import_path58.default.join(home, ".gemini", "tmp");
26979
27206
  const allFiles = [
26980
27207
  ...findJsonlFiles(claudeDir).map((p) => ({ path: p, type: "jsonl" })),
26981
27208
  ...findJsonFiles(geminiDir).map((p) => ({ path: p, type: "json" }))
@@ -26983,7 +27210,7 @@ function registerMaskCommand(program2) {
26983
27210
  const cutoff = options.all ? null : new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3);
26984
27211
  const filtered = cutoff ? allFiles.filter((f) => {
26985
27212
  try {
26986
- return import_fs59.default.statSync(f.path).mtime >= cutoff;
27213
+ return import_fs60.default.statSync(f.path).mtime >= cutoff;
26987
27214
  } catch {
26988
27215
  return false;
26989
27216
  }
@@ -27039,20 +27266,20 @@ function registerMaskCommand(program2) {
27039
27266
  // src/cli.ts
27040
27267
  init_blast();
27041
27268
  var { version } = JSON.parse(
27042
- import_fs62.default.readFileSync(import_path60.default.join(__dirname, "../package.json"), "utf-8")
27269
+ import_fs63.default.readFileSync(import_path61.default.join(__dirname, "../package.json"), "utf-8")
27043
27270
  );
27044
27271
  var program = new import_commander.Command();
27045
27272
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
27046
27273
  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) => {
27047
27274
  const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
27048
- const credPath = import_path60.default.join(import_os54.default.homedir(), ".node9", "credentials.json");
27049
- if (!import_fs62.default.existsSync(import_path60.default.dirname(credPath)))
27050
- import_fs62.default.mkdirSync(import_path60.default.dirname(credPath), { recursive: true });
27275
+ const credPath = import_path61.default.join(import_os55.default.homedir(), ".node9", "credentials.json");
27276
+ if (!import_fs63.default.existsSync(import_path61.default.dirname(credPath)))
27277
+ import_fs63.default.mkdirSync(import_path61.default.dirname(credPath), { recursive: true });
27051
27278
  const profileName = options.profile || "default";
27052
27279
  let existingCreds = {};
27053
27280
  try {
27054
- if (import_fs62.default.existsSync(credPath)) {
27055
- const raw = JSON.parse(import_fs62.default.readFileSync(credPath, "utf-8"));
27281
+ if (import_fs63.default.existsSync(credPath)) {
27282
+ const raw = JSON.parse(import_fs63.default.readFileSync(credPath, "utf-8"));
27056
27283
  if (raw.apiKey) {
27057
27284
  existingCreds = {
27058
27285
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
@@ -27064,14 +27291,14 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
27064
27291
  } catch {
27065
27292
  }
27066
27293
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
27067
- import_fs62.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
27294
+ import_fs63.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
27068
27295
  let effectiveCloud = null;
27069
27296
  if (profileName === "default") {
27070
- const configPath2 = import_path60.default.join(import_os54.default.homedir(), ".node9", "config.json");
27297
+ const configPath2 = import_path61.default.join(import_os55.default.homedir(), ".node9", "config.json");
27071
27298
  let config = {};
27072
27299
  try {
27073
- if (import_fs62.default.existsSync(configPath2))
27074
- config = JSON.parse(import_fs62.default.readFileSync(configPath2, "utf-8"));
27300
+ if (import_fs63.default.existsSync(configPath2))
27301
+ config = JSON.parse(import_fs63.default.readFileSync(configPath2, "utf-8"));
27075
27302
  } catch {
27076
27303
  }
27077
27304
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -27086,9 +27313,9 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
27086
27313
  approvers.cloud = false;
27087
27314
  }
27088
27315
  s.approvers = approvers;
27089
- if (!import_fs62.default.existsSync(import_path60.default.dirname(configPath2)))
27090
- import_fs62.default.mkdirSync(import_path60.default.dirname(configPath2), { recursive: true });
27091
- import_fs62.default.writeFileSync(configPath2, JSON.stringify(config, null, 2), { mode: 384 });
27316
+ if (!import_fs63.default.existsSync(import_path61.default.dirname(configPath2)))
27317
+ import_fs63.default.mkdirSync(import_path61.default.dirname(configPath2), { recursive: true });
27318
+ import_fs63.default.writeFileSync(configPath2, JSON.stringify(config, null, 2), { mode: 384 });
27092
27319
  effectiveCloud = approvers.cloud === true;
27093
27320
  }
27094
27321
  if (options.profile && profileName !== "default") {
@@ -27266,15 +27493,15 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
27266
27493
  }
27267
27494
  }
27268
27495
  if (options.purge) {
27269
- const node9Dir = import_path60.default.join(import_os54.default.homedir(), ".node9");
27270
- if (import_fs62.default.existsSync(node9Dir)) {
27496
+ const node9Dir = import_path61.default.join(import_os55.default.homedir(), ".node9");
27497
+ if (import_fs63.default.existsSync(node9Dir)) {
27271
27498
  const confirmed = await (0, import_prompts2.confirm)({
27272
27499
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
27273
27500
  default: false
27274
27501
  });
27275
27502
  if (confirmed) {
27276
- import_fs62.default.rmSync(node9Dir, { recursive: true });
27277
- if (import_fs62.default.existsSync(node9Dir)) {
27503
+ import_fs63.default.rmSync(node9Dir, { recursive: true });
27504
+ if (import_fs63.default.existsSync(node9Dir)) {
27278
27505
  console.error(
27279
27506
  import_chalk35.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
27280
27507
  );
@@ -27389,7 +27616,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
27389
27616
  });
27390
27617
  program.command("monitor").description("Live interactive dashboard \u2014 activity feed, approvals, security signals").action(async () => {
27391
27618
  try {
27392
- const dashboardPath = import_path60.default.join(__dirname, "dashboard.mjs");
27619
+ const dashboardPath = import_path61.default.join(__dirname, "dashboard.mjs");
27393
27620
  const dynamicImport = new Function("id", "return import(id)");
27394
27621
  const mod = await dynamicImport(`file://${dashboardPath}`);
27395
27622
  await mod.startMonitor();
@@ -27427,14 +27654,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
27427
27654
  Run "node9 addto claude" to register it as the statusLine.`
27428
27655
  ).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
27429
27656
  if (subcommand === "debug") {
27430
- const flagFile = import_path60.default.join(import_os54.default.homedir(), ".node9", "hud-debug");
27657
+ const flagFile = import_path61.default.join(import_os55.default.homedir(), ".node9", "hud-debug");
27431
27658
  if (state === "on") {
27432
- import_fs62.default.mkdirSync(import_path60.default.dirname(flagFile), { recursive: true });
27433
- import_fs62.default.writeFileSync(flagFile, "");
27659
+ import_fs63.default.mkdirSync(import_path61.default.dirname(flagFile), { recursive: true });
27660
+ import_fs63.default.writeFileSync(flagFile, "");
27434
27661
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
27435
27662
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
27436
27663
  } else if (state === "off") {
27437
- if (import_fs62.default.existsSync(flagFile)) import_fs62.default.unlinkSync(flagFile);
27664
+ if (import_fs63.default.existsSync(flagFile)) import_fs63.default.unlinkSync(flagFile);
27438
27665
  console.log("HUD debug logging disabled.");
27439
27666
  } else {
27440
27667
  console.error("Usage: node9 hud debug on|off");
@@ -27555,9 +27782,9 @@ if (process.argv[2] !== "daemon") {
27555
27782
  const isCheckHook = process.argv[2] === "check";
27556
27783
  if (isCheckHook) {
27557
27784
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
27558
- const logPath = import_path60.default.join(import_os54.default.homedir(), ".node9", "hook-debug.log");
27785
+ const logPath = import_path61.default.join(import_os55.default.homedir(), ".node9", "hook-debug.log");
27559
27786
  const msg = reason instanceof Error ? reason.message : String(reason);
27560
- import_fs62.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
27787
+ import_fs63.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
27561
27788
  `);
27562
27789
  }
27563
27790
  process.exit(0);