@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.mjs CHANGED
@@ -185,8 +185,8 @@ function sanitizeConfig(raw) {
185
185
  }
186
186
  }
187
187
  const lines = result.error.issues.map((issue) => {
188
- const path61 = issue.path.length > 0 ? issue.path.join(".") : "root";
189
- return ` \u2022 ${path61}: ${issue.message}`;
188
+ const path62 = issue.path.length > 0 ? issue.path.join(".") : "root";
189
+ return ` \u2022 ${path62}: ${issue.message}`;
190
190
  });
191
191
  return {
192
192
  sanitized,
@@ -271,6 +271,11 @@ var init_config_schema = __esm({
271
271
  allowGlobalPause: z.boolean().optional(),
272
272
  auditHashArgs: z.boolean().optional(),
273
273
  agentPolicy: z.enum(["require_approval", "block_on_rules"]).optional(),
274
+ // Where a `review` verdict's prompt is rendered: 'ask' = the agent's own
275
+ // inline approve/deny prompt (Claude Code / GitHub Copilot); 'approver' =
276
+ // node9's own approver (terminal/native/cloud). Unset → smart default
277
+ // (ask for ask-capable agents unless a cloud approver is configured).
278
+ reviewChannel: z.enum(["ask", "approver"]).optional(),
274
279
  cloudSyncIntervalHours: z.number().positive().optional(),
275
280
  // Outbox shipper (audit.log → SaaS batch ingest). enabled defaults
276
281
  // to true; set false to fall back to local-only auditing.
@@ -1258,9 +1263,9 @@ function matchesPattern(text, patterns) {
1258
1263
  const withoutDotSlash = text.replace(/^\.\//, "");
1259
1264
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1260
1265
  }
1261
- function getNestedValue(obj, path61) {
1266
+ function getNestedValue(obj, path62) {
1262
1267
  if (!obj || typeof obj !== "object") return null;
1263
- const segments = path61.split(".");
1268
+ const segments = path62.split(".");
1264
1269
  for (const seg of segments) {
1265
1270
  if (FORBIDDEN_PATH_SEGMENTS.has(seg)) return null;
1266
1271
  }
@@ -4178,6 +4183,7 @@ function getConfig(cwd) {
4178
4183
  if (s.approvalTimeoutSeconds !== void 0 && s.approvalTimeoutMs === void 0)
4179
4184
  mergedSettings.approvalTimeoutMs = s.approvalTimeoutSeconds * 1e3;
4180
4185
  if (s.environment !== void 0) mergedSettings.environment = s.environment;
4186
+ if (s.reviewChannel !== void 0) mergedSettings.reviewChannel = s.reviewChannel;
4181
4187
  if (s.cloudSyncIntervalHours !== void 0)
4182
4188
  mergedSettings.cloudSyncIntervalHours = s.cloudSyncIntervalHours;
4183
4189
  if (s.hud !== void 0) mergedSettings.hud = { ...mergedSettings.hud, ...s.hud };
@@ -6330,7 +6336,7 @@ async function authorizeHeadless(toolName, args, meta, options) {
6330
6336
  tool: toolName,
6331
6337
  args,
6332
6338
  ts: actTs,
6333
- status: result.approved ? "allow" : result.blockedByLabel?.includes("DLP") ? "dlp" : result.blockedByLabel?.includes("Taint") ? "taint" : "block",
6339
+ status: result.review ? "review" : result.approved ? "allow" : result.blockedByLabel?.includes("DLP") ? "dlp" : result.blockedByLabel?.includes("Taint") ? "taint" : "block",
6334
6340
  label: result.blockedByLabel,
6335
6341
  ruleHit: result.ruleHit,
6336
6342
  observeWouldBlock: result.observeWouldBlock,
@@ -6693,6 +6699,16 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
6693
6699
  taintWarning
6694
6700
  );
6695
6701
  }
6702
+ const cloudEnforcedForDefer = approvers.cloud && !!creds?.apiKey;
6703
+ if (options?.deferReview && !taintWarning && !cloudEnforcedForDefer) {
6704
+ return {
6705
+ approved: false,
6706
+ review: true,
6707
+ reason: explainableLabel || "Node9 flagged this action for review.",
6708
+ ruleDescription: policyRuleDescription,
6709
+ blockedByLabel: explainableLabel
6710
+ };
6711
+ }
6696
6712
  let cloudRequestId = null;
6697
6713
  const cloudEnforced = approvers.cloud && !!creds?.apiKey;
6698
6714
  const forceReview = localSmartRuleMatched === true || options?.localSmartRuleMatched === true || void 0;
@@ -7504,6 +7520,18 @@ function removeNode9McpServer(servers) {
7504
7520
  function printDaemonTip() {
7505
7521
  console.log(chalk.cyan("\n \u{1F4A1} Node9 will protect you automatically using Native OS popups."));
7506
7522
  }
7523
+ function printInlineAskNotice() {
7524
+ console.log(
7525
+ chalk.cyan(
7526
+ " \u{1F4AC} Review prompts appear inline in your agent (approve/deny in the chat) by default."
7527
+ )
7528
+ );
7529
+ console.log(
7530
+ chalk.gray(
7531
+ ' 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.)'
7532
+ )
7533
+ );
7534
+ }
7507
7535
  function fullPathCommand(subcommand) {
7508
7536
  if (process.env.NODE9_TESTING === "1") return `node9 ${subcommand}`;
7509
7537
  const nodeExec = toForwardSlashes(process.execPath);
@@ -7828,6 +7856,7 @@ async function setupClaude() {
7828
7856
  console.log(chalk.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Claude Code!"));
7829
7857
  console.log(chalk.gray(" Restart Claude Code for changes to take effect."));
7830
7858
  printDaemonTip();
7859
+ printInlineAskNotice();
7831
7860
  }
7832
7861
  }
7833
7862
  async function setupGemini() {
@@ -8224,6 +8253,7 @@ async function setupCopilot() {
8224
8253
  console.log(chalk.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting GitHub Copilot CLI!"));
8225
8254
  console.log(chalk.gray(" Restart Copilot CLI for changes to take effect."));
8226
8255
  printDaemonTip();
8256
+ printInlineAskNotice();
8227
8257
  }
8228
8258
  function teardownCopilot() {
8229
8259
  const homeDir2 = os12.homedir();
@@ -15510,8 +15540,8 @@ function fileSignature(filePath) {
15510
15540
  const fd = fs27.openSync(filePath, "r");
15511
15541
  try {
15512
15542
  const buf = Buffer.alloc(512);
15513
- const read = fs27.readSync(fd, buf, 0, 512, 0);
15514
- const slice = buf.subarray(0, read);
15543
+ const read2 = fs27.readSync(fd, buf, 0, 512, 0);
15544
+ const slice = buf.subarray(0, read2);
15515
15545
  const nl = slice.indexOf(10);
15516
15546
  const firstLine = nl === -1 ? slice : slice.subarray(0, nl);
15517
15547
  return crypto5.createHash("sha256").update(firstLine).digest("hex").slice(0, 16);
@@ -15615,13 +15645,13 @@ async function shipOnce(deps = {}) {
15615
15645
  const toRead = Math.min(size - offset, MAX_CHUNK_BYTES);
15616
15646
  const buf = Buffer.alloc(toRead);
15617
15647
  const fd = fs27.openSync(auditLogPath, "r");
15618
- let read;
15648
+ let read2;
15619
15649
  try {
15620
- read = fs27.readSync(fd, buf, 0, toRead, offset);
15650
+ read2 = fs27.readSync(fd, buf, 0, toRead, offset);
15621
15651
  } finally {
15622
15652
  fs27.closeSync(fd);
15623
15653
  }
15624
- const { rows, consumed } = buildWireRows(buf.subarray(0, read));
15654
+ const { rows, consumed } = buildWireRows(buf.subarray(0, read2));
15625
15655
  if (consumed === 0) break;
15626
15656
  for (let i = 0; i < rows.length; i += MAX_BATCH) {
15627
15657
  const batch = rows.slice(i, i + MAX_BATCH);
@@ -17199,9 +17229,9 @@ __export(tail_exports, {
17199
17229
  });
17200
17230
  import http3 from "http";
17201
17231
  import chalk34 from "chalk";
17202
- import fs60 from "fs";
17203
- import os52 from "os";
17204
- import path58 from "path";
17232
+ import fs61 from "fs";
17233
+ import os53 from "os";
17234
+ import path59 from "path";
17205
17235
  import readline6 from "readline";
17206
17236
  import { spawn as spawn8 } from "child_process";
17207
17237
  function shortenPathSummary(s) {
@@ -17225,20 +17255,20 @@ function getModelContextLimit(model) {
17225
17255
  return 2e5;
17226
17256
  }
17227
17257
  function readSessionUsage() {
17228
- const projectsDir = path58.join(os52.homedir(), ".claude", "projects");
17229
- if (!fs60.existsSync(projectsDir)) return null;
17258
+ const projectsDir = path59.join(os53.homedir(), ".claude", "projects");
17259
+ if (!fs61.existsSync(projectsDir)) return null;
17230
17260
  let latestFile = null;
17231
17261
  let latestMtime = 0;
17232
17262
  try {
17233
- for (const dir of fs60.readdirSync(projectsDir)) {
17234
- const dirPath = path58.join(projectsDir, dir);
17263
+ for (const dir of fs61.readdirSync(projectsDir)) {
17264
+ const dirPath = path59.join(projectsDir, dir);
17235
17265
  try {
17236
- if (!fs60.statSync(dirPath).isDirectory()) continue;
17237
- for (const file of fs60.readdirSync(dirPath)) {
17266
+ if (!fs61.statSync(dirPath).isDirectory()) continue;
17267
+ for (const file of fs61.readdirSync(dirPath)) {
17238
17268
  if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
17239
- const filePath = path58.join(dirPath, file);
17269
+ const filePath = path59.join(dirPath, file);
17240
17270
  try {
17241
- const mtime = fs60.statSync(filePath).mtimeMs;
17271
+ const mtime = fs61.statSync(filePath).mtimeMs;
17242
17272
  if (mtime > latestMtime) {
17243
17273
  latestMtime = mtime;
17244
17274
  latestFile = filePath;
@@ -17253,7 +17283,7 @@ function readSessionUsage() {
17253
17283
  }
17254
17284
  if (!latestFile) return null;
17255
17285
  try {
17256
- const lines = fs60.readFileSync(latestFile, "utf-8").split("\n");
17286
+ const lines = fs61.readFileSync(latestFile, "utf-8").split("\n");
17257
17287
  let lastModel = "";
17258
17288
  let lastInput = 0;
17259
17289
  let lastOutput = 0;
@@ -17314,7 +17344,7 @@ function formatBase(activity) {
17314
17344
  const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
17315
17345
  const icon = getIcon(activity.tool);
17316
17346
  const toolName = activity.tool.slice(0, 16).padEnd(16);
17317
- const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os52.homedir(), "~");
17347
+ const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os53.homedir(), "~");
17318
17348
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
17319
17349
  return `${chalk34.gray(time)} ${icon} ${agentLabel(activity.agent, activity.mcpServer, activity.sessionId)}${chalk34.white.bold(toolName)} ${chalk34.dim(argsPreview)}`;
17320
17350
  }
@@ -17353,9 +17383,9 @@ function renderPending(activity) {
17353
17383
  }
17354
17384
  async function ensureDaemon() {
17355
17385
  let pidPort = null;
17356
- if (fs60.existsSync(PID_FILE)) {
17386
+ if (fs61.existsSync(PID_FILE)) {
17357
17387
  try {
17358
- const { port } = JSON.parse(fs60.readFileSync(PID_FILE, "utf-8"));
17388
+ const { port } = JSON.parse(fs61.readFileSync(PID_FILE, "utf-8"));
17359
17389
  pidPort = port;
17360
17390
  } catch {
17361
17391
  console.error(chalk34.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
@@ -17511,9 +17541,9 @@ function buildRecoveryCardLines(req) {
17511
17541
  ];
17512
17542
  }
17513
17543
  function readApproversFromDisk() {
17514
- const configPath2 = path58.join(os52.homedir(), ".node9", "config.json");
17544
+ const configPath2 = path59.join(os53.homedir(), ".node9", "config.json");
17515
17545
  try {
17516
- const raw = JSON.parse(fs60.readFileSync(configPath2, "utf-8"));
17546
+ const raw = JSON.parse(fs61.readFileSync(configPath2, "utf-8"));
17517
17547
  const settings = raw.settings ?? {};
17518
17548
  return settings.approvers ?? {};
17519
17549
  } catch {
@@ -17529,15 +17559,15 @@ function approverStatusLine() {
17529
17559
  return `${fmt("native", "native")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
17530
17560
  }
17531
17561
  function toggleApprover(channel) {
17532
- const configPath2 = path58.join(os52.homedir(), ".node9", "config.json");
17562
+ const configPath2 = path59.join(os53.homedir(), ".node9", "config.json");
17533
17563
  try {
17534
- const raw = JSON.parse(fs60.readFileSync(configPath2, "utf-8"));
17564
+ const raw = JSON.parse(fs61.readFileSync(configPath2, "utf-8"));
17535
17565
  const settings = raw.settings ?? {};
17536
17566
  const approvers = settings.approvers ?? {};
17537
17567
  approvers[channel] = approvers[channel] === false;
17538
17568
  settings.approvers = approvers;
17539
17569
  raw.settings = settings;
17540
- fs60.writeFileSync(configPath2, JSON.stringify(raw, null, 2) + "\n");
17570
+ fs61.writeFileSync(configPath2, JSON.stringify(raw, null, 2) + "\n");
17541
17571
  } catch (err2) {
17542
17572
  process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
17543
17573
  `);
@@ -17709,8 +17739,8 @@ async function startTail(options = {}) {
17709
17739
  }
17710
17740
  postDecisionHttp(req2.id, httpDecision, authToken, port, httpOpts).catch((err2) => {
17711
17741
  try {
17712
- fs60.appendFileSync(
17713
- path58.join(os52.homedir(), ".node9", "hook-debug.log"),
17742
+ fs61.appendFileSync(
17743
+ path59.join(os53.homedir(), ".node9", "hook-debug.log"),
17714
17744
  `[tail] POST /decision failed: ${String(err2)}
17715
17745
  `
17716
17746
  );
@@ -17774,9 +17804,9 @@ async function startTail(options = {}) {
17774
17804
  };
17775
17805
  process.stdin.on("keypress", onKeypress);
17776
17806
  }
17777
- const auditLog = path58.join(os52.homedir(), ".node9", "audit.log");
17807
+ const auditLog = path59.join(os53.homedir(), ".node9", "audit.log");
17778
17808
  try {
17779
- const unackedDlp = fs60.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
17809
+ const unackedDlp = fs61.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
17780
17810
  if (unackedDlp > 0) {
17781
17811
  console.log("");
17782
17812
  console.log(
@@ -17816,7 +17846,7 @@ async function startTail(options = {}) {
17816
17846
  if (stallWarned) return;
17817
17847
  if (Date.now() - lastActivityFromDaemon < STALL_THRESHOLD_MS) return;
17818
17848
  try {
17819
- const auditMtime = fs60.statSync(auditLog).mtimeMs;
17849
+ const auditMtime = fs61.statSync(auditLog).mtimeMs;
17820
17850
  if (Date.now() - auditMtime >= STALL_THRESHOLD_MS) return;
17821
17851
  console.log("");
17822
17852
  console.log(
@@ -18007,7 +18037,7 @@ var init_tail = __esm({
18007
18037
  "use strict";
18008
18038
  init_daemon2();
18009
18039
  init_daemon();
18010
- PID_FILE = path58.join(os52.homedir(), ".node9", "daemon.pid");
18040
+ PID_FILE = path59.join(os53.homedir(), ".node9", "daemon.pid");
18011
18041
  ICONS = {
18012
18042
  bash: "\u{1F4BB}",
18013
18043
  shell: "\u{1F4BB}",
@@ -18055,9 +18085,9 @@ __export(hud_exports, {
18055
18085
  main: () => main,
18056
18086
  renderEnvironmentLine: () => renderEnvironmentLine
18057
18087
  });
18058
- import fs61 from "fs";
18059
- import path59 from "path";
18060
- import os53 from "os";
18088
+ import fs62 from "fs";
18089
+ import path60 from "path";
18090
+ import os54 from "os";
18061
18091
  import http4 from "http";
18062
18092
  async function readStdin() {
18063
18093
  const chunks = [];
@@ -18133,9 +18163,9 @@ function formatTimeLeft(resetsAt) {
18133
18163
  return ` (${m}m left)`;
18134
18164
  }
18135
18165
  function safeReadJson(filePath) {
18136
- if (!fs61.existsSync(filePath)) return null;
18166
+ if (!fs62.existsSync(filePath)) return null;
18137
18167
  try {
18138
- return JSON.parse(fs61.readFileSync(filePath, "utf-8"));
18168
+ return JSON.parse(fs62.readFileSync(filePath, "utf-8"));
18139
18169
  } catch {
18140
18170
  return null;
18141
18171
  }
@@ -18156,12 +18186,12 @@ function countHooksInFile(filePath) {
18156
18186
  return Object.keys(cfg.hooks).length;
18157
18187
  }
18158
18188
  function countRulesInDir(rulesDir) {
18159
- if (!fs61.existsSync(rulesDir)) return 0;
18189
+ if (!fs62.existsSync(rulesDir)) return 0;
18160
18190
  let count = 0;
18161
18191
  try {
18162
- for (const entry of fs61.readdirSync(rulesDir, { withFileTypes: true })) {
18192
+ for (const entry of fs62.readdirSync(rulesDir, { withFileTypes: true })) {
18163
18193
  if (entry.isDirectory()) {
18164
- count += countRulesInDir(path59.join(rulesDir, entry.name));
18194
+ count += countRulesInDir(path60.join(rulesDir, entry.name));
18165
18195
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
18166
18196
  count++;
18167
18197
  }
@@ -18172,46 +18202,46 @@ function countRulesInDir(rulesDir) {
18172
18202
  }
18173
18203
  function isSamePath(a, b) {
18174
18204
  try {
18175
- return path59.resolve(a) === path59.resolve(b);
18205
+ return path60.resolve(a) === path60.resolve(b);
18176
18206
  } catch {
18177
18207
  return false;
18178
18208
  }
18179
18209
  }
18180
18210
  function countConfigs(cwd) {
18181
- const homeDir2 = os53.homedir();
18182
- const claudeDir = path59.join(homeDir2, ".claude");
18211
+ const homeDir2 = os54.homedir();
18212
+ const claudeDir = path60.join(homeDir2, ".claude");
18183
18213
  let claudeMdCount = 0;
18184
18214
  let rulesCount = 0;
18185
18215
  let hooksCount = 0;
18186
18216
  const userMcpServers = /* @__PURE__ */ new Set();
18187
18217
  const projectMcpServers = /* @__PURE__ */ new Set();
18188
- if (fs61.existsSync(path59.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
18189
- rulesCount += countRulesInDir(path59.join(claudeDir, "rules"));
18190
- const userSettings = path59.join(claudeDir, "settings.json");
18218
+ if (fs62.existsSync(path60.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
18219
+ rulesCount += countRulesInDir(path60.join(claudeDir, "rules"));
18220
+ const userSettings = path60.join(claudeDir, "settings.json");
18191
18221
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
18192
18222
  hooksCount += countHooksInFile(userSettings);
18193
- const userClaudeJson = path59.join(homeDir2, ".claude.json");
18223
+ const userClaudeJson = path60.join(homeDir2, ".claude.json");
18194
18224
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
18195
18225
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
18196
18226
  userMcpServers.delete(name);
18197
18227
  }
18198
18228
  if (cwd) {
18199
- if (fs61.existsSync(path59.join(cwd, "CLAUDE.md"))) claudeMdCount++;
18200
- if (fs61.existsSync(path59.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
18201
- const projectClaudeDir = path59.join(cwd, ".claude");
18229
+ if (fs62.existsSync(path60.join(cwd, "CLAUDE.md"))) claudeMdCount++;
18230
+ if (fs62.existsSync(path60.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
18231
+ const projectClaudeDir = path60.join(cwd, ".claude");
18202
18232
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
18203
18233
  if (!overlapsUserScope) {
18204
- if (fs61.existsSync(path59.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
18205
- rulesCount += countRulesInDir(path59.join(projectClaudeDir, "rules"));
18206
- const projSettings = path59.join(projectClaudeDir, "settings.json");
18234
+ if (fs62.existsSync(path60.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
18235
+ rulesCount += countRulesInDir(path60.join(projectClaudeDir, "rules"));
18236
+ const projSettings = path60.join(projectClaudeDir, "settings.json");
18207
18237
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
18208
18238
  hooksCount += countHooksInFile(projSettings);
18209
18239
  }
18210
- if (fs61.existsSync(path59.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
18211
- const localSettings = path59.join(projectClaudeDir, "settings.local.json");
18240
+ if (fs62.existsSync(path60.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
18241
+ const localSettings = path60.join(projectClaudeDir, "settings.local.json");
18212
18242
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
18213
18243
  hooksCount += countHooksInFile(localSettings);
18214
- const mcpJsonServers = getMcpServerNames(path59.join(cwd, ".mcp.json"));
18244
+ const mcpJsonServers = getMcpServerNames(path60.join(cwd, ".mcp.json"));
18215
18245
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
18216
18246
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
18217
18247
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -18244,12 +18274,12 @@ function readActiveShieldsHud() {
18244
18274
  return shieldsCache.value;
18245
18275
  }
18246
18276
  try {
18247
- const shieldsPath = path59.join(os53.homedir(), ".node9", "shields.json");
18248
- if (!fs61.existsSync(shieldsPath)) {
18277
+ const shieldsPath = path60.join(os54.homedir(), ".node9", "shields.json");
18278
+ if (!fs62.existsSync(shieldsPath)) {
18249
18279
  shieldsCache = { value: [], ts: now };
18250
18280
  return [];
18251
18281
  }
18252
- const parsed = JSON.parse(fs61.readFileSync(shieldsPath, "utf-8"));
18282
+ const parsed = JSON.parse(fs62.readFileSync(shieldsPath, "utf-8"));
18253
18283
  if (!Array.isArray(parsed.active)) {
18254
18284
  shieldsCache = { value: [], ts: now };
18255
18285
  return [];
@@ -18351,17 +18381,17 @@ function renderContextLine(stdin) {
18351
18381
  async function main() {
18352
18382
  try {
18353
18383
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
18354
- if (fs61.existsSync(path59.join(os53.homedir(), ".node9", "hud-debug"))) {
18384
+ if (fs62.existsSync(path60.join(os54.homedir(), ".node9", "hud-debug"))) {
18355
18385
  try {
18356
- const logPath = path59.join(os53.homedir(), ".node9", "hud-debug.log");
18386
+ const logPath = path60.join(os54.homedir(), ".node9", "hud-debug.log");
18357
18387
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
18358
18388
  let size = 0;
18359
18389
  try {
18360
- size = fs61.statSync(logPath).size;
18390
+ size = fs62.statSync(logPath).size;
18361
18391
  } catch {
18362
18392
  }
18363
18393
  if (size < MAX_LOG_SIZE) {
18364
- fs61.appendFileSync(
18394
+ fs62.appendFileSync(
18365
18395
  logPath,
18366
18396
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
18367
18397
  );
@@ -18382,11 +18412,11 @@ async function main() {
18382
18412
  try {
18383
18413
  const cwd = stdin.cwd ?? process.cwd();
18384
18414
  for (const configPath2 of [
18385
- path59.join(cwd, "node9.config.json"),
18386
- path59.join(os53.homedir(), ".node9", "config.json")
18415
+ path60.join(cwd, "node9.config.json"),
18416
+ path60.join(os54.homedir(), ".node9", "config.json")
18387
18417
  ]) {
18388
- if (!fs61.existsSync(configPath2)) continue;
18389
- const cfg = JSON.parse(fs61.readFileSync(configPath2, "utf-8"));
18418
+ if (!fs62.existsSync(configPath2)) continue;
18419
+ const cfg = JSON.parse(fs62.readFileSync(configPath2, "utf-8"));
18390
18420
  const hud = cfg.settings?.hud;
18391
18421
  if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
18392
18422
  }
@@ -18433,9 +18463,9 @@ init_setup();
18433
18463
  init_daemon2();
18434
18464
  import { Command } from "commander";
18435
18465
  import chalk35 from "chalk";
18436
- import fs62 from "fs";
18437
- import path60 from "path";
18438
- import os54 from "os";
18466
+ import fs63 from "fs";
18467
+ import path61 from "path";
18468
+ import os55 from "os";
18439
18469
  import { spawn as spawn9 } from "child_process";
18440
18470
  import { confirm as confirm2 } from "@inquirer/prompts";
18441
18471
 
@@ -18530,6 +18560,10 @@ INSTRUCTIONS:
18530
18560
  - Do NOT retry this exact command or attempt to bypass the rule.${recovery}
18531
18561
  - Inform the user which security rule was triggered and ask how to proceed.`;
18532
18562
  }
18563
+ function buildReviewMessage(blockedByLabel, ruleDescription) {
18564
+ const why = ruleDescription || blockedByLabel || "this action needs your review";
18565
+ return `Node9 flagged this for your review: ${why}. Approve to proceed, or deny to cancel.`;
18566
+ }
18533
18567
 
18534
18568
  // src/proxy/index.ts
18535
18569
  function sanitize(value) {
@@ -18660,10 +18694,10 @@ init_daemon();
18660
18694
  init_config();
18661
18695
  init_policy();
18662
18696
  import chalk9 from "chalk";
18663
- import fs36 from "fs";
18697
+ import fs37 from "fs";
18664
18698
  import { spawn as spawn5 } from "child_process";
18665
- import path37 from "path";
18666
- import os32 from "os";
18699
+ import path38 from "path";
18700
+ import os33 from "os";
18667
18701
 
18668
18702
  // src/undo.ts
18669
18703
  import { spawnSync as spawnSync3, spawn as spawn4 } from "child_process";
@@ -19201,6 +19235,78 @@ function resolveUserSkillRoot(entry, cwd) {
19201
19235
  // src/cli/commands/check.ts
19202
19236
  init_dlp();
19203
19237
  init_audit();
19238
+
19239
+ // src/review-pending.ts
19240
+ init_hasher();
19241
+ import fs36 from "fs";
19242
+ import os32 from "os";
19243
+ import path37 from "path";
19244
+ function storePath() {
19245
+ return process.env.NODE9_PENDING_STORE || path37.join(os32.homedir(), ".node9", "pending-reviews.json");
19246
+ }
19247
+ var TTL_MS2 = 6 * 60 * 60 * 1e3;
19248
+ var MAX_ENTRIES = 500;
19249
+ function reviewCorrelationKey(payload) {
19250
+ if (typeof payload.tool_use_id === "string" && payload.tool_use_id) {
19251
+ return `tuid:${payload.tool_use_id}`;
19252
+ }
19253
+ const sid = payload.session_id ?? payload.conversationId;
19254
+ const tool = payload.tool_name;
19255
+ if (typeof sid === "string" && sid && typeof tool === "string" && tool) {
19256
+ return `h:${sid}|${tool}|${hashArgs(payload.tool_input)}`;
19257
+ }
19258
+ return null;
19259
+ }
19260
+ function read() {
19261
+ try {
19262
+ const parsed = JSON.parse(fs36.readFileSync(storePath(), "utf-8"));
19263
+ if (parsed && Array.isArray(parsed.entries)) return parsed;
19264
+ } catch {
19265
+ }
19266
+ return { entries: [] };
19267
+ }
19268
+ function write(store) {
19269
+ try {
19270
+ const p = storePath();
19271
+ const dir = path37.dirname(p);
19272
+ if (!fs36.existsSync(dir)) fs36.mkdirSync(dir, { recursive: true });
19273
+ const tmp = `${p}.${process.pid}.tmp`;
19274
+ fs36.writeFileSync(tmp, JSON.stringify(store));
19275
+ fs36.renameSync(tmp, p);
19276
+ } catch {
19277
+ }
19278
+ }
19279
+ function prune(entries, now) {
19280
+ const fresh = entries.filter((e) => now - e.ts < TTL_MS2);
19281
+ return fresh.length > MAX_ENTRIES ? fresh.slice(fresh.length - MAX_ENTRIES) : fresh;
19282
+ }
19283
+ function recordPendingReview(entry) {
19284
+ try {
19285
+ const store = read();
19286
+ store.entries = prune(store.entries, entry.ts);
19287
+ store.entries.push(entry);
19288
+ write(store);
19289
+ } catch {
19290
+ }
19291
+ }
19292
+ function resolvePendingReview(key, now = Date.now()) {
19293
+ try {
19294
+ const store = read();
19295
+ const idx = store.entries.findIndex((e) => e.key === key);
19296
+ if (idx === -1) {
19297
+ const pruned = prune(store.entries, now);
19298
+ if (pruned.length !== store.entries.length) write({ entries: pruned });
19299
+ return null;
19300
+ }
19301
+ const [match] = store.entries.splice(idx, 1);
19302
+ write({ entries: prune(store.entries, now) });
19303
+ return match;
19304
+ } catch {
19305
+ return null;
19306
+ }
19307
+ }
19308
+
19309
+ // src/cli/commands/check.ts
19204
19310
  init_hook_payload();
19205
19311
  function sanitize2(value) {
19206
19312
  return value.replace(/[\x00-\x1F\x7F]/g, "");
@@ -19249,11 +19355,26 @@ function detectAiAgent(payload) {
19249
19355
  }
19250
19356
  return "Terminal";
19251
19357
  }
19358
+ function agentSupportsAsk(agent) {
19359
+ return agent === "Claude Code" || agent === "GitHub Copilot";
19360
+ }
19361
+ function resolveAskMode(agent, opts, config) {
19362
+ if (!agentSupportsAsk(agent)) return false;
19363
+ if (config.settings.approvers.cloud === true) return false;
19364
+ if (opts.ask === true) return true;
19365
+ if (opts.ask === false) return false;
19366
+ if (config.settings.reviewChannel === "ask") return true;
19367
+ if (config.settings.reviewChannel === "approver") return false;
19368
+ return true;
19369
+ }
19252
19370
  function registerCheckCommand(program2) {
19253
19371
  program2.command("check", { hidden: true }).description("Hook handler \u2014 evaluates a tool call before execution").argument("[data]", "JSON string of the tool call").option(
19254
19372
  "--agent <name>",
19255
19373
  "Agent identity override, set by node9-authored hook registrations (e.g. antigravity)"
19256
- ).action(async (data, opts) => {
19374
+ ).option(
19375
+ "--ask",
19376
+ "Route review verdicts to the agent\u2019s native inline approve/deny prompt (Claude Code / GitHub Copilot only)"
19377
+ ).option("--no-ask", "Force node9\u2019s own approver for review verdicts (override default-on)").action(async (data, opts) => {
19257
19378
  const agentOverride = agentLabelFromFlag(opts?.agent);
19258
19379
  const processPayload = async (raw) => {
19259
19380
  try {
@@ -19264,9 +19385,9 @@ function registerCheckCommand(program2) {
19264
19385
  } catch (err2) {
19265
19386
  const tempConfig = getConfig();
19266
19387
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
19267
- const logPath = path37.join(os32.homedir(), ".node9", "hook-debug.log");
19388
+ const logPath = path38.join(os33.homedir(), ".node9", "hook-debug.log");
19268
19389
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
19269
- fs36.appendFileSync(
19390
+ fs37.appendFileSync(
19270
19391
  logPath,
19271
19392
  `[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
19272
19393
  RAW: ${raw}
@@ -19279,14 +19400,14 @@ RAW: ${raw}
19279
19400
  const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
19280
19401
  if (process.env.NODE9_DEBUG === "1") {
19281
19402
  try {
19282
- const logPath = path37.join(os32.homedir(), ".node9", "hook-debug.log");
19283
- if (!fs36.existsSync(path37.dirname(logPath)))
19284
- fs36.mkdirSync(path37.dirname(logPath), { recursive: true });
19403
+ const logPath = path38.join(os33.homedir(), ".node9", "hook-debug.log");
19404
+ if (!fs37.existsSync(path38.dirname(logPath)))
19405
+ fs37.mkdirSync(path38.dirname(logPath), { recursive: true });
19285
19406
  const sanitized = JSON.stringify({
19286
19407
  ...payload,
19287
19408
  prompt: `<redacted, ${prompt.length} bytes>`
19288
19409
  });
19289
- fs36.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${sanitized}
19410
+ fs37.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${sanitized}
19290
19411
  `);
19291
19412
  } catch {
19292
19413
  }
@@ -19306,8 +19427,8 @@ RAW: ${raw}
19306
19427
  );
19307
19428
  const reason = `\u{1F6A8} Node9 DLP: ${dlpMatch.patternName} detected in prompt (${dlpMatch.redactedSample}). Prompt was not submitted \u2014 remove the credential and try again.`;
19308
19429
  try {
19309
- const ttyFd = fs36.openSync("/dev/tty", "w");
19310
- fs36.writeSync(
19430
+ const ttyFd = fs37.openSync("/dev/tty", "w");
19431
+ fs37.writeSync(
19311
19432
  ttyFd,
19312
19433
  chalk9.bgRed.white.bold(`
19313
19434
  \u{1F6A8} NODE9 DLP \u2014 PROMPT BLOCKED
@@ -19317,7 +19438,7 @@ RAW: ${raw}
19317
19438
 
19318
19439
  `)
19319
19440
  );
19320
- fs36.closeSync(ttyFd);
19441
+ fs37.closeSync(ttyFd);
19321
19442
  } catch {
19322
19443
  }
19323
19444
  const isCodex = agent2 === "Codex";
@@ -19336,16 +19457,16 @@ RAW: ${raw}
19336
19457
  process.exit(2);
19337
19458
  }
19338
19459
  const payloadCwd = typeof payload.cwd === "string" ? payload.cwd : Array.isArray(payload.workspacePaths) && typeof payload.workspacePaths[0] === "string" ? payload.workspacePaths[0] : void 0;
19339
- const safeCwdForConfig = typeof payloadCwd === "string" && path37.isAbsolute(payloadCwd) ? payloadCwd : void 0;
19460
+ const safeCwdForConfig = typeof payloadCwd === "string" && path38.isAbsolute(payloadCwd) ? payloadCwd : void 0;
19340
19461
  const config = getConfig(safeCwdForConfig);
19341
19462
  if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
19342
19463
  try {
19343
19464
  const scriptPath = process.argv[1];
19344
- if (typeof scriptPath !== "string" || !path37.isAbsolute(scriptPath))
19465
+ if (typeof scriptPath !== "string" || !path38.isAbsolute(scriptPath))
19345
19466
  throw new Error("node9: argv[1] is not an absolute path");
19346
- const resolvedScript = fs36.realpathSync(scriptPath);
19347
- const packageDist = fs36.realpathSync(path37.resolve(__dirname, "../.."));
19348
- if (!resolvedScript.startsWith(packageDist + path37.sep) && resolvedScript !== packageDist)
19467
+ const resolvedScript = fs37.realpathSync(scriptPath);
19468
+ const packageDist = fs37.realpathSync(path38.resolve(__dirname, "../.."));
19469
+ if (!resolvedScript.startsWith(packageDist + path38.sep) && resolvedScript !== packageDist)
19349
19470
  throw new Error(
19350
19471
  `node9: daemon spawn aborted \u2014 argv[1] (${resolvedScript}) is outside package dist (${packageDist})`
19351
19472
  );
@@ -19367,10 +19488,10 @@ RAW: ${raw}
19367
19488
  });
19368
19489
  d.unref();
19369
19490
  } catch (spawnErr) {
19370
- const logPath = path37.join(os32.homedir(), ".node9", "hook-debug.log");
19491
+ const logPath = path38.join(os33.homedir(), ".node9", "hook-debug.log");
19371
19492
  const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
19372
19493
  try {
19373
- fs36.appendFileSync(
19494
+ fs37.appendFileSync(
19374
19495
  logPath,
19375
19496
  `[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-failed: ${msg}
19376
19497
  `
@@ -19380,10 +19501,10 @@ RAW: ${raw}
19380
19501
  }
19381
19502
  }
19382
19503
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
19383
- const logPath = path37.join(os32.homedir(), ".node9", "hook-debug.log");
19384
- if (!fs36.existsSync(path37.dirname(logPath)))
19385
- fs36.mkdirSync(path37.dirname(logPath), { recursive: true });
19386
- fs36.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
19504
+ const logPath = path38.join(os33.homedir(), ".node9", "hook-debug.log");
19505
+ if (!fs37.existsSync(path38.dirname(logPath)))
19506
+ fs37.mkdirSync(path38.dirname(logPath), { recursive: true });
19507
+ fs37.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
19387
19508
  `);
19388
19509
  }
19389
19510
  const rawToolName = sanitize2(extractToolName(payload));
@@ -19397,8 +19518,8 @@ RAW: ${raw}
19397
19518
  const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
19398
19519
  let ttyFd = null;
19399
19520
  try {
19400
- ttyFd = fs36.openSync("/dev/tty", "w");
19401
- const writeTty = (line) => fs36.writeSync(ttyFd, line + "\n");
19521
+ ttyFd = fs37.openSync("/dev/tty", "w");
19522
+ const writeTty = (line) => fs37.writeSync(ttyFd, line + "\n");
19402
19523
  if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
19403
19524
  writeTty(chalk9.bgRed.white.bold(`
19404
19525
  \u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
@@ -19417,7 +19538,7 @@ RAW: ${raw}
19417
19538
  } finally {
19418
19539
  if (ttyFd !== null)
19419
19540
  try {
19420
- fs36.closeSync(ttyFd);
19541
+ fs37.closeSync(ttyFd);
19421
19542
  } catch {
19422
19543
  }
19423
19544
  }
@@ -19456,6 +19577,53 @@ RAW: ${raw}
19456
19577
  );
19457
19578
  process.exit(2);
19458
19579
  };
19580
+ const sendAsk = (result2) => {
19581
+ const msg = buildReviewMessage(result2.blockedByLabel, result2.ruleDescription);
19582
+ try {
19583
+ const key = reviewCorrelationKey(payload);
19584
+ if (key) {
19585
+ const sid = typeof payload.session_id === "string" ? payload.session_id : typeof payload.conversationId === "string" ? payload.conversationId : void 0;
19586
+ recordPendingReview({
19587
+ key,
19588
+ agent,
19589
+ tool: toolName,
19590
+ sessionId: sid,
19591
+ ts: Date.now(),
19592
+ label: result2.blockedByLabel
19593
+ });
19594
+ }
19595
+ } catch {
19596
+ }
19597
+ try {
19598
+ const ttyFd = fs37.openSync("/dev/tty", "w");
19599
+ fs37.writeSync(
19600
+ ttyFd,
19601
+ chalk9.yellow(
19602
+ `
19603
+ \u26A0\uFE0F Node9: review requested for "${toolName}" \u2014 answer in the prompt.
19604
+ `
19605
+ )
19606
+ );
19607
+ fs37.closeSync(ttyFd);
19608
+ } catch {
19609
+ }
19610
+ if (agent === "GitHub Copilot") {
19611
+ process.stdout.write(
19612
+ JSON.stringify({ permissionDecision: "ask", permissionDecisionReason: msg }) + "\n"
19613
+ );
19614
+ } else {
19615
+ process.stdout.write(
19616
+ JSON.stringify({
19617
+ hookSpecificOutput: {
19618
+ hookEventName: "PreToolUse",
19619
+ permissionDecision: "ask",
19620
+ permissionDecisionReason: msg
19621
+ }
19622
+ }) + "\n"
19623
+ );
19624
+ }
19625
+ process.exit(0);
19626
+ };
19459
19627
  if (!toolName) {
19460
19628
  sendBlock("Node9: unrecognised hook payload \u2014 tool name missing.");
19461
19629
  return;
@@ -19468,17 +19636,17 @@ RAW: ${raw}
19468
19636
  const safeSessionId = /^[A-Za-z0-9_\-]{1,128}$/.test(rawSessionId) ? rawSessionId : "";
19469
19637
  if (skillPinCfg.enabled && safeSessionId) {
19470
19638
  try {
19471
- const sessionsDir = path37.join(os32.homedir(), ".node9", "skill-sessions");
19472
- const flagPath = path37.join(sessionsDir, `${safeSessionId}.json`);
19639
+ const sessionsDir = path38.join(os33.homedir(), ".node9", "skill-sessions");
19640
+ const flagPath = path38.join(sessionsDir, `${safeSessionId}.json`);
19473
19641
  let flag = null;
19474
19642
  try {
19475
- flag = JSON.parse(fs36.readFileSync(flagPath, "utf-8"));
19643
+ flag = JSON.parse(fs37.readFileSync(flagPath, "utf-8"));
19476
19644
  } catch {
19477
19645
  }
19478
19646
  const writeFlag = (data2) => {
19479
19647
  try {
19480
- fs36.mkdirSync(sessionsDir, { recursive: true });
19481
- fs36.writeFileSync(
19648
+ fs37.mkdirSync(sessionsDir, { recursive: true });
19649
+ fs37.writeFileSync(
19482
19650
  flagPath,
19483
19651
  JSON.stringify({ ...data2, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
19484
19652
  { mode: 384 }
@@ -19489,8 +19657,8 @@ RAW: ${raw}
19489
19657
  const sendSkillWarn = (detail, recoveryCmd) => {
19490
19658
  let ttyFd = null;
19491
19659
  try {
19492
- ttyFd = fs36.openSync("/dev/tty", "w");
19493
- const w = (line) => fs36.writeSync(ttyFd, line + "\n");
19660
+ ttyFd = fs37.openSync("/dev/tty", "w");
19661
+ const w = (line) => fs37.writeSync(ttyFd, line + "\n");
19494
19662
  w(chalk9.yellow(`
19495
19663
  \u26A0\uFE0F Node9: installed skill drift detected`));
19496
19664
  w(chalk9.gray(` ${detail}`));
@@ -19505,7 +19673,7 @@ RAW: ${raw}
19505
19673
  } finally {
19506
19674
  if (ttyFd !== null)
19507
19675
  try {
19508
- fs36.closeSync(ttyFd);
19676
+ fs37.closeSync(ttyFd);
19509
19677
  } catch {
19510
19678
  }
19511
19679
  }
@@ -19521,7 +19689,7 @@ RAW: ${raw}
19521
19689
  return;
19522
19690
  }
19523
19691
  if (!flag || flag.state !== "verified" && flag.state !== "warned") {
19524
- const absoluteCwd = typeof payloadCwd === "string" && path37.isAbsolute(payloadCwd) ? payloadCwd : void 0;
19692
+ const absoluteCwd = typeof payloadCwd === "string" && path38.isAbsolute(payloadCwd) ? payloadCwd : void 0;
19525
19693
  const extraRoots = skillPinCfg.roots;
19526
19694
  const resolvedExtra = extraRoots.map((r) => resolveUserSkillRoot(r, absoluteCwd)).filter((r) => typeof r === "string");
19527
19695
  const roots = [...defaultSkillRoots(absoluteCwd), ...resolvedExtra];
@@ -19562,10 +19730,10 @@ RAW: ${raw}
19562
19730
  }
19563
19731
  try {
19564
19732
  const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
19565
- for (const name of fs36.readdirSync(sessionsDir)) {
19566
- const p = path37.join(sessionsDir, name);
19733
+ for (const name of fs37.readdirSync(sessionsDir)) {
19734
+ const p = path38.join(sessionsDir, name);
19567
19735
  try {
19568
- if (fs36.statSync(p).mtimeMs < cutoff) fs36.unlinkSync(p);
19736
+ if (fs37.statSync(p).mtimeMs < cutoff) fs37.unlinkSync(p);
19569
19737
  } catch {
19570
19738
  }
19571
19739
  }
@@ -19575,9 +19743,9 @@ RAW: ${raw}
19575
19743
  } catch (err2) {
19576
19744
  if (process.env.NODE9_DEBUG === "1") {
19577
19745
  try {
19578
- const dbg = path37.join(os32.homedir(), ".node9", "hook-debug.log");
19746
+ const dbg = path38.join(os33.homedir(), ".node9", "hook-debug.log");
19579
19747
  const msg = err2 instanceof Error ? err2.message : String(err2);
19580
- fs36.appendFileSync(dbg, `[${(/* @__PURE__ */ new Date()).toISOString()}] SKILL_PIN_ERROR: ${msg}
19748
+ fs37.appendFileSync(dbg, `[${(/* @__PURE__ */ new Date()).toISOString()}] SKILL_PIN_ERROR: ${msg}
19581
19749
  `);
19582
19750
  } catch {
19583
19751
  }
@@ -19587,9 +19755,11 @@ RAW: ${raw}
19587
19755
  if (shouldSnapshot(toolName, toolInput, config)) {
19588
19756
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
19589
19757
  }
19590
- const safeCwdForAuth = typeof payloadCwd === "string" && path37.isAbsolute(payloadCwd) ? payloadCwd : void 0;
19758
+ const safeCwdForAuth = typeof payloadCwd === "string" && path38.isAbsolute(payloadCwd) ? payloadCwd : void 0;
19759
+ const askMode = resolveAskMode(agent, opts, config);
19591
19760
  const result = await authorizeHeadless(toolName, toolInput, meta, {
19592
- cwd: safeCwdForAuth
19761
+ cwd: safeCwdForAuth,
19762
+ deferReview: askMode
19593
19763
  });
19594
19764
  if (result.approved) {
19595
19765
  if (result.checkedBy && process.env.NODE9_DEBUG === "1")
@@ -19597,14 +19767,18 @@ RAW: ${raw}
19597
19767
  `);
19598
19768
  process.exit(0);
19599
19769
  }
19770
+ if (result.review) {
19771
+ sendAsk(result);
19772
+ return;
19773
+ }
19600
19774
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
19601
19775
  try {
19602
- const tty = fs36.openSync("/dev/tty", "w");
19603
- fs36.writeSync(
19776
+ const tty = fs37.openSync("/dev/tty", "w");
19777
+ fs37.writeSync(
19604
19778
  tty,
19605
19779
  chalk9.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
19606
19780
  );
19607
- fs36.closeSync(tty);
19781
+ fs37.closeSync(tty);
19608
19782
  } catch {
19609
19783
  }
19610
19784
  const daemonReady = await autoStartDaemonAndWait();
@@ -19631,9 +19805,9 @@ RAW: ${raw}
19631
19805
  });
19632
19806
  } catch (err2) {
19633
19807
  if (process.env.NODE9_DEBUG === "1") {
19634
- const logPath = path37.join(os32.homedir(), ".node9", "hook-debug.log");
19808
+ const logPath = path38.join(os33.homedir(), ".node9", "hook-debug.log");
19635
19809
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
19636
- fs36.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
19810
+ fs37.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
19637
19811
  `);
19638
19812
  }
19639
19813
  process.exit(0);
@@ -19669,9 +19843,9 @@ RAW: ${raw}
19669
19843
  // src/cli/commands/log.ts
19670
19844
  init_audit();
19671
19845
  init_config();
19672
- import fs37 from "fs";
19673
- import path38 from "path";
19674
- import os33 from "os";
19846
+ import fs38 from "fs";
19847
+ import path39 from "path";
19848
+ import os34 from "os";
19675
19849
  init_daemon();
19676
19850
  init_dlp();
19677
19851
 
@@ -19762,21 +19936,27 @@ function registerLogCommand(program2) {
19762
19936
  return void 0;
19763
19937
  })();
19764
19938
  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;
19939
+ let reviewApproved = false;
19940
+ try {
19941
+ const key = reviewCorrelationKey(payload);
19942
+ if (key && resolvePendingReview(key)) reviewApproved = true;
19943
+ } catch {
19944
+ }
19765
19945
  const entry = {
19766
19946
  ts: (/* @__PURE__ */ new Date()).toISOString(),
19767
19947
  tool,
19768
19948
  args: JSON.parse(redactSecrets(JSON.stringify(rawInput))),
19769
19949
  decision: "allowed",
19770
- source: "post-hook"
19950
+ source: reviewApproved ? "inline-review-approved" : "post-hook"
19771
19951
  };
19772
19952
  if (agent) entry.agent = agent;
19773
19953
  if (rawToolName !== tool) entry.agentToolName = rawToolName;
19774
19954
  const payloadSessionId = payload.session_id ?? payload.conversationId;
19775
19955
  if (payloadSessionId) entry.sessionId = payloadSessionId;
19776
- const logPath = path38.join(os33.homedir(), ".node9", "audit.log");
19777
- if (!fs37.existsSync(path38.dirname(logPath)))
19778
- fs37.mkdirSync(path38.dirname(logPath), { recursive: true });
19779
- fs37.appendFileSync(logPath, JSON.stringify(entry) + "\n");
19956
+ const logPath = path39.join(os34.homedir(), ".node9", "audit.log");
19957
+ if (!fs38.existsSync(path39.dirname(logPath)))
19958
+ fs38.mkdirSync(path39.dirname(logPath), { recursive: true });
19959
+ fs38.appendFileSync(logPath, JSON.stringify(entry) + "\n");
19780
19960
  if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
19781
19961
  const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
19782
19962
  if (command) {
@@ -19810,7 +19990,7 @@ function registerLogCommand(program2) {
19810
19990
  }
19811
19991
  }
19812
19992
  const payloadCwd = typeof payload.cwd === "string" ? payload.cwd : Array.isArray(payload.workspacePaths) && typeof payload.workspacePaths[0] === "string" ? payload.workspacePaths[0] : void 0;
19813
- const safeCwd = typeof payloadCwd === "string" && path38.isAbsolute(payloadCwd) ? payloadCwd : void 0;
19993
+ const safeCwd = typeof payloadCwd === "string" && path39.isAbsolute(payloadCwd) ? payloadCwd : void 0;
19814
19994
  const config = getConfig(safeCwd);
19815
19995
  {
19816
19996
  const toolOutput = payload.tool_response?.output;
@@ -19887,9 +20067,9 @@ function registerLogCommand(program2) {
19887
20067
  const msg = err2 instanceof Error ? err2.message : String(err2);
19888
20068
  process.stderr.write(`[Node9] audit log error: ${msg}
19889
20069
  `);
19890
- const debugPath = path38.join(os33.homedir(), ".node9", "hook-debug.log");
20070
+ const debugPath = path39.join(os34.homedir(), ".node9", "hook-debug.log");
19891
20071
  try {
19892
- fs37.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
20072
+ fs38.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
19893
20073
  `);
19894
20074
  } catch {
19895
20075
  }
@@ -20291,22 +20471,22 @@ function registerConfigShowCommand(program2) {
20291
20471
  init_daemon();
20292
20472
  init_config();
20293
20473
  import chalk11 from "chalk";
20294
- import fs39 from "fs";
20295
- import path40 from "path";
20296
- import os35 from "os";
20474
+ import fs40 from "fs";
20475
+ import path41 from "path";
20476
+ import os36 from "os";
20297
20477
  import { execSync } from "child_process";
20298
20478
 
20299
20479
  // src/agent-wiring.ts
20300
20480
  init_setup();
20301
- import fs38 from "fs";
20302
- import path39 from "path";
20303
- import os34 from "os";
20481
+ import fs39 from "fs";
20482
+ import path40 from "path";
20483
+ import os35 from "os";
20304
20484
  import * as yaml2 from "yaml";
20305
20485
  import { parse as parseToml2 } from "smol-toml";
20306
20486
  function readJson2(filePath) {
20307
- if (!fs38.existsSync(filePath)) return null;
20487
+ if (!fs39.existsSync(filePath)) return null;
20308
20488
  try {
20309
- return JSON.parse(fs38.readFileSync(filePath, "utf-8"));
20489
+ return JSON.parse(fs39.readFileSync(filePath, "utf-8"));
20310
20490
  } catch {
20311
20491
  return "invalid";
20312
20492
  }
@@ -20318,10 +20498,10 @@ function flatHaveNode9Hook(entries) {
20318
20498
  return (Array.isArray(entries) ? entries : []).some((h) => isNode9Hook(h.command));
20319
20499
  }
20320
20500
  function readHookRoot(filePath, format) {
20321
- if (!fs38.existsSync(filePath)) return "absent";
20501
+ if (!fs39.existsSync(filePath)) return "absent";
20322
20502
  let raw;
20323
20503
  try {
20324
- raw = fs38.readFileSync(filePath, "utf-8");
20504
+ raw = fs39.readFileSync(filePath, "utf-8");
20325
20505
  } catch {
20326
20506
  return "absent";
20327
20507
  }
@@ -20344,10 +20524,10 @@ function detectMcp(servers) {
20344
20524
  return { wrapped, present };
20345
20525
  }
20346
20526
  function readMcp(filePath, format) {
20347
- if (!fs38.existsSync(filePath)) return { wrapped: [], present: false };
20527
+ if (!fs39.existsSync(filePath)) return { wrapped: [], present: false };
20348
20528
  try {
20349
20529
  if (format === "toml") {
20350
- const parsed2 = parseToml2(fs38.readFileSync(filePath, "utf-8"));
20530
+ const parsed2 = parseToml2(fs39.readFileSync(filePath, "utf-8"));
20351
20531
  return detectMcp(parsed2?.mcp_servers);
20352
20532
  }
20353
20533
  const parsed = readJson2(filePath);
@@ -20359,7 +20539,7 @@ function readMcp(filePath, format) {
20359
20539
  }
20360
20540
  var exists = (p) => {
20361
20541
  try {
20362
- return fs38.existsSync(p);
20542
+ return fs39.existsSync(p);
20363
20543
  } catch {
20364
20544
  return false;
20365
20545
  }
@@ -20373,52 +20553,52 @@ var AGENT_SPECS = [
20373
20553
  id: "claude",
20374
20554
  label: "Claude Code",
20375
20555
  setupCommand: "node9 agents add claude",
20376
- hookFile: (h) => path39.join(h, ".claude", "settings.json"),
20556
+ hookFile: (h) => path40.join(h, ".claude", "settings.json"),
20377
20557
  hookFormat: "matcher",
20378
20558
  hookEvents: [ck("PreToolUse"), lg("PostToolUse")],
20379
- mcpFile: (h) => path39.join(h, ".claude.json"),
20380
- present: (h) => exists(path39.join(h, ".claude", "settings.json")) || exists(path39.join(h, ".claude.json"))
20559
+ mcpFile: (h) => path40.join(h, ".claude.json"),
20560
+ present: (h) => exists(path40.join(h, ".claude", "settings.json")) || exists(path40.join(h, ".claude.json"))
20381
20561
  },
20382
20562
  {
20383
20563
  id: "gemini",
20384
20564
  label: "Gemini CLI",
20385
20565
  setupCommand: "node9 agents add gemini",
20386
- hookFile: (h) => path39.join(h, ".gemini", "settings.json"),
20566
+ hookFile: (h) => path40.join(h, ".gemini", "settings.json"),
20387
20567
  hookFormat: "matcher",
20388
20568
  hookEvents: [ck("BeforeTool"), lg("AfterTool")],
20389
- mcpFile: (h) => path39.join(h, ".gemini", "settings.json"),
20390
- present: (h) => exists(path39.join(h, ".gemini", "settings.json"))
20569
+ mcpFile: (h) => path40.join(h, ".gemini", "settings.json"),
20570
+ present: (h) => exists(path40.join(h, ".gemini", "settings.json"))
20391
20571
  },
20392
20572
  {
20393
20573
  id: "codex",
20394
20574
  label: "Codex",
20395
20575
  setupCommand: "node9 agents add codex",
20396
- hookFile: (h) => path39.join(h, ".codex", "hooks.json"),
20576
+ hookFile: (h) => path40.join(h, ".codex", "hooks.json"),
20397
20577
  hookFormat: "matcher",
20398
20578
  hookEvents: [ck("PreToolUse"), ck("UserPromptSubmit")],
20399
- mcpFile: (h) => path39.join(h, ".codex", "config.toml"),
20579
+ mcpFile: (h) => path40.join(h, ".codex", "config.toml"),
20400
20580
  mcpFormat: "toml",
20401
- present: (h) => exists(path39.join(h, ".codex"))
20581
+ present: (h) => exists(path40.join(h, ".codex"))
20402
20582
  },
20403
20583
  {
20404
20584
  id: "antigravity",
20405
20585
  label: "Antigravity",
20406
20586
  setupCommand: "node9 agents add antigravity",
20407
- hookFile: (h) => path39.join(h, ".gemini", "config", "hooks.json"),
20587
+ hookFile: (h) => path40.join(h, ".gemini", "config", "hooks.json"),
20408
20588
  hookFormat: "matcher",
20409
20589
  hookEvents: [ck("PreToolUse"), lg("PostToolUse")],
20410
- mcpFile: (h) => path39.join(h, ".gemini", "config", "mcp_config.json"),
20411
- present: (h) => exists(path39.join(h, ".gemini", "config", "hooks.json")) || exists(path39.join(h, ".gemini", "antigravity-cli")) || exists(path39.join(h, ".gemini", "antigravity-ide"))
20590
+ mcpFile: (h) => path40.join(h, ".gemini", "config", "mcp_config.json"),
20591
+ present: (h) => exists(path40.join(h, ".gemini", "config", "hooks.json")) || exists(path40.join(h, ".gemini", "antigravity-cli")) || exists(path40.join(h, ".gemini", "antigravity-ide"))
20412
20592
  },
20413
20593
  {
20414
20594
  id: "copilot",
20415
20595
  label: "GitHub Copilot",
20416
20596
  setupCommand: "node9 agents add copilot",
20417
- hookFile: (h) => path39.join(h, ".copilot", "hooks", "node9.json"),
20597
+ hookFile: (h) => path40.join(h, ".copilot", "hooks", "node9.json"),
20418
20598
  hookFormat: "flat",
20419
20599
  hookEvents: [ck("PreToolUse"), lg("PostToolUse"), ck("UserPromptSubmit")],
20420
- mcpFile: (h) => path39.join(h, ".copilot", "mcp-config.json"),
20421
- present: (h) => exists(path39.join(h, ".copilot"))
20600
+ mcpFile: (h) => path40.join(h, ".copilot", "mcp-config.json"),
20601
+ present: (h) => exists(path40.join(h, ".copilot"))
20422
20602
  },
20423
20603
  {
20424
20604
  id: "cursor",
@@ -20427,8 +20607,8 @@ var AGENT_SPECS = [
20427
20607
  // MCP-only — no hook file (see note above).
20428
20608
  hookFormat: "flat",
20429
20609
  hookEvents: [],
20430
- mcpFile: (h) => path39.join(h, ".cursor", "mcp.json"),
20431
- present: (h) => exists(path39.join(h, ".cursor", "mcp.json"))
20610
+ mcpFile: (h) => path40.join(h, ".cursor", "mcp.json"),
20611
+ present: (h) => exists(path40.join(h, ".cursor", "mcp.json"))
20432
20612
  },
20433
20613
  {
20434
20614
  id: "hermes",
@@ -20449,8 +20629,8 @@ var AGENT_SPECS = [
20449
20629
  setupCommand: "node9 agents add opencode",
20450
20630
  hookFormat: "flat",
20451
20631
  hookEvents: [],
20452
- shimFile: (h) => path39.join(h, ".config", "opencode", "plugins", "node9.js"),
20453
- present: (h) => exists(path39.join(h, ".config", "opencode")) || exists(path39.join(h, ".config", "opencode", "plugins", "node9.js"))
20632
+ shimFile: (h) => path40.join(h, ".config", "opencode", "plugins", "node9.js"),
20633
+ present: (h) => exists(path40.join(h, ".config", "opencode")) || exists(path40.join(h, ".config", "opencode", "plugins", "node9.js"))
20454
20634
  },
20455
20635
  {
20456
20636
  id: "pi",
@@ -20458,11 +20638,11 @@ var AGENT_SPECS = [
20458
20638
  setupCommand: "node9 agents add pi",
20459
20639
  hookFormat: "flat",
20460
20640
  hookEvents: [],
20461
- shimFile: (h) => path39.join(h, ".pi", "agent", "extensions", "node9.js"),
20462
- present: (h) => exists(path39.join(h, ".pi", "agent")) || exists(path39.join(h, ".pi", "agent", "extensions", "node9.js"))
20641
+ shimFile: (h) => path40.join(h, ".pi", "agent", "extensions", "node9.js"),
20642
+ present: (h) => exists(path40.join(h, ".pi", "agent")) || exists(path40.join(h, ".pi", "agent", "extensions", "node9.js"))
20463
20643
  }
20464
20644
  ];
20465
- function getAgentWiring(home = os34.homedir()) {
20645
+ function getAgentWiring(home = os35.homedir()) {
20466
20646
  const detected = detectAgents(home);
20467
20647
  return AGENT_SPECS.map((spec) => {
20468
20648
  const present = spec.present(home);
@@ -20514,7 +20694,7 @@ function getAgentWiring(home = os34.homedir()) {
20514
20694
  // src/cli/commands/doctor.ts
20515
20695
  function registerDoctorCommand(program2, version2) {
20516
20696
  program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(async () => {
20517
- const homeDir2 = os35.homedir();
20697
+ const homeDir2 = os36.homedir();
20518
20698
  let failures = 0;
20519
20699
  function pass(msg) {
20520
20700
  console.log(chalk11.green(" \u2705 ") + msg);
@@ -20560,10 +20740,10 @@ function registerDoctorCommand(program2, version2) {
20560
20740
  );
20561
20741
  }
20562
20742
  section("Configuration");
20563
- const globalConfigPath = path40.join(homeDir2, ".node9", "config.json");
20564
- if (fs39.existsSync(globalConfigPath)) {
20743
+ const globalConfigPath = path41.join(homeDir2, ".node9", "config.json");
20744
+ if (fs40.existsSync(globalConfigPath)) {
20565
20745
  try {
20566
- JSON.parse(fs39.readFileSync(globalConfigPath, "utf-8"));
20746
+ JSON.parse(fs40.readFileSync(globalConfigPath, "utf-8"));
20567
20747
  pass("~/.node9/config.json found and valid");
20568
20748
  } catch {
20569
20749
  fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
@@ -20571,10 +20751,10 @@ function registerDoctorCommand(program2, version2) {
20571
20751
  } else {
20572
20752
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
20573
20753
  }
20574
- const projectConfigPath = path40.join(process.cwd(), "node9.config.json");
20575
- if (fs39.existsSync(projectConfigPath)) {
20754
+ const projectConfigPath = path41.join(process.cwd(), "node9.config.json");
20755
+ if (fs40.existsSync(projectConfigPath)) {
20576
20756
  try {
20577
- JSON.parse(fs39.readFileSync(projectConfigPath, "utf-8"));
20757
+ JSON.parse(fs40.readFileSync(projectConfigPath, "utf-8"));
20578
20758
  pass("node9.config.json found and valid (project)");
20579
20759
  } catch {
20580
20760
  fail(
@@ -20583,8 +20763,8 @@ function registerDoctorCommand(program2, version2) {
20583
20763
  );
20584
20764
  }
20585
20765
  }
20586
- const credsPath = path40.join(homeDir2, ".node9", "credentials.json");
20587
- if (fs39.existsSync(credsPath)) {
20766
+ const credsPath = path41.join(homeDir2, ".node9", "credentials.json");
20767
+ if (fs40.existsSync(credsPath)) {
20588
20768
  pass("Cloud credentials found (~/.node9/credentials.json)");
20589
20769
  } else {
20590
20770
  warn(
@@ -20628,7 +20808,7 @@ function registerDoctorCommand(program2, version2) {
20628
20808
  try {
20629
20809
  const { shipLagBytes: shipLagBytes2, readWatermark: readWatermark2, AUDIT_SHIP_WATERMARK: AUDIT_SHIP_WATERMARK2 } = await Promise.resolve().then(() => (init_audit_shipper(), audit_shipper_exports));
20630
20810
  const cfg = getConfig();
20631
- const creds = fs39.existsSync(path40.join(os35.homedir(), ".node9", "credentials.json"));
20811
+ const creds = fs40.existsSync(path41.join(os36.homedir(), ".node9", "credentials.json"));
20632
20812
  if (!creds) {
20633
20813
  warn("Not logged in \u2014 audit rows stay local", "Run: node9 login <api-key>");
20634
20814
  } else if (!cfg.settings.approvers.cloud) {
@@ -20678,9 +20858,9 @@ function registerDoctorCommand(program2, version2) {
20678
20858
 
20679
20859
  // src/cli/commands/audit.ts
20680
20860
  import chalk12 from "chalk";
20681
- import fs40 from "fs";
20682
- import path41 from "path";
20683
- import os36 from "os";
20861
+ import fs41 from "fs";
20862
+ import path42 from "path";
20863
+ import os37 from "os";
20684
20864
  function formatRelativeTime(timestamp) {
20685
20865
  const diff = Date.now() - new Date(timestamp).getTime();
20686
20866
  const sec = Math.floor(diff / 1e3);
@@ -20693,14 +20873,14 @@ function formatRelativeTime(timestamp) {
20693
20873
  }
20694
20874
  function registerAuditCommand(program2) {
20695
20875
  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) => {
20696
- const logPath = path41.join(os36.homedir(), ".node9", "audit.log");
20697
- if (!fs40.existsSync(logPath)) {
20876
+ const logPath = path42.join(os37.homedir(), ".node9", "audit.log");
20877
+ if (!fs41.existsSync(logPath)) {
20698
20878
  console.log(
20699
20879
  chalk12.yellow("No audit logs found. Run node9 with an agent to generate entries.")
20700
20880
  );
20701
20881
  return;
20702
20882
  }
20703
- const raw = fs40.readFileSync(logPath, "utf-8");
20883
+ const raw = fs41.readFileSync(logPath, "utf-8");
20704
20884
  const lines = raw.split("\n").filter((l) => l.trim() !== "");
20705
20885
  let entries = lines.flatMap((line) => {
20706
20886
  try {
@@ -20759,9 +20939,9 @@ import chalk13 from "chalk";
20759
20939
  init_costSync();
20760
20940
  init_litellm();
20761
20941
  init_cost_codex();
20762
- import fs41 from "fs";
20763
- import os37 from "os";
20764
- import path42 from "path";
20942
+ import fs42 from "fs";
20943
+ import os38 from "os";
20944
+ import path43 from "path";
20765
20945
  var TEST_COMMAND_RE3 = /(?:^|\s)(npm\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|yarn\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|vitest|jest|mocha|pytest|py\.test|cargo\s+test|go\s+test|bundle\s+exec\s+rspec|rspec|phpunit|dotnet\s+test)\b/i;
20766
20946
  function buildTestTimestamps(allEntries) {
20767
20947
  const testTs = /* @__PURE__ */ new Set();
@@ -20841,8 +21021,8 @@ function getDateRange(period, now) {
20841
21021
  }
20842
21022
  }
20843
21023
  function parseAuditLog(logPath) {
20844
- if (!fs41.existsSync(logPath)) return [];
20845
- const raw = fs41.readFileSync(logPath, "utf-8");
21024
+ if (!fs42.existsSync(logPath)) return [];
21025
+ const raw = fs42.readFileSync(logPath, "utf-8");
20846
21026
  return raw.split("\n").flatMap((line) => {
20847
21027
  if (!line.trim()) return [];
20848
21028
  try {
@@ -20889,25 +21069,25 @@ function freezeClaudeCost(acc) {
20889
21069
  };
20890
21070
  }
20891
21071
  function processClaudeCostProject(proj, projectsDir, start, end, acc) {
20892
- const projPath = path42.join(projectsDir, proj);
21072
+ const projPath = path43.join(projectsDir, proj);
20893
21073
  let files;
20894
21074
  try {
20895
- const stat = fs41.statSync(projPath);
21075
+ const stat = fs42.statSync(projPath);
20896
21076
  if (!stat.isDirectory()) return;
20897
- files = fs41.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
21077
+ files = fs42.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
20898
21078
  } catch {
20899
21079
  return;
20900
21080
  }
20901
21081
  const startMs = start.getTime();
20902
21082
  for (const file of files) {
20903
- const filePath = path42.join(projPath, file);
21083
+ const filePath = path43.join(projPath, file);
20904
21084
  try {
20905
- if (fs41.statSync(filePath).mtimeMs < startMs) continue;
21085
+ if (fs42.statSync(filePath).mtimeMs < startMs) continue;
20906
21086
  } catch {
20907
21087
  continue;
20908
21088
  }
20909
21089
  try {
20910
- const raw = fs41.readFileSync(filePath, "utf-8");
21090
+ const raw = fs42.readFileSync(filePath, "utf-8");
20911
21091
  for (const line of raw.split("\n")) {
20912
21092
  if (!line.trim()) continue;
20913
21093
  let entry;
@@ -20957,10 +21137,10 @@ function processClaudeCostProject(proj, projectsDir, start, end, acc) {
20957
21137
  }
20958
21138
  function loadClaudeCost(start, end, projectsDir) {
20959
21139
  const acc = emptyClaudeCostAccumulator();
20960
- if (!fs41.existsSync(projectsDir)) return freezeClaudeCost(acc);
21140
+ if (!fs42.existsSync(projectsDir)) return freezeClaudeCost(acc);
20961
21141
  let dirs;
20962
21142
  try {
20963
- dirs = fs41.readdirSync(projectsDir);
21143
+ dirs = fs42.readdirSync(projectsDir);
20964
21144
  } catch {
20965
21145
  return freezeClaudeCost(acc);
20966
21146
  }
@@ -20972,7 +21152,7 @@ function loadClaudeCost(start, end, projectsDir) {
20972
21152
  function processCodexCostFile(filePath, start, end, acc) {
20973
21153
  let lines;
20974
21154
  try {
20975
- lines = fs41.readFileSync(filePath, "utf-8").split("\n");
21155
+ lines = fs42.readFileSync(filePath, "utf-8").split("\n");
20976
21156
  } catch {
20977
21157
  return;
20978
21158
  }
@@ -21027,31 +21207,31 @@ function processCodexCostFile(filePath, start, end, acc) {
21027
21207
  }
21028
21208
  function listCodexSessionFiles2(sessionsBase) {
21029
21209
  const jsonlFiles = [];
21030
- if (!fs41.existsSync(sessionsBase)) return jsonlFiles;
21210
+ if (!fs42.existsSync(sessionsBase)) return jsonlFiles;
21031
21211
  try {
21032
- for (const year of fs41.readdirSync(sessionsBase)) {
21033
- const yearPath = path42.join(sessionsBase, year);
21212
+ for (const year of fs42.readdirSync(sessionsBase)) {
21213
+ const yearPath = path43.join(sessionsBase, year);
21034
21214
  try {
21035
- if (!fs41.statSync(yearPath).isDirectory()) continue;
21215
+ if (!fs42.statSync(yearPath).isDirectory()) continue;
21036
21216
  } catch {
21037
21217
  continue;
21038
21218
  }
21039
- for (const month of fs41.readdirSync(yearPath)) {
21040
- const monthPath = path42.join(yearPath, month);
21219
+ for (const month of fs42.readdirSync(yearPath)) {
21220
+ const monthPath = path43.join(yearPath, month);
21041
21221
  try {
21042
- if (!fs41.statSync(monthPath).isDirectory()) continue;
21222
+ if (!fs42.statSync(monthPath).isDirectory()) continue;
21043
21223
  } catch {
21044
21224
  continue;
21045
21225
  }
21046
- for (const day of fs41.readdirSync(monthPath)) {
21047
- const dayPath = path42.join(monthPath, day);
21226
+ for (const day of fs42.readdirSync(monthPath)) {
21227
+ const dayPath = path43.join(monthPath, day);
21048
21228
  try {
21049
- if (!fs41.statSync(dayPath).isDirectory()) continue;
21229
+ if (!fs42.statSync(dayPath).isDirectory()) continue;
21050
21230
  } catch {
21051
21231
  continue;
21052
21232
  }
21053
- for (const file of fs41.readdirSync(dayPath)) {
21054
- if (file.endsWith(".jsonl")) jsonlFiles.push(path42.join(dayPath, file));
21233
+ for (const file of fs42.readdirSync(dayPath)) {
21234
+ if (file.endsWith(".jsonl")) jsonlFiles.push(path43.join(dayPath, file));
21055
21235
  }
21056
21236
  }
21057
21237
  }
@@ -21116,13 +21296,13 @@ function freezeGeminiCost(acc) {
21116
21296
  function processGeminiCostFile(filePath, projectKey, start, end, acc) {
21117
21297
  const startMs = start.getTime();
21118
21298
  try {
21119
- if (fs41.statSync(filePath).mtimeMs < startMs) return;
21299
+ if (fs42.statSync(filePath).mtimeMs < startMs) return;
21120
21300
  } catch {
21121
21301
  return;
21122
21302
  }
21123
21303
  let raw;
21124
21304
  try {
21125
- raw = fs41.readFileSync(filePath, "utf-8");
21305
+ raw = fs42.readFileSync(filePath, "utf-8");
21126
21306
  } catch {
21127
21307
  return;
21128
21308
  }
@@ -21171,30 +21351,30 @@ function listGeminiSessionFiles2(geminiTmpDir2) {
21171
21351
  const out = [];
21172
21352
  let dirs;
21173
21353
  try {
21174
- if (!fs41.statSync(geminiTmpDir2).isDirectory()) return out;
21175
- dirs = fs41.readdirSync(geminiTmpDir2);
21354
+ if (!fs42.statSync(geminiTmpDir2).isDirectory()) return out;
21355
+ dirs = fs42.readdirSync(geminiTmpDir2);
21176
21356
  } catch {
21177
21357
  return out;
21178
21358
  }
21179
21359
  for (const proj of dirs) {
21180
- const chatsDir = path42.join(geminiTmpDir2, proj, "chats");
21360
+ const chatsDir = path43.join(geminiTmpDir2, proj, "chats");
21181
21361
  let files;
21182
21362
  try {
21183
- if (!fs41.statSync(chatsDir).isDirectory()) continue;
21184
- files = fs41.readdirSync(chatsDir);
21363
+ if (!fs42.statSync(chatsDir).isDirectory()) continue;
21364
+ files = fs42.readdirSync(chatsDir);
21185
21365
  } catch {
21186
21366
  continue;
21187
21367
  }
21188
21368
  for (const f of files) {
21189
21369
  if (!f.endsWith(".jsonl")) continue;
21190
- out.push({ projectKey: proj, file: path42.join(chatsDir, f) });
21370
+ out.push({ projectKey: proj, file: path43.join(chatsDir, f) });
21191
21371
  }
21192
21372
  }
21193
21373
  return out;
21194
21374
  }
21195
21375
  function loadGeminiCost(start, end, geminiTmpDir2) {
21196
21376
  const acc = emptyGeminiAccumulator();
21197
- if (!fs41.existsSync(geminiTmpDir2)) return freezeGeminiCost(acc);
21377
+ if (!fs42.existsSync(geminiTmpDir2)) return freezeGeminiCost(acc);
21198
21378
  for (const { projectKey, file } of listGeminiSessionFiles2(geminiTmpDir2)) {
21199
21379
  processGeminiCostFile(file, projectKey, start, end, acc);
21200
21380
  }
@@ -21202,11 +21382,11 @@ function loadGeminiCost(start, end, geminiTmpDir2) {
21202
21382
  }
21203
21383
  function aggregateReportFromAudit(period, opts = {}) {
21204
21384
  const now = opts.now ?? /* @__PURE__ */ new Date();
21205
- const auditLogPath = opts.auditLogPath ?? path42.join(os37.homedir(), ".node9", "audit.log");
21206
- const claudeProjectsDir = opts.claudeProjectsDir ?? path42.join(os37.homedir(), ".claude", "projects");
21207
- const codexSessionsDir2 = opts.codexSessionsDir ?? path42.join(os37.homedir(), ".codex", "sessions");
21208
- const geminiTmpDir2 = opts.geminiTmpDir ?? path42.join(os37.homedir(), ".gemini", "tmp");
21209
- const hasAuditFile = fs41.existsSync(auditLogPath);
21385
+ const auditLogPath = opts.auditLogPath ?? path43.join(os38.homedir(), ".node9", "audit.log");
21386
+ const claudeProjectsDir = opts.claudeProjectsDir ?? path43.join(os38.homedir(), ".claude", "projects");
21387
+ const codexSessionsDir2 = opts.codexSessionsDir ?? path43.join(os38.homedir(), ".codex", "sessions");
21388
+ const geminiTmpDir2 = opts.geminiTmpDir ?? path43.join(os38.homedir(), ".gemini", "tmp");
21389
+ const hasAuditFile = fs42.existsSync(auditLogPath);
21210
21390
  const allEntries = opts.preloadedAuditEntries ?? parseAuditLog(auditLogPath);
21211
21391
  const unackedDlp = allEntries.filter((e) => e.source === "response-dlp");
21212
21392
  const { start, end } = getDateRange(period, now);
@@ -21904,9 +22084,9 @@ function registerDaemonCommand(program2) {
21904
22084
  init_core();
21905
22085
  init_daemon();
21906
22086
  import chalk15 from "chalk";
21907
- import fs42 from "fs";
21908
- import path43 from "path";
21909
- import os38 from "os";
22087
+ import fs43 from "fs";
22088
+ import path44 from "path";
22089
+ import os39 from "os";
21910
22090
  function printAgentSection(label2, hookPairs, wrapped) {
21911
22091
  console.log(chalk15.bold(` ${label2}`));
21912
22092
  for (const { name, present } of hookPairs) {
@@ -21960,20 +22140,20 @@ function registerStatusCommand(program2) {
21960
22140
  console.log("");
21961
22141
  const modeLabel = settings.mode === "audit" ? chalk15.blue("audit") : settings.mode === "strict" ? chalk15.red("strict") : chalk15.white("standard");
21962
22142
  console.log(` Mode: ${modeLabel}`);
21963
- const projectConfig = path43.join(process.cwd(), "node9.config.json");
21964
- const globalConfig = path43.join(os38.homedir(), ".node9", "config.json");
22143
+ const projectConfig = path44.join(process.cwd(), "node9.config.json");
22144
+ const globalConfig = path44.join(os39.homedir(), ".node9", "config.json");
21965
22145
  console.log(
21966
- ` Local: ${fs42.existsSync(projectConfig) ? chalk15.green("Active (node9.config.json)") : chalk15.gray("Not present")}`
22146
+ ` Local: ${fs43.existsSync(projectConfig) ? chalk15.green("Active (node9.config.json)") : chalk15.gray("Not present")}`
21967
22147
  );
21968
22148
  console.log(
21969
- ` Global: ${fs42.existsSync(globalConfig) ? chalk15.green("Active (~/.node9/config.json)") : chalk15.gray("Not present")}`
22149
+ ` Global: ${fs43.existsSync(globalConfig) ? chalk15.green("Active (~/.node9/config.json)") : chalk15.gray("Not present")}`
21970
22150
  );
21971
22151
  if (mergedConfig.policy.sandboxPaths.length > 0) {
21972
22152
  console.log(
21973
22153
  ` Sandbox: ${chalk15.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
21974
22154
  );
21975
22155
  }
21976
- const wiring = getAgentWiring(os38.homedir()).filter((a) => a.present);
22156
+ const wiring = getAgentWiring(os39.homedir()).filter((a) => a.present);
21977
22157
  if (wiring.length > 0) {
21978
22158
  console.log("");
21979
22159
  console.log(chalk15.bold(" Agent Wiring:"));
@@ -22012,9 +22192,9 @@ init_setup();
22012
22192
  init_shields();
22013
22193
  init_service();
22014
22194
  import chalk16 from "chalk";
22015
- import fs43 from "fs";
22016
- import path44 from "path";
22017
- import os39 from "os";
22195
+ import fs44 from "fs";
22196
+ import path45 from "path";
22197
+ import os40 from "os";
22018
22198
  import https4 from "https";
22019
22199
  var DEFAULT_SHIELDS = ["bash-safe", "filesystem", "project-jail"];
22020
22200
  function buildTelemetryPayload(agents, firstInstall) {
@@ -22100,16 +22280,16 @@ function registerInitCommand(program2) {
22100
22280
  }
22101
22281
  console.log("");
22102
22282
  }
22103
- const configPath2 = path44.join(os39.homedir(), ".node9", "config.json");
22104
- const isFirstInstall = !fs43.existsSync(configPath2);
22105
- if (fs43.existsSync(configPath2) && !options.force) {
22283
+ const configPath2 = path45.join(os40.homedir(), ".node9", "config.json");
22284
+ const isFirstInstall = !fs44.existsSync(configPath2);
22285
+ if (fs44.existsSync(configPath2) && !options.force) {
22106
22286
  try {
22107
- const existing = JSON.parse(fs43.readFileSync(configPath2, "utf-8"));
22287
+ const existing = JSON.parse(fs44.readFileSync(configPath2, "utf-8"));
22108
22288
  const settings = existing.settings ?? {};
22109
22289
  if (settings.mode !== chosenMode) {
22110
22290
  settings.mode = chosenMode;
22111
22291
  existing.settings = settings;
22112
- fs43.writeFileSync(configPath2, JSON.stringify(existing, null, 2) + "\n");
22292
+ fs44.writeFileSync(configPath2, JSON.stringify(existing, null, 2) + "\n");
22113
22293
  console.log(chalk16.green(`\u2705 Mode updated: ${chosenMode}`));
22114
22294
  } else {
22115
22295
  console.log(chalk16.blue(`\u2139\uFE0F Config already exists: ${configPath2}`));
@@ -22122,9 +22302,9 @@ function registerInitCommand(program2) {
22122
22302
  ...DEFAULT_CONFIG,
22123
22303
  settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
22124
22304
  };
22125
- const dir = path44.dirname(configPath2);
22126
- if (!fs43.existsSync(dir)) fs43.mkdirSync(dir, { recursive: true });
22127
- fs43.writeFileSync(configPath2, JSON.stringify(configToSave, null, 2) + "\n");
22305
+ const dir = path45.dirname(configPath2);
22306
+ if (!fs44.existsSync(dir)) fs44.mkdirSync(dir, { recursive: true });
22307
+ fs44.writeFileSync(configPath2, JSON.stringify(configToSave, null, 2) + "\n");
22128
22308
  console.log(chalk16.green(`\u2705 Config created: ${configPath2}`));
22129
22309
  console.log(chalk16.gray(` Mode: ${chosenMode}`));
22130
22310
  }
@@ -22229,7 +22409,7 @@ function registerInitCommand(program2) {
22229
22409
  }
22230
22410
 
22231
22411
  // src/cli/commands/undo.ts
22232
- import path45 from "path";
22412
+ import path46 from "path";
22233
22413
  import chalk18 from "chalk";
22234
22414
 
22235
22415
  // src/tui/undo-navigator.ts
@@ -22388,7 +22568,7 @@ function findMatchingCwd(startDir, history) {
22388
22568
  let dir = startDir;
22389
22569
  while (true) {
22390
22570
  if (cwds.has(dir)) return dir;
22391
- const parent = path45.dirname(dir);
22571
+ const parent = path46.dirname(dir);
22392
22572
  if (parent === dir) return null;
22393
22573
  dir = parent;
22394
22574
  }
@@ -23023,9 +23203,9 @@ function registerMcpGatewayCommand(program2) {
23023
23203
 
23024
23204
  // src/mcp-server/index.ts
23025
23205
  import readline5 from "readline";
23026
- import fs44 from "fs";
23027
- import os40 from "os";
23028
- import path46 from "path";
23206
+ import fs45 from "fs";
23207
+ import os41 from "os";
23208
+ import path47 from "path";
23029
23209
  import { spawnSync as spawnSync4 } from "child_process";
23030
23210
  init_core();
23031
23211
  init_daemon();
@@ -23221,6 +23401,38 @@ var TOOLS = [
23221
23401
  required: []
23222
23402
  }
23223
23403
  },
23404
+ {
23405
+ name: "node9_posture",
23406
+ 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.",
23407
+ inputSchema: {
23408
+ type: "object",
23409
+ properties: {
23410
+ agent: {
23411
+ type: "string",
23412
+ description: "Optional label / policy scope for the agent being graded."
23413
+ }
23414
+ },
23415
+ required: []
23416
+ }
23417
+ },
23418
+ {
23419
+ name: "node9_explain",
23420
+ 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.',
23421
+ inputSchema: {
23422
+ type: "object",
23423
+ properties: {
23424
+ tool: {
23425
+ type: "string",
23426
+ description: 'Tool name to evaluate. Defaults to "bash" for plain shell commands.'
23427
+ },
23428
+ args: {
23429
+ type: "string",
23430
+ description: 'Tool arguments as JSON, or a plain shell command string (e.g. "git push --force").'
23431
+ }
23432
+ },
23433
+ required: []
23434
+ }
23435
+ },
23224
23436
  {
23225
23437
  name: "node9_rule_add",
23226
23438
  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.',
@@ -23276,13 +23488,13 @@ function handleStatus() {
23276
23488
  lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
23277
23489
  lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
23278
23490
  lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
23279
- const projectConfig = path46.join(process.cwd(), "node9.config.json");
23280
- const globalConfig = path46.join(os40.homedir(), ".node9", "config.json");
23491
+ const projectConfig = path47.join(process.cwd(), "node9.config.json");
23492
+ const globalConfig = path47.join(os41.homedir(), ".node9", "config.json");
23281
23493
  lines.push(
23282
- `Project config (node9.config.json): ${fs44.existsSync(projectConfig) ? "present" : "not found"}`
23494
+ `Project config (node9.config.json): ${fs45.existsSync(projectConfig) ? "present" : "not found"}`
23283
23495
  );
23284
23496
  lines.push(
23285
- `Global config (~/.node9/config.json): ${fs44.existsSync(globalConfig) ? "present" : "not found"}`
23497
+ `Global config (~/.node9/config.json): ${fs45.existsSync(globalConfig) ? "present" : "not found"}`
23286
23498
  );
23287
23499
  return lines.join("\n");
23288
23500
  }
@@ -23356,21 +23568,21 @@ function handleShieldDisable(args) {
23356
23568
  writeActiveShields(active.filter((s) => s !== name));
23357
23569
  return `Shield "${name}" disabled.`;
23358
23570
  }
23359
- var GLOBAL_CONFIG_PATH = path46.join(os40.homedir(), ".node9", "config.json");
23571
+ var GLOBAL_CONFIG_PATH = path47.join(os41.homedir(), ".node9", "config.json");
23360
23572
  var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
23361
23573
  function readGlobalConfigRaw() {
23362
23574
  try {
23363
- if (fs44.existsSync(GLOBAL_CONFIG_PATH)) {
23364
- return JSON.parse(fs44.readFileSync(GLOBAL_CONFIG_PATH, "utf-8"));
23575
+ if (fs45.existsSync(GLOBAL_CONFIG_PATH)) {
23576
+ return JSON.parse(fs45.readFileSync(GLOBAL_CONFIG_PATH, "utf-8"));
23365
23577
  }
23366
23578
  } catch {
23367
23579
  }
23368
23580
  return {};
23369
23581
  }
23370
23582
  function writeGlobalConfigRaw(data) {
23371
- const dir = path46.dirname(GLOBAL_CONFIG_PATH);
23372
- if (!fs44.existsSync(dir)) fs44.mkdirSync(dir, { recursive: true });
23373
- fs44.writeFileSync(GLOBAL_CONFIG_PATH, JSON.stringify(data, null, 2) + "\n");
23583
+ const dir = path47.dirname(GLOBAL_CONFIG_PATH);
23584
+ if (!fs45.existsSync(dir)) fs45.mkdirSync(dir, { recursive: true });
23585
+ fs45.writeFileSync(GLOBAL_CONFIG_PATH, JSON.stringify(data, null, 2) + "\n");
23374
23586
  }
23375
23587
  function handleApproverList() {
23376
23588
  const config = getConfig();
@@ -23414,9 +23626,9 @@ function handleApproverSet(args) {
23414
23626
  function handleAuditGet(args) {
23415
23627
  const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
23416
23628
  const filter = typeof args.filter === "string" && args.filter !== "all" ? args.filter : null;
23417
- const auditPath = path46.join(os40.homedir(), ".node9", "audit.log");
23418
- if (!fs44.existsSync(auditPath)) return "No audit log found.";
23419
- const rawLines = fs44.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
23629
+ const auditPath = path47.join(os41.homedir(), ".node9", "audit.log");
23630
+ if (!fs45.existsSync(auditPath)) return "No audit log found.";
23631
+ const rawLines = fs45.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
23420
23632
  const parsed = [];
23421
23633
  for (const line of rawLines) {
23422
23634
  try {
@@ -23527,6 +23739,17 @@ function handleSessionMcp(args) {
23527
23739
  if (typeof args.detail === "string" && args.detail) cliArgs.push("--detail", args.detail);
23528
23740
  return runCliCommand(cliArgs);
23529
23741
  }
23742
+ function handlePostureMcp(args) {
23743
+ const cliArgs = ["posture"];
23744
+ if (typeof args.agent === "string" && args.agent) cliArgs.push("--agent", args.agent);
23745
+ return runCliCommand(cliArgs);
23746
+ }
23747
+ function handleExplainMcp(args) {
23748
+ const tool = typeof args.tool === "string" && args.tool ? args.tool : "bash";
23749
+ const cliArgs = ["explain", tool];
23750
+ if (typeof args.args === "string" && args.args) cliArgs.push(args.args);
23751
+ return runCliCommand(cliArgs);
23752
+ }
23530
23753
  function handleUndoList(args) {
23531
23754
  const cwdFilter = typeof args.cwd === "string" && args.cwd ? args.cwd : null;
23532
23755
  let history = getSnapshotHistory();
@@ -23661,6 +23884,10 @@ function runMcpServer() {
23661
23884
  text = handleReportMcp(toolArgs);
23662
23885
  } else if (toolName === "node9_session") {
23663
23886
  text = handleSessionMcp(toolArgs);
23887
+ } else if (toolName === "node9_posture") {
23888
+ text = handlePostureMcp(toolArgs);
23889
+ } else if (toolName === "node9_explain") {
23890
+ text = handleExplainMcp(toolArgs);
23664
23891
  } else {
23665
23892
  process.stdout.write(err(id, -32601, `Unknown tool: ${toolName}`) + "\n");
23666
23893
  return;
@@ -23751,7 +23978,7 @@ function registerTrustCommand(program2) {
23751
23978
  // src/cli/commands/mcp-pin.ts
23752
23979
  init_mcp_pin();
23753
23980
  import chalk21 from "chalk";
23754
- import fs45 from "fs";
23981
+ import fs46 from "fs";
23755
23982
  function registerMcpPinCommand(program2) {
23756
23983
  const pinCmd = program2.command("mcp").description("Manage MCP server tool definition pinning (rug pull defense)");
23757
23984
  const pinSubCmd = pinCmd.command("pin").description("Manage pinned MCP server tool definitions");
@@ -23762,7 +23989,7 @@ function registerMcpPinCommand(program2) {
23762
23989
  let repoCorrupt = false;
23763
23990
  if (found.source === "repo") {
23764
23991
  try {
23765
- const raw = fs45.readFileSync(found.path, "utf-8");
23992
+ const raw = fs46.readFileSync(found.path, "utf-8");
23766
23993
  const parsed = JSON.parse(raw);
23767
23994
  repoEntries = parsed.servers ?? {};
23768
23995
  } catch {
@@ -24077,25 +24304,25 @@ init_scan();
24077
24304
  import chalk25 from "chalk";
24078
24305
 
24079
24306
  // src/posture/index.ts
24080
- import os44 from "os";
24307
+ import os45 from "os";
24081
24308
 
24082
24309
  // src/posture/secrets.ts
24083
24310
  init_dist();
24084
- import fs46 from "fs";
24085
- import path47 from "path";
24086
- import os41 from "os";
24311
+ import fs47 from "fs";
24312
+ import path48 from "path";
24313
+ import os42 from "os";
24087
24314
  var MAX_FILE_BYTES = 256 * 1024;
24088
24315
  function displayPath(p, home) {
24089
24316
  if (p === home) return "~";
24090
- const prefix = home.endsWith(path47.sep) ? home : home + path47.sep;
24091
- if (p.startsWith(prefix)) return "~" + path47.sep + p.slice(prefix.length);
24317
+ const prefix = home.endsWith(path48.sep) ? home : home + path48.sep;
24318
+ if (p.startsWith(prefix)) return "~" + path48.sep + p.slice(prefix.length);
24092
24319
  return p;
24093
24320
  }
24094
24321
  function safeRead(file) {
24095
24322
  try {
24096
- const stat = fs46.statSync(file);
24323
+ const stat = fs47.statSync(file);
24097
24324
  if (!stat.isFile() || stat.size === 0 || stat.size > MAX_FILE_BYTES) return null;
24098
- return fs46.readFileSync(file, "utf8");
24325
+ return fs47.readFileSync(file, "utf8");
24099
24326
  } catch {
24100
24327
  return null;
24101
24328
  }
@@ -24103,8 +24330,8 @@ function safeRead(file) {
24103
24330
  function candidateFiles(home, cwd) {
24104
24331
  const files = /* @__PURE__ */ new Set();
24105
24332
  try {
24106
- for (const name of fs46.readdirSync(cwd)) {
24107
- if (name === ".env" || name.startsWith(".env.")) files.add(path47.join(cwd, name));
24333
+ for (const name of fs47.readdirSync(cwd)) {
24334
+ if (name === ".env" || name.startsWith(".env.")) files.add(path48.join(cwd, name));
24108
24335
  }
24109
24336
  } catch {
24110
24337
  }
@@ -24112,21 +24339,21 @@ function candidateFiles(home, cwd) {
24112
24339
  if (spec.hookFile) files.add(spec.hookFile(home));
24113
24340
  if (spec.mcpFile) files.add(spec.mcpFile(home));
24114
24341
  }
24115
- files.add(path47.join(home, ".env"));
24342
+ files.add(path48.join(home, ".env"));
24116
24343
  return [...files];
24117
24344
  }
24118
24345
  function credentialMaterial(home) {
24119
24346
  return [
24120
- path47.join(home, ".ssh", "id_rsa"),
24121
- path47.join(home, ".ssh", "id_dsa"),
24122
- path47.join(home, ".ssh", "id_ecdsa"),
24123
- path47.join(home, ".ssh", "id_ed25519"),
24124
- path47.join(home, ".aws", "credentials"),
24125
- path47.join(home, ".config", "gcloud", "application_default_credentials.json")
24347
+ path48.join(home, ".ssh", "id_rsa"),
24348
+ path48.join(home, ".ssh", "id_dsa"),
24349
+ path48.join(home, ".ssh", "id_ecdsa"),
24350
+ path48.join(home, ".ssh", "id_ed25519"),
24351
+ path48.join(home, ".aws", "credentials"),
24352
+ path48.join(home, ".config", "gcloud", "application_default_credentials.json")
24126
24353
  ];
24127
24354
  }
24128
24355
  function checkSecrets(ctx) {
24129
- const home = ctx.home || os41.homedir();
24356
+ const home = ctx.home || os42.homedir();
24130
24357
  const findings = [];
24131
24358
  const plaintext = [];
24132
24359
  const plaintextPaths = [];
@@ -24159,7 +24386,7 @@ function checkSecrets(ctx) {
24159
24386
  const credPaths = [];
24160
24387
  for (const file of credentialMaterial(home)) {
24161
24388
  try {
24162
- if (fs46.statSync(file).isFile()) {
24389
+ if (fs47.statSync(file).isFile()) {
24163
24390
  creds.push(displayPath(file, home));
24164
24391
  credPaths.push(file);
24165
24392
  }
@@ -24185,7 +24412,7 @@ function checkSecrets(ctx) {
24185
24412
 
24186
24413
  // src/posture/egress.ts
24187
24414
  init_config();
24188
- import fs47 from "fs";
24415
+ import fs48 from "fs";
24189
24416
 
24190
24417
  // src/sandbox/templates.ts
24191
24418
  var AGENT_NPM_PACKAGE = {
@@ -24306,7 +24533,7 @@ exec gosu "$RUN_AS_USER" bash -lc '
24306
24533
  // src/posture/egress.ts
24307
24534
  function sandboxEgressWallActive() {
24308
24535
  try {
24309
- return fs47.existsSync(ALLOWED_DOMAINS_PATH);
24536
+ return fs48.existsSync(ALLOWED_DOMAINS_PATH);
24310
24537
  } catch {
24311
24538
  return false;
24312
24539
  }
@@ -24416,25 +24643,25 @@ async function checkGate(ctx) {
24416
24643
 
24417
24644
  // src/posture/supply-chain.ts
24418
24645
  init_provenance();
24419
- import fs48 from "fs";
24420
- import os42 from "os";
24421
- import path48 from "path";
24646
+ import fs49 from "fs";
24647
+ import os43 from "os";
24648
+ import path49 from "path";
24422
24649
  import { parse as parseToml3 } from "smol-toml";
24423
24650
  var PACKAGE_RUNNERS = /* @__PURE__ */ new Set(["npx", "pnpx", "bunx", "dlx", "yarn", "pnpm", "bun"]);
24424
24651
  function isNode9Managed(command, args = []) {
24425
24652
  if (!command) return false;
24426
- if (path48.basename(command).toLowerCase() === "node9") return true;
24427
- if (PACKAGE_RUNNERS.has(path48.basename(command).toLowerCase())) {
24428
- return args.some((a) => a === "node9" || path48.basename(a).toLowerCase() === "node9");
24653
+ if (path49.basename(command).toLowerCase() === "node9") return true;
24654
+ if (PACKAGE_RUNNERS.has(path49.basename(command).toLowerCase())) {
24655
+ return args.some((a) => a === "node9" || path49.basename(a).toLowerCase() === "node9");
24429
24656
  }
24430
24657
  return false;
24431
24658
  }
24432
24659
  var MAX_CONFIG_BYTES = 10 * 1024 * 1024;
24433
24660
  function readServers(file, format, agent) {
24434
24661
  try {
24435
- const stat = fs48.statSync(file);
24662
+ const stat = fs49.statSync(file);
24436
24663
  if (!stat.isFile() || stat.size > MAX_CONFIG_BYTES) return [];
24437
- const text = fs48.readFileSync(file, "utf8");
24664
+ const text = fs49.readFileSync(file, "utf8");
24438
24665
  const map = format === "toml" ? parseToml3(text)?.mcp_servers : JSON.parse(text)?.mcpServers;
24439
24666
  if (!map || typeof map !== "object") return [];
24440
24667
  return Object.entries(map).map(([name, v]) => ({
@@ -24448,7 +24675,7 @@ function readServers(file, format, agent) {
24448
24675
  }
24449
24676
  }
24450
24677
  function checkSupplyChain(ctx) {
24451
- const home = ctx.home || os42.homedir();
24678
+ const home = ctx.home || os43.homedir();
24452
24679
  const servers = [];
24453
24680
  for (const spec of AGENT_SPECS) {
24454
24681
  if (!spec.mcpFile) continue;
@@ -24530,12 +24757,12 @@ async function checkPrivilege(ctx) {
24530
24757
  }
24531
24758
 
24532
24759
  // src/posture/containment.ts
24533
- import fs49 from "fs";
24760
+ import fs50 from "fs";
24534
24761
  var ISOLATION_WEIGHT = 12;
24535
24762
  function inContainer() {
24536
- if (fs49.existsSync("/.dockerenv") || fs49.existsSync("/run/.containerenv")) return true;
24763
+ if (fs50.existsSync("/.dockerenv") || fs50.existsSync("/run/.containerenv")) return true;
24537
24764
  try {
24538
- const cgroup = fs49.readFileSync("/proc/1/cgroup", "utf8");
24765
+ const cgroup = fs50.readFileSync("/proc/1/cgroup", "utf8");
24539
24766
  if (/docker|kubepods|containerd|lxc|libpod/.test(cgroup)) return true;
24540
24767
  } catch {
24541
24768
  }
@@ -24574,7 +24801,7 @@ Lighter \u2014 harden in place, keep full host access (about +${Math.round(
24574
24801
  }
24575
24802
 
24576
24803
  // src/posture/inbound.ts
24577
- import fs50 from "fs";
24804
+ import fs51 from "fs";
24578
24805
  var DB_EXPOSURE_WEIGHT = 4;
24579
24806
  var KNOWN_SERVICE_PORTS = {
24580
24807
  5432: "PostgreSQL",
@@ -24661,7 +24888,7 @@ function collectListeners() {
24661
24888
  const byPort = /* @__PURE__ */ new Map();
24662
24889
  for (const file of ["/proc/net/tcp", "/proc/net/tcp6"]) {
24663
24890
  try {
24664
- for (const l of parseListeners(fs50.readFileSync(file, "utf8"))) {
24891
+ for (const l of parseListeners(fs51.readFileSync(file, "utf8"))) {
24665
24892
  if (!byPort.has(l.port)) byPort.set(l.port, l);
24666
24893
  }
24667
24894
  } catch {
@@ -24673,11 +24900,11 @@ function readProc(pid) {
24673
24900
  let comm = "unknown";
24674
24901
  let cmdline = "";
24675
24902
  try {
24676
- comm = fs50.readFileSync(`/proc/${pid}/comm`, "utf8").trim().replace(/-MainThread$/, "") || "unknown";
24903
+ comm = fs51.readFileSync(`/proc/${pid}/comm`, "utf8").trim().replace(/-MainThread$/, "") || "unknown";
24677
24904
  } catch {
24678
24905
  }
24679
24906
  try {
24680
- cmdline = fs50.readFileSync(`/proc/${pid}/cmdline`).toString().replace(/\0/g, " ").trim();
24907
+ cmdline = fs51.readFileSync(`/proc/${pid}/cmdline`).toString().replace(/\0/g, " ").trim();
24681
24908
  } catch {
24682
24909
  }
24683
24910
  return { comm, cmdline };
@@ -24687,21 +24914,21 @@ function resolveProcesses(inodes) {
24687
24914
  if (inodes.size === 0) return map;
24688
24915
  let pids;
24689
24916
  try {
24690
- pids = fs50.readdirSync("/proc").filter((d) => /^\d+$/.test(d));
24917
+ pids = fs51.readdirSync("/proc").filter((d) => /^\d+$/.test(d));
24691
24918
  } catch {
24692
24919
  return map;
24693
24920
  }
24694
24921
  for (const pid of pids) {
24695
24922
  let fds;
24696
24923
  try {
24697
- fds = fs50.readdirSync(`/proc/${pid}/fd`);
24924
+ fds = fs51.readdirSync(`/proc/${pid}/fd`);
24698
24925
  } catch {
24699
24926
  continue;
24700
24927
  }
24701
24928
  for (const fd of fds) {
24702
24929
  let link;
24703
24930
  try {
24704
- link = fs50.readlinkSync(`/proc/${pid}/fd/${fd}`);
24931
+ link = fs51.readlinkSync(`/proc/${pid}/fd/${fd}`);
24705
24932
  } catch {
24706
24933
  continue;
24707
24934
  }
@@ -24772,9 +24999,9 @@ function checkInbound(ctx) {
24772
24999
 
24773
25000
  // src/posture/coverage.ts
24774
25001
  init_config();
24775
- import os43 from "os";
25002
+ import os44 from "os";
24776
25003
  function checkCoverage(ctx) {
24777
- const home = ctx.home || os43.homedir();
25004
+ const home = ctx.home || os44.homedir();
24778
25005
  const findings = [];
24779
25006
  const protectedAgents = getAgentWiring(home).filter((r) => r.isProtected);
24780
25007
  if (protectedAgents.length === 0) {
@@ -24982,7 +25209,7 @@ async function runChecks(checks, ctx) {
24982
25209
  }
24983
25210
  async function runPosture(opts = {}) {
24984
25211
  const ctx = {
24985
- home: opts.home ?? os44.homedir(),
25212
+ home: opts.home ?? os45.homedir(),
24986
25213
  cwd: opts.cwd ?? process.cwd(),
24987
25214
  agent: opts.agent
24988
25215
  };
@@ -25252,9 +25479,9 @@ function registerPostureCommand(program2) {
25252
25479
  init_config();
25253
25480
  init_dist();
25254
25481
  import chalk26 from "chalk";
25255
- import fs51 from "fs";
25256
- import os45 from "os";
25257
- import path49 from "path";
25482
+ import fs52 from "fs";
25483
+ import os46 from "os";
25484
+ import path50 from "path";
25258
25485
  var DEFAULT_EGRESS = {
25259
25486
  enabled: false,
25260
25487
  mode: "review",
@@ -25263,12 +25490,12 @@ var DEFAULT_EGRESS = {
25263
25490
  allowPrivate: true
25264
25491
  };
25265
25492
  function configPath() {
25266
- return path49.join(os45.homedir(), ".node9", "config.json");
25493
+ return path50.join(os46.homedir(), ".node9", "config.json");
25267
25494
  }
25268
25495
  function readRawConfig() {
25269
25496
  let text;
25270
25497
  try {
25271
- text = fs51.readFileSync(configPath(), "utf8");
25498
+ text = fs52.readFileSync(configPath(), "utf8");
25272
25499
  } catch (err2) {
25273
25500
  if (err2.code === "ENOENT") return {};
25274
25501
  throw err2;
@@ -25283,8 +25510,8 @@ function readRawConfig() {
25283
25510
  }
25284
25511
  function writeRawConfig(config) {
25285
25512
  const p = configPath();
25286
- fs51.mkdirSync(path49.dirname(p), { recursive: true });
25287
- fs51.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
25513
+ fs52.mkdirSync(path50.dirname(p), { recursive: true });
25514
+ fs52.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
25288
25515
  }
25289
25516
  function applyEgress(config, change) {
25290
25517
  const policy = config.policy = config.policy ?? {};
@@ -25378,13 +25605,13 @@ function registerEgressCommand(program2) {
25378
25605
  // src/cli/commands/sandbox.ts
25379
25606
  init_config();
25380
25607
  import chalk27 from "chalk";
25381
- import fs54 from "fs";
25382
- import path52 from "path";
25608
+ import fs55 from "fs";
25609
+ import path53 from "path";
25383
25610
  import { spawnSync as spawnSync6 } from "child_process";
25384
25611
 
25385
25612
  // src/sandbox/config.ts
25386
- import fs52 from "fs";
25387
- import path50 from "path";
25613
+ import fs53 from "fs";
25614
+ import path51 from "path";
25388
25615
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
25389
25616
  var SANDBOX_CONFIG_FILE = "node9.sandbox.yaml";
25390
25617
  var FORBIDDEN_ENV = /* @__PURE__ */ new Set(["NODE9_API_KEY", "NODE9_API_URL"]);
@@ -25457,16 +25684,16 @@ function scaffoldSandboxYaml(agent) {
25457
25684
  return header + stringifyYaml(defaultSandboxConfig(agent));
25458
25685
  }
25459
25686
  function sandboxConfigPath(cwd = process.cwd()) {
25460
- return path50.join(cwd, SANDBOX_CONFIG_FILE);
25687
+ return path51.join(cwd, SANDBOX_CONFIG_FILE);
25461
25688
  }
25462
25689
  function loadSandboxConfig(cwd = process.cwd(), fallbackAgent = "claude") {
25463
25690
  const p = sandboxConfigPath(cwd);
25464
- if (!fs52.existsSync(p)) {
25691
+ if (!fs53.existsSync(p)) {
25465
25692
  throw new Error(`sandbox: ${SANDBOX_CONFIG_FILE} not found \u2014 run \`node9 sandbox new\` first.`);
25466
25693
  }
25467
25694
  let raw;
25468
25695
  try {
25469
- raw = parseYaml(fs52.readFileSync(p, "utf-8"));
25696
+ raw = parseYaml(fs53.readFileSync(p, "utf-8"));
25470
25697
  } catch (err2) {
25471
25698
  throw new Error(
25472
25699
  `sandbox: ${SANDBOX_CONFIG_FILE} is not valid YAML \u2014 ${err2.message}`
@@ -25521,13 +25748,13 @@ function compileAllowlist(input) {
25521
25748
  }
25522
25749
 
25523
25750
  // src/sandbox/runtime.ts
25524
- import fs53 from "fs";
25525
- import os46 from "os";
25526
- import path51 from "path";
25751
+ import fs54 from "fs";
25752
+ import os47 from "os";
25753
+ import path52 from "path";
25527
25754
  import crypto8 from "crypto";
25528
25755
  import { spawnSync as spawnSync5 } from "child_process";
25529
25756
  function sandboxDataDir(cwd = process.cwd()) {
25530
- return path51.join(cwd, ".node9", "sandbox", "data");
25757
+ return path52.join(cwd, ".node9", "sandbox", "data");
25531
25758
  }
25532
25759
  function detectEngine(engine) {
25533
25760
  const r = spawnSync5(engine, ["--version"], { encoding: "utf-8" });
@@ -25538,7 +25765,7 @@ function detectEngine(engine) {
25538
25765
  }
25539
25766
  function agentCredentialsMount(agent) {
25540
25767
  const rel = agent === "codex" ? ".codex/auth.json" : ".claude/.credentials.json";
25541
- return { hostPath: path51.join(os46.homedir(), rel), target: `/home/${RUN_AS_USER}/${rel}` };
25768
+ return { hostPath: path52.join(os47.homedir(), rel), target: `/home/${RUN_AS_USER}/${rel}` };
25542
25769
  }
25543
25770
  function buildRunArgs(opts) {
25544
25771
  const { config, workspaceHostPath, dataHostPath, allowlistHostPath, agentArgs } = opts;
@@ -25548,7 +25775,7 @@ function buildRunArgs(opts) {
25548
25775
  args.push("-v", `${allowlistHostPath}:${ALLOWED_DOMAINS_PATH}:ro`);
25549
25776
  if (config.node9.mountAgentCredentials) {
25550
25777
  const creds = agentCredentialsMount(config.agent);
25551
- if (fs53.existsSync(creds.hostPath)) {
25778
+ if (fs54.existsSync(creds.hostPath)) {
25552
25779
  args.push("-v", `${creds.hostPath}:${creds.target}`);
25553
25780
  }
25554
25781
  }
@@ -25566,30 +25793,30 @@ function imageContentHash(dockerfile, entrypoint) {
25566
25793
  return crypto8.createHash("sha256").update(dockerfile).update("\0").update(entrypoint).digest("hex").slice(0, 16);
25567
25794
  }
25568
25795
  function sandboxBuildDir(cwd = process.cwd()) {
25569
- return path51.join(cwd, ".node9", "sandbox", "build");
25796
+ return path52.join(cwd, ".node9", "sandbox", "build");
25570
25797
  }
25571
25798
  function writeBuildContext(cwd, dockerfile, entrypoint) {
25572
25799
  const dir = sandboxBuildDir(cwd);
25573
- fs53.mkdirSync(dir, { recursive: true });
25574
- fs53.writeFileSync(path51.join(dir, "Dockerfile"), dockerfile);
25575
- fs53.writeFileSync(path51.join(dir, "entrypoint.sh"), entrypoint);
25800
+ fs54.mkdirSync(dir, { recursive: true });
25801
+ fs54.writeFileSync(path52.join(dir, "Dockerfile"), dockerfile);
25802
+ fs54.writeFileSync(path52.join(dir, "entrypoint.sh"), entrypoint);
25576
25803
  return dir;
25577
25804
  }
25578
25805
  function writeAllowlist(cwd, hosts) {
25579
- const dir = path51.join(cwd, ".node9", "sandbox");
25580
- fs53.mkdirSync(dir, { recursive: true });
25581
- const p = path51.join(dir, "allowed-domains.txt");
25582
- fs53.writeFileSync(p, hosts.join("\n") + "\n");
25806
+ const dir = path52.join(cwd, ".node9", "sandbox");
25807
+ fs54.mkdirSync(dir, { recursive: true });
25808
+ const p = path52.join(dir, "allowed-domains.txt");
25809
+ fs54.writeFileSync(p, hosts.join("\n") + "\n");
25583
25810
  return p;
25584
25811
  }
25585
25812
  function resolveHomePath(p) {
25586
- return p.startsWith("~") ? path51.join(os46.homedir(), p.slice(1)) : path51.resolve(p);
25813
+ return p.startsWith("~") ? path52.join(os47.homedir(), p.slice(1)) : path52.resolve(p);
25587
25814
  }
25588
25815
 
25589
25816
  // src/cli/commands/sandbox.ts
25590
25817
  function seedDataDirConfig(dataDir, sandbox) {
25591
- fs54.mkdirSync(dataDir, { recursive: true });
25592
- const configPath2 = path52.join(dataDir, "config.json");
25818
+ fs55.mkdirSync(dataDir, { recursive: true });
25819
+ const configPath2 = path53.join(dataDir, "config.json");
25593
25820
  const seed = {
25594
25821
  settings: {
25595
25822
  approvers: {
@@ -25600,7 +25827,7 @@ function seedDataDirConfig(dataDir, sandbox) {
25600
25827
  }
25601
25828
  }
25602
25829
  };
25603
- fs54.writeFileSync(configPath2, JSON.stringify(seed, null, 2), { mode: 384 });
25830
+ fs55.writeFileSync(configPath2, JSON.stringify(seed, null, 2), { mode: 384 });
25604
25831
  }
25605
25832
  function registerSandboxCommand(program2, version2) {
25606
25833
  const node9Version2 = pinnedNode9Version(version2);
@@ -25608,13 +25835,13 @@ function registerSandboxCommand(program2, version2) {
25608
25835
  cmd.command("new").description(`Scaffold ${SANDBOX_CONFIG_FILE} in this project`).option("--agent <agent>", "claude (default) or codex", "claude").action((opts) => {
25609
25836
  const agent = opts.agent === "codex" ? "codex" : "claude";
25610
25837
  const p = sandboxConfigPath();
25611
- if (fs54.existsSync(p)) {
25838
+ if (fs55.existsSync(p)) {
25612
25839
  console.log(
25613
25840
  chalk27.yellow(` ${SANDBOX_CONFIG_FILE} already exists \u2014 leaving it untouched.`)
25614
25841
  );
25615
25842
  return;
25616
25843
  }
25617
- fs54.writeFileSync(p, scaffoldSandboxYaml(agent));
25844
+ fs55.writeFileSync(p, scaffoldSandboxYaml(agent));
25618
25845
  console.log(
25619
25846
  chalk27.green(` \u2713 wrote ${SANDBOX_CONFIG_FILE}`) + chalk27.dim(` (agent: ${agent})`)
25620
25847
  );
@@ -25654,8 +25881,8 @@ function registerSandboxCommand(program2, version2) {
25654
25881
  const buildDir = writeBuildContext(cwd, dockerfile, entrypoint);
25655
25882
  const hash = imageContentHash(dockerfile, entrypoint);
25656
25883
  const image = sandbox.runtime.image;
25657
- const hashFile = path52.join(sandboxBuildDir(cwd), ".image-hash");
25658
- const lastHash = fs54.existsSync(hashFile) ? fs54.readFileSync(hashFile, "utf-8").trim() : "";
25884
+ const hashFile = path53.join(sandboxBuildDir(cwd), ".image-hash");
25885
+ const lastHash = fs55.existsSync(hashFile) ? fs55.readFileSync(hashFile, "utf-8").trim() : "";
25659
25886
  const imageExists = spawnSync6(sandbox.runtime.engine, ["image", "inspect", image], { stdio: "ignore" }).status === 0;
25660
25887
  const needBuild = sandbox.runtime.rebuild === "always" || !imageExists || sandbox.runtime.rebuild !== "never" && lastHash !== hash;
25661
25888
  if (needBuild) {
@@ -25667,7 +25894,7 @@ function registerSandboxCommand(program2, version2) {
25667
25894
  console.error(chalk27.red(" build failed."));
25668
25895
  process.exit(b.status ?? 1);
25669
25896
  }
25670
- fs54.writeFileSync(hashFile, hash);
25897
+ fs55.writeFileSync(hashFile, hash);
25671
25898
  }
25672
25899
  const dataDir = sandboxDataDir(cwd);
25673
25900
  seedDataDirConfig(dataDir, sandbox);
@@ -25681,7 +25908,7 @@ function registerSandboxCommand(program2, version2) {
25681
25908
  });
25682
25909
  if (sandbox.node9.mountAgentCredentials) {
25683
25910
  const creds = agentCredentialsMount(sandbox.agent);
25684
- if (fs54.existsSync(creds.hostPath)) {
25911
+ if (fs55.existsSync(creds.hostPath)) {
25685
25912
  console.log(chalk27.dim(` mounting ${creds.hostPath} (agent credentials, rw)`));
25686
25913
  } else {
25687
25914
  console.log(
@@ -25697,20 +25924,20 @@ function registerSandboxCommand(program2, version2) {
25697
25924
  process.exit(r.status ?? 0);
25698
25925
  });
25699
25926
  cmd.command("tail").description("Stream the sandbox's audit log (host-side)").action(() => {
25700
- const auditPath = path52.join(sandboxDataDir(), "audit.log");
25701
- if (!fs54.existsSync(auditPath)) {
25927
+ const auditPath = path53.join(sandboxDataDir(), "audit.log");
25928
+ if (!fs55.existsSync(auditPath)) {
25702
25929
  console.log(chalk27.dim(" no sandbox audit yet."));
25703
25930
  return;
25704
25931
  }
25705
25932
  spawnSync6("tail", ["-f", auditPath], { stdio: "inherit" });
25706
25933
  });
25707
25934
  cmd.command("logs").description("Dump the sandbox's audit log").action(() => {
25708
- const auditPath = path52.join(sandboxDataDir(), "audit.log");
25709
- if (!fs54.existsSync(auditPath)) {
25935
+ const auditPath = path53.join(sandboxDataDir(), "audit.log");
25936
+ if (!fs55.existsSync(auditPath)) {
25710
25937
  console.log(chalk27.dim(" no sandbox audit yet."));
25711
25938
  return;
25712
25939
  }
25713
- process.stdout.write(fs54.readFileSync(auditPath, "utf-8"));
25940
+ process.stdout.write(fs55.readFileSync(auditPath, "utf-8"));
25714
25941
  });
25715
25942
  cmd.command("clean").description("Remove the sandbox image, build context, and data").action(() => {
25716
25943
  const cwd = process.cwd();
@@ -25724,7 +25951,7 @@ function registerSandboxCommand(program2, version2) {
25724
25951
  stdio: "ignore"
25725
25952
  });
25726
25953
  }
25727
- fs54.rmSync(path52.join(cwd, ".node9", "sandbox"), { recursive: true, force: true });
25954
+ fs55.rmSync(path53.join(cwd, ".node9", "sandbox"), { recursive: true, force: true });
25728
25955
  console.log(chalk27.green(" \u2713 sandbox image + build + data removed."));
25729
25956
  });
25730
25957
  }
@@ -25735,9 +25962,9 @@ init_litellm();
25735
25962
  init_cost_gemini();
25736
25963
  init_cost_codex();
25737
25964
  import chalk28 from "chalk";
25738
- import fs55 from "fs";
25739
- import path53 from "path";
25740
- import os47 from "os";
25965
+ import fs56 from "fs";
25966
+ import path54 from "path";
25967
+ import os48 from "os";
25741
25968
  function modelPrice(model) {
25742
25969
  const t = pricingFor(model);
25743
25970
  if (!t) return null;
@@ -25754,10 +25981,10 @@ function encodeProjectPath(projectPath) {
25754
25981
  }
25755
25982
  function sessionJsonlPath(projectPath, sessionId) {
25756
25983
  const encoded = encodeProjectPath(projectPath);
25757
- return path53.join(os47.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
25984
+ return path54.join(os48.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
25758
25985
  }
25759
25986
  function projectLabel(projectPath) {
25760
- return projectPath.replace(os47.homedir(), "~");
25987
+ return projectPath.replace(os48.homedir(), "~");
25761
25988
  }
25762
25989
  function parseHistoryLines(lines) {
25763
25990
  const entries = [];
@@ -25826,10 +26053,10 @@ function parseSessionLines(lines) {
25826
26053
  return { toolCalls, costUSD, hasSnapshot, modifiedFiles };
25827
26054
  }
25828
26055
  function loadAuditEntries(auditPath) {
25829
- const aPath = auditPath ?? path53.join(os47.homedir(), ".node9", "audit.log");
26056
+ const aPath = auditPath ?? path54.join(os48.homedir(), ".node9", "audit.log");
25830
26057
  let raw;
25831
26058
  try {
25832
- raw = fs55.readFileSync(aPath, "utf-8");
26059
+ raw = fs56.readFileSync(aPath, "utf-8");
25833
26060
  } catch {
25834
26061
  return [];
25835
26062
  }
@@ -25865,8 +26092,8 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
25865
26092
  return result;
25866
26093
  }
25867
26094
  function buildGeminiSessions(days, allAuditEntries) {
25868
- const tmpDir = path53.join(os47.homedir(), ".gemini", "tmp");
25869
- if (!fs55.existsSync(tmpDir)) return [];
26095
+ const tmpDir = path54.join(os48.homedir(), ".gemini", "tmp");
26096
+ if (!fs56.existsSync(tmpDir)) return [];
25870
26097
  const cutoff = days !== null ? (() => {
25871
26098
  const d = /* @__PURE__ */ new Date();
25872
26099
  d.setDate(d.getDate() - days);
@@ -25875,35 +26102,35 @@ function buildGeminiSessions(days, allAuditEntries) {
25875
26102
  })() : null;
25876
26103
  let slugDirs;
25877
26104
  try {
25878
- slugDirs = fs55.readdirSync(tmpDir);
26105
+ slugDirs = fs56.readdirSync(tmpDir);
25879
26106
  } catch {
25880
26107
  return [];
25881
26108
  }
25882
26109
  const summaries = [];
25883
26110
  for (const slug of slugDirs) {
25884
- const slugPath = path53.join(tmpDir, slug);
26111
+ const slugPath = path54.join(tmpDir, slug);
25885
26112
  try {
25886
- if (!fs55.statSync(slugPath).isDirectory()) continue;
26113
+ if (!fs56.statSync(slugPath).isDirectory()) continue;
25887
26114
  } catch {
25888
26115
  continue;
25889
26116
  }
25890
- let projectRoot = path53.join(os47.homedir(), slug);
26117
+ let projectRoot = path54.join(os48.homedir(), slug);
25891
26118
  try {
25892
- projectRoot = fs55.readFileSync(path53.join(slugPath, ".project_root"), "utf-8").trim();
26119
+ projectRoot = fs56.readFileSync(path54.join(slugPath, ".project_root"), "utf-8").trim();
25893
26120
  } catch {
25894
26121
  }
25895
- const chatsDir = path53.join(slugPath, "chats");
25896
- if (!fs55.existsSync(chatsDir)) continue;
26122
+ const chatsDir = path54.join(slugPath, "chats");
26123
+ if (!fs56.existsSync(chatsDir)) continue;
25897
26124
  let chatFiles;
25898
26125
  try {
25899
- chatFiles = fs55.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
26126
+ chatFiles = fs56.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
25900
26127
  } catch {
25901
26128
  continue;
25902
26129
  }
25903
26130
  for (const chatFile of chatFiles) {
25904
26131
  let raw;
25905
26132
  try {
25906
- raw = fs55.readFileSync(path53.join(chatsDir, chatFile), "utf-8");
26133
+ raw = fs56.readFileSync(path54.join(chatsDir, chatFile), "utf-8");
25907
26134
  } catch {
25908
26135
  continue;
25909
26136
  }
@@ -25983,8 +26210,8 @@ function buildGeminiSessions(days, allAuditEntries) {
25983
26210
  return summaries;
25984
26211
  }
25985
26212
  function buildCodexSessions(days, allAuditEntries) {
25986
- const sessionsBase = path53.join(os47.homedir(), ".codex", "sessions");
25987
- if (!fs55.existsSync(sessionsBase)) return [];
26213
+ const sessionsBase = path54.join(os48.homedir(), ".codex", "sessions");
26214
+ if (!fs56.existsSync(sessionsBase)) return [];
25988
26215
  const cutoff = days !== null ? (() => {
25989
26216
  const d = /* @__PURE__ */ new Date();
25990
26217
  d.setDate(d.getDate() - days);
@@ -25993,29 +26220,29 @@ function buildCodexSessions(days, allAuditEntries) {
25993
26220
  })() : null;
25994
26221
  const jsonlFiles = [];
25995
26222
  try {
25996
- for (const year of fs55.readdirSync(sessionsBase)) {
25997
- const yearPath = path53.join(sessionsBase, year);
26223
+ for (const year of fs56.readdirSync(sessionsBase)) {
26224
+ const yearPath = path54.join(sessionsBase, year);
25998
26225
  try {
25999
- if (!fs55.statSync(yearPath).isDirectory()) continue;
26226
+ if (!fs56.statSync(yearPath).isDirectory()) continue;
26000
26227
  } catch {
26001
26228
  continue;
26002
26229
  }
26003
- for (const month of fs55.readdirSync(yearPath)) {
26004
- const monthPath = path53.join(yearPath, month);
26230
+ for (const month of fs56.readdirSync(yearPath)) {
26231
+ const monthPath = path54.join(yearPath, month);
26005
26232
  try {
26006
- if (!fs55.statSync(monthPath).isDirectory()) continue;
26233
+ if (!fs56.statSync(monthPath).isDirectory()) continue;
26007
26234
  } catch {
26008
26235
  continue;
26009
26236
  }
26010
- for (const day of fs55.readdirSync(monthPath)) {
26011
- const dayPath = path53.join(monthPath, day);
26237
+ for (const day of fs56.readdirSync(monthPath)) {
26238
+ const dayPath = path54.join(monthPath, day);
26012
26239
  try {
26013
- if (!fs55.statSync(dayPath).isDirectory()) continue;
26240
+ if (!fs56.statSync(dayPath).isDirectory()) continue;
26014
26241
  } catch {
26015
26242
  continue;
26016
26243
  }
26017
- for (const file of fs55.readdirSync(dayPath)) {
26018
- if (file.endsWith(".jsonl")) jsonlFiles.push(path53.join(dayPath, file));
26244
+ for (const file of fs56.readdirSync(dayPath)) {
26245
+ if (file.endsWith(".jsonl")) jsonlFiles.push(path54.join(dayPath, file));
26019
26246
  }
26020
26247
  }
26021
26248
  }
@@ -26027,7 +26254,7 @@ function buildCodexSessions(days, allAuditEntries) {
26027
26254
  for (const filePath of jsonlFiles) {
26028
26255
  let lines;
26029
26256
  try {
26030
- lines = fs55.readFileSync(filePath, "utf-8").split("\n");
26257
+ lines = fs56.readFileSync(filePath, "utf-8").split("\n");
26031
26258
  } catch {
26032
26259
  continue;
26033
26260
  }
@@ -26113,10 +26340,10 @@ function buildCodexSessions(days, allAuditEntries) {
26113
26340
  return summaries;
26114
26341
  }
26115
26342
  function buildSessions(days, historyPath) {
26116
- const hPath = historyPath ?? path53.join(os47.homedir(), ".claude", "history.jsonl");
26343
+ const hPath = historyPath ?? path54.join(os48.homedir(), ".claude", "history.jsonl");
26117
26344
  let historyRaw = "";
26118
26345
  try {
26119
- historyRaw = fs55.readFileSync(hPath, "utf-8");
26346
+ historyRaw = fs56.readFileSync(hPath, "utf-8");
26120
26347
  } catch {
26121
26348
  }
26122
26349
  const cutoff = days !== null ? (() => {
@@ -26140,7 +26367,7 @@ function buildSessions(days, historyPath) {
26140
26367
  const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
26141
26368
  let sessionLines = [];
26142
26369
  try {
26143
- sessionLines = fs55.readFileSync(jsonlFile, "utf-8").split("\n");
26370
+ sessionLines = fs56.readFileSync(jsonlFile, "utf-8").split("\n");
26144
26371
  } catch {
26145
26372
  }
26146
26373
  const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
@@ -26534,12 +26761,12 @@ function registerSessionTaintCommand(program2) {
26534
26761
 
26535
26762
  // src/cli/commands/skill-pin.ts
26536
26763
  import chalk30 from "chalk";
26537
- import fs56 from "fs";
26538
- import os48 from "os";
26539
- import path54 from "path";
26764
+ import fs57 from "fs";
26765
+ import os49 from "os";
26766
+ import path55 from "path";
26540
26767
  function wipeSkillSessions() {
26541
26768
  try {
26542
- fs56.rmSync(path54.join(os48.homedir(), ".node9", "skill-sessions"), {
26769
+ fs57.rmSync(path55.join(os49.homedir(), ".node9", "skill-sessions"), {
26543
26770
  recursive: true,
26544
26771
  force: true
26545
26772
  });
@@ -26621,15 +26848,15 @@ function registerSkillPinCommand(program2) {
26621
26848
  }
26622
26849
 
26623
26850
  // src/cli/commands/decisions.ts
26624
- import fs57 from "fs";
26625
- import os49 from "os";
26626
- import path55 from "path";
26851
+ import fs58 from "fs";
26852
+ import os50 from "os";
26853
+ import path56 from "path";
26627
26854
  import chalk31 from "chalk";
26628
- var DECISIONS_FILE2 = path55.join(os49.homedir(), ".node9", "decisions.json");
26855
+ var DECISIONS_FILE2 = path56.join(os50.homedir(), ".node9", "decisions.json");
26629
26856
  function readDecisions() {
26630
26857
  try {
26631
- if (!fs57.existsSync(DECISIONS_FILE2)) return {};
26632
- const raw = fs57.readFileSync(DECISIONS_FILE2, "utf-8");
26858
+ if (!fs58.existsSync(DECISIONS_FILE2)) return {};
26859
+ const raw = fs58.readFileSync(DECISIONS_FILE2, "utf-8");
26633
26860
  const parsed = JSON.parse(raw);
26634
26861
  const out = {};
26635
26862
  for (const [k, v] of Object.entries(parsed)) {
@@ -26641,11 +26868,11 @@ function readDecisions() {
26641
26868
  }
26642
26869
  }
26643
26870
  function writeDecisions(d) {
26644
- const dir = path55.dirname(DECISIONS_FILE2);
26645
- if (!fs57.existsSync(dir)) fs57.mkdirSync(dir, { recursive: true });
26871
+ const dir = path56.dirname(DECISIONS_FILE2);
26872
+ if (!fs58.existsSync(dir)) fs58.mkdirSync(dir, { recursive: true });
26646
26873
  const tmp = `${DECISIONS_FILE2}.${process.pid}.tmp`;
26647
- fs57.writeFileSync(tmp, JSON.stringify(d, null, 2));
26648
- fs57.renameSync(tmp, DECISIONS_FILE2);
26874
+ fs58.writeFileSync(tmp, JSON.stringify(d, null, 2));
26875
+ fs58.renameSync(tmp, DECISIONS_FILE2);
26649
26876
  }
26650
26877
  function registerDecisionsCommand(program2) {
26651
26878
  const cmd = program2.command("decisions").description('Manage persistent "Always Allow" / "Always Deny" tool decisions');
@@ -26702,18 +26929,18 @@ Persistent decisions (${entries.length})
26702
26929
 
26703
26930
  // src/cli/commands/dlp.ts
26704
26931
  import chalk32 from "chalk";
26705
- import fs58 from "fs";
26706
- import path56 from "path";
26707
- import os50 from "os";
26708
- var AUDIT_LOG = path56.join(os50.homedir(), ".node9", "audit.log");
26709
- var RESOLVED_FILE = path56.join(os50.homedir(), ".node9", "dlp-resolved.json");
26932
+ import fs59 from "fs";
26933
+ import path57 from "path";
26934
+ import os51 from "os";
26935
+ var AUDIT_LOG = path57.join(os51.homedir(), ".node9", "audit.log");
26936
+ var RESOLVED_FILE = path57.join(os51.homedir(), ".node9", "dlp-resolved.json");
26710
26937
  var ANSI_RE = /\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g;
26711
26938
  function stripAnsi(s) {
26712
26939
  return s.replace(ANSI_RE, "");
26713
26940
  }
26714
26941
  function loadResolved() {
26715
26942
  try {
26716
- const raw = JSON.parse(fs58.readFileSync(RESOLVED_FILE, "utf-8"));
26943
+ const raw = JSON.parse(fs59.readFileSync(RESOLVED_FILE, "utf-8"));
26717
26944
  return new Set(raw);
26718
26945
  } catch {
26719
26946
  return /* @__PURE__ */ new Set();
@@ -26721,13 +26948,13 @@ function loadResolved() {
26721
26948
  }
26722
26949
  function saveResolved(resolved) {
26723
26950
  try {
26724
- fs58.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
26951
+ fs59.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
26725
26952
  } catch {
26726
26953
  }
26727
26954
  }
26728
26955
  function loadDlpFindings() {
26729
- if (!fs58.existsSync(AUDIT_LOG)) return [];
26730
- return fs58.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
26956
+ if (!fs59.existsSync(AUDIT_LOG)) return [];
26957
+ return fs59.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
26731
26958
  if (!line.trim()) return [];
26732
26959
  try {
26733
26960
  const e = JSON.parse(line);
@@ -26826,14 +27053,14 @@ function registerDlpCommand(program2) {
26826
27053
  // src/cli/commands/mask.ts
26827
27054
  init_dlp();
26828
27055
  import chalk33 from "chalk";
26829
- import fs59 from "fs";
26830
- import path57 from "path";
26831
- import os51 from "os";
27056
+ import fs60 from "fs";
27057
+ import path58 from "path";
27058
+ import os52 from "os";
26832
27059
  function findJsonlFiles(dir) {
26833
27060
  const results = [];
26834
- if (!fs59.existsSync(dir)) return results;
26835
- for (const entry of fs59.readdirSync(dir, { withFileTypes: true })) {
26836
- const full = path57.join(dir, entry.name);
27061
+ if (!fs60.existsSync(dir)) return results;
27062
+ for (const entry of fs60.readdirSync(dir, { withFileTypes: true })) {
27063
+ const full = path58.join(dir, entry.name);
26837
27064
  if (entry.isDirectory()) results.push(...findJsonlFiles(full));
26838
27065
  else if (entry.isFile() && entry.name.endsWith(".jsonl")) results.push(full);
26839
27066
  }
@@ -26876,7 +27103,7 @@ function redactJson(obj) {
26876
27103
  function processFile(filePath, dryRun) {
26877
27104
  let raw;
26878
27105
  try {
26879
- raw = fs59.readFileSync(filePath, "utf-8");
27106
+ raw = fs60.readFileSync(filePath, "utf-8");
26880
27107
  } catch {
26881
27108
  return { redactedLines: 0, patterns: [] };
26882
27109
  }
@@ -26908,14 +27135,14 @@ function processFile(filePath, dryRun) {
26908
27135
  }
26909
27136
  }
26910
27137
  if (!dryRun && redactedLines > 0) {
26911
- fs59.writeFileSync(filePath, newLines.join("\n"), "utf-8");
27138
+ fs60.writeFileSync(filePath, newLines.join("\n"), "utf-8");
26912
27139
  }
26913
27140
  return { redactedLines, patterns };
26914
27141
  }
26915
27142
  function processJsonFile(filePath, dryRun) {
26916
27143
  let raw;
26917
27144
  try {
26918
- raw = fs59.readFileSync(filePath, "utf-8");
27145
+ raw = fs60.readFileSync(filePath, "utf-8");
26919
27146
  } catch {
26920
27147
  return { redactedLines: 0, patterns: [] };
26921
27148
  }
@@ -26928,15 +27155,15 @@ function processJsonFile(filePath, dryRun) {
26928
27155
  const { value, modified, found } = redactJson(parsed);
26929
27156
  if (!modified) return { redactedLines: 0, patterns: [] };
26930
27157
  if (!dryRun) {
26931
- fs59.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
27158
+ fs60.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
26932
27159
  }
26933
27160
  return { redactedLines: 1, patterns: found };
26934
27161
  }
26935
27162
  function findJsonFiles(dir) {
26936
27163
  const results = [];
26937
- if (!fs59.existsSync(dir)) return results;
26938
- for (const entry of fs59.readdirSync(dir, { withFileTypes: true })) {
26939
- const full = path57.join(dir, entry.name);
27164
+ if (!fs60.existsSync(dir)) return results;
27165
+ for (const entry of fs60.readdirSync(dir, { withFileTypes: true })) {
27166
+ const full = path58.join(dir, entry.name);
26940
27167
  if (entry.isDirectory()) results.push(...findJsonFiles(full));
26941
27168
  else if (entry.isFile() && entry.name.endsWith(".json")) results.push(full);
26942
27169
  }
@@ -26945,9 +27172,9 @@ function findJsonFiles(dir) {
26945
27172
  function registerMaskCommand(program2) {
26946
27173
  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) => {
26947
27174
  const dryRun = !!options.dryRun;
26948
- const home = os51.homedir();
26949
- const claudeDir = path57.join(home, ".claude", "projects");
26950
- const geminiDir = path57.join(home, ".gemini", "tmp");
27175
+ const home = os52.homedir();
27176
+ const claudeDir = path58.join(home, ".claude", "projects");
27177
+ const geminiDir = path58.join(home, ".gemini", "tmp");
26951
27178
  const allFiles = [
26952
27179
  ...findJsonlFiles(claudeDir).map((p) => ({ path: p, type: "jsonl" })),
26953
27180
  ...findJsonFiles(geminiDir).map((p) => ({ path: p, type: "json" }))
@@ -26955,7 +27182,7 @@ function registerMaskCommand(program2) {
26955
27182
  const cutoff = options.all ? null : new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3);
26956
27183
  const filtered = cutoff ? allFiles.filter((f) => {
26957
27184
  try {
26958
- return fs59.statSync(f.path).mtime >= cutoff;
27185
+ return fs60.statSync(f.path).mtime >= cutoff;
26959
27186
  } catch {
26960
27187
  return false;
26961
27188
  }
@@ -27011,20 +27238,20 @@ function registerMaskCommand(program2) {
27011
27238
  // src/cli.ts
27012
27239
  init_blast();
27013
27240
  var { version } = JSON.parse(
27014
- fs62.readFileSync(path60.join(__dirname, "../package.json"), "utf-8")
27241
+ fs63.readFileSync(path61.join(__dirname, "../package.json"), "utf-8")
27015
27242
  );
27016
27243
  var program = new Command();
27017
27244
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
27018
27245
  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) => {
27019
27246
  const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
27020
- const credPath = path60.join(os54.homedir(), ".node9", "credentials.json");
27021
- if (!fs62.existsSync(path60.dirname(credPath)))
27022
- fs62.mkdirSync(path60.dirname(credPath), { recursive: true });
27247
+ const credPath = path61.join(os55.homedir(), ".node9", "credentials.json");
27248
+ if (!fs63.existsSync(path61.dirname(credPath)))
27249
+ fs63.mkdirSync(path61.dirname(credPath), { recursive: true });
27023
27250
  const profileName = options.profile || "default";
27024
27251
  let existingCreds = {};
27025
27252
  try {
27026
- if (fs62.existsSync(credPath)) {
27027
- const raw = JSON.parse(fs62.readFileSync(credPath, "utf-8"));
27253
+ if (fs63.existsSync(credPath)) {
27254
+ const raw = JSON.parse(fs63.readFileSync(credPath, "utf-8"));
27028
27255
  if (raw.apiKey) {
27029
27256
  existingCreds = {
27030
27257
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
@@ -27036,14 +27263,14 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
27036
27263
  } catch {
27037
27264
  }
27038
27265
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
27039
- fs62.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
27266
+ fs63.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
27040
27267
  let effectiveCloud = null;
27041
27268
  if (profileName === "default") {
27042
- const configPath2 = path60.join(os54.homedir(), ".node9", "config.json");
27269
+ const configPath2 = path61.join(os55.homedir(), ".node9", "config.json");
27043
27270
  let config = {};
27044
27271
  try {
27045
- if (fs62.existsSync(configPath2))
27046
- config = JSON.parse(fs62.readFileSync(configPath2, "utf-8"));
27272
+ if (fs63.existsSync(configPath2))
27273
+ config = JSON.parse(fs63.readFileSync(configPath2, "utf-8"));
27047
27274
  } catch {
27048
27275
  }
27049
27276
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -27058,9 +27285,9 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
27058
27285
  approvers.cloud = false;
27059
27286
  }
27060
27287
  s.approvers = approvers;
27061
- if (!fs62.existsSync(path60.dirname(configPath2)))
27062
- fs62.mkdirSync(path60.dirname(configPath2), { recursive: true });
27063
- fs62.writeFileSync(configPath2, JSON.stringify(config, null, 2), { mode: 384 });
27288
+ if (!fs63.existsSync(path61.dirname(configPath2)))
27289
+ fs63.mkdirSync(path61.dirname(configPath2), { recursive: true });
27290
+ fs63.writeFileSync(configPath2, JSON.stringify(config, null, 2), { mode: 384 });
27064
27291
  effectiveCloud = approvers.cloud === true;
27065
27292
  }
27066
27293
  if (options.profile && profileName !== "default") {
@@ -27238,15 +27465,15 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
27238
27465
  }
27239
27466
  }
27240
27467
  if (options.purge) {
27241
- const node9Dir = path60.join(os54.homedir(), ".node9");
27242
- if (fs62.existsSync(node9Dir)) {
27468
+ const node9Dir = path61.join(os55.homedir(), ".node9");
27469
+ if (fs63.existsSync(node9Dir)) {
27243
27470
  const confirmed = await confirm2({
27244
27471
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
27245
27472
  default: false
27246
27473
  });
27247
27474
  if (confirmed) {
27248
- fs62.rmSync(node9Dir, { recursive: true });
27249
- if (fs62.existsSync(node9Dir)) {
27475
+ fs63.rmSync(node9Dir, { recursive: true });
27476
+ if (fs63.existsSync(node9Dir)) {
27250
27477
  console.error(
27251
27478
  chalk35.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
27252
27479
  );
@@ -27361,7 +27588,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
27361
27588
  });
27362
27589
  program.command("monitor").description("Live interactive dashboard \u2014 activity feed, approvals, security signals").action(async () => {
27363
27590
  try {
27364
- const dashboardPath = path60.join(__dirname, "dashboard.mjs");
27591
+ const dashboardPath = path61.join(__dirname, "dashboard.mjs");
27365
27592
  const dynamicImport = new Function("id", "return import(id)");
27366
27593
  const mod = await dynamicImport(`file://${dashboardPath}`);
27367
27594
  await mod.startMonitor();
@@ -27399,14 +27626,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
27399
27626
  Run "node9 addto claude" to register it as the statusLine.`
27400
27627
  ).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
27401
27628
  if (subcommand === "debug") {
27402
- const flagFile = path60.join(os54.homedir(), ".node9", "hud-debug");
27629
+ const flagFile = path61.join(os55.homedir(), ".node9", "hud-debug");
27403
27630
  if (state === "on") {
27404
- fs62.mkdirSync(path60.dirname(flagFile), { recursive: true });
27405
- fs62.writeFileSync(flagFile, "");
27631
+ fs63.mkdirSync(path61.dirname(flagFile), { recursive: true });
27632
+ fs63.writeFileSync(flagFile, "");
27406
27633
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
27407
27634
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
27408
27635
  } else if (state === "off") {
27409
- if (fs62.existsSync(flagFile)) fs62.unlinkSync(flagFile);
27636
+ if (fs63.existsSync(flagFile)) fs63.unlinkSync(flagFile);
27410
27637
  console.log("HUD debug logging disabled.");
27411
27638
  } else {
27412
27639
  console.error("Usage: node9 hud debug on|off");
@@ -27527,9 +27754,9 @@ if (process.argv[2] !== "daemon") {
27527
27754
  const isCheckHook = process.argv[2] === "check";
27528
27755
  if (isCheckHook) {
27529
27756
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
27530
- const logPath = path60.join(os54.homedir(), ".node9", "hook-debug.log");
27757
+ const logPath = path61.join(os55.homedir(), ".node9", "hook-debug.log");
27531
27758
  const msg = reason instanceof Error ? reason.message : String(reason);
27532
- fs62.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
27759
+ fs63.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
27533
27760
  `);
27534
27761
  }
27535
27762
  process.exit(0);