@node9/proxy 1.11.0 → 1.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -168,8 +168,8 @@ function sanitizeConfig(raw) {
168
168
  }
169
169
  }
170
170
  const lines = result.error.issues.map((issue) => {
171
- const path41 = issue.path.length > 0 ? issue.path.join(".") : "root";
172
- return ` \u2022 ${path41}: ${issue.message}`;
171
+ const path43 = issue.path.length > 0 ? issue.path.join(".") : "root";
172
+ return ` \u2022 ${path43}: ${issue.message}`;
173
173
  });
174
174
  return {
175
175
  sanitized,
@@ -842,9 +842,8 @@ var init_config = __esm({
842
842
  {
843
843
  field: "command",
844
844
  op: "matches",
845
- // Require the recursive flag to be preceded by whitespace so that
846
- // filenames containing "-r" (e.g. "ai-review.yml") don't false-positive.
847
- value: "rm\\b.*\\s(-[rRfF]*[rR][rRfF]*|--recursive)(\\s|$)"
845
+ // Anchor rm as a shell command (not inside a string arg like a git commit message).
846
+ value: "(^|&&|\\|\\||;)\\s*rm\\b[^;&|]*\\s(-[rRfF]*[rR][rRfF]*|--recursive)(\\s|$)"
848
847
  },
849
848
  {
850
849
  field: "command",
@@ -873,6 +872,13 @@ var init_config = __esm({
873
872
  name: "review-drop-truncate-shell",
874
873
  tool: "bash",
875
874
  conditions: [
875
+ {
876
+ field: "command",
877
+ op: "matches",
878
+ // Require a DB CLI in the command so grep/cat/echo of SQL strings don't trigger.
879
+ value: "(^|&&|\\|\\||;|\\|)\\s*(psql|mysql|sqlite3|sqlplus|cockroach|clickhouse-client|mongo)\\b",
880
+ flags: "i"
881
+ },
876
882
  {
877
883
  field: "command",
878
884
  op: "matches",
@@ -893,7 +899,9 @@ var init_config = __esm({
893
899
  {
894
900
  field: "command",
895
901
  op: "matches",
896
- value: "\\bgit\\b.*\\bpush\\b.*(--force|--force-with-lease|-f\\b)",
902
+ // Anchor git as a shell command so node -e / python -c scripts containing
903
+ // "git push --force" as a string don't false-positive.
904
+ value: "(^|&&|\\|\\||;)\\s*git\\s+push[^;&|]*(--force|--force-with-lease|-f\\b)",
897
905
  flags: "i"
898
906
  }
899
907
  ],
@@ -903,29 +911,20 @@ var init_config = __esm({
903
911
  description: "The AI wants to force push to a remote git branch. This rewrites shared history and can permanently destroy commits that teammates have already pulled."
904
912
  },
905
913
  {
906
- name: "review-git-push",
914
+ name: "review-git-destructive",
907
915
  tool: "bash",
908
916
  conditions: [
909
917
  {
910
918
  field: "command",
911
919
  op: "matches",
912
- value: "\\bgit\\b.*\\bpush\\b(?!.*(-f\\b|--force|--force-with-lease))",
920
+ value: "\\bgit\\s+(reset\\s+--hard|clean\\s+-[fdxX]|rebase\\b|tag\\s+-d|branch\\s+-[dD])",
913
921
  flags: "i"
914
- }
915
- ],
916
- conditionMode: "all",
917
- verdict: "review",
918
- reason: "git push sends changes to a shared remote",
919
- description: "The AI wants to push commits to a remote repository. Once pushed, those changes are visible to everyone with access."
920
- },
921
- {
922
- name: "review-git-destructive",
923
- tool: "bash",
924
- conditions: [
922
+ },
925
923
  {
926
924
  field: "command",
927
- op: "matches",
928
- value: "\\bgit\\b.*(reset\\s+--hard|clean\\s+-[fdxX]|\\brebase\\b|tag\\s+-d|branch\\s+-[dD])",
925
+ op: "notMatches",
926
+ // Exclude recovery ops — these resolve a conflict, not start a destructive action.
927
+ value: "\\bgit\\s+rebase\\s+--(abort|continue|skip)\\b",
929
928
  flags: "i"
930
929
  }
931
930
  ],
@@ -951,7 +950,9 @@ var init_config = __esm({
951
950
  {
952
951
  field: "command",
953
952
  op: "matches",
954
- value: "(curl|wget)[^|]*\\|\\s*(ba|z|da|fi|c|k)?sh",
953
+ // Anchor curl/wget as a shell command so node -e scripts testing this
954
+ // regex pattern don't self-match as a false positive.
955
+ value: "(^|&&|\\|\\||;)\\s*(curl|wget)[^|]*\\|\\s*(ba|z|da|fi|c|k)?sh",
955
956
  flags: "i"
956
957
  }
957
958
  ],
@@ -1173,6 +1174,20 @@ function scanArgs(args, depth = 0, fieldPath = "args") {
1173
1174
  }
1174
1175
  return null;
1175
1176
  }
1177
+ function scanText(text) {
1178
+ const t = text.length > MAX_STRING_BYTES ? text.slice(0, MAX_STRING_BYTES) : text;
1179
+ for (const pattern of DLP_PATTERNS) {
1180
+ if (pattern.regex.test(t)) {
1181
+ return {
1182
+ patternName: pattern.name,
1183
+ fieldPath: "response-text",
1184
+ redactedSample: maskSecret(t, pattern.regex),
1185
+ severity: pattern.severity
1186
+ };
1187
+ }
1188
+ }
1189
+ return null;
1190
+ }
1176
1191
  var import_fs4, import_path4, DLP_PATTERNS, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES;
1177
1192
  var init_dlp = __esm({
1178
1193
  "src/dlp.ts"() {
@@ -1204,7 +1219,7 @@ var init_dlp = __esm({
1204
1219
  regex: /_authToken\s*=\s*[A-Za-z0-9_\-]{20,}/,
1205
1220
  severity: "block"
1206
1221
  },
1207
- { name: "Bearer Token", regex: /Bearer\s+[a-zA-Z0-9\-._~+/]+=*/i, severity: "review" }
1222
+ { name: "Bearer Token", regex: /Bearer\s+[a-zA-Z0-9\-._~+/]{20,}=*/i, severity: "review" }
1208
1223
  ];
1209
1224
  SENSITIVE_PATH_PATTERNS = [
1210
1225
  /[/\\]\.ssh[/\\]/i,
@@ -1767,9 +1782,21 @@ function matchesPattern(text, patterns) {
1767
1782
  const withoutDotSlash = text.replace(/^\.\//, "");
1768
1783
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1769
1784
  }
1770
- function getNestedValue(obj, path41) {
1785
+ function getNestedValue(obj, path43) {
1771
1786
  if (!obj || typeof obj !== "object") return null;
1772
- return path41.split(".").reduce((prev, curr) => prev?.[curr], obj);
1787
+ return path43.split(".").reduce((prev, curr) => prev?.[curr], obj);
1788
+ }
1789
+ function stripStringArguments(cmd) {
1790
+ let result = cmd;
1791
+ result = result.replace(
1792
+ /\b(node|python3?|ruby|perl|php|deno)\s+(-[ecr]|eval)\s+("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/gi,
1793
+ '$1 $2 ""'
1794
+ );
1795
+ result = result.replace(
1796
+ /\s(-m|--message|--body|--title|--description)\s+("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/g,
1797
+ ' $1 ""'
1798
+ );
1799
+ return result;
1773
1800
  }
1774
1801
  function shouldSnapshot(toolName, args, config) {
1775
1802
  if (!config.settings.enableUndo) return false;
@@ -1788,7 +1815,8 @@ function evaluateSmartConditions(args, rule) {
1788
1815
  const mode = rule.conditionMode ?? "all";
1789
1816
  const results = rule.conditions.map((cond) => {
1790
1817
  const rawVal = getNestedValue(args, cond.field);
1791
- const val = rawVal !== null && rawVal !== void 0 ? String(rawVal).replace(/\s+/g, " ").trim() : null;
1818
+ const normalized = rawVal !== null && rawVal !== void 0 ? String(rawVal).replace(/\s+/g, " ").trim() : null;
1819
+ const val = cond.field === "command" && normalized !== null ? stripStringArguments(normalized) : normalized;
1792
1820
  switch (cond.op) {
1793
1821
  case "exists":
1794
1822
  return val !== null && val !== "";
@@ -2897,13 +2925,30 @@ ${smartTruncate(str, 500)}`
2897
2925
  }
2898
2926
  return { intent: "EXEC", message: smartTruncate(JSON.stringify(parsed), 200) };
2899
2927
  }
2928
+ function sendDesktopNotification(title, body) {
2929
+ if (isTestEnv()) return;
2930
+ try {
2931
+ if (process.platform === "darwin") {
2932
+ const esc = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
2933
+ const script = `display notification "${esc(body)}" with title "${esc(title)}"`;
2934
+ (0, import_child_process2.spawn)("osascript", ["-e", script], { detached: true, stdio: "ignore" }).unref();
2935
+ } else if (process.platform === "linux") {
2936
+ (0, import_child_process2.spawn)("notify-send", [title, body, "--icon=dialog-warning"], {
2937
+ detached: true,
2938
+ stdio: "ignore"
2939
+ }).unref();
2940
+ }
2941
+ } catch {
2942
+ }
2943
+ }
2900
2944
  function escapePango(text) {
2901
2945
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2902
2946
  }
2903
2947
  function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
2904
2948
  const lines = [];
2905
2949
  if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
2906
- lines.push(`\u{1F916} ${agent || "AI Agent"} | \u{1F527} ${toolName}`);
2950
+ const safeAgent = (agent ?? "AI Agent").replace(/\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g, "").slice(0, 80);
2951
+ lines.push(`\u{1F916} ${safeAgent} | \u{1F527} ${toolName}`);
2907
2952
  lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
2908
2953
  if (ruleDescription) lines.push(`\u2139 ${ruleDescription}`);
2909
2954
  lines.push("");
@@ -3290,7 +3335,16 @@ async function authorizeHeadless(toolName, args, meta, options) {
3290
3335
  if (!options?.calledFromDaemon) {
3291
3336
  const actId = (0, import_crypto4.randomUUID)();
3292
3337
  const actTs = Date.now();
3293
- await notifyActivity({ id: actId, ts: actTs, tool: toolName, args, status: "pending" });
3338
+ await notifyActivity({
3339
+ id: actId,
3340
+ ts: actTs,
3341
+ tool: toolName,
3342
+ args,
3343
+ status: "pending",
3344
+ // Strip ANSI escape sequences — agent name comes from caller-supplied metadata
3345
+ // and may be displayed in a terminal (node9 tail/watch), enabling injection.
3346
+ agent: meta?.agent ? meta.agent.replace(/\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g, "").slice(0, 80) : void 0
3347
+ });
3294
3348
  const result = await _authorizeHeadlessCore(toolName, args, meta, {
3295
3349
  ...options,
3296
3350
  activityId: actId
@@ -6007,7 +6061,8 @@ function startActivitySocket() {
6007
6061
  ts: data.ts,
6008
6062
  tool: data.tool,
6009
6063
  args: redactArgs(data.args),
6010
- status: "pending"
6064
+ status: "pending",
6065
+ agent: data.agent
6011
6066
  });
6012
6067
  } else {
6013
6068
  if (data.status === "allow") {
@@ -6471,10 +6526,161 @@ var init_sync = __esm({
6471
6526
  }
6472
6527
  });
6473
6528
 
6529
+ // src/daemon/dlp-scanner.ts
6530
+ function loadIndex() {
6531
+ try {
6532
+ return JSON.parse(import_fs18.default.readFileSync(INDEX_FILE, "utf-8"));
6533
+ } catch {
6534
+ return {};
6535
+ }
6536
+ }
6537
+ function saveIndex(index) {
6538
+ try {
6539
+ import_fs18.default.writeFileSync(INDEX_FILE, JSON.stringify(index), { encoding: "utf-8", mode: 384 });
6540
+ } catch {
6541
+ }
6542
+ }
6543
+ function appendAuditEntry(entry) {
6544
+ try {
6545
+ import_fs18.default.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
6546
+ } catch {
6547
+ }
6548
+ }
6549
+ function runDlpScan() {
6550
+ if (!import_fs18.default.existsSync(PROJECTS_DIR)) return;
6551
+ const index = loadIndex();
6552
+ let updated = false;
6553
+ let projDirs;
6554
+ try {
6555
+ projDirs = import_fs18.default.readdirSync(PROJECTS_DIR);
6556
+ } catch {
6557
+ return;
6558
+ }
6559
+ for (const proj of projDirs) {
6560
+ const projPath = import_path21.default.join(PROJECTS_DIR, proj);
6561
+ try {
6562
+ if (!import_fs18.default.lstatSync(projPath).isDirectory()) continue;
6563
+ const real = import_fs18.default.realpathSync(projPath);
6564
+ if (!real.startsWith(PROJECTS_DIR + import_path21.default.sep) && real !== PROJECTS_DIR) continue;
6565
+ } catch {
6566
+ continue;
6567
+ }
6568
+ let files;
6569
+ try {
6570
+ files = import_fs18.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
6571
+ } catch {
6572
+ continue;
6573
+ }
6574
+ for (const file of files) {
6575
+ const filePath = import_path21.default.join(projPath, file);
6576
+ const lastOffset = index[filePath] ?? 0;
6577
+ let size;
6578
+ try {
6579
+ size = import_fs18.default.statSync(filePath).size;
6580
+ } catch {
6581
+ continue;
6582
+ }
6583
+ if (size <= lastOffset) continue;
6584
+ let fd;
6585
+ try {
6586
+ fd = import_fs18.default.openSync(filePath, "r");
6587
+ } catch {
6588
+ continue;
6589
+ }
6590
+ try {
6591
+ const chunkSize = size - lastOffset;
6592
+ const buf = Buffer.alloc(chunkSize);
6593
+ import_fs18.default.readSync(fd, buf, 0, chunkSize, lastOffset);
6594
+ const chunk = buf.toString("utf-8");
6595
+ for (const line of chunk.split("\n")) {
6596
+ if (!line.trim()) continue;
6597
+ let entry;
6598
+ try {
6599
+ entry = JSON.parse(line);
6600
+ } catch {
6601
+ continue;
6602
+ }
6603
+ if (entry.type !== "assistant") continue;
6604
+ const content = entry.message?.content;
6605
+ if (!Array.isArray(content)) continue;
6606
+ for (const block of content) {
6607
+ if (typeof block !== "object" || block === null || block.type !== "text")
6608
+ continue;
6609
+ const text = block.text;
6610
+ if (typeof text !== "string") continue;
6611
+ const match = scanText(text);
6612
+ if (!match) continue;
6613
+ const projLabel = decodeURIComponent(proj).replace(import_os16.default.homedir(), "~").slice(0, 40);
6614
+ const ts = entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
6615
+ appendAuditEntry({
6616
+ ts,
6617
+ tool: "response-text",
6618
+ decision: "dlp",
6619
+ checkedBy: "response-dlp",
6620
+ source: "response-dlp",
6621
+ dlpPattern: match.patternName,
6622
+ dlpSample: match.redactedSample,
6623
+ project: projLabel
6624
+ });
6625
+ sendDesktopNotification(
6626
+ "\u26A0\uFE0F node9 DLP Alert",
6627
+ `${match.patternName} found in Claude response
6628
+ Sample: ${match.redactedSample}
6629
+ Project: ${projLabel}
6630
+ Run: node9 report --period 30d`
6631
+ );
6632
+ }
6633
+ }
6634
+ index[filePath] = size;
6635
+ updated = true;
6636
+ } finally {
6637
+ try {
6638
+ import_fs18.default.closeSync(fd);
6639
+ } catch {
6640
+ }
6641
+ }
6642
+ }
6643
+ }
6644
+ if (updated) saveIndex(index);
6645
+ }
6646
+ function startDlpScanner() {
6647
+ setImmediate(() => {
6648
+ try {
6649
+ runDlpScan();
6650
+ } catch {
6651
+ }
6652
+ });
6653
+ const timer = setInterval(
6654
+ () => {
6655
+ try {
6656
+ runDlpScan();
6657
+ } catch {
6658
+ }
6659
+ },
6660
+ 60 * 60 * 1e3
6661
+ );
6662
+ timer.unref();
6663
+ }
6664
+ var import_fs18, import_path21, import_os16, INDEX_FILE, PROJECTS_DIR;
6665
+ var init_dlp_scanner = __esm({
6666
+ "src/daemon/dlp-scanner.ts"() {
6667
+ "use strict";
6668
+ import_fs18 = __toESM(require("fs"));
6669
+ import_path21 = __toESM(require("path"));
6670
+ import_os16 = __toESM(require("os"));
6671
+ init_dlp();
6672
+ init_native();
6673
+ init_state2();
6674
+ INDEX_FILE = import_path21.default.join(import_os16.default.homedir(), ".node9", "dlp-index.json");
6675
+ PROJECTS_DIR = import_path21.default.join(import_os16.default.homedir(), ".claude", "projects");
6676
+ }
6677
+ });
6678
+
6474
6679
  // src/daemon/server.ts
6475
6680
  function startDaemon() {
6476
6681
  startCostSync();
6477
6682
  startCloudSync();
6683
+ startDlpScanner();
6478
6684
  loadInsightCounts();
6479
6685
  const csrfToken = (0, import_crypto7.randomUUID)();
6480
6686
  const internalToken = (0, import_crypto7.randomUUID)();
@@ -6490,7 +6696,7 @@ function startDaemon() {
6490
6696
  idleTimer = setTimeout(() => {
6491
6697
  if (autoStarted) {
6492
6698
  try {
6493
- import_fs18.default.unlinkSync(DAEMON_PID_FILE);
6699
+ import_fs19.default.unlinkSync(DAEMON_PID_FILE);
6494
6700
  } catch {
6495
6701
  }
6496
6702
  }
@@ -6653,7 +6859,7 @@ data: ${JSON.stringify(item.data)}
6653
6859
  status: "pending"
6654
6860
  });
6655
6861
  }
6656
- const projectCwd = typeof cwd === "string" && import_path21.default.isAbsolute(cwd) ? cwd : void 0;
6862
+ const projectCwd = typeof cwd === "string" && import_path22.default.isAbsolute(cwd) ? cwd : void 0;
6657
6863
  const projectConfig = getConfig(projectCwd);
6658
6864
  const browserEnabled = projectConfig.settings.approvers?.browser !== false;
6659
6865
  const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
@@ -7044,8 +7250,8 @@ data: ${JSON.stringify(item.data)}
7044
7250
  const body = await readBody(req);
7045
7251
  const data = body ? JSON.parse(body) : {};
7046
7252
  const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
7047
- const node9Dir = import_path21.default.dirname(GLOBAL_CONFIG_PATH);
7048
- if (!import_path21.default.resolve(configPath).startsWith(node9Dir + import_path21.default.sep)) {
7253
+ const node9Dir = import_path22.default.dirname(GLOBAL_CONFIG_PATH);
7254
+ if (!import_path22.default.resolve(configPath).startsWith(node9Dir + import_path22.default.sep)) {
7049
7255
  res.writeHead(400, { "Content-Type": "application/json" });
7050
7256
  return res.end(
7051
7257
  JSON.stringify({ error: "configPath must be within the node9 config directory" })
@@ -7156,14 +7362,14 @@ data: ${JSON.stringify(item.data)}
7156
7362
  server.on("error", (e) => {
7157
7363
  if (e.code === "EADDRINUSE") {
7158
7364
  try {
7159
- if (import_fs18.default.existsSync(DAEMON_PID_FILE)) {
7160
- const { pid } = JSON.parse(import_fs18.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
7365
+ if (import_fs19.default.existsSync(DAEMON_PID_FILE)) {
7366
+ const { pid } = JSON.parse(import_fs19.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
7161
7367
  process.kill(pid, 0);
7162
7368
  return process.exit(0);
7163
7369
  }
7164
7370
  } catch {
7165
7371
  try {
7166
- import_fs18.default.unlinkSync(DAEMON_PID_FILE);
7372
+ import_fs19.default.unlinkSync(DAEMON_PID_FILE);
7167
7373
  } catch {
7168
7374
  }
7169
7375
  server.listen(DAEMON_PORT, DAEMON_HOST);
@@ -7222,13 +7428,13 @@ data: ${JSON.stringify(item.data)}
7222
7428
  }
7223
7429
  startActivitySocket();
7224
7430
  }
7225
- var import_http, import_fs18, import_path21, import_crypto7, import_child_process4, import_chalk2;
7431
+ var import_http, import_fs19, import_path22, import_crypto7, import_child_process4, import_chalk2;
7226
7432
  var init_server = __esm({
7227
7433
  "src/daemon/server.ts"() {
7228
7434
  "use strict";
7229
7435
  import_http = __toESM(require("http"));
7230
- import_fs18 = __toESM(require("fs"));
7231
- import_path21 = __toESM(require("path"));
7436
+ import_fs19 = __toESM(require("fs"));
7437
+ import_path22 = __toESM(require("path"));
7232
7438
  import_crypto7 = require("crypto");
7233
7439
  import_child_process4 = require("child_process");
7234
7440
  import_chalk2 = __toESM(require("chalk"));
@@ -7241,6 +7447,7 @@ var init_server = __esm({
7241
7447
  init_config_schema();
7242
7448
  init_costSync();
7243
7449
  init_sync();
7450
+ init_dlp_scanner();
7244
7451
  }
7245
7452
  });
7246
7453
 
@@ -7248,8 +7455,8 @@ var init_server = __esm({
7248
7455
  function resolveNode9Binary() {
7249
7456
  try {
7250
7457
  const script = process.argv[1];
7251
- if (typeof script === "string" && import_path22.default.isAbsolute(script) && import_fs19.default.existsSync(script)) {
7252
- return import_fs19.default.realpathSync(script);
7458
+ if (typeof script === "string" && import_path23.default.isAbsolute(script) && import_fs20.default.existsSync(script)) {
7459
+ return import_fs20.default.realpathSync(script);
7253
7460
  }
7254
7461
  } catch {
7255
7462
  }
@@ -7267,11 +7474,11 @@ function xmlEscape(s) {
7267
7474
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
7268
7475
  }
7269
7476
  function launchdPlist(binaryPath) {
7270
- const logDir = import_path22.default.join(import_os16.default.homedir(), ".node9");
7477
+ const logDir = import_path23.default.join(import_os17.default.homedir(), ".node9");
7271
7478
  const nodePath = xmlEscape(process.execPath);
7272
7479
  const scriptPath = xmlEscape(binaryPath);
7273
- const outLog = xmlEscape(import_path22.default.join(logDir, "daemon.log"));
7274
- const errLog = xmlEscape(import_path22.default.join(logDir, "daemon-error.log"));
7480
+ const outLog = xmlEscape(import_path23.default.join(logDir, "daemon.log"));
7481
+ const errLog = xmlEscape(import_path23.default.join(logDir, "daemon-error.log"));
7275
7482
  return `<?xml version="1.0" encoding="UTF-8"?>
7276
7483
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
7277
7484
  <plist version="1.0">
@@ -7306,9 +7513,9 @@ function launchdPlist(binaryPath) {
7306
7513
  `;
7307
7514
  }
7308
7515
  function installLaunchd(binaryPath) {
7309
- const dir = import_path22.default.dirname(LAUNCHD_PLIST);
7310
- if (!import_fs19.default.existsSync(dir)) import_fs19.default.mkdirSync(dir, { recursive: true });
7311
- import_fs19.default.writeFileSync(LAUNCHD_PLIST, launchdPlist(binaryPath), "utf-8");
7516
+ const dir = import_path23.default.dirname(LAUNCHD_PLIST);
7517
+ if (!import_fs20.default.existsSync(dir)) import_fs20.default.mkdirSync(dir, { recursive: true });
7518
+ import_fs20.default.writeFileSync(LAUNCHD_PLIST, launchdPlist(binaryPath), "utf-8");
7312
7519
  (0, import_child_process5.spawnSync)("launchctl", ["unload", LAUNCHD_PLIST], { encoding: "utf8" });
7313
7520
  const r = (0, import_child_process5.spawnSync)("launchctl", ["load", "-w", LAUNCHD_PLIST], {
7314
7521
  encoding: "utf8",
@@ -7319,13 +7526,13 @@ function installLaunchd(binaryPath) {
7319
7526
  }
7320
7527
  }
7321
7528
  function uninstallLaunchd() {
7322
- if (import_fs19.default.existsSync(LAUNCHD_PLIST)) {
7529
+ if (import_fs20.default.existsSync(LAUNCHD_PLIST)) {
7323
7530
  (0, import_child_process5.spawnSync)("launchctl", ["unload", "-w", LAUNCHD_PLIST], { encoding: "utf8", timeout: 5e3 });
7324
- import_fs19.default.unlinkSync(LAUNCHD_PLIST);
7531
+ import_fs20.default.unlinkSync(LAUNCHD_PLIST);
7325
7532
  }
7326
7533
  }
7327
7534
  function isLaunchdInstalled() {
7328
- return import_fs19.default.existsSync(LAUNCHD_PLIST);
7535
+ return import_fs20.default.existsSync(LAUNCHD_PLIST);
7329
7536
  }
7330
7537
  function systemdUnit(binaryPath) {
7331
7538
  return `[Unit]
@@ -7345,12 +7552,12 @@ WantedBy=default.target
7345
7552
  `;
7346
7553
  }
7347
7554
  function installSystemd(binaryPath) {
7348
- if (!import_fs19.default.existsSync(SYSTEMD_UNIT_DIR)) {
7349
- import_fs19.default.mkdirSync(SYSTEMD_UNIT_DIR, { recursive: true });
7555
+ if (!import_fs20.default.existsSync(SYSTEMD_UNIT_DIR)) {
7556
+ import_fs20.default.mkdirSync(SYSTEMD_UNIT_DIR, { recursive: true });
7350
7557
  }
7351
- import_fs19.default.writeFileSync(SYSTEMD_UNIT, systemdUnit(binaryPath), "utf-8");
7558
+ import_fs20.default.writeFileSync(SYSTEMD_UNIT, systemdUnit(binaryPath), "utf-8");
7352
7559
  try {
7353
- (0, import_child_process5.execFileSync)("loginctl", ["enable-linger", import_os16.default.userInfo().username], { timeout: 3e3 });
7560
+ (0, import_child_process5.execFileSync)("loginctl", ["enable-linger", import_os17.default.userInfo().username], { timeout: 3e3 });
7354
7561
  } catch {
7355
7562
  }
7356
7563
  const reload = (0, import_child_process5.spawnSync)("systemctl", ["--user", "daemon-reload"], {
@@ -7370,23 +7577,23 @@ function installSystemd(binaryPath) {
7370
7577
  }
7371
7578
  }
7372
7579
  function uninstallSystemd() {
7373
- if (import_fs19.default.existsSync(SYSTEMD_UNIT)) {
7580
+ if (import_fs20.default.existsSync(SYSTEMD_UNIT)) {
7374
7581
  (0, import_child_process5.spawnSync)("systemctl", ["--user", "disable", "--now", "node9-daemon"], {
7375
7582
  encoding: "utf8",
7376
7583
  timeout: 5e3
7377
7584
  });
7378
7585
  (0, import_child_process5.spawnSync)("systemctl", ["--user", "daemon-reload"], { encoding: "utf8", timeout: 5e3 });
7379
- import_fs19.default.unlinkSync(SYSTEMD_UNIT);
7586
+ import_fs20.default.unlinkSync(SYSTEMD_UNIT);
7380
7587
  }
7381
7588
  }
7382
7589
  function isSystemdInstalled() {
7383
- return import_fs19.default.existsSync(SYSTEMD_UNIT);
7590
+ return import_fs20.default.existsSync(SYSTEMD_UNIT);
7384
7591
  }
7385
7592
  function stopRunningDaemon() {
7386
- const pidFile = import_path22.default.join(import_os16.default.homedir(), ".node9", "daemon.pid");
7387
- if (!import_fs19.default.existsSync(pidFile)) return;
7593
+ const pidFile = import_path23.default.join(import_os17.default.homedir(), ".node9", "daemon.pid");
7594
+ if (!import_fs20.default.existsSync(pidFile)) return;
7388
7595
  try {
7389
- const data = JSON.parse(import_fs19.default.readFileSync(pidFile, "utf-8"));
7596
+ const data = JSON.parse(import_fs20.default.readFileSync(pidFile, "utf-8"));
7390
7597
  const pid = data.pid;
7391
7598
  const MAX_PID2 = 4194304;
7392
7599
  if (typeof pid === "number" && Number.isInteger(pid) && pid > 0 && pid <= MAX_PID2) {
@@ -7406,7 +7613,7 @@ function stopRunningDaemon() {
7406
7613
  }
7407
7614
  }
7408
7615
  try {
7409
- import_fs19.default.unlinkSync(pidFile);
7616
+ import_fs20.default.unlinkSync(pidFile);
7410
7617
  } catch {
7411
7618
  }
7412
7619
  } catch {
@@ -7476,26 +7683,26 @@ function isDaemonServiceInstalled() {
7476
7683
  if (process.platform === "linux") return isSystemdInstalled();
7477
7684
  return false;
7478
7685
  }
7479
- var import_fs19, import_path22, import_os16, import_child_process5, LAUNCHD_LABEL, LAUNCHD_PLIST, SYSTEMD_UNIT_DIR, SYSTEMD_UNIT;
7686
+ var import_fs20, import_path23, import_os17, import_child_process5, LAUNCHD_LABEL, LAUNCHD_PLIST, SYSTEMD_UNIT_DIR, SYSTEMD_UNIT;
7480
7687
  var init_service = __esm({
7481
7688
  "src/daemon/service.ts"() {
7482
7689
  "use strict";
7483
- import_fs19 = __toESM(require("fs"));
7484
- import_path22 = __toESM(require("path"));
7485
- import_os16 = __toESM(require("os"));
7690
+ import_fs20 = __toESM(require("fs"));
7691
+ import_path23 = __toESM(require("path"));
7692
+ import_os17 = __toESM(require("os"));
7486
7693
  import_child_process5 = require("child_process");
7487
7694
  LAUNCHD_LABEL = "ai.node9.daemon";
7488
- LAUNCHD_PLIST = import_path22.default.join(import_os16.default.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
7489
- SYSTEMD_UNIT_DIR = import_path22.default.join(import_os16.default.homedir(), ".config", "systemd", "user");
7490
- SYSTEMD_UNIT = import_path22.default.join(SYSTEMD_UNIT_DIR, "node9-daemon.service");
7695
+ LAUNCHD_PLIST = import_path23.default.join(import_os17.default.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
7696
+ SYSTEMD_UNIT_DIR = import_path23.default.join(import_os17.default.homedir(), ".config", "systemd", "user");
7697
+ SYSTEMD_UNIT = import_path23.default.join(SYSTEMD_UNIT_DIR, "node9-daemon.service");
7491
7698
  }
7492
7699
  });
7493
7700
 
7494
7701
  // src/daemon/index.ts
7495
7702
  function stopDaemon() {
7496
- if (!import_fs20.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
7703
+ if (!import_fs21.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
7497
7704
  try {
7498
- const data = JSON.parse(import_fs20.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
7705
+ const data = JSON.parse(import_fs21.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
7499
7706
  const pid = data.pid;
7500
7707
  if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
7501
7708
  console.log(import_chalk3.default.gray("Cleaned up invalid PID file."));
@@ -7507,7 +7714,7 @@ function stopDaemon() {
7507
7714
  console.log(import_chalk3.default.gray("Cleaned up stale PID file."));
7508
7715
  } finally {
7509
7716
  try {
7510
- import_fs20.default.unlinkSync(DAEMON_PID_FILE);
7717
+ import_fs21.default.unlinkSync(DAEMON_PID_FILE);
7511
7718
  } catch {
7512
7719
  }
7513
7720
  }
@@ -7516,9 +7723,9 @@ function daemonStatus() {
7516
7723
  const serviceInstalled = isDaemonServiceInstalled();
7517
7724
  const serviceLabel = serviceInstalled ? import_chalk3.default.green("installed (starts on login)") : import_chalk3.default.yellow("not installed \u2014 run: node9 daemon install");
7518
7725
  let processStatus;
7519
- if (import_fs20.default.existsSync(DAEMON_PID_FILE)) {
7726
+ if (import_fs21.default.existsSync(DAEMON_PID_FILE)) {
7520
7727
  try {
7521
- const data = JSON.parse(import_fs20.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
7728
+ const data = JSON.parse(import_fs21.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
7522
7729
  const pid = data.pid;
7523
7730
  const port = data.port;
7524
7731
  if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
@@ -7548,11 +7755,11 @@ function daemonStatus() {
7548
7755
  console.log(` Service : ${serviceLabel}
7549
7756
  `);
7550
7757
  }
7551
- var import_fs20, import_chalk3, import_child_process6, MAX_PID;
7758
+ var import_fs21, import_chalk3, import_child_process6, MAX_PID;
7552
7759
  var init_daemon2 = __esm({
7553
7760
  "src/daemon/index.ts"() {
7554
7761
  "use strict";
7555
- import_fs20 = __toESM(require("fs"));
7762
+ import_fs21 = __toESM(require("fs"));
7556
7763
  import_chalk3 = __toESM(require("chalk"));
7557
7764
  import_child_process6 = require("child_process");
7558
7765
  init_server();
@@ -7576,6 +7783,74 @@ function getIcon(tool) {
7576
7783
  }
7577
7784
  return "\u{1F6E0}\uFE0F";
7578
7785
  }
7786
+ function getModelContextLimit(model) {
7787
+ const base = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
7788
+ for (const [key, limit] of Object.entries(MODEL_CONTEXT_LIMITS)) {
7789
+ if (base.startsWith(key)) return limit;
7790
+ }
7791
+ return 2e5;
7792
+ }
7793
+ function readSessionUsage() {
7794
+ const projectsDir = import_path40.default.join(import_os33.default.homedir(), ".claude", "projects");
7795
+ if (!import_fs37.default.existsSync(projectsDir)) return null;
7796
+ let latestFile = null;
7797
+ let latestMtime = 0;
7798
+ try {
7799
+ for (const dir of import_fs37.default.readdirSync(projectsDir)) {
7800
+ const dirPath = import_path40.default.join(projectsDir, dir);
7801
+ try {
7802
+ if (!import_fs37.default.statSync(dirPath).isDirectory()) continue;
7803
+ for (const file of import_fs37.default.readdirSync(dirPath)) {
7804
+ if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
7805
+ const filePath = import_path40.default.join(dirPath, file);
7806
+ try {
7807
+ const mtime = import_fs37.default.statSync(filePath).mtimeMs;
7808
+ if (mtime > latestMtime) {
7809
+ latestMtime = mtime;
7810
+ latestFile = filePath;
7811
+ }
7812
+ } catch {
7813
+ }
7814
+ }
7815
+ } catch {
7816
+ }
7817
+ }
7818
+ } catch {
7819
+ }
7820
+ if (!latestFile) return null;
7821
+ try {
7822
+ const lines = import_fs37.default.readFileSync(latestFile, "utf-8").split("\n");
7823
+ let lastModel = "";
7824
+ let lastInput = 0;
7825
+ let lastOutput = 0;
7826
+ for (const line of lines) {
7827
+ if (!line.trim()) continue;
7828
+ try {
7829
+ const entry = JSON.parse(line);
7830
+ if (entry.type !== "assistant" || !entry.message?.usage) continue;
7831
+ const u = entry.message.usage;
7832
+ lastInput = (u.input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0);
7833
+ lastOutput = u.output_tokens ?? 0;
7834
+ if (entry.message.model) lastModel = entry.message.model;
7835
+ } catch {
7836
+ }
7837
+ }
7838
+ if (!lastModel || lastInput === 0) return null;
7839
+ const limit = getModelContextLimit(lastModel);
7840
+ const fillPct = Math.round(lastInput / limit * 100);
7841
+ return { inputTokens: lastInput, outputTokens: lastOutput, model: lastModel, fillPct };
7842
+ } catch {
7843
+ return null;
7844
+ }
7845
+ }
7846
+ function formatContextStat(stat) {
7847
+ const pctColor = stat.fillPct >= 80 ? import_chalk25.default.red : stat.fillPct >= 50 ? import_chalk25.default.yellow : import_chalk25.default.cyan;
7848
+ const k = (n) => `${Math.round(n / 1e3)}k`;
7849
+ const modelShort = stat.model.replace(/@.*$/, "").replace(/-\d{8}$/, "").replace(/^claude-/, "");
7850
+ return import_chalk25.default.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + import_chalk25.default.dim(
7851
+ ` (${k(stat.inputTokens)}/${k(getModelContextLimit(stat.model))} out ${k(stat.outputTokens)} \xB7 ${modelShort})`
7852
+ );
7853
+ }
7579
7854
  function visibleLength(s) {
7580
7855
  return s.replace(/\x1B\[[0-9;]*m/g, "").length;
7581
7856
  }
@@ -7585,26 +7860,31 @@ function wrappedLineCount(text) {
7585
7860
  const len = visibleLength(text);
7586
7861
  return Math.max(1, Math.ceil(len / cols));
7587
7862
  }
7863
+ function agentLabel(agent) {
7864
+ if (!agent || agent === "Terminal") return "";
7865
+ const short = agent === "Claude Code" ? "Claude" : agent === "Gemini CLI" ? "Gemini" : agent === "Unknown Agent" ? "" : agent.split(" ")[0];
7866
+ return short ? import_chalk25.default.dim(`[${short}] `) : "";
7867
+ }
7588
7868
  function formatBase(activity) {
7589
7869
  const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
7590
7870
  const icon = getIcon(activity.tool);
7591
7871
  const toolName = activity.tool.slice(0, 16).padEnd(16);
7592
- const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os31.default.homedir(), "~");
7872
+ const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os33.default.homedir(), "~");
7593
7873
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
7594
- return `${import_chalk24.default.gray(time)} ${icon} ${import_chalk24.default.white.bold(toolName)} ${import_chalk24.default.dim(argsPreview)}`;
7874
+ return `${import_chalk25.default.gray(time)} ${icon} ${agentLabel(activity.agent)}${import_chalk25.default.white.bold(toolName)} ${import_chalk25.default.dim(argsPreview)}`;
7595
7875
  }
7596
7876
  function renderResult(activity, result) {
7597
7877
  const base = formatBase(activity);
7598
7878
  let status;
7599
7879
  if (result.status === "allow") {
7600
- status = import_chalk24.default.green("\u2713 ALLOW");
7880
+ status = import_chalk25.default.green("\u2713 ALLOW");
7601
7881
  } else if (result.status === "dlp") {
7602
- status = import_chalk24.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
7882
+ status = import_chalk25.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
7603
7883
  } else {
7604
- status = import_chalk24.default.red("\u2717 BLOCK");
7884
+ status = import_chalk25.default.red("\u2717 BLOCK");
7605
7885
  }
7606
7886
  const cost = result.costEstimate ?? activity.costEstimate;
7607
- const costSuffix = cost == null ? "" : import_chalk24.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
7887
+ const costSuffix = cost == null ? "" : import_chalk25.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
7608
7888
  if (process.stdout.isTTY) {
7609
7889
  if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
7610
7890
  import_readline5.default.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
@@ -7621,19 +7901,19 @@ function renderResult(activity, result) {
7621
7901
  }
7622
7902
  function renderPending(activity) {
7623
7903
  if (!process.stdout.isTTY) return;
7624
- const line = `${formatBase(activity)} ${import_chalk24.default.yellow("\u25CF \u2026")}`;
7904
+ const line = `${formatBase(activity)} ${import_chalk25.default.yellow("\u25CF \u2026")}`;
7625
7905
  pendingShownForId = activity.id;
7626
7906
  pendingWrappedLines = wrappedLineCount(line);
7627
7907
  process.stdout.write(`${line}\r`);
7628
7908
  }
7629
7909
  async function ensureDaemon() {
7630
7910
  let pidPort = null;
7631
- if (import_fs35.default.existsSync(PID_FILE)) {
7911
+ if (import_fs37.default.existsSync(PID_FILE)) {
7632
7912
  try {
7633
- const { port } = JSON.parse(import_fs35.default.readFileSync(PID_FILE, "utf-8"));
7913
+ const { port } = JSON.parse(import_fs37.default.readFileSync(PID_FILE, "utf-8"));
7634
7914
  pidPort = port;
7635
7915
  } catch {
7636
- console.error(import_chalk24.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
7916
+ console.error(import_chalk25.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
7637
7917
  }
7638
7918
  }
7639
7919
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -7644,7 +7924,7 @@ async function ensureDaemon() {
7644
7924
  if (res.ok) return checkPort;
7645
7925
  } catch {
7646
7926
  }
7647
- console.log(import_chalk24.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
7927
+ console.log(import_chalk25.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
7648
7928
  const child = (0, import_child_process15.spawn)(process.execPath, [process.argv[1], "daemon"], {
7649
7929
  detached: true,
7650
7930
  stdio: "ignore",
@@ -7661,7 +7941,7 @@ async function ensureDaemon() {
7661
7941
  } catch {
7662
7942
  }
7663
7943
  }
7664
- console.error(import_chalk24.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
7944
+ console.error(import_chalk25.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
7665
7945
  process.exit(1);
7666
7946
  }
7667
7947
  function postDecisionHttp(id, decision, csrfToken, port, opts) {
@@ -7727,10 +8007,11 @@ function buildCardLines(req, localCount = 0) {
7727
8007
  const severityIcon = isBlock ? `${RED}\u{1F6D1}` : `${YELLOW}\u26A0 `;
7728
8008
  const rawDesc = req.riskMetadata?.ruleDescription ?? "";
7729
8009
  const description = rawDesc ? cleanReason(rawDesc) : "";
8010
+ const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${import_chalk25.default.dim(`(${req.agent})`)}` : "";
7730
8011
  const lines = [
7731
8012
  ``,
7732
8013
  `${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
7733
- `${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}`,
8014
+ `${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}${agentSuffix}`,
7734
8015
  `${CYAN}\u2551${RESET2} Policy: ${severityIcon} ${blockedBy}${RESET2}`
7735
8016
  ];
7736
8017
  if (description) {
@@ -7782,9 +8063,9 @@ function buildRecoveryCardLines(req) {
7782
8063
  ];
7783
8064
  }
7784
8065
  function readApproversFromDisk() {
7785
- const configPath = import_path38.default.join(import_os31.default.homedir(), ".node9", "config.json");
8066
+ const configPath = import_path40.default.join(import_os33.default.homedir(), ".node9", "config.json");
7786
8067
  try {
7787
- const raw = JSON.parse(import_fs35.default.readFileSync(configPath, "utf-8"));
8068
+ const raw = JSON.parse(import_fs37.default.readFileSync(configPath, "utf-8"));
7788
8069
  const settings = raw.settings ?? {};
7789
8070
  return settings.approvers ?? {};
7790
8071
  } catch {
@@ -7795,20 +8076,20 @@ function approverStatusLine() {
7795
8076
  const a = readApproversFromDisk();
7796
8077
  const fmt = (label, key) => {
7797
8078
  const on = a[key] !== false;
7798
- return `[${key[0]}]${label.slice(1)} ${on ? import_chalk24.default.green("\u2713") : import_chalk24.default.dim("\u2717")}`;
8079
+ return `[${key[0]}]${label.slice(1)} ${on ? import_chalk25.default.green("\u2713") : import_chalk25.default.dim("\u2717")}`;
7799
8080
  };
7800
8081
  return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
7801
8082
  }
7802
8083
  function toggleApprover(channel) {
7803
- const configPath = import_path38.default.join(import_os31.default.homedir(), ".node9", "config.json");
8084
+ const configPath = import_path40.default.join(import_os33.default.homedir(), ".node9", "config.json");
7804
8085
  try {
7805
- const raw = JSON.parse(import_fs35.default.readFileSync(configPath, "utf-8"));
8086
+ const raw = JSON.parse(import_fs37.default.readFileSync(configPath, "utf-8"));
7806
8087
  const settings = raw.settings ?? {};
7807
8088
  const approvers = settings.approvers ?? {};
7808
8089
  approvers[channel] = approvers[channel] === false;
7809
8090
  settings.approvers = approvers;
7810
8091
  raw.settings = settings;
7811
- import_fs35.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
8092
+ import_fs37.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
7812
8093
  } catch (err2) {
7813
8094
  process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
7814
8095
  `);
@@ -7840,7 +8121,7 @@ async function startTail(options = {}) {
7840
8121
  req2.end();
7841
8122
  });
7842
8123
  if (result.ok) {
7843
- console.log(import_chalk24.default.green("\u2713 Flight Recorder buffer cleared."));
8124
+ console.log(import_chalk25.default.green("\u2713 Flight Recorder buffer cleared."));
7844
8125
  } else if (result.code === "ECONNREFUSED") {
7845
8126
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
7846
8127
  } else if (result.code === "ETIMEDOUT") {
@@ -7884,7 +8165,7 @@ async function startTail(options = {}) {
7884
8165
  const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
7885
8166
  if (channel) {
7886
8167
  toggleApprover(channel);
7887
- console.log(import_chalk24.default.dim(` Approvers: ${approverStatusLine()}`));
8168
+ console.log(import_chalk25.default.dim(` Approvers: ${approverStatusLine()}`));
7888
8169
  }
7889
8170
  };
7890
8171
  process.stdin.on("keypress", idleKeypressHandler);
@@ -7950,7 +8231,7 @@ async function startTail(options = {}) {
7950
8231
  localAllowCounts.get(req2.toolName) ?? 0
7951
8232
  )
7952
8233
  );
7953
- const decisionStamp = action === "always-allow" ? import_chalk24.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk24.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk24.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk24.default.yellow("\u21A9 REDIRECT AI") : import_chalk24.default.red("\u2717 DENIED");
8234
+ const decisionStamp = action === "always-allow" ? import_chalk25.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk25.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk25.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk25.default.yellow("\u21A9 REDIRECT AI") : import_chalk25.default.red("\u2717 DENIED");
7954
8235
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
7955
8236
  for (const line of stampedLines) process.stdout.write(line + "\n");
7956
8237
  process.stdout.write(SHOW_CURSOR);
@@ -7978,8 +8259,8 @@ async function startTail(options = {}) {
7978
8259
  }
7979
8260
  postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
7980
8261
  try {
7981
- import_fs35.default.appendFileSync(
7982
- import_path38.default.join(import_os31.default.homedir(), ".node9", "hook-debug.log"),
8262
+ import_fs37.default.appendFileSync(
8263
+ import_path40.default.join(import_os33.default.homedir(), ".node9", "hook-debug.log"),
7983
8264
  `[tail] POST /decision failed: ${String(err2)}
7984
8265
  `
7985
8266
  );
@@ -8001,7 +8282,7 @@ async function startTail(options = {}) {
8001
8282
  );
8002
8283
  const stampedLines = buildCardLines(req2, priorCount);
8003
8284
  if (externalDecision) {
8004
- const source = externalDecision === "allow" ? import_chalk24.default.green("\u2713 ALLOWED") : import_chalk24.default.red("\u2717 DENIED");
8285
+ const source = externalDecision === "allow" ? import_chalk25.default.green("\u2713 ALLOWED") : import_chalk25.default.red("\u2717 DENIED");
8005
8286
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
8006
8287
  }
8007
8288
  for (const line of stampedLines) process.stdout.write(line + "\n");
@@ -8060,16 +8341,31 @@ async function startTail(options = {}) {
8060
8341
  }
8061
8342
  } catch {
8062
8343
  }
8063
- console.log(import_chalk24.default.cyan.bold(`
8064
- \u{1F6F0}\uFE0F Node9 tail `) + import_chalk24.default.dim(`\u2192 ${dashboardUrl}`));
8344
+ const auditLog = import_path40.default.join(import_os33.default.homedir(), ".node9", "audit.log");
8345
+ try {
8346
+ const unackedDlp = import_fs37.default.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
8347
+ if (unackedDlp > 0) {
8348
+ console.log("");
8349
+ console.log(
8350
+ import_chalk25.default.bgRed.white.bold(
8351
+ ` \u26A0\uFE0F DLP ALERT: ${unackedDlp} secret${unackedDlp !== 1 ? "s" : ""} found in Claude response text \u2014 run: node9 dlp `
8352
+ )
8353
+ );
8354
+ }
8355
+ } catch {
8356
+ }
8357
+ console.log(import_chalk25.default.cyan.bold(`
8358
+ \u{1F6F0}\uFE0F Node9 tail `) + import_chalk25.default.dim(`\u2192 ${dashboardUrl}`));
8065
8359
  if (canApprove) {
8066
- console.log(import_chalk24.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
8067
- console.log(import_chalk24.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
8360
+ console.log(import_chalk25.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
8361
+ console.log(import_chalk25.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
8068
8362
  }
8363
+ const ctxStat = readSessionUsage();
8364
+ if (ctxStat) console.log(" " + formatContextStat(ctxStat));
8069
8365
  if (options.history) {
8070
- console.log(import_chalk24.default.dim("Showing history + live events.\n"));
8366
+ console.log(import_chalk25.default.dim("Showing history + live events.\n"));
8071
8367
  } else {
8072
- console.log(import_chalk24.default.dim("Showing live events only. Use --history to include past.\n"));
8368
+ console.log(import_chalk25.default.dim("Showing live events only. Use --history to include past.\n"));
8073
8369
  }
8074
8370
  process.on("SIGINT", () => {
8075
8371
  exitIdleMode();
@@ -8079,13 +8375,13 @@ async function startTail(options = {}) {
8079
8375
  import_readline5.default.clearLine(process.stdout, 0);
8080
8376
  import_readline5.default.cursorTo(process.stdout, 0);
8081
8377
  }
8082
- console.log(import_chalk24.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
8378
+ console.log(import_chalk25.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
8083
8379
  process.exit(0);
8084
8380
  });
8085
8381
  const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
8086
8382
  const req = import_http2.default.get(sseUrl, (res) => {
8087
8383
  if (res.statusCode !== 200) {
8088
- console.error(import_chalk24.default.red(`Failed to connect: HTTP ${res.statusCode}`));
8384
+ console.error(import_chalk25.default.red(`Failed to connect: HTTP ${res.statusCode}`));
8089
8385
  process.exit(1);
8090
8386
  }
8091
8387
  if (canApprove) enterIdleMode();
@@ -8116,7 +8412,7 @@ async function startTail(options = {}) {
8116
8412
  import_readline5.default.clearLine(process.stdout, 0);
8117
8413
  import_readline5.default.cursorTo(process.stdout, 0);
8118
8414
  }
8119
- console.log(import_chalk24.default.red("\n\u274C Daemon disconnected."));
8415
+ console.log(import_chalk25.default.red("\n\u274C Daemon disconnected."));
8120
8416
  process.exit(1);
8121
8417
  });
8122
8418
  });
@@ -8208,9 +8504,9 @@ async function startTail(options = {}) {
8208
8504
  const hash = data.hash ?? "";
8209
8505
  const summary = data.argsSummary ?? data.tool;
8210
8506
  const fileCount = data.fileCount ?? 0;
8211
- const files = fileCount > 0 ? import_chalk24.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
8507
+ const files = fileCount > 0 ? import_chalk25.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
8212
8508
  process.stdout.write(
8213
- `${import_chalk24.default.dim(time)} ${import_chalk24.default.cyan("\u{1F4F8} snapshot")} ${import_chalk24.default.dim(hash)} ${summary}${files}
8509
+ `${import_chalk25.default.dim(time)} ${import_chalk25.default.cyan("\u{1F4F8} snapshot")} ${import_chalk25.default.dim(hash)} ${summary}${files}
8214
8510
  `
8215
8511
  );
8216
8512
  return;
@@ -8227,26 +8523,26 @@ async function startTail(options = {}) {
8227
8523
  }
8228
8524
  req.on("error", (err2) => {
8229
8525
  const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
8230
- console.error(import_chalk24.default.red(`
8526
+ console.error(import_chalk25.default.red(`
8231
8527
  \u274C ${msg}`));
8232
8528
  process.exit(1);
8233
8529
  });
8234
8530
  }
8235
- var import_http2, import_chalk24, import_fs35, import_os31, import_path38, import_readline5, import_child_process15, PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
8531
+ var import_http2, import_chalk25, import_fs37, import_os33, import_path40, import_readline5, import_child_process15, PID_FILE, ICONS, MODEL_CONTEXT_LIMITS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
8236
8532
  var init_tail = __esm({
8237
8533
  "src/tui/tail.ts"() {
8238
8534
  "use strict";
8239
8535
  import_http2 = __toESM(require("http"));
8240
- import_chalk24 = __toESM(require("chalk"));
8241
- import_fs35 = __toESM(require("fs"));
8242
- import_os31 = __toESM(require("os"));
8243
- import_path38 = __toESM(require("path"));
8536
+ import_chalk25 = __toESM(require("chalk"));
8537
+ import_fs37 = __toESM(require("fs"));
8538
+ import_os33 = __toESM(require("os"));
8539
+ import_path40 = __toESM(require("path"));
8244
8540
  import_readline5 = __toESM(require("readline"));
8245
8541
  import_child_process15 = require("child_process");
8246
8542
  init_daemon2();
8247
8543
  init_daemon();
8248
8544
  init_core();
8249
- PID_FILE = import_path38.default.join(import_os31.default.homedir(), ".node9", "daemon.pid");
8545
+ PID_FILE = import_path40.default.join(import_os33.default.homedir(), ".node9", "daemon.pid");
8250
8546
  ICONS = {
8251
8547
  bash: "\u{1F4BB}",
8252
8548
  shell: "\u{1F4BB}",
@@ -8264,6 +8560,13 @@ var init_tail = __esm({
8264
8560
  delete: "\u{1F5D1}\uFE0F",
8265
8561
  web: "\u{1F310}"
8266
8562
  };
8563
+ MODEL_CONTEXT_LIMITS = {
8564
+ "claude-opus-4": 2e5,
8565
+ "claude-sonnet-4": 2e5,
8566
+ "claude-haiku-4": 2e5,
8567
+ "claude-3-7": 2e5,
8568
+ "claude-3-5": 2e5
8569
+ };
8267
8570
  RESET2 = "\x1B[0m";
8268
8571
  BOLD2 = "\x1B[1m";
8269
8572
  RED = "\x1B[31m";
@@ -8361,9 +8664,9 @@ function formatTimeLeft(resetsAt) {
8361
8664
  return ` (${m}m left)`;
8362
8665
  }
8363
8666
  function safeReadJson(filePath) {
8364
- if (!import_fs36.default.existsSync(filePath)) return null;
8667
+ if (!import_fs38.default.existsSync(filePath)) return null;
8365
8668
  try {
8366
- return JSON.parse(import_fs36.default.readFileSync(filePath, "utf-8"));
8669
+ return JSON.parse(import_fs38.default.readFileSync(filePath, "utf-8"));
8367
8670
  } catch {
8368
8671
  return null;
8369
8672
  }
@@ -8384,12 +8687,12 @@ function countHooksInFile(filePath) {
8384
8687
  return Object.keys(cfg.hooks).length;
8385
8688
  }
8386
8689
  function countRulesInDir(rulesDir) {
8387
- if (!import_fs36.default.existsSync(rulesDir)) return 0;
8690
+ if (!import_fs38.default.existsSync(rulesDir)) return 0;
8388
8691
  let count = 0;
8389
8692
  try {
8390
- for (const entry of import_fs36.default.readdirSync(rulesDir, { withFileTypes: true })) {
8693
+ for (const entry of import_fs38.default.readdirSync(rulesDir, { withFileTypes: true })) {
8391
8694
  if (entry.isDirectory()) {
8392
- count += countRulesInDir(import_path39.default.join(rulesDir, entry.name));
8695
+ count += countRulesInDir(import_path41.default.join(rulesDir, entry.name));
8393
8696
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
8394
8697
  count++;
8395
8698
  }
@@ -8400,46 +8703,46 @@ function countRulesInDir(rulesDir) {
8400
8703
  }
8401
8704
  function isSamePath(a, b) {
8402
8705
  try {
8403
- return import_path39.default.resolve(a) === import_path39.default.resolve(b);
8706
+ return import_path41.default.resolve(a) === import_path41.default.resolve(b);
8404
8707
  } catch {
8405
8708
  return false;
8406
8709
  }
8407
8710
  }
8408
8711
  function countConfigs(cwd) {
8409
- const homeDir2 = import_os32.default.homedir();
8410
- const claudeDir = import_path39.default.join(homeDir2, ".claude");
8712
+ const homeDir2 = import_os34.default.homedir();
8713
+ const claudeDir = import_path41.default.join(homeDir2, ".claude");
8411
8714
  let claudeMdCount = 0;
8412
8715
  let rulesCount = 0;
8413
8716
  let hooksCount = 0;
8414
8717
  const userMcpServers = /* @__PURE__ */ new Set();
8415
8718
  const projectMcpServers = /* @__PURE__ */ new Set();
8416
- if (import_fs36.default.existsSync(import_path39.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
8417
- rulesCount += countRulesInDir(import_path39.default.join(claudeDir, "rules"));
8418
- const userSettings = import_path39.default.join(claudeDir, "settings.json");
8719
+ if (import_fs38.default.existsSync(import_path41.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
8720
+ rulesCount += countRulesInDir(import_path41.default.join(claudeDir, "rules"));
8721
+ const userSettings = import_path41.default.join(claudeDir, "settings.json");
8419
8722
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
8420
8723
  hooksCount += countHooksInFile(userSettings);
8421
- const userClaudeJson = import_path39.default.join(homeDir2, ".claude.json");
8724
+ const userClaudeJson = import_path41.default.join(homeDir2, ".claude.json");
8422
8725
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
8423
8726
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
8424
8727
  userMcpServers.delete(name);
8425
8728
  }
8426
8729
  if (cwd) {
8427
- if (import_fs36.default.existsSync(import_path39.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
8428
- if (import_fs36.default.existsSync(import_path39.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
8429
- const projectClaudeDir = import_path39.default.join(cwd, ".claude");
8730
+ if (import_fs38.default.existsSync(import_path41.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
8731
+ if (import_fs38.default.existsSync(import_path41.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
8732
+ const projectClaudeDir = import_path41.default.join(cwd, ".claude");
8430
8733
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
8431
8734
  if (!overlapsUserScope) {
8432
- if (import_fs36.default.existsSync(import_path39.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
8433
- rulesCount += countRulesInDir(import_path39.default.join(projectClaudeDir, "rules"));
8434
- const projSettings = import_path39.default.join(projectClaudeDir, "settings.json");
8735
+ if (import_fs38.default.existsSync(import_path41.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
8736
+ rulesCount += countRulesInDir(import_path41.default.join(projectClaudeDir, "rules"));
8737
+ const projSettings = import_path41.default.join(projectClaudeDir, "settings.json");
8435
8738
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
8436
8739
  hooksCount += countHooksInFile(projSettings);
8437
8740
  }
8438
- if (import_fs36.default.existsSync(import_path39.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
8439
- const localSettings = import_path39.default.join(projectClaudeDir, "settings.local.json");
8741
+ if (import_fs38.default.existsSync(import_path41.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
8742
+ const localSettings = import_path41.default.join(projectClaudeDir, "settings.local.json");
8440
8743
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
8441
8744
  hooksCount += countHooksInFile(localSettings);
8442
- const mcpJsonServers = getMcpServerNames(import_path39.default.join(cwd, ".mcp.json"));
8745
+ const mcpJsonServers = getMcpServerNames(import_path41.default.join(cwd, ".mcp.json"));
8443
8746
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
8444
8747
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
8445
8748
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -8472,12 +8775,12 @@ function readActiveShieldsHud() {
8472
8775
  return shieldsCache.value;
8473
8776
  }
8474
8777
  try {
8475
- const shieldsPath = import_path39.default.join(import_os32.default.homedir(), ".node9", "shields.json");
8476
- if (!import_fs36.default.existsSync(shieldsPath)) {
8778
+ const shieldsPath = import_path41.default.join(import_os34.default.homedir(), ".node9", "shields.json");
8779
+ if (!import_fs38.default.existsSync(shieldsPath)) {
8477
8780
  shieldsCache = { value: [], ts: now };
8478
8781
  return [];
8479
8782
  }
8480
- const parsed = JSON.parse(import_fs36.default.readFileSync(shieldsPath, "utf-8"));
8783
+ const parsed = JSON.parse(import_fs38.default.readFileSync(shieldsPath, "utf-8"));
8481
8784
  if (!Array.isArray(parsed.active)) {
8482
8785
  shieldsCache = { value: [], ts: now };
8483
8786
  return [];
@@ -8579,17 +8882,17 @@ function renderContextLine(stdin) {
8579
8882
  async function main() {
8580
8883
  try {
8581
8884
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
8582
- if (import_fs36.default.existsSync(import_path39.default.join(import_os32.default.homedir(), ".node9", "hud-debug"))) {
8885
+ if (import_fs38.default.existsSync(import_path41.default.join(import_os34.default.homedir(), ".node9", "hud-debug"))) {
8583
8886
  try {
8584
- const logPath = import_path39.default.join(import_os32.default.homedir(), ".node9", "hud-debug.log");
8887
+ const logPath = import_path41.default.join(import_os34.default.homedir(), ".node9", "hud-debug.log");
8585
8888
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
8586
8889
  let size = 0;
8587
8890
  try {
8588
- size = import_fs36.default.statSync(logPath).size;
8891
+ size = import_fs38.default.statSync(logPath).size;
8589
8892
  } catch {
8590
8893
  }
8591
8894
  if (size < MAX_LOG_SIZE) {
8592
- import_fs36.default.appendFileSync(
8895
+ import_fs38.default.appendFileSync(
8593
8896
  logPath,
8594
8897
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
8595
8898
  );
@@ -8610,11 +8913,11 @@ async function main() {
8610
8913
  try {
8611
8914
  const cwd = stdin.cwd ?? process.cwd();
8612
8915
  for (const configPath of [
8613
- import_path39.default.join(cwd, "node9.config.json"),
8614
- import_path39.default.join(import_os32.default.homedir(), ".node9", "config.json")
8916
+ import_path41.default.join(cwd, "node9.config.json"),
8917
+ import_path41.default.join(import_os34.default.homedir(), ".node9", "config.json")
8615
8918
  ]) {
8616
- if (!import_fs36.default.existsSync(configPath)) continue;
8617
- const cfg = JSON.parse(import_fs36.default.readFileSync(configPath, "utf-8"));
8919
+ if (!import_fs38.default.existsSync(configPath)) continue;
8920
+ const cfg = JSON.parse(import_fs38.default.readFileSync(configPath, "utf-8"));
8618
8921
  const hud = cfg.settings?.hud;
8619
8922
  if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
8620
8923
  }
@@ -8632,13 +8935,13 @@ async function main() {
8632
8935
  renderOffline();
8633
8936
  }
8634
8937
  }
8635
- var import_fs36, import_path39, import_os32, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
8938
+ var import_fs38, import_path41, import_os34, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
8636
8939
  var init_hud = __esm({
8637
8940
  "src/cli/hud.ts"() {
8638
8941
  "use strict";
8639
- import_fs36 = __toESM(require("fs"));
8640
- import_path39 = __toESM(require("path"));
8641
- import_os32 = __toESM(require("os"));
8942
+ import_fs38 = __toESM(require("fs"));
8943
+ import_path41 = __toESM(require("path"));
8944
+ import_os34 = __toESM(require("os"));
8642
8945
  import_http3 = __toESM(require("http"));
8643
8946
  init_daemon();
8644
8947
  RESET3 = "\x1B[0m";
@@ -9553,10 +9856,10 @@ function getAgentsStatus(homeDir2 = import_os11.default.homedir()) {
9553
9856
 
9554
9857
  // src/cli.ts
9555
9858
  init_daemon2();
9556
- var import_chalk25 = __toESM(require("chalk"));
9557
- var import_fs37 = __toESM(require("fs"));
9558
- var import_path40 = __toESM(require("path"));
9559
- var import_os33 = __toESM(require("os"));
9859
+ var import_chalk26 = __toESM(require("chalk"));
9860
+ var import_fs39 = __toESM(require("fs"));
9861
+ var import_path42 = __toESM(require("path"));
9862
+ var import_os35 = __toESM(require("os"));
9560
9863
  var import_prompts2 = require("@inquirer/prompts");
9561
9864
 
9562
9865
  // src/utils/duration.ts
@@ -9781,10 +10084,10 @@ async function autoStartDaemonAndWait() {
9781
10084
 
9782
10085
  // src/cli/commands/check.ts
9783
10086
  var import_chalk5 = __toESM(require("chalk"));
9784
- var import_fs23 = __toESM(require("fs"));
10087
+ var import_fs24 = __toESM(require("fs"));
9785
10088
  var import_child_process10 = require("child_process");
9786
- var import_path25 = __toESM(require("path"));
9787
- var import_os19 = __toESM(require("os"));
10089
+ var import_path26 = __toESM(require("path"));
10090
+ var import_os20 = __toESM(require("os"));
9788
10091
  init_orchestrator();
9789
10092
  init_daemon();
9790
10093
  init_config();
@@ -9793,11 +10096,11 @@ init_policy();
9793
10096
  // src/undo.ts
9794
10097
  var import_child_process9 = require("child_process");
9795
10098
  var import_crypto8 = __toESM(require("crypto"));
9796
- var import_fs21 = __toESM(require("fs"));
10099
+ var import_fs22 = __toESM(require("fs"));
9797
10100
  var import_net3 = __toESM(require("net"));
9798
- var import_path23 = __toESM(require("path"));
9799
- var import_os17 = __toESM(require("os"));
9800
- var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path23.default.join(import_os17.default.tmpdir(), "node9-activity.sock");
10101
+ var import_path24 = __toESM(require("path"));
10102
+ var import_os18 = __toESM(require("os"));
10103
+ var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path24.default.join(import_os18.default.tmpdir(), "node9-activity.sock");
9801
10104
  function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
9802
10105
  try {
9803
10106
  const payload = JSON.stringify({
@@ -9817,22 +10120,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
9817
10120
  } catch {
9818
10121
  }
9819
10122
  }
9820
- var SNAPSHOT_STACK_PATH = import_path23.default.join(import_os17.default.homedir(), ".node9", "snapshots.json");
9821
- var UNDO_LATEST_PATH = import_path23.default.join(import_os17.default.homedir(), ".node9", "undo_latest.txt");
10123
+ var SNAPSHOT_STACK_PATH = import_path24.default.join(import_os18.default.homedir(), ".node9", "snapshots.json");
10124
+ var UNDO_LATEST_PATH = import_path24.default.join(import_os18.default.homedir(), ".node9", "undo_latest.txt");
9822
10125
  var MAX_SNAPSHOTS = 10;
9823
10126
  var GIT_TIMEOUT = 15e3;
9824
10127
  function readStack() {
9825
10128
  try {
9826
- if (import_fs21.default.existsSync(SNAPSHOT_STACK_PATH))
9827
- return JSON.parse(import_fs21.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
10129
+ if (import_fs22.default.existsSync(SNAPSHOT_STACK_PATH))
10130
+ return JSON.parse(import_fs22.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
9828
10131
  } catch {
9829
10132
  }
9830
10133
  return [];
9831
10134
  }
9832
10135
  function writeStack(stack) {
9833
- const dir = import_path23.default.dirname(SNAPSHOT_STACK_PATH);
9834
- if (!import_fs21.default.existsSync(dir)) import_fs21.default.mkdirSync(dir, { recursive: true });
9835
- import_fs21.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
10136
+ const dir = import_path24.default.dirname(SNAPSHOT_STACK_PATH);
10137
+ if (!import_fs22.default.existsSync(dir)) import_fs22.default.mkdirSync(dir, { recursive: true });
10138
+ import_fs22.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
9836
10139
  }
9837
10140
  function extractFilePath(args) {
9838
10141
  if (!args || typeof args !== "object") return null;
@@ -9852,12 +10155,12 @@ function buildArgsSummary(tool, args) {
9852
10155
  return "";
9853
10156
  }
9854
10157
  function findProjectRoot(filePath) {
9855
- let dir = import_path23.default.dirname(filePath);
10158
+ let dir = import_path24.default.dirname(filePath);
9856
10159
  while (true) {
9857
- if (import_fs21.default.existsSync(import_path23.default.join(dir, ".git")) || import_fs21.default.existsSync(import_path23.default.join(dir, "package.json"))) {
10160
+ if (import_fs22.default.existsSync(import_path24.default.join(dir, ".git")) || import_fs22.default.existsSync(import_path24.default.join(dir, "package.json"))) {
9858
10161
  return dir;
9859
10162
  }
9860
- const parent = import_path23.default.dirname(dir);
10163
+ const parent = import_path24.default.dirname(dir);
9861
10164
  if (parent === dir) return process.cwd();
9862
10165
  dir = parent;
9863
10166
  }
@@ -9865,7 +10168,7 @@ function findProjectRoot(filePath) {
9865
10168
  function normalizeCwdForHash(cwd) {
9866
10169
  let normalized;
9867
10170
  try {
9868
- normalized = import_fs21.default.realpathSync(cwd);
10171
+ normalized = import_fs22.default.realpathSync(cwd);
9869
10172
  } catch {
9870
10173
  normalized = cwd;
9871
10174
  }
@@ -9875,16 +10178,16 @@ function normalizeCwdForHash(cwd) {
9875
10178
  }
9876
10179
  function getShadowRepoDir(cwd) {
9877
10180
  const hash = import_crypto8.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
9878
- return import_path23.default.join(import_os17.default.homedir(), ".node9", "snapshots", hash);
10181
+ return import_path24.default.join(import_os18.default.homedir(), ".node9", "snapshots", hash);
9879
10182
  }
9880
10183
  function cleanOrphanedIndexFiles(shadowDir) {
9881
10184
  try {
9882
10185
  const cutoff = Date.now() - 6e4;
9883
- for (const f of import_fs21.default.readdirSync(shadowDir)) {
10186
+ for (const f of import_fs22.default.readdirSync(shadowDir)) {
9884
10187
  if (f.startsWith("index_")) {
9885
- const fp = import_path23.default.join(shadowDir, f);
10188
+ const fp = import_path24.default.join(shadowDir, f);
9886
10189
  try {
9887
- if (import_fs21.default.statSync(fp).mtimeMs < cutoff) import_fs21.default.unlinkSync(fp);
10190
+ if (import_fs22.default.statSync(fp).mtimeMs < cutoff) import_fs22.default.unlinkSync(fp);
9888
10191
  } catch {
9889
10192
  }
9890
10193
  }
@@ -9896,7 +10199,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
9896
10199
  const hardcoded = [".git", ".node9"];
9897
10200
  const lines = [...hardcoded, ...ignorePaths].join("\n");
9898
10201
  try {
9899
- import_fs21.default.writeFileSync(import_path23.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
10202
+ import_fs22.default.writeFileSync(import_path24.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
9900
10203
  } catch {
9901
10204
  }
9902
10205
  }
@@ -9909,25 +10212,25 @@ function ensureShadowRepo(shadowDir, cwd) {
9909
10212
  timeout: 3e3
9910
10213
  });
9911
10214
  if (check.status === 0) {
9912
- const ptPath = import_path23.default.join(shadowDir, "project-path.txt");
10215
+ const ptPath = import_path24.default.join(shadowDir, "project-path.txt");
9913
10216
  try {
9914
- const stored = import_fs21.default.readFileSync(ptPath, "utf8").trim();
10217
+ const stored = import_fs22.default.readFileSync(ptPath, "utf8").trim();
9915
10218
  if (stored === normalizedCwd) return true;
9916
10219
  if (process.env.NODE9_DEBUG === "1")
9917
10220
  console.error(
9918
10221
  `[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
9919
10222
  );
9920
- import_fs21.default.rmSync(shadowDir, { recursive: true, force: true });
10223
+ import_fs22.default.rmSync(shadowDir, { recursive: true, force: true });
9921
10224
  } catch {
9922
10225
  try {
9923
- import_fs21.default.writeFileSync(ptPath, normalizedCwd, "utf8");
10226
+ import_fs22.default.writeFileSync(ptPath, normalizedCwd, "utf8");
9924
10227
  } catch {
9925
10228
  }
9926
10229
  return true;
9927
10230
  }
9928
10231
  }
9929
10232
  try {
9930
- import_fs21.default.mkdirSync(shadowDir, { recursive: true });
10233
+ import_fs22.default.mkdirSync(shadowDir, { recursive: true });
9931
10234
  } catch {
9932
10235
  }
9933
10236
  const init = (0, import_child_process9.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
@@ -9936,7 +10239,7 @@ function ensureShadowRepo(shadowDir, cwd) {
9936
10239
  if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
9937
10240
  return false;
9938
10241
  }
9939
- const configFile = import_path23.default.join(shadowDir, "config");
10242
+ const configFile = import_path24.default.join(shadowDir, "config");
9940
10243
  (0, import_child_process9.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
9941
10244
  timeout: 3e3
9942
10245
  });
@@ -9944,7 +10247,7 @@ function ensureShadowRepo(shadowDir, cwd) {
9944
10247
  timeout: 3e3
9945
10248
  });
9946
10249
  try {
9947
- import_fs21.default.writeFileSync(import_path23.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
10250
+ import_fs22.default.writeFileSync(import_path24.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
9948
10251
  } catch {
9949
10252
  }
9950
10253
  return true;
@@ -9964,12 +10267,12 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
9964
10267
  let indexFile = null;
9965
10268
  try {
9966
10269
  const rawFilePath = extractFilePath(args);
9967
- const absFilePath = rawFilePath && import_path23.default.isAbsolute(rawFilePath) ? rawFilePath : null;
10270
+ const absFilePath = rawFilePath && import_path24.default.isAbsolute(rawFilePath) ? rawFilePath : null;
9968
10271
  const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
9969
10272
  const shadowDir = getShadowRepoDir(cwd);
9970
10273
  if (!ensureShadowRepo(shadowDir, cwd)) return null;
9971
10274
  writeShadowExcludes(shadowDir, ignorePaths);
9972
- indexFile = import_path23.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
10275
+ indexFile = import_path24.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
9973
10276
  const shadowEnv = {
9974
10277
  ...process.env,
9975
10278
  GIT_DIR: shadowDir,
@@ -10041,7 +10344,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
10041
10344
  writeStack(stack);
10042
10345
  const entry = stack[stack.length - 1];
10043
10346
  notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
10044
- import_fs21.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
10347
+ import_fs22.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
10045
10348
  if (shouldGc) {
10046
10349
  (0, import_child_process9.spawn)("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
10047
10350
  }
@@ -10052,7 +10355,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
10052
10355
  } finally {
10053
10356
  if (indexFile) {
10054
10357
  try {
10055
- import_fs21.default.unlinkSync(indexFile);
10358
+ import_fs22.default.unlinkSync(indexFile);
10056
10359
  } catch {
10057
10360
  }
10058
10361
  }
@@ -10128,9 +10431,9 @@ function applyUndo(hash, cwd) {
10128
10431
  timeout: GIT_TIMEOUT
10129
10432
  }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
10130
10433
  for (const file of [...tracked, ...untracked]) {
10131
- const fullPath = import_path23.default.join(dir, file);
10132
- if (!snapshotFiles.has(file) && import_fs21.default.existsSync(fullPath)) {
10133
- import_fs21.default.unlinkSync(fullPath);
10434
+ const fullPath = import_path24.default.join(dir, file);
10435
+ if (!snapshotFiles.has(file) && import_fs22.default.existsSync(fullPath)) {
10436
+ import_fs22.default.unlinkSync(fullPath);
10134
10437
  }
10135
10438
  }
10136
10439
  return true;
@@ -10140,12 +10443,12 @@ function applyUndo(hash, cwd) {
10140
10443
  }
10141
10444
 
10142
10445
  // src/skill-pin.ts
10143
- var import_fs22 = __toESM(require("fs"));
10144
- var import_path24 = __toESM(require("path"));
10145
- var import_os18 = __toESM(require("os"));
10446
+ var import_fs23 = __toESM(require("fs"));
10447
+ var import_path25 = __toESM(require("path"));
10448
+ var import_os19 = __toESM(require("os"));
10146
10449
  var import_crypto9 = __toESM(require("crypto"));
10147
10450
  function getPinsFilePath() {
10148
- return import_path24.default.join(import_os18.default.homedir(), ".node9", "skill-pins.json");
10451
+ return import_path25.default.join(import_os19.default.homedir(), ".node9", "skill-pins.json");
10149
10452
  }
10150
10453
  var MAX_FILES = 5e3;
10151
10454
  var MAX_TOTAL_BYTES = 50 * 1024 * 1024;
@@ -10159,18 +10462,18 @@ function walkDir(root) {
10159
10462
  if (out.length >= MAX_FILES) return;
10160
10463
  let entries;
10161
10464
  try {
10162
- entries = import_fs22.default.readdirSync(dir, { withFileTypes: true });
10465
+ entries = import_fs23.default.readdirSync(dir, { withFileTypes: true });
10163
10466
  } catch {
10164
10467
  return;
10165
10468
  }
10166
10469
  entries.sort((a, b) => a.name.localeCompare(b.name));
10167
10470
  for (const entry of entries) {
10168
10471
  if (out.length >= MAX_FILES) return;
10169
- const full = import_path24.default.join(dir, entry.name);
10170
- const rel = relDir ? import_path24.default.posix.join(relDir, entry.name) : entry.name;
10472
+ const full = import_path25.default.join(dir, entry.name);
10473
+ const rel = relDir ? import_path25.default.posix.join(relDir, entry.name) : entry.name;
10171
10474
  let lst;
10172
10475
  try {
10173
- lst = import_fs22.default.lstatSync(full);
10476
+ lst = import_fs23.default.lstatSync(full);
10174
10477
  } catch {
10175
10478
  continue;
10176
10479
  }
@@ -10182,7 +10485,7 @@ function walkDir(root) {
10182
10485
  if (!lst.isFile()) continue;
10183
10486
  if (totalBytes + lst.size > MAX_TOTAL_BYTES) continue;
10184
10487
  try {
10185
- const buf = import_fs22.default.readFileSync(full);
10488
+ const buf = import_fs23.default.readFileSync(full);
10186
10489
  totalBytes += buf.length;
10187
10490
  out.push({ rel, hash: sha256Bytes(buf) });
10188
10491
  } catch {
@@ -10196,14 +10499,14 @@ function walkDir(root) {
10196
10499
  function hashSkillRoot(absPath) {
10197
10500
  let lst;
10198
10501
  try {
10199
- lst = import_fs22.default.lstatSync(absPath);
10502
+ lst = import_fs23.default.lstatSync(absPath);
10200
10503
  } catch {
10201
10504
  return { exists: false, contentHash: "", fileCount: 0 };
10202
10505
  }
10203
10506
  if (lst.isSymbolicLink()) return { exists: false, contentHash: "", fileCount: 0 };
10204
10507
  if (lst.isFile()) {
10205
10508
  try {
10206
- return { exists: true, contentHash: sha256Bytes(import_fs22.default.readFileSync(absPath)), fileCount: 1 };
10509
+ return { exists: true, contentHash: sha256Bytes(import_fs23.default.readFileSync(absPath)), fileCount: 1 };
10207
10510
  } catch {
10208
10511
  return { exists: false, contentHash: "", fileCount: 0 };
10209
10512
  }
@@ -10221,7 +10524,7 @@ function getRootKey(absPath) {
10221
10524
  function readSkillPinsSafe() {
10222
10525
  const filePath = getPinsFilePath();
10223
10526
  try {
10224
- const raw = import_fs22.default.readFileSync(filePath, "utf-8");
10527
+ const raw = import_fs23.default.readFileSync(filePath, "utf-8");
10225
10528
  if (!raw.trim()) return { ok: false, reason: "corrupt", detail: "empty file" };
10226
10529
  const parsed = JSON.parse(raw);
10227
10530
  if (!parsed.roots || typeof parsed.roots !== "object" || Array.isArray(parsed.roots)) {
@@ -10241,10 +10544,10 @@ function readSkillPins() {
10241
10544
  }
10242
10545
  function writeSkillPins(data) {
10243
10546
  const filePath = getPinsFilePath();
10244
- import_fs22.default.mkdirSync(import_path24.default.dirname(filePath), { recursive: true });
10547
+ import_fs23.default.mkdirSync(import_path25.default.dirname(filePath), { recursive: true });
10245
10548
  const tmp = `${filePath}.${import_crypto9.default.randomBytes(6).toString("hex")}.tmp`;
10246
- import_fs22.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
10247
- import_fs22.default.renameSync(tmp, filePath);
10549
+ import_fs23.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
10550
+ import_fs23.default.renameSync(tmp, filePath);
10248
10551
  }
10249
10552
  function removePin(rootKey) {
10250
10553
  const pins = readSkillPins();
@@ -10288,36 +10591,36 @@ function verifyAndPinRoots(roots) {
10288
10591
  return { kind: "verified" };
10289
10592
  }
10290
10593
  function defaultSkillRoots(_cwd) {
10291
- const marketplaces = import_path24.default.join(import_os18.default.homedir(), ".claude", "plugins", "marketplaces");
10594
+ const marketplaces = import_path25.default.join(import_os19.default.homedir(), ".claude", "plugins", "marketplaces");
10292
10595
  const roots = [];
10293
10596
  let registries;
10294
10597
  try {
10295
- registries = import_fs22.default.readdirSync(marketplaces, { withFileTypes: true });
10598
+ registries = import_fs23.default.readdirSync(marketplaces, { withFileTypes: true });
10296
10599
  } catch {
10297
10600
  return [];
10298
10601
  }
10299
10602
  for (const registry of registries) {
10300
10603
  if (!registry.isDirectory()) continue;
10301
- const pluginsDir = import_path24.default.join(marketplaces, registry.name, "plugins");
10604
+ const pluginsDir = import_path25.default.join(marketplaces, registry.name, "plugins");
10302
10605
  let plugins;
10303
10606
  try {
10304
- plugins = import_fs22.default.readdirSync(pluginsDir, { withFileTypes: true });
10607
+ plugins = import_fs23.default.readdirSync(pluginsDir, { withFileTypes: true });
10305
10608
  } catch {
10306
10609
  continue;
10307
10610
  }
10308
10611
  for (const plugin of plugins) {
10309
10612
  if (!plugin.isDirectory()) continue;
10310
- roots.push(import_path24.default.join(pluginsDir, plugin.name));
10613
+ roots.push(import_path25.default.join(pluginsDir, plugin.name));
10311
10614
  }
10312
10615
  }
10313
10616
  return roots;
10314
10617
  }
10315
10618
  function resolveUserSkillRoot(entry, cwd) {
10316
10619
  if (!entry) return null;
10317
- if (entry.startsWith("~/") || entry === "~") return import_path24.default.join(import_os18.default.homedir(), entry.slice(1));
10318
- if (import_path24.default.isAbsolute(entry)) return entry;
10319
- if (!cwd || !import_path24.default.isAbsolute(cwd)) return null;
10320
- return import_path24.default.join(cwd, entry);
10620
+ if (entry.startsWith("~/") || entry === "~") return import_path25.default.join(import_os19.default.homedir(), entry.slice(1));
10621
+ if (import_path25.default.isAbsolute(entry)) return entry;
10622
+ if (!cwd || !import_path25.default.isAbsolute(cwd)) return null;
10623
+ return import_path25.default.join(cwd, entry);
10321
10624
  }
10322
10625
 
10323
10626
  // src/cli/commands/check.ts
@@ -10335,9 +10638,9 @@ function registerCheckCommand(program2) {
10335
10638
  } catch (err2) {
10336
10639
  const tempConfig = getConfig();
10337
10640
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
10338
- const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "hook-debug.log");
10641
+ const logPath = import_path26.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log");
10339
10642
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
10340
- import_fs23.default.appendFileSync(
10643
+ import_fs24.default.appendFileSync(
10341
10644
  logPath,
10342
10645
  `[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
10343
10646
  RAW: ${raw}
@@ -10350,11 +10653,11 @@ RAW: ${raw}
10350
10653
  if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
10351
10654
  try {
10352
10655
  const scriptPath = process.argv[1];
10353
- if (typeof scriptPath !== "string" || !import_path25.default.isAbsolute(scriptPath))
10656
+ if (typeof scriptPath !== "string" || !import_path26.default.isAbsolute(scriptPath))
10354
10657
  throw new Error("node9: argv[1] is not an absolute path");
10355
- const resolvedScript = import_fs23.default.realpathSync(scriptPath);
10356
- const packageDist = import_fs23.default.realpathSync(import_path25.default.resolve(__dirname, "../.."));
10357
- if (!resolvedScript.startsWith(packageDist + import_path25.default.sep) && resolvedScript !== packageDist)
10658
+ const resolvedScript = import_fs24.default.realpathSync(scriptPath);
10659
+ const packageDist = import_fs24.default.realpathSync(import_path26.default.resolve(__dirname, "../.."));
10660
+ if (!resolvedScript.startsWith(packageDist + import_path26.default.sep) && resolvedScript !== packageDist)
10358
10661
  throw new Error(
10359
10662
  `node9: daemon spawn aborted \u2014 argv[1] (${resolvedScript}) is outside package dist (${packageDist})`
10360
10663
  );
@@ -10376,10 +10679,10 @@ RAW: ${raw}
10376
10679
  });
10377
10680
  d.unref();
10378
10681
  } catch (spawnErr) {
10379
- const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "hook-debug.log");
10682
+ const logPath = import_path26.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log");
10380
10683
  const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
10381
10684
  try {
10382
- import_fs23.default.appendFileSync(
10685
+ import_fs24.default.appendFileSync(
10383
10686
  logPath,
10384
10687
  `[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-failed: ${msg}
10385
10688
  `
@@ -10389,10 +10692,10 @@ RAW: ${raw}
10389
10692
  }
10390
10693
  }
10391
10694
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
10392
- const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "hook-debug.log");
10393
- if (!import_fs23.default.existsSync(import_path25.default.dirname(logPath)))
10394
- import_fs23.default.mkdirSync(import_path25.default.dirname(logPath), { recursive: true });
10395
- import_fs23.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
10695
+ const logPath = import_path26.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log");
10696
+ if (!import_fs24.default.existsSync(import_path26.default.dirname(logPath)))
10697
+ import_fs24.default.mkdirSync(import_path26.default.dirname(logPath), { recursive: true });
10698
+ import_fs24.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
10396
10699
  `);
10397
10700
  }
10398
10701
  const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
@@ -10405,8 +10708,8 @@ RAW: ${raw}
10405
10708
  const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
10406
10709
  let ttyFd = null;
10407
10710
  try {
10408
- ttyFd = import_fs23.default.openSync("/dev/tty", "w");
10409
- const writeTty = (line) => import_fs23.default.writeSync(ttyFd, line + "\n");
10711
+ ttyFd = import_fs24.default.openSync("/dev/tty", "w");
10712
+ const writeTty = (line) => import_fs24.default.writeSync(ttyFd, line + "\n");
10410
10713
  if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
10411
10714
  writeTty(import_chalk5.default.bgRed.white.bold(`
10412
10715
  \u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
@@ -10425,7 +10728,7 @@ RAW: ${raw}
10425
10728
  } finally {
10426
10729
  if (ttyFd !== null)
10427
10730
  try {
10428
- import_fs23.default.closeSync(ttyFd);
10731
+ import_fs24.default.closeSync(ttyFd);
10429
10732
  } catch {
10430
10733
  }
10431
10734
  }
@@ -10459,17 +10762,17 @@ RAW: ${raw}
10459
10762
  const safeSessionId = /^[A-Za-z0-9_\-]{1,128}$/.test(rawSessionId) ? rawSessionId : "";
10460
10763
  if (skillPinCfg.enabled && safeSessionId) {
10461
10764
  try {
10462
- const sessionsDir = import_path25.default.join(import_os19.default.homedir(), ".node9", "skill-sessions");
10463
- const flagPath = import_path25.default.join(sessionsDir, `${safeSessionId}.json`);
10765
+ const sessionsDir = import_path26.default.join(import_os20.default.homedir(), ".node9", "skill-sessions");
10766
+ const flagPath = import_path26.default.join(sessionsDir, `${safeSessionId}.json`);
10464
10767
  let flag = null;
10465
10768
  try {
10466
- flag = JSON.parse(import_fs23.default.readFileSync(flagPath, "utf-8"));
10769
+ flag = JSON.parse(import_fs24.default.readFileSync(flagPath, "utf-8"));
10467
10770
  } catch {
10468
10771
  }
10469
10772
  const writeFlag = (data2) => {
10470
10773
  try {
10471
- import_fs23.default.mkdirSync(sessionsDir, { recursive: true });
10472
- import_fs23.default.writeFileSync(
10774
+ import_fs24.default.mkdirSync(sessionsDir, { recursive: true });
10775
+ import_fs24.default.writeFileSync(
10473
10776
  flagPath,
10474
10777
  JSON.stringify({ ...data2, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
10475
10778
  { mode: 384 }
@@ -10480,8 +10783,8 @@ RAW: ${raw}
10480
10783
  const sendSkillWarn = (detail, recoveryCmd) => {
10481
10784
  let ttyFd = null;
10482
10785
  try {
10483
- ttyFd = import_fs23.default.openSync("/dev/tty", "w");
10484
- const w = (line) => import_fs23.default.writeSync(ttyFd, line + "\n");
10786
+ ttyFd = import_fs24.default.openSync("/dev/tty", "w");
10787
+ const w = (line) => import_fs24.default.writeSync(ttyFd, line + "\n");
10485
10788
  w(import_chalk5.default.yellow(`
10486
10789
  \u26A0\uFE0F Node9: installed skill drift detected`));
10487
10790
  w(import_chalk5.default.gray(` ${detail}`));
@@ -10496,7 +10799,7 @@ RAW: ${raw}
10496
10799
  } finally {
10497
10800
  if (ttyFd !== null)
10498
10801
  try {
10499
- import_fs23.default.closeSync(ttyFd);
10802
+ import_fs24.default.closeSync(ttyFd);
10500
10803
  } catch {
10501
10804
  }
10502
10805
  }
@@ -10512,7 +10815,7 @@ RAW: ${raw}
10512
10815
  return;
10513
10816
  }
10514
10817
  if (!flag || flag.state !== "verified" && flag.state !== "warned") {
10515
- const absoluteCwd = typeof payload.cwd === "string" && import_path25.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
10818
+ const absoluteCwd = typeof payload.cwd === "string" && import_path26.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
10516
10819
  const extraRoots = skillPinCfg.roots;
10517
10820
  const resolvedExtra = extraRoots.map((r) => resolveUserSkillRoot(r, absoluteCwd)).filter((r) => typeof r === "string");
10518
10821
  const roots = [...defaultSkillRoots(absoluteCwd), ...resolvedExtra];
@@ -10553,10 +10856,10 @@ RAW: ${raw}
10553
10856
  }
10554
10857
  try {
10555
10858
  const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
10556
- for (const name of import_fs23.default.readdirSync(sessionsDir)) {
10557
- const p = import_path25.default.join(sessionsDir, name);
10859
+ for (const name of import_fs24.default.readdirSync(sessionsDir)) {
10860
+ const p = import_path26.default.join(sessionsDir, name);
10558
10861
  try {
10559
- if (import_fs23.default.statSync(p).mtimeMs < cutoff) import_fs23.default.unlinkSync(p);
10862
+ if (import_fs24.default.statSync(p).mtimeMs < cutoff) import_fs24.default.unlinkSync(p);
10560
10863
  } catch {
10561
10864
  }
10562
10865
  }
@@ -10566,9 +10869,9 @@ RAW: ${raw}
10566
10869
  } catch (err2) {
10567
10870
  if (process.env.NODE9_DEBUG === "1") {
10568
10871
  try {
10569
- const dbg = import_path25.default.join(import_os19.default.homedir(), ".node9", "hook-debug.log");
10872
+ const dbg = import_path26.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log");
10570
10873
  const msg = err2 instanceof Error ? err2.message : String(err2);
10571
- import_fs23.default.appendFileSync(dbg, `[${(/* @__PURE__ */ new Date()).toISOString()}] SKILL_PIN_ERROR: ${msg}
10874
+ import_fs24.default.appendFileSync(dbg, `[${(/* @__PURE__ */ new Date()).toISOString()}] SKILL_PIN_ERROR: ${msg}
10572
10875
  `);
10573
10876
  } catch {
10574
10877
  }
@@ -10578,7 +10881,7 @@ RAW: ${raw}
10578
10881
  if (shouldSnapshot(toolName, toolInput, config)) {
10579
10882
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
10580
10883
  }
10581
- const safeCwdForAuth = typeof payload.cwd === "string" && import_path25.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
10884
+ const safeCwdForAuth = typeof payload.cwd === "string" && import_path26.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
10582
10885
  const result = await authorizeHeadless(toolName, toolInput, meta, {
10583
10886
  cwd: safeCwdForAuth
10584
10887
  });
@@ -10590,12 +10893,12 @@ RAW: ${raw}
10590
10893
  }
10591
10894
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
10592
10895
  try {
10593
- const tty = import_fs23.default.openSync("/dev/tty", "w");
10594
- import_fs23.default.writeSync(
10896
+ const tty = import_fs24.default.openSync("/dev/tty", "w");
10897
+ import_fs24.default.writeSync(
10595
10898
  tty,
10596
10899
  import_chalk5.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
10597
10900
  );
10598
- import_fs23.default.closeSync(tty);
10901
+ import_fs24.default.closeSync(tty);
10599
10902
  } catch {
10600
10903
  }
10601
10904
  const daemonReady = await autoStartDaemonAndWait();
@@ -10622,9 +10925,9 @@ RAW: ${raw}
10622
10925
  });
10623
10926
  } catch (err2) {
10624
10927
  if (process.env.NODE9_DEBUG === "1") {
10625
- const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "hook-debug.log");
10928
+ const logPath = import_path26.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log");
10626
10929
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
10627
- import_fs23.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
10930
+ import_fs24.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
10628
10931
  `);
10629
10932
  }
10630
10933
  process.exit(0);
@@ -10658,9 +10961,9 @@ RAW: ${raw}
10658
10961
  }
10659
10962
 
10660
10963
  // src/cli/commands/log.ts
10661
- var import_fs24 = __toESM(require("fs"));
10662
- var import_path26 = __toESM(require("path"));
10663
- var import_os20 = __toESM(require("os"));
10964
+ var import_fs25 = __toESM(require("fs"));
10965
+ var import_path27 = __toESM(require("path"));
10966
+ var import_os21 = __toESM(require("os"));
10664
10967
  init_audit();
10665
10968
  init_config();
10666
10969
  init_policy();
@@ -10736,10 +11039,10 @@ function registerLogCommand(program2) {
10736
11039
  decision: "allowed",
10737
11040
  source: "post-hook"
10738
11041
  };
10739
- const logPath = import_path26.default.join(import_os20.default.homedir(), ".node9", "audit.log");
10740
- if (!import_fs24.default.existsSync(import_path26.default.dirname(logPath)))
10741
- import_fs24.default.mkdirSync(import_path26.default.dirname(logPath), { recursive: true });
10742
- import_fs24.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
11042
+ const logPath = import_path27.default.join(import_os21.default.homedir(), ".node9", "audit.log");
11043
+ if (!import_fs25.default.existsSync(import_path27.default.dirname(logPath)))
11044
+ import_fs25.default.mkdirSync(import_path27.default.dirname(logPath), { recursive: true });
11045
+ import_fs25.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
10743
11046
  if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
10744
11047
  const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
10745
11048
  if (command) {
@@ -10772,7 +11075,7 @@ function registerLogCommand(program2) {
10772
11075
  }
10773
11076
  }
10774
11077
  }
10775
- const safeCwd = typeof payload.cwd === "string" && import_path26.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
11078
+ const safeCwd = typeof payload.cwd === "string" && import_path27.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
10776
11079
  const config = getConfig(safeCwd);
10777
11080
  if (shouldSnapshot(tool, {}, config)) {
10778
11081
  await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
@@ -10781,9 +11084,9 @@ function registerLogCommand(program2) {
10781
11084
  const msg = err2 instanceof Error ? err2.message : String(err2);
10782
11085
  process.stderr.write(`[Node9] audit log error: ${msg}
10783
11086
  `);
10784
- const debugPath = import_path26.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log");
11087
+ const debugPath = import_path27.default.join(import_os21.default.homedir(), ".node9", "hook-debug.log");
10785
11088
  try {
10786
- import_fs24.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
11089
+ import_fs25.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
10787
11090
  `);
10788
11091
  } catch {
10789
11092
  }
@@ -11183,14 +11486,14 @@ function registerConfigShowCommand(program2) {
11183
11486
 
11184
11487
  // src/cli/commands/doctor.ts
11185
11488
  var import_chalk7 = __toESM(require("chalk"));
11186
- var import_fs25 = __toESM(require("fs"));
11187
- var import_path27 = __toESM(require("path"));
11188
- var import_os21 = __toESM(require("os"));
11489
+ var import_fs26 = __toESM(require("fs"));
11490
+ var import_path28 = __toESM(require("path"));
11491
+ var import_os22 = __toESM(require("os"));
11189
11492
  var import_child_process11 = require("child_process");
11190
11493
  init_daemon();
11191
11494
  function registerDoctorCommand(program2, version2) {
11192
11495
  program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
11193
- const homeDir2 = import_os21.default.homedir();
11496
+ const homeDir2 = import_os22.default.homedir();
11194
11497
  let failures = 0;
11195
11498
  function pass(msg) {
11196
11499
  console.log(import_chalk7.default.green(" \u2705 ") + msg);
@@ -11239,10 +11542,10 @@ function registerDoctorCommand(program2, version2) {
11239
11542
  );
11240
11543
  }
11241
11544
  section("Configuration");
11242
- const globalConfigPath = import_path27.default.join(homeDir2, ".node9", "config.json");
11243
- if (import_fs25.default.existsSync(globalConfigPath)) {
11545
+ const globalConfigPath = import_path28.default.join(homeDir2, ".node9", "config.json");
11546
+ if (import_fs26.default.existsSync(globalConfigPath)) {
11244
11547
  try {
11245
- JSON.parse(import_fs25.default.readFileSync(globalConfigPath, "utf-8"));
11548
+ JSON.parse(import_fs26.default.readFileSync(globalConfigPath, "utf-8"));
11246
11549
  pass("~/.node9/config.json found and valid");
11247
11550
  } catch {
11248
11551
  fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
@@ -11250,10 +11553,10 @@ function registerDoctorCommand(program2, version2) {
11250
11553
  } else {
11251
11554
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
11252
11555
  }
11253
- const projectConfigPath = import_path27.default.join(process.cwd(), "node9.config.json");
11254
- if (import_fs25.default.existsSync(projectConfigPath)) {
11556
+ const projectConfigPath = import_path28.default.join(process.cwd(), "node9.config.json");
11557
+ if (import_fs26.default.existsSync(projectConfigPath)) {
11255
11558
  try {
11256
- JSON.parse(import_fs25.default.readFileSync(projectConfigPath, "utf-8"));
11559
+ JSON.parse(import_fs26.default.readFileSync(projectConfigPath, "utf-8"));
11257
11560
  pass("node9.config.json found and valid (project)");
11258
11561
  } catch {
11259
11562
  fail(
@@ -11262,8 +11565,8 @@ function registerDoctorCommand(program2, version2) {
11262
11565
  );
11263
11566
  }
11264
11567
  }
11265
- const credsPath = import_path27.default.join(homeDir2, ".node9", "credentials.json");
11266
- if (import_fs25.default.existsSync(credsPath)) {
11568
+ const credsPath = import_path28.default.join(homeDir2, ".node9", "credentials.json");
11569
+ if (import_fs26.default.existsSync(credsPath)) {
11267
11570
  pass("Cloud credentials found (~/.node9/credentials.json)");
11268
11571
  } else {
11269
11572
  warn(
@@ -11272,10 +11575,10 @@ function registerDoctorCommand(program2, version2) {
11272
11575
  );
11273
11576
  }
11274
11577
  section("Agent Hooks");
11275
- const claudeSettingsPath = import_path27.default.join(homeDir2, ".claude", "settings.json");
11276
- if (import_fs25.default.existsSync(claudeSettingsPath)) {
11578
+ const claudeSettingsPath = import_path28.default.join(homeDir2, ".claude", "settings.json");
11579
+ if (import_fs26.default.existsSync(claudeSettingsPath)) {
11277
11580
  try {
11278
- const cs = JSON.parse(import_fs25.default.readFileSync(claudeSettingsPath, "utf-8"));
11581
+ const cs = JSON.parse(import_fs26.default.readFileSync(claudeSettingsPath, "utf-8"));
11279
11582
  const hasHook = cs.hooks?.PreToolUse?.some(
11280
11583
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
11281
11584
  );
@@ -11291,10 +11594,10 @@ function registerDoctorCommand(program2, version2) {
11291
11594
  } else {
11292
11595
  warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
11293
11596
  }
11294
- const geminiSettingsPath = import_path27.default.join(homeDir2, ".gemini", "settings.json");
11295
- if (import_fs25.default.existsSync(geminiSettingsPath)) {
11597
+ const geminiSettingsPath = import_path28.default.join(homeDir2, ".gemini", "settings.json");
11598
+ if (import_fs26.default.existsSync(geminiSettingsPath)) {
11296
11599
  try {
11297
- const gs = JSON.parse(import_fs25.default.readFileSync(geminiSettingsPath, "utf-8"));
11600
+ const gs = JSON.parse(import_fs26.default.readFileSync(geminiSettingsPath, "utf-8"));
11298
11601
  const hasHook = gs.hooks?.BeforeTool?.some(
11299
11602
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
11300
11603
  );
@@ -11310,10 +11613,10 @@ function registerDoctorCommand(program2, version2) {
11310
11613
  } else {
11311
11614
  warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
11312
11615
  }
11313
- const cursorHooksPath = import_path27.default.join(homeDir2, ".cursor", "hooks.json");
11314
- if (import_fs25.default.existsSync(cursorHooksPath)) {
11616
+ const cursorHooksPath = import_path28.default.join(homeDir2, ".cursor", "hooks.json");
11617
+ if (import_fs26.default.existsSync(cursorHooksPath)) {
11315
11618
  try {
11316
- const cur = JSON.parse(import_fs25.default.readFileSync(cursorHooksPath, "utf-8"));
11619
+ const cur = JSON.parse(import_fs26.default.readFileSync(cursorHooksPath, "utf-8"));
11317
11620
  const hasHook = cur.hooks?.preToolUse?.some(
11318
11621
  (h) => h.command?.includes("node9") || h.command?.includes("cli.js")
11319
11622
  );
@@ -11351,9 +11654,9 @@ function registerDoctorCommand(program2, version2) {
11351
11654
 
11352
11655
  // src/cli/commands/audit.ts
11353
11656
  var import_chalk8 = __toESM(require("chalk"));
11354
- var import_fs26 = __toESM(require("fs"));
11355
- var import_path28 = __toESM(require("path"));
11356
- var import_os22 = __toESM(require("os"));
11657
+ var import_fs27 = __toESM(require("fs"));
11658
+ var import_path29 = __toESM(require("path"));
11659
+ var import_os23 = __toESM(require("os"));
11357
11660
  function formatRelativeTime(timestamp) {
11358
11661
  const diff = Date.now() - new Date(timestamp).getTime();
11359
11662
  const sec = Math.floor(diff / 1e3);
@@ -11366,14 +11669,14 @@ function formatRelativeTime(timestamp) {
11366
11669
  }
11367
11670
  function registerAuditCommand(program2) {
11368
11671
  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) => {
11369
- const logPath = import_path28.default.join(import_os22.default.homedir(), ".node9", "audit.log");
11370
- if (!import_fs26.default.existsSync(logPath)) {
11672
+ const logPath = import_path29.default.join(import_os23.default.homedir(), ".node9", "audit.log");
11673
+ if (!import_fs27.default.existsSync(logPath)) {
11371
11674
  console.log(
11372
11675
  import_chalk8.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
11373
11676
  );
11374
11677
  return;
11375
11678
  }
11376
- const raw = import_fs26.default.readFileSync(logPath, "utf-8");
11679
+ const raw = import_fs27.default.readFileSync(logPath, "utf-8");
11377
11680
  const lines = raw.split("\n").filter((l) => l.trim() !== "");
11378
11681
  let entries = lines.flatMap((line) => {
11379
11682
  try {
@@ -11427,9 +11730,9 @@ function registerAuditCommand(program2) {
11427
11730
 
11428
11731
  // src/cli/commands/report.ts
11429
11732
  var import_chalk9 = __toESM(require("chalk"));
11430
- var import_fs27 = __toESM(require("fs"));
11431
- var import_path29 = __toESM(require("path"));
11432
- var import_os23 = __toESM(require("os"));
11733
+ var import_fs28 = __toESM(require("fs"));
11734
+ var import_path30 = __toESM(require("path"));
11735
+ var import_os24 = __toESM(require("os"));
11433
11736
  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;
11434
11737
  function buildTestTimestamps(allEntries) {
11435
11738
  const testTs = /* @__PURE__ */ new Set();
@@ -11476,8 +11779,8 @@ function getDateRange(period) {
11476
11779
  }
11477
11780
  }
11478
11781
  function parseAuditLog(logPath) {
11479
- if (!import_fs27.default.existsSync(logPath)) return [];
11480
- const raw = import_fs27.default.readFileSync(logPath, "utf-8");
11782
+ if (!import_fs28.default.existsSync(logPath)) return [];
11783
+ const raw = import_fs28.default.readFileSync(logPath, "utf-8");
11481
11784
  return raw.split("\n").flatMap((line) => {
11482
11785
  if (!line.trim()) return [];
11483
11786
  try {
@@ -11544,34 +11847,38 @@ function loadClaudeCost(start, end) {
11544
11847
  byDay: /* @__PURE__ */ new Map(),
11545
11848
  byModel: /* @__PURE__ */ new Map(),
11546
11849
  inputTokens: 0,
11850
+ outputTokens: 0,
11851
+ cacheWriteTokens: 0,
11547
11852
  cacheReadTokens: 0
11548
11853
  };
11549
- const projectsDir = import_path29.default.join(import_os23.default.homedir(), ".claude", "projects");
11550
- if (!import_fs27.default.existsSync(projectsDir)) return empty;
11854
+ const projectsDir = import_path30.default.join(import_os24.default.homedir(), ".claude", "projects");
11855
+ if (!import_fs28.default.existsSync(projectsDir)) return empty;
11551
11856
  let dirs;
11552
11857
  try {
11553
- dirs = import_fs27.default.readdirSync(projectsDir);
11858
+ dirs = import_fs28.default.readdirSync(projectsDir);
11554
11859
  } catch {
11555
11860
  return empty;
11556
11861
  }
11557
11862
  let total = 0;
11558
11863
  let inputTokens = 0;
11864
+ let outputTokens = 0;
11865
+ let cacheWriteTokens = 0;
11559
11866
  let cacheReadTokens = 0;
11560
11867
  const byDay = /* @__PURE__ */ new Map();
11561
11868
  const byModel = /* @__PURE__ */ new Map();
11562
11869
  for (const proj of dirs) {
11563
- const projPath = import_path29.default.join(projectsDir, proj);
11870
+ const projPath = import_path30.default.join(projectsDir, proj);
11564
11871
  let files;
11565
11872
  try {
11566
- const stat = import_fs27.default.statSync(projPath);
11873
+ const stat = import_fs28.default.statSync(projPath);
11567
11874
  if (!stat.isDirectory()) continue;
11568
- files = import_fs27.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
11875
+ files = import_fs28.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
11569
11876
  } catch {
11570
11877
  continue;
11571
11878
  }
11572
11879
  for (const file of files) {
11573
11880
  try {
11574
- const raw = import_fs27.default.readFileSync(import_path29.default.join(projPath, file), "utf-8");
11881
+ const raw = import_fs28.default.readFileSync(import_path30.default.join(projPath, file), "utf-8");
11575
11882
  for (const line of raw.split("\n")) {
11576
11883
  if (!line.trim()) continue;
11577
11884
  let entry;
@@ -11596,6 +11903,8 @@ function loadClaudeCost(start, end) {
11596
11903
  const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
11597
11904
  total += cost;
11598
11905
  inputTokens += inp;
11906
+ outputTokens += out;
11907
+ cacheWriteTokens += cw;
11599
11908
  cacheReadTokens += cr;
11600
11909
  const dateKey = entry.timestamp.slice(0, 10);
11601
11910
  byDay.set(dateKey, (byDay.get(dateKey) ?? 0) + cost);
@@ -11607,15 +11916,24 @@ function loadClaudeCost(start, end) {
11607
11916
  }
11608
11917
  }
11609
11918
  }
11610
- return { total, byDay, byModel, inputTokens, cacheReadTokens };
11919
+ return { total, byDay, byModel, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens };
11611
11920
  }
11612
11921
  function registerReportCommand(program2) {
11613
11922
  program2.command("report").description("Activity and security report \u2014 what Claude did, what was blocked").option("--period <period>", "today | 7d | 30d | month", "7d").option("--no-tests", "exclude test runner calls (npm test, vitest, pytest\u2026) from stats").action((options) => {
11614
11923
  const period = ["today", "7d", "30d", "month"].includes(
11615
11924
  options.period
11616
11925
  ) ? options.period : "7d";
11617
- const logPath = import_path29.default.join(import_os23.default.homedir(), ".node9", "audit.log");
11926
+ const logPath = import_path30.default.join(import_os24.default.homedir(), ".node9", "audit.log");
11618
11927
  const allEntries = parseAuditLog(logPath);
11928
+ const unackedDlp = allEntries.filter((e) => e.source === "response-dlp");
11929
+ if (unackedDlp.length > 0) {
11930
+ console.log("");
11931
+ console.log(
11932
+ import_chalk9.default.bgRed.white.bold(
11933
+ ` \u26A0\uFE0F DLP ALERT: ${unackedDlp.length} secret${unackedDlp.length !== 1 ? "s" : ""} found in Claude response text `
11934
+ ) + " " + import_chalk9.default.yellow("\u2192 run: node9 dlp")
11935
+ );
11936
+ }
11619
11937
  if (allEntries.length === 0) {
11620
11938
  console.log(
11621
11939
  import_chalk9.default.yellow("\n No audit data found. Run node9 with Claude Code to generate entries.\n")
@@ -11628,6 +11946,8 @@ function registerReportCommand(program2) {
11628
11946
  byDay: costByDay,
11629
11947
  byModel: costByModel,
11630
11948
  inputTokens: costInputTokens,
11949
+ outputTokens: costOutputTokens,
11950
+ cacheWriteTokens: costCacheWrite,
11631
11951
  cacheReadTokens: costCacheRead
11632
11952
  } = loadClaudeCost(start, end);
11633
11953
  const periodMs = end.getTime() - start.getTime();
@@ -11645,6 +11965,7 @@ function registerReportCommand(program2) {
11645
11965
  let filteredTestCount = 0;
11646
11966
  const entries = allEntries.filter((e) => {
11647
11967
  if (e.source === "post-hook") return false;
11968
+ if (e.source === "response-dlp") return false;
11648
11969
  const ts = new Date(e.ts);
11649
11970
  if (ts < start || ts > end) return false;
11650
11971
  if (excludeTests && isTestEntry(e, testTs)) {
@@ -11778,7 +12099,7 @@ function registerReportCommand(program2) {
11778
12099
  if (topBlocks.length === 0) {
11779
12100
  console.log(" " + " ".repeat(COL) + " " + import_chalk9.default.dim("nothing blocked \u2713"));
11780
12101
  }
11781
- if (agentMap.size > 1) {
12102
+ if (agentMap.size >= 1) {
11782
12103
  console.log("");
11783
12104
  console.log(" " + import_chalk9.default.bold("Agents"));
11784
12105
  console.log(" " + import_chalk9.default.dim("\u2500".repeat(Math.min(50, W - 4))));
@@ -11828,6 +12149,40 @@ function registerReportCommand(program2) {
11828
12149
  );
11829
12150
  }
11830
12151
  }
12152
+ const totalTokens = costInputTokens + costOutputTokens + costCacheWrite + costCacheRead;
12153
+ if (totalTokens > 0) {
12154
+ const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
12155
+ console.log("");
12156
+ console.log(" " + import_chalk9.default.bold("Tokens") + " " + import_chalk9.default.dim(`${num(totalTokens)} total`));
12157
+ console.log(" " + import_chalk9.default.dim("\u2500".repeat(Math.min(50, W - 4))));
12158
+ const tokenRows = [
12159
+ ["Input", costInputTokens, import_chalk9.default.cyan(num(costInputTokens))],
12160
+ ["Output", costOutputTokens, import_chalk9.default.white(num(costOutputTokens))],
12161
+ ["Cache write", costCacheWrite, import_chalk9.default.yellow(num(costCacheWrite))],
12162
+ ["Cache read", costCacheRead, import_chalk9.default.green(num(costCacheRead))]
12163
+ ];
12164
+ const maxTok = Math.max(
12165
+ costInputTokens,
12166
+ costOutputTokens,
12167
+ costCacheWrite,
12168
+ costCacheRead,
12169
+ 1
12170
+ );
12171
+ const TOK_BAR = Math.max(6, Math.min(20, W - 30));
12172
+ const TOK_LABEL = 14;
12173
+ for (const [label, count, colored] of tokenRows) {
12174
+ if (count === 0) continue;
12175
+ const b = colorBar(count, maxTok, TOK_BAR);
12176
+ console.log(" " + import_chalk9.default.white(label.padEnd(TOK_LABEL)) + b + " " + colored);
12177
+ }
12178
+ if (cacheHitPct > 0) {
12179
+ console.log(
12180
+ " " + import_chalk9.default.dim(
12181
+ `Cache hit rate: ${cacheHitPct}% (saves ~${fmtCost(costCacheRead * 27e-7)} vs fresh input)`
12182
+ )
12183
+ );
12184
+ }
12185
+ }
11831
12186
  if (costUSD > 0) {
11832
12187
  const periodDays = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 864e5));
11833
12188
  const avgPerDay = costUSD / periodDays;
@@ -11852,6 +12207,33 @@ function registerReportCommand(program2) {
11852
12207
  );
11853
12208
  }
11854
12209
  }
12210
+ const responseDlpEntries = allEntries.filter((e) => {
12211
+ if (e.source !== "response-dlp") return false;
12212
+ const ts = new Date(e.ts);
12213
+ return ts >= start && ts <= end;
12214
+ });
12215
+ if (responseDlpEntries.length > 0) {
12216
+ console.log("");
12217
+ console.log(
12218
+ " " + import_chalk9.default.red.bold("\u26A0\uFE0F Response DLP") + import_chalk9.default.dim(" \xB7 ") + import_chalk9.default.red(
12219
+ `${responseDlpEntries.length} secret${responseDlpEntries.length !== 1 ? "s" : ""} found in Claude response text`
12220
+ )
12221
+ );
12222
+ console.log(" " + import_chalk9.default.dim("\u2500".repeat(Math.min(60, W - 4))));
12223
+ console.log(
12224
+ " " + import_chalk9.default.yellow("These were NOT blocked \u2014 Claude included them in response prose.")
12225
+ );
12226
+ console.log(" " + import_chalk9.default.yellow("Rotate affected keys immediately."));
12227
+ for (const e of responseDlpEntries.slice(0, 5)) {
12228
+ const ts = import_chalk9.default.dim(fmtDate(e.ts) + " ");
12229
+ const pattern = import_chalk9.default.red(e.dlpPattern ?? "DLP");
12230
+ const sample = import_chalk9.default.gray(e.dlpSample ?? "");
12231
+ console.log(` ${ts}${pattern} ${sample}`);
12232
+ }
12233
+ if (responseDlpEntries.length > 5) {
12234
+ console.log(import_chalk9.default.dim(` \u2026 and ${responseDlpEntries.length - 5} more`));
12235
+ }
12236
+ }
11855
12237
  console.log("");
11856
12238
  console.log(
11857
12239
  " " + import_chalk9.default.dim("node9 audit --deny") + import_chalk9.default.dim(" \xB7 ") + import_chalk9.default.dim("node9 report --period today|7d|30d|month --no-tests")
@@ -11967,14 +12349,14 @@ function registerDaemonCommand(program2) {
11967
12349
 
11968
12350
  // src/cli/commands/status.ts
11969
12351
  var import_chalk11 = __toESM(require("chalk"));
11970
- var import_fs28 = __toESM(require("fs"));
11971
- var import_path30 = __toESM(require("path"));
11972
- var import_os24 = __toESM(require("os"));
12352
+ var import_fs29 = __toESM(require("fs"));
12353
+ var import_path31 = __toESM(require("path"));
12354
+ var import_os25 = __toESM(require("os"));
11973
12355
  init_core();
11974
12356
  init_daemon();
11975
12357
  function readJson2(filePath) {
11976
12358
  try {
11977
- if (import_fs28.default.existsSync(filePath)) return JSON.parse(import_fs28.default.readFileSync(filePath, "utf-8"));
12359
+ if (import_fs29.default.existsSync(filePath)) return JSON.parse(import_fs29.default.readFileSync(filePath, "utf-8"));
11978
12360
  } catch {
11979
12361
  }
11980
12362
  return null;
@@ -12039,28 +12421,28 @@ function registerStatusCommand(program2) {
12039
12421
  console.log("");
12040
12422
  const modeLabel = settings.mode === "audit" ? import_chalk11.default.blue("audit") : settings.mode === "strict" ? import_chalk11.default.red("strict") : import_chalk11.default.white("standard");
12041
12423
  console.log(` Mode: ${modeLabel}`);
12042
- const projectConfig = import_path30.default.join(process.cwd(), "node9.config.json");
12043
- const globalConfig = import_path30.default.join(import_os24.default.homedir(), ".node9", "config.json");
12424
+ const projectConfig = import_path31.default.join(process.cwd(), "node9.config.json");
12425
+ const globalConfig = import_path31.default.join(import_os25.default.homedir(), ".node9", "config.json");
12044
12426
  console.log(
12045
- ` Local: ${import_fs28.default.existsSync(projectConfig) ? import_chalk11.default.green("Active (node9.config.json)") : import_chalk11.default.gray("Not present")}`
12427
+ ` Local: ${import_fs29.default.existsSync(projectConfig) ? import_chalk11.default.green("Active (node9.config.json)") : import_chalk11.default.gray("Not present")}`
12046
12428
  );
12047
12429
  console.log(
12048
- ` Global: ${import_fs28.default.existsSync(globalConfig) ? import_chalk11.default.green("Active (~/.node9/config.json)") : import_chalk11.default.gray("Not present")}`
12430
+ ` Global: ${import_fs29.default.existsSync(globalConfig) ? import_chalk11.default.green("Active (~/.node9/config.json)") : import_chalk11.default.gray("Not present")}`
12049
12431
  );
12050
12432
  if (mergedConfig.policy.sandboxPaths.length > 0) {
12051
12433
  console.log(
12052
12434
  ` Sandbox: ${import_chalk11.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
12053
12435
  );
12054
12436
  }
12055
- const homeDir2 = import_os24.default.homedir();
12437
+ const homeDir2 = import_os25.default.homedir();
12056
12438
  const claudeSettings = readJson2(
12057
- import_path30.default.join(homeDir2, ".claude", "settings.json")
12439
+ import_path31.default.join(homeDir2, ".claude", "settings.json")
12058
12440
  );
12059
- const claudeConfig = readJson2(import_path30.default.join(homeDir2, ".claude.json"));
12441
+ const claudeConfig = readJson2(import_path31.default.join(homeDir2, ".claude.json"));
12060
12442
  const geminiSettings = readJson2(
12061
- import_path30.default.join(homeDir2, ".gemini", "settings.json")
12443
+ import_path31.default.join(homeDir2, ".gemini", "settings.json")
12062
12444
  );
12063
- const cursorConfig = readJson2(import_path30.default.join(homeDir2, ".cursor", "mcp.json"));
12445
+ const cursorConfig = readJson2(import_path31.default.join(homeDir2, ".cursor", "mcp.json"));
12064
12446
  const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
12065
12447
  if (agentFound) {
12066
12448
  console.log("");
@@ -12119,9 +12501,9 @@ function registerStatusCommand(program2) {
12119
12501
 
12120
12502
  // src/cli/commands/init.ts
12121
12503
  var import_chalk12 = __toESM(require("chalk"));
12122
- var import_fs29 = __toESM(require("fs"));
12123
- var import_path31 = __toESM(require("path"));
12124
- var import_os25 = __toESM(require("os"));
12504
+ var import_fs30 = __toESM(require("fs"));
12505
+ var import_path32 = __toESM(require("path"));
12506
+ var import_os26 = __toESM(require("os"));
12125
12507
  var import_https3 = __toESM(require("https"));
12126
12508
  init_core();
12127
12509
  init_shields();
@@ -12182,15 +12564,15 @@ function registerInitCommand(program2) {
12182
12564
  }
12183
12565
  console.log("");
12184
12566
  }
12185
- const configPath = import_path31.default.join(import_os25.default.homedir(), ".node9", "config.json");
12186
- if (import_fs29.default.existsSync(configPath) && !options.force) {
12567
+ const configPath = import_path32.default.join(import_os26.default.homedir(), ".node9", "config.json");
12568
+ if (import_fs30.default.existsSync(configPath) && !options.force) {
12187
12569
  try {
12188
- const existing = JSON.parse(import_fs29.default.readFileSync(configPath, "utf-8"));
12570
+ const existing = JSON.parse(import_fs30.default.readFileSync(configPath, "utf-8"));
12189
12571
  const settings = existing.settings ?? {};
12190
12572
  if (settings.mode !== chosenMode) {
12191
12573
  settings.mode = chosenMode;
12192
12574
  existing.settings = settings;
12193
- import_fs29.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
12575
+ import_fs30.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
12194
12576
  console.log(import_chalk12.default.green(`\u2705 Mode updated: ${chosenMode}`));
12195
12577
  } else {
12196
12578
  console.log(import_chalk12.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
@@ -12203,9 +12585,9 @@ function registerInitCommand(program2) {
12203
12585
  ...DEFAULT_CONFIG,
12204
12586
  settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
12205
12587
  };
12206
- const dir = import_path31.default.dirname(configPath);
12207
- if (!import_fs29.default.existsSync(dir)) import_fs29.default.mkdirSync(dir, { recursive: true });
12208
- import_fs29.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
12588
+ const dir = import_path32.default.dirname(configPath);
12589
+ if (!import_fs30.default.existsSync(dir)) import_fs30.default.mkdirSync(dir, { recursive: true });
12590
+ import_fs30.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
12209
12591
  console.log(import_chalk12.default.green(`\u2705 Config created: ${configPath}`));
12210
12592
  console.log(import_chalk12.default.gray(` Mode: ${chosenMode}`));
12211
12593
  }
@@ -12289,7 +12671,7 @@ function registerInitCommand(program2) {
12289
12671
  }
12290
12672
 
12291
12673
  // src/cli/commands/undo.ts
12292
- var import_path32 = __toESM(require("path"));
12674
+ var import_path33 = __toESM(require("path"));
12293
12675
  var import_chalk14 = __toESM(require("chalk"));
12294
12676
 
12295
12677
  // src/tui/undo-navigator.ts
@@ -12448,7 +12830,7 @@ function findMatchingCwd(startDir, history) {
12448
12830
  let dir = startDir;
12449
12831
  while (true) {
12450
12832
  if (cwds.has(dir)) return dir;
12451
- const parent = import_path32.default.dirname(dir);
12833
+ const parent = import_path33.default.dirname(dir);
12452
12834
  if (parent === dir) return null;
12453
12835
  dir = parent;
12454
12836
  }
@@ -12644,12 +13026,12 @@ init_orchestrator();
12644
13026
  init_provenance();
12645
13027
 
12646
13028
  // src/mcp-pin.ts
12647
- var import_fs30 = __toESM(require("fs"));
12648
- var import_path33 = __toESM(require("path"));
12649
- var import_os26 = __toESM(require("os"));
13029
+ var import_fs31 = __toESM(require("fs"));
13030
+ var import_path34 = __toESM(require("path"));
13031
+ var import_os27 = __toESM(require("os"));
12650
13032
  var import_crypto10 = __toESM(require("crypto"));
12651
13033
  function getPinsFilePath2() {
12652
- return import_path33.default.join(import_os26.default.homedir(), ".node9", "mcp-pins.json");
13034
+ return import_path34.default.join(import_os27.default.homedir(), ".node9", "mcp-pins.json");
12653
13035
  }
12654
13036
  function hashToolDefinitions(tools) {
12655
13037
  const sorted = [...tools].sort((a, b) => {
@@ -12666,7 +13048,7 @@ function getServerKey(upstreamCommand) {
12666
13048
  function readMcpPinsSafe() {
12667
13049
  const filePath = getPinsFilePath2();
12668
13050
  try {
12669
- const raw = import_fs30.default.readFileSync(filePath, "utf-8");
13051
+ const raw = import_fs31.default.readFileSync(filePath, "utf-8");
12670
13052
  if (!raw.trim()) {
12671
13053
  return { ok: false, reason: "corrupt", detail: "empty file" };
12672
13054
  }
@@ -12690,10 +13072,10 @@ function readMcpPins() {
12690
13072
  }
12691
13073
  function writeMcpPins(data) {
12692
13074
  const filePath = getPinsFilePath2();
12693
- import_fs30.default.mkdirSync(import_path33.default.dirname(filePath), { recursive: true });
13075
+ import_fs31.default.mkdirSync(import_path34.default.dirname(filePath), { recursive: true });
12694
13076
  const tmp = `${filePath}.${import_crypto10.default.randomBytes(6).toString("hex")}.tmp`;
12695
- import_fs30.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
12696
- import_fs30.default.renameSync(tmp, filePath);
13077
+ import_fs31.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
13078
+ import_fs31.default.renameSync(tmp, filePath);
12697
13079
  }
12698
13080
  function checkPin(serverKey, currentHash) {
12699
13081
  const result = readMcpPinsSafe();
@@ -13065,9 +13447,9 @@ function registerMcpGatewayCommand(program2) {
13065
13447
 
13066
13448
  // src/mcp-server/index.ts
13067
13449
  var import_readline4 = __toESM(require("readline"));
13068
- var import_fs31 = __toESM(require("fs"));
13069
- var import_os27 = __toESM(require("os"));
13070
- var import_path34 = __toESM(require("path"));
13450
+ var import_fs32 = __toESM(require("fs"));
13451
+ var import_os28 = __toESM(require("os"));
13452
+ var import_path35 = __toESM(require("path"));
13071
13453
  init_core();
13072
13454
  init_daemon();
13073
13455
  init_shields();
@@ -13242,13 +13624,13 @@ function handleStatus() {
13242
13624
  lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
13243
13625
  lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
13244
13626
  lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
13245
- const projectConfig = import_path34.default.join(process.cwd(), "node9.config.json");
13246
- const globalConfig = import_path34.default.join(import_os27.default.homedir(), ".node9", "config.json");
13627
+ const projectConfig = import_path35.default.join(process.cwd(), "node9.config.json");
13628
+ const globalConfig = import_path35.default.join(import_os28.default.homedir(), ".node9", "config.json");
13247
13629
  lines.push(
13248
- `Project config (node9.config.json): ${import_fs31.default.existsSync(projectConfig) ? "present" : "not found"}`
13630
+ `Project config (node9.config.json): ${import_fs32.default.existsSync(projectConfig) ? "present" : "not found"}`
13249
13631
  );
13250
13632
  lines.push(
13251
- `Global config (~/.node9/config.json): ${import_fs31.default.existsSync(globalConfig) ? "present" : "not found"}`
13633
+ `Global config (~/.node9/config.json): ${import_fs32.default.existsSync(globalConfig) ? "present" : "not found"}`
13252
13634
  );
13253
13635
  return lines.join("\n");
13254
13636
  }
@@ -13322,21 +13704,21 @@ function handleShieldDisable(args) {
13322
13704
  writeActiveShields(active.filter((s) => s !== name));
13323
13705
  return `Shield "${name}" disabled.`;
13324
13706
  }
13325
- var GLOBAL_CONFIG_PATH2 = import_path34.default.join(import_os27.default.homedir(), ".node9", "config.json");
13707
+ var GLOBAL_CONFIG_PATH2 = import_path35.default.join(import_os28.default.homedir(), ".node9", "config.json");
13326
13708
  var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
13327
13709
  function readGlobalConfigRaw() {
13328
13710
  try {
13329
- if (import_fs31.default.existsSync(GLOBAL_CONFIG_PATH2)) {
13330
- return JSON.parse(import_fs31.default.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
13711
+ if (import_fs32.default.existsSync(GLOBAL_CONFIG_PATH2)) {
13712
+ return JSON.parse(import_fs32.default.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
13331
13713
  }
13332
13714
  } catch {
13333
13715
  }
13334
13716
  return {};
13335
13717
  }
13336
13718
  function writeGlobalConfigRaw(data) {
13337
- const dir = import_path34.default.dirname(GLOBAL_CONFIG_PATH2);
13338
- if (!import_fs31.default.existsSync(dir)) import_fs31.default.mkdirSync(dir, { recursive: true });
13339
- import_fs31.default.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
13719
+ const dir = import_path35.default.dirname(GLOBAL_CONFIG_PATH2);
13720
+ if (!import_fs32.default.existsSync(dir)) import_fs32.default.mkdirSync(dir, { recursive: true });
13721
+ import_fs32.default.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
13340
13722
  }
13341
13723
  function handleApproverList() {
13342
13724
  const config = getConfig();
@@ -13379,9 +13761,9 @@ function handleApproverSet(args) {
13379
13761
  }
13380
13762
  function handleAuditGet(args) {
13381
13763
  const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
13382
- const auditPath = import_path34.default.join(import_os27.default.homedir(), ".node9", "audit.log");
13383
- if (!import_fs31.default.existsSync(auditPath)) return "No audit log found.";
13384
- const lines = import_fs31.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
13764
+ const auditPath = import_path35.default.join(import_os28.default.homedir(), ".node9", "audit.log");
13765
+ if (!import_fs32.default.existsSync(auditPath)) return "No audit log found.";
13766
+ const lines = import_fs32.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
13385
13767
  const recent = lines.slice(-limit);
13386
13768
  const entries = recent.map((line) => {
13387
13769
  try {
@@ -13839,9 +14221,9 @@ function registerAgentsCommand(program2) {
13839
14221
 
13840
14222
  // src/cli/commands/scan.ts
13841
14223
  var import_chalk21 = __toESM(require("chalk"));
13842
- var import_fs32 = __toESM(require("fs"));
13843
- var import_path35 = __toESM(require("path"));
13844
- var import_os28 = __toESM(require("os"));
14224
+ var import_fs33 = __toESM(require("fs"));
14225
+ var import_path36 = __toESM(require("path"));
14226
+ var import_os29 = __toESM(require("os"));
13845
14227
  init_shields();
13846
14228
  init_config();
13847
14229
  init_policy();
@@ -13913,7 +14295,7 @@ function buildRuleSources() {
13913
14295
  return sources;
13914
14296
  }
13915
14297
  function scanClaudeHistory(startDate) {
13916
- const projectsDir = import_path35.default.join(import_os28.default.homedir(), ".claude", "projects");
14298
+ const projectsDir = import_path36.default.join(import_os29.default.homedir(), ".claude", "projects");
13917
14299
  const result = {
13918
14300
  filesScanned: 0,
13919
14301
  sessions: 0,
@@ -13925,25 +14307,25 @@ function scanClaudeHistory(startDate) {
13925
14307
  firstDate: null,
13926
14308
  lastDate: null
13927
14309
  };
13928
- if (!import_fs32.default.existsSync(projectsDir)) return result;
14310
+ if (!import_fs33.default.existsSync(projectsDir)) return result;
13929
14311
  let projDirs;
13930
14312
  try {
13931
- projDirs = import_fs32.default.readdirSync(projectsDir);
14313
+ projDirs = import_fs33.default.readdirSync(projectsDir);
13932
14314
  } catch {
13933
14315
  return result;
13934
14316
  }
13935
14317
  const ruleSources = buildRuleSources();
13936
14318
  for (const proj of projDirs) {
13937
- const projPath = import_path35.default.join(projectsDir, proj);
14319
+ const projPath = import_path36.default.join(projectsDir, proj);
13938
14320
  try {
13939
- if (!import_fs32.default.statSync(projPath).isDirectory()) continue;
14321
+ if (!import_fs33.default.statSync(projPath).isDirectory()) continue;
13940
14322
  } catch {
13941
14323
  continue;
13942
14324
  }
13943
- const projLabel = decodeURIComponent(proj).replace(import_os28.default.homedir(), "~").slice(0, 40);
14325
+ const projLabel = decodeURIComponent(proj).replace(import_os29.default.homedir(), "~").slice(0, 40);
13944
14326
  let files;
13945
14327
  try {
13946
- files = import_fs32.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
14328
+ files = import_fs33.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
13947
14329
  } catch {
13948
14330
  continue;
13949
14331
  }
@@ -13952,7 +14334,7 @@ function scanClaudeHistory(startDate) {
13952
14334
  result.sessions++;
13953
14335
  let raw;
13954
14336
  try {
13955
- raw = import_fs32.default.readFileSync(import_path35.default.join(projPath, file), "utf-8");
14337
+ raw = import_fs33.default.readFileSync(import_path36.default.join(projPath, file), "utf-8");
13956
14338
  } catch {
13957
14339
  continue;
13958
14340
  }
@@ -13993,6 +14375,9 @@ function scanClaudeHistory(startDate) {
13993
14375
  if (toolNameLower === "bash" || toolNameLower === "execute_bash") {
13994
14376
  result.bashCalls++;
13995
14377
  }
14378
+ const rawCmd = String(input.command ?? "").trimStart();
14379
+ if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd))
14380
+ continue;
13996
14381
  const dlpMatch = scanArgs(input);
13997
14382
  if (dlpMatch) {
13998
14383
  const isDupe = result.dlpFindings.some(
@@ -14010,6 +14395,7 @@ function scanClaudeHistory(startDate) {
14010
14395
  }
14011
14396
  for (const source of ruleSources) {
14012
14397
  const { rule } = source;
14398
+ if (rule.verdict === "allow") continue;
14013
14399
  if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
14014
14400
  if (!evaluateSmartConditions(input, rule)) continue;
14015
14401
  const inputPreview = preview(input, 120);
@@ -14045,8 +14431,8 @@ function registerScanCommand(program2) {
14045
14431
  console.log("");
14046
14432
  console.log(import_chalk21.default.cyan.bold("\u{1F50D} node9 scan") + import_chalk21.default.dim(" \u2014 what would node9 catch?"));
14047
14433
  console.log("");
14048
- const projectsDir = import_path35.default.join(import_os28.default.homedir(), ".claude", "projects");
14049
- if (!import_fs32.default.existsSync(projectsDir)) {
14434
+ const projectsDir = import_path36.default.join(import_os29.default.homedir(), ".claude", "projects");
14435
+ if (!import_fs33.default.existsSync(projectsDir)) {
14050
14436
  console.log(import_chalk21.default.yellow(" No Claude history found at ~/.claude/projects/"));
14051
14437
  console.log(import_chalk21.default.gray(" Install Claude Code, run a few sessions, then try again.\n"));
14052
14438
  return;
@@ -14158,8 +14544,8 @@ function registerScanCommand(program2) {
14158
14544
  );
14159
14545
  console.log("");
14160
14546
  }
14161
- const auditLog = import_path35.default.join(import_os28.default.homedir(), ".node9", "audit.log");
14162
- if (import_fs32.default.existsSync(auditLog)) {
14547
+ const auditLog = import_path36.default.join(import_os29.default.homedir(), ".node9", "audit.log");
14548
+ if (import_fs33.default.existsSync(auditLog)) {
14163
14549
  console.log(import_chalk21.default.green(" \u2705 node9 is active \u2014 future sessions are protected."));
14164
14550
  console.log(
14165
14551
  import_chalk21.default.dim(" Run ") + import_chalk21.default.cyan("node9 report") + import_chalk21.default.dim(" to see live stats.")
@@ -14176,9 +14562,9 @@ function registerScanCommand(program2) {
14176
14562
 
14177
14563
  // src/cli/commands/sessions.ts
14178
14564
  var import_chalk22 = __toESM(require("chalk"));
14179
- var import_fs33 = __toESM(require("fs"));
14180
- var import_path36 = __toESM(require("path"));
14181
- var import_os29 = __toESM(require("os"));
14565
+ var import_fs34 = __toESM(require("fs"));
14566
+ var import_path37 = __toESM(require("path"));
14567
+ var import_os30 = __toESM(require("os"));
14182
14568
  var CLAUDE_PRICING3 = {
14183
14569
  "claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
14184
14570
  "claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
@@ -14203,10 +14589,10 @@ function encodeProjectPath(projectPath) {
14203
14589
  }
14204
14590
  function sessionJsonlPath(projectPath, sessionId) {
14205
14591
  const encoded = encodeProjectPath(projectPath);
14206
- return import_path36.default.join(import_os29.default.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
14592
+ return import_path37.default.join(import_os30.default.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
14207
14593
  }
14208
14594
  function projectLabel(projectPath) {
14209
- return projectPath.replace(import_os29.default.homedir(), "~");
14595
+ return projectPath.replace(import_os30.default.homedir(), "~");
14210
14596
  }
14211
14597
  function parseHistoryLines(lines) {
14212
14598
  const entries = [];
@@ -14275,10 +14661,10 @@ function parseSessionLines(lines) {
14275
14661
  return { toolCalls, costUSD, hasSnapshot, modifiedFiles };
14276
14662
  }
14277
14663
  function loadAuditEntries(auditPath) {
14278
- const aPath = auditPath ?? import_path36.default.join(import_os29.default.homedir(), ".node9", "audit.log");
14664
+ const aPath = auditPath ?? import_path37.default.join(import_os30.default.homedir(), ".node9", "audit.log");
14279
14665
  let raw;
14280
14666
  try {
14281
- raw = import_fs33.default.readFileSync(aPath, "utf-8");
14667
+ raw = import_fs34.default.readFileSync(aPath, "utf-8");
14282
14668
  } catch {
14283
14669
  return [];
14284
14670
  }
@@ -14314,10 +14700,10 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
14314
14700
  return result;
14315
14701
  }
14316
14702
  function buildSessions(days, historyPath) {
14317
- const hPath = historyPath ?? import_path36.default.join(import_os29.default.homedir(), ".claude", "history.jsonl");
14703
+ const hPath = historyPath ?? import_path37.default.join(import_os30.default.homedir(), ".claude", "history.jsonl");
14318
14704
  let historyRaw;
14319
14705
  try {
14320
- historyRaw = import_fs33.default.readFileSync(hPath, "utf-8");
14706
+ historyRaw = import_fs34.default.readFileSync(hPath, "utf-8");
14321
14707
  } catch {
14322
14708
  return [];
14323
14709
  }
@@ -14342,7 +14728,7 @@ function buildSessions(days, historyPath) {
14342
14728
  const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
14343
14729
  let sessionLines = [];
14344
14730
  try {
14345
- sessionLines = import_fs33.default.readFileSync(jsonlFile, "utf-8").split("\n");
14731
+ sessionLines = import_fs34.default.readFileSync(jsonlFile, "utf-8").split("\n");
14346
14732
  } catch {
14347
14733
  }
14348
14734
  const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
@@ -14593,8 +14979,8 @@ function registerSessionsCommand(program2) {
14593
14979
  console.log("");
14594
14980
  console.log(import_chalk22.default.cyan.bold("\u{1F4CB} node9 sessions") + import_chalk22.default.dim(" \u2014 what your AI agent did"));
14595
14981
  console.log("");
14596
- const historyPath = import_path36.default.join(import_os29.default.homedir(), ".claude", "history.jsonl");
14597
- if (!import_fs33.default.existsSync(historyPath)) {
14982
+ const historyPath = import_path37.default.join(import_os30.default.homedir(), ".claude", "history.jsonl");
14983
+ if (!import_fs34.default.existsSync(historyPath)) {
14598
14984
  console.log(import_chalk22.default.yellow(" No Claude session history found at ~/.claude/history.jsonl"));
14599
14985
  console.log(import_chalk22.default.gray(" Install Claude Code, run a few sessions, then try again.\n"));
14600
14986
  return;
@@ -14626,12 +15012,12 @@ function registerSessionsCommand(program2) {
14626
15012
 
14627
15013
  // src/cli/commands/skill-pin.ts
14628
15014
  var import_chalk23 = __toESM(require("chalk"));
14629
- var import_fs34 = __toESM(require("fs"));
14630
- var import_os30 = __toESM(require("os"));
14631
- var import_path37 = __toESM(require("path"));
15015
+ var import_fs35 = __toESM(require("fs"));
15016
+ var import_os31 = __toESM(require("os"));
15017
+ var import_path38 = __toESM(require("path"));
14632
15018
  function wipeSkillSessions() {
14633
15019
  try {
14634
- import_fs34.default.rmSync(import_path37.default.join(import_os30.default.homedir(), ".node9", "skill-sessions"), {
15020
+ import_fs35.default.rmSync(import_path38.default.join(import_os31.default.homedir(), ".node9", "skill-sessions"), {
14635
15021
  recursive: true,
14636
15022
  force: true
14637
15023
  });
@@ -14712,22 +15098,145 @@ function registerSkillPinCommand(program2) {
14712
15098
  });
14713
15099
  }
14714
15100
 
15101
+ // src/cli/commands/dlp.ts
15102
+ var import_chalk24 = __toESM(require("chalk"));
15103
+ var import_fs36 = __toESM(require("fs"));
15104
+ var import_path39 = __toESM(require("path"));
15105
+ var import_os32 = __toESM(require("os"));
15106
+ var AUDIT_LOG = import_path39.default.join(import_os32.default.homedir(), ".node9", "audit.log");
15107
+ var RESOLVED_FILE = import_path39.default.join(import_os32.default.homedir(), ".node9", "dlp-resolved.json");
15108
+ var ANSI_RE = /\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g;
15109
+ function stripAnsi(s) {
15110
+ return s.replace(ANSI_RE, "");
15111
+ }
15112
+ function loadResolved() {
15113
+ try {
15114
+ const raw = JSON.parse(import_fs36.default.readFileSync(RESOLVED_FILE, "utf-8"));
15115
+ return new Set(raw);
15116
+ } catch {
15117
+ return /* @__PURE__ */ new Set();
15118
+ }
15119
+ }
15120
+ function saveResolved(resolved) {
15121
+ try {
15122
+ import_fs36.default.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
15123
+ } catch {
15124
+ }
15125
+ }
15126
+ function loadDlpFindings() {
15127
+ if (!import_fs36.default.existsSync(AUDIT_LOG)) return [];
15128
+ return import_fs36.default.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
15129
+ if (!line.trim()) return [];
15130
+ try {
15131
+ const e = JSON.parse(line);
15132
+ return e.source === "response-dlp" ? [e] : [];
15133
+ } catch {
15134
+ return [];
15135
+ }
15136
+ });
15137
+ }
15138
+ function entryKey(e) {
15139
+ return `${e.ts}:${e.dlpPattern}:${e.dlpSample}`;
15140
+ }
15141
+ function fmtDate3(ts) {
15142
+ try {
15143
+ return new Date(ts).toLocaleDateString("en-US", {
15144
+ month: "short",
15145
+ day: "numeric",
15146
+ year: "numeric"
15147
+ });
15148
+ } catch {
15149
+ return ts.slice(0, 10);
15150
+ }
15151
+ }
15152
+ function registerDlpCommand(program2) {
15153
+ const cmd = program2.command("dlp").description("Show secrets detected in Claude response text and mark them resolved");
15154
+ cmd.command("resolve").description("Mark all current DLP findings as resolved").action(() => {
15155
+ const findings = loadDlpFindings();
15156
+ if (findings.length === 0) {
15157
+ console.log(import_chalk24.default.green("\n \u2705 No response-DLP findings to resolve.\n"));
15158
+ return;
15159
+ }
15160
+ const resolved = loadResolved();
15161
+ for (const e of findings) resolved.add(entryKey(e));
15162
+ saveResolved(resolved);
15163
+ console.log(
15164
+ import_chalk24.default.green(
15165
+ `
15166
+ \u2705 ${findings.length} finding${findings.length !== 1 ? "s" : ""} marked as resolved.
15167
+ `
15168
+ )
15169
+ );
15170
+ });
15171
+ cmd.action(() => {
15172
+ const findings = loadDlpFindings();
15173
+ const resolved = loadResolved();
15174
+ const open = findings.filter((e) => !resolved.has(entryKey(e)));
15175
+ const resolvedCount = findings.length - open.length;
15176
+ console.log("");
15177
+ console.log(
15178
+ import_chalk24.default.bold.cyan("\u{1F510} node9 dlp") + import_chalk24.default.dim(" \u2014 secrets found in Claude response text")
15179
+ );
15180
+ console.log("");
15181
+ if (open.length === 0) {
15182
+ if (resolvedCount > 0) {
15183
+ console.log(import_chalk24.default.green(` \u2705 No open findings \xB7 ${resolvedCount} previously resolved`));
15184
+ } else {
15185
+ console.log(
15186
+ import_chalk24.default.green(" \u2705 No findings \u2014 Claude has not leaked secrets in response text")
15187
+ );
15188
+ }
15189
+ console.log("");
15190
+ return;
15191
+ }
15192
+ console.log(
15193
+ import_chalk24.default.bgRed.white.bold(` \u26A0\uFE0F ${open.length} open finding${open.length !== 1 ? "s" : ""} `) + import_chalk24.default.dim(resolvedCount > 0 ? ` (${resolvedCount} resolved)` : "")
15194
+ );
15195
+ console.log("");
15196
+ console.log(
15197
+ import_chalk24.default.dim(" These secrets were included in Claude's response text \u2014 NOT blocked.")
15198
+ );
15199
+ console.log(import_chalk24.default.dim(" Rotate each affected key immediately.\n"));
15200
+ for (const e of open) {
15201
+ console.log(
15202
+ " " + import_chalk24.default.red("\u25CF") + " " + import_chalk24.default.white(e.dlpPattern ?? "Secret") + import_chalk24.default.dim(" " + fmtDate3(e.ts))
15203
+ );
15204
+ if (e.dlpSample) {
15205
+ console.log(" " + import_chalk24.default.dim("Sample: ") + import_chalk24.default.yellow(stripAnsi(e.dlpSample)));
15206
+ }
15207
+ if (e.project) {
15208
+ console.log(" " + import_chalk24.default.dim("Project: ") + import_chalk24.default.dim(stripAnsi(e.project)));
15209
+ }
15210
+ console.log("");
15211
+ }
15212
+ console.log(" " + import_chalk24.default.bold("Next steps:"));
15213
+ console.log(" " + import_chalk24.default.cyan("1.") + " Rotate any exposed keys shown above");
15214
+ console.log(
15215
+ " " + import_chalk24.default.cyan("2.") + " Run " + import_chalk24.default.white("node9 dlp resolve") + " to acknowledge"
15216
+ );
15217
+ console.log(
15218
+ " " + import_chalk24.default.cyan("3.") + " Run " + import_chalk24.default.white("node9 report") + " for full audit history"
15219
+ );
15220
+ console.log("");
15221
+ });
15222
+ }
15223
+
14715
15224
  // src/cli.ts
14716
15225
  var { version } = JSON.parse(
14717
- import_fs37.default.readFileSync(import_path40.default.join(__dirname, "../package.json"), "utf-8")
15226
+ import_fs39.default.readFileSync(import_path42.default.join(__dirname, "../package.json"), "utf-8")
14718
15227
  );
14719
15228
  var program = new import_commander.Command();
14720
15229
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
14721
15230
  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) => {
14722
15231
  const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
14723
- const credPath = import_path40.default.join(import_os33.default.homedir(), ".node9", "credentials.json");
14724
- if (!import_fs37.default.existsSync(import_path40.default.dirname(credPath)))
14725
- import_fs37.default.mkdirSync(import_path40.default.dirname(credPath), { recursive: true });
15232
+ const credPath = import_path42.default.join(import_os35.default.homedir(), ".node9", "credentials.json");
15233
+ if (!import_fs39.default.existsSync(import_path42.default.dirname(credPath)))
15234
+ import_fs39.default.mkdirSync(import_path42.default.dirname(credPath), { recursive: true });
14726
15235
  const profileName = options.profile || "default";
14727
15236
  let existingCreds = {};
14728
15237
  try {
14729
- if (import_fs37.default.existsSync(credPath)) {
14730
- const raw = JSON.parse(import_fs37.default.readFileSync(credPath, "utf-8"));
15238
+ if (import_fs39.default.existsSync(credPath)) {
15239
+ const raw = JSON.parse(import_fs39.default.readFileSync(credPath, "utf-8"));
14731
15240
  if (raw.apiKey) {
14732
15241
  existingCreds = {
14733
15242
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
@@ -14739,13 +15248,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
14739
15248
  } catch {
14740
15249
  }
14741
15250
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
14742
- import_fs37.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
15251
+ import_fs39.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
14743
15252
  if (profileName === "default") {
14744
- const configPath = import_path40.default.join(import_os33.default.homedir(), ".node9", "config.json");
15253
+ const configPath = import_path42.default.join(import_os35.default.homedir(), ".node9", "config.json");
14745
15254
  let config = {};
14746
15255
  try {
14747
- if (import_fs37.default.existsSync(configPath))
14748
- config = JSON.parse(import_fs37.default.readFileSync(configPath, "utf-8"));
15256
+ if (import_fs39.default.existsSync(configPath))
15257
+ config = JSON.parse(import_fs39.default.readFileSync(configPath, "utf-8"));
14749
15258
  } catch {
14750
15259
  }
14751
15260
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -14760,47 +15269,61 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
14760
15269
  approvers.cloud = false;
14761
15270
  }
14762
15271
  s.approvers = approvers;
14763
- if (!import_fs37.default.existsSync(import_path40.default.dirname(configPath)))
14764
- import_fs37.default.mkdirSync(import_path40.default.dirname(configPath), { recursive: true });
14765
- import_fs37.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
15272
+ if (!import_fs39.default.existsSync(import_path42.default.dirname(configPath)))
15273
+ import_fs39.default.mkdirSync(import_path42.default.dirname(configPath), { recursive: true });
15274
+ import_fs39.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
14766
15275
  }
14767
15276
  if (options.profile && profileName !== "default") {
14768
- console.log(import_chalk25.default.green(`\u2705 Profile "${profileName}" saved`));
14769
- console.log(import_chalk25.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
15277
+ console.log(import_chalk26.default.green(`\u2705 Profile "${profileName}" saved`));
15278
+ console.log(import_chalk26.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
14770
15279
  } else if (options.local) {
14771
- console.log(import_chalk25.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
14772
- console.log(import_chalk25.default.gray(` All decisions stay on this machine.`));
15280
+ console.log(import_chalk26.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
15281
+ console.log(import_chalk26.default.gray(` All decisions stay on this machine.`));
14773
15282
  } else {
14774
- console.log(import_chalk25.default.green(`\u2705 Logged in \u2014 agent mode`));
14775
- console.log(import_chalk25.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
15283
+ console.log(import_chalk26.default.green(`\u2705 Logged in \u2014 agent mode`));
15284
+ console.log(import_chalk26.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
14776
15285
  }
14777
15286
  });
14778
- program.command("addto").description("Integrate Node9 with an AI agent").addHelpText("after", "\n Supported targets: claude gemini cursor windsurf vscode hud").argument("<target>", "The agent to protect: claude | gemini | cursor | windsurf | vscode | hud").action(async (target) => {
15287
+ program.command("addto").description("Integrate Node9 with an AI agent").addHelpText(
15288
+ "after",
15289
+ "\n Supported targets: claude gemini cursor codex windsurf vscode hud"
15290
+ ).argument(
15291
+ "<target>",
15292
+ "The agent to protect: claude | gemini | cursor | codex | windsurf | vscode | hud"
15293
+ ).action(async (target) => {
14779
15294
  if (target === "gemini") return await setupGemini();
14780
15295
  if (target === "claude") return await setupClaude();
14781
15296
  if (target === "cursor") return await setupCursor();
15297
+ if (target === "codex") return await setupCodex();
14782
15298
  if (target === "windsurf") return await setupWindsurf();
14783
15299
  if (target === "vscode") return await setupVSCode();
14784
15300
  if (target === "hud") return setupHud();
14785
15301
  console.error(
14786
- import_chalk25.default.red(
14787
- `Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
15302
+ import_chalk26.default.red(
15303
+ `Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
14788
15304
  )
14789
15305
  );
14790
15306
  process.exit(1);
14791
15307
  });
14792
- program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText("after", "\n Supported targets: claude gemini cursor windsurf vscode hud").argument("[target]", "The agent to protect: claude | gemini | cursor | windsurf | vscode | hud").action(async (target) => {
15308
+ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText(
15309
+ "after",
15310
+ "\n Supported targets: claude gemini cursor codex windsurf vscode hud"
15311
+ ).argument(
15312
+ "[target]",
15313
+ "The agent to protect: claude | gemini | cursor | codex | windsurf | vscode | hud"
15314
+ ).action(async (target) => {
14793
15315
  if (!target) {
14794
- console.log(import_chalk25.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
14795
- console.log(" Usage: " + import_chalk25.default.white("node9 setup <target>") + "\n");
15316
+ console.log(import_chalk26.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
15317
+ console.log(" Usage: " + import_chalk26.default.white("node9 setup <target>") + "\n");
14796
15318
  console.log(" Targets:");
14797
- console.log(" " + import_chalk25.default.green("claude") + " \u2014 Claude Code (hook mode)");
14798
- console.log(" " + import_chalk25.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
14799
- console.log(" " + import_chalk25.default.green("cursor") + " \u2014 Cursor (MCP proxy)");
14800
- console.log(" " + import_chalk25.default.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
14801
- console.log(" " + import_chalk25.default.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
15319
+ console.log(" " + import_chalk26.default.green("claude") + " \u2014 Claude Code (hook mode)");
15320
+ console.log(" " + import_chalk26.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
15321
+ console.log(" " + import_chalk26.default.green("cursor") + " \u2014 Cursor (MCP proxy)");
15322
+ console.log(" " + import_chalk26.default.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
15323
+ console.log(" " + import_chalk26.default.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
15324
+ console.log(" " + import_chalk26.default.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
14802
15325
  process.stdout.write(
14803
- " " + import_chalk25.default.green("hud") + " \u2014 Claude Code security statusline\n"
15326
+ " " + import_chalk26.default.green("hud") + " \u2014 Claude Code security statusline\n"
14804
15327
  );
14805
15328
  console.log("");
14806
15329
  return;
@@ -14809,61 +15332,67 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
14809
15332
  if (t === "gemini") return await setupGemini();
14810
15333
  if (t === "claude") return await setupClaude();
14811
15334
  if (t === "cursor") return await setupCursor();
15335
+ if (t === "codex") return await setupCodex();
14812
15336
  if (t === "windsurf") return await setupWindsurf();
14813
15337
  if (t === "vscode") return await setupVSCode();
14814
15338
  if (t === "hud") return setupHud();
14815
15339
  console.error(
14816
- import_chalk25.default.red(
14817
- `Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
15340
+ import_chalk26.default.red(
15341
+ `Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
14818
15342
  )
14819
15343
  );
14820
15344
  process.exit(1);
14821
15345
  });
14822
- program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText("after", "\n Supported targets: claude gemini cursor windsurf vscode hud").argument(
15346
+ program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText(
15347
+ "after",
15348
+ "\n Supported targets: claude gemini cursor codex windsurf vscode hud"
15349
+ ).argument(
14823
15350
  "<target>",
14824
- "The agent to remove from: claude | gemini | cursor | windsurf | vscode | hud"
15351
+ "The agent to remove from: claude | gemini | cursor | codex | windsurf | vscode | hud"
14825
15352
  ).action((target) => {
14826
15353
  let fn;
14827
15354
  if (target === "claude") fn = teardownClaude;
14828
15355
  else if (target === "gemini") fn = teardownGemini;
14829
15356
  else if (target === "cursor") fn = teardownCursor;
15357
+ else if (target === "codex") fn = teardownCodex;
14830
15358
  else if (target === "windsurf") fn = teardownWindsurf;
14831
15359
  else if (target === "vscode") fn = teardownVSCode;
14832
15360
  else if (target === "hud") fn = teardownHud;
14833
15361
  else {
14834
15362
  console.error(
14835
- import_chalk25.default.red(
14836
- `Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
15363
+ import_chalk26.default.red(
15364
+ `Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
14837
15365
  )
14838
15366
  );
14839
15367
  process.exit(1);
14840
15368
  }
14841
- console.log(import_chalk25.default.cyan(`
15369
+ console.log(import_chalk26.default.cyan(`
14842
15370
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
14843
15371
  `));
14844
15372
  try {
14845
15373
  fn();
14846
15374
  } catch (err2) {
14847
- console.error(import_chalk25.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
15375
+ console.error(import_chalk26.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
14848
15376
  process.exit(1);
14849
15377
  }
14850
- console.log(import_chalk25.default.gray("\n Restart the agent for changes to take effect."));
15378
+ console.log(import_chalk26.default.gray("\n Restart the agent for changes to take effect."));
14851
15379
  });
14852
15380
  program.command("uninstall").description("Remove all Node9 hooks and optionally delete config files").option("--purge", "Also delete ~/.node9/ directory (config, audit log, credentials)").action(async (options) => {
14853
- console.log(import_chalk25.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
14854
- console.log(import_chalk25.default.bold("Stopping daemon..."));
15381
+ console.log(import_chalk26.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
15382
+ console.log(import_chalk26.default.bold("Stopping daemon..."));
14855
15383
  try {
14856
15384
  stopDaemon();
14857
- console.log(import_chalk25.default.green(" \u2705 Daemon stopped"));
15385
+ console.log(import_chalk26.default.green(" \u2705 Daemon stopped"));
14858
15386
  } catch {
14859
- console.log(import_chalk25.default.blue(" \u2139\uFE0F Daemon was not running"));
15387
+ console.log(import_chalk26.default.blue(" \u2139\uFE0F Daemon was not running"));
14860
15388
  }
14861
- console.log(import_chalk25.default.bold("\nRemoving hooks..."));
15389
+ console.log(import_chalk26.default.bold("\nRemoving hooks..."));
14862
15390
  let teardownFailed = false;
14863
15391
  for (const [label, fn] of [
14864
15392
  ["Claude", teardownClaude],
14865
15393
  ["Gemini", teardownGemini],
14866
15394
  ["Cursor", teardownCursor],
15395
+ ["Codex", teardownCodex],
14867
15396
  ["Windsurf", teardownWindsurf],
14868
15397
  ["VSCode", teardownVSCode]
14869
15398
  ]) {
@@ -14872,45 +15401,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
14872
15401
  } catch (err2) {
14873
15402
  teardownFailed = true;
14874
15403
  console.error(
14875
- import_chalk25.default.red(
15404
+ import_chalk26.default.red(
14876
15405
  ` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
14877
15406
  )
14878
15407
  );
14879
15408
  }
14880
15409
  }
14881
15410
  if (options.purge) {
14882
- const node9Dir = import_path40.default.join(import_os33.default.homedir(), ".node9");
14883
- if (import_fs37.default.existsSync(node9Dir)) {
15411
+ const node9Dir = import_path42.default.join(import_os35.default.homedir(), ".node9");
15412
+ if (import_fs39.default.existsSync(node9Dir)) {
14884
15413
  const confirmed = await (0, import_prompts2.confirm)({
14885
15414
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
14886
15415
  default: false
14887
15416
  });
14888
15417
  if (confirmed) {
14889
- import_fs37.default.rmSync(node9Dir, { recursive: true });
14890
- if (import_fs37.default.existsSync(node9Dir)) {
15418
+ import_fs39.default.rmSync(node9Dir, { recursive: true });
15419
+ if (import_fs39.default.existsSync(node9Dir)) {
14891
15420
  console.error(
14892
- import_chalk25.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
15421
+ import_chalk26.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
14893
15422
  );
14894
15423
  } else {
14895
- console.log(import_chalk25.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
15424
+ console.log(import_chalk26.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
14896
15425
  }
14897
15426
  } else {
14898
- console.log(import_chalk25.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
15427
+ console.log(import_chalk26.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
14899
15428
  }
14900
15429
  } else {
14901
- console.log(import_chalk25.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
15430
+ console.log(import_chalk26.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
14902
15431
  }
14903
15432
  } else {
14904
15433
  console.log(
14905
- import_chalk25.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
15434
+ import_chalk26.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
14906
15435
  );
14907
15436
  }
14908
15437
  if (teardownFailed) {
14909
- console.error(import_chalk25.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
15438
+ console.error(import_chalk26.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
14910
15439
  process.exit(1);
14911
15440
  }
14912
- console.log(import_chalk25.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
14913
- console.log(import_chalk25.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
15441
+ console.log(import_chalk26.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
15442
+ console.log(import_chalk26.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
14914
15443
  });
14915
15444
  registerDoctorCommand(program, version);
14916
15445
  program.command("explain").description(
@@ -14923,7 +15452,7 @@ program.command("explain").description(
14923
15452
  try {
14924
15453
  args = JSON.parse(trimmed);
14925
15454
  } catch {
14926
- console.error(import_chalk25.default.red(`
15455
+ console.error(import_chalk26.default.red(`
14927
15456
  \u274C Invalid JSON: ${trimmed}
14928
15457
  `));
14929
15458
  process.exit(1);
@@ -14934,54 +15463,54 @@ program.command("explain").description(
14934
15463
  }
14935
15464
  const result = await explainPolicy(tool, args);
14936
15465
  console.log("");
14937
- console.log(import_chalk25.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
15466
+ console.log(import_chalk26.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
14938
15467
  console.log("");
14939
- console.log(` ${import_chalk25.default.bold("Tool:")} ${import_chalk25.default.white(result.tool)}`);
15468
+ console.log(` ${import_chalk26.default.bold("Tool:")} ${import_chalk26.default.white(result.tool)}`);
14940
15469
  if (argsRaw) {
14941
15470
  const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
14942
- console.log(` ${import_chalk25.default.bold("Input:")} ${import_chalk25.default.gray(preview2)}`);
15471
+ console.log(` ${import_chalk26.default.bold("Input:")} ${import_chalk26.default.gray(preview2)}`);
14943
15472
  }
14944
15473
  console.log("");
14945
- console.log(import_chalk25.default.bold("Config Sources (Waterfall):"));
15474
+ console.log(import_chalk26.default.bold("Config Sources (Waterfall):"));
14946
15475
  for (const tier of result.waterfall) {
14947
- const num3 = import_chalk25.default.gray(` ${tier.tier}.`);
15476
+ const num3 = import_chalk26.default.gray(` ${tier.tier}.`);
14948
15477
  const label = tier.label.padEnd(16);
14949
15478
  let statusStr;
14950
15479
  if (tier.tier === 1) {
14951
- statusStr = import_chalk25.default.gray(tier.note ?? "");
15480
+ statusStr = import_chalk26.default.gray(tier.note ?? "");
14952
15481
  } else if (tier.status === "active") {
14953
- const loc = tier.path ? import_chalk25.default.gray(tier.path) : "";
14954
- const note = tier.note ? import_chalk25.default.gray(`(${tier.note})`) : "";
14955
- statusStr = import_chalk25.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
15482
+ const loc = tier.path ? import_chalk26.default.gray(tier.path) : "";
15483
+ const note = tier.note ? import_chalk26.default.gray(`(${tier.note})`) : "";
15484
+ statusStr = import_chalk26.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
14956
15485
  } else {
14957
- statusStr = import_chalk25.default.gray("\u25CB " + (tier.note ?? "not found"));
15486
+ statusStr = import_chalk26.default.gray("\u25CB " + (tier.note ?? "not found"));
14958
15487
  }
14959
- console.log(`${num3} ${import_chalk25.default.white(label)} ${statusStr}`);
15488
+ console.log(`${num3} ${import_chalk26.default.white(label)} ${statusStr}`);
14960
15489
  }
14961
15490
  console.log("");
14962
- console.log(import_chalk25.default.bold("Policy Evaluation:"));
15491
+ console.log(import_chalk26.default.bold("Policy Evaluation:"));
14963
15492
  for (const step of result.steps) {
14964
15493
  const isFinal = step.isFinal;
14965
15494
  let icon;
14966
- if (step.outcome === "allow") icon = import_chalk25.default.green(" \u2705");
14967
- else if (step.outcome === "review") icon = import_chalk25.default.red(" \u{1F534}");
14968
- else if (step.outcome === "skip") icon = import_chalk25.default.gray(" \u2500 ");
14969
- else icon = import_chalk25.default.gray(" \u25CB ");
15495
+ if (step.outcome === "allow") icon = import_chalk26.default.green(" \u2705");
15496
+ else if (step.outcome === "review") icon = import_chalk26.default.red(" \u{1F534}");
15497
+ else if (step.outcome === "skip") icon = import_chalk26.default.gray(" \u2500 ");
15498
+ else icon = import_chalk26.default.gray(" \u25CB ");
14970
15499
  const name = step.name.padEnd(18);
14971
- const nameStr = isFinal ? import_chalk25.default.white.bold(name) : import_chalk25.default.white(name);
14972
- const detail = isFinal ? import_chalk25.default.white(step.detail) : import_chalk25.default.gray(step.detail);
14973
- const arrow = isFinal ? import_chalk25.default.yellow(" \u2190 STOP") : "";
15500
+ const nameStr = isFinal ? import_chalk26.default.white.bold(name) : import_chalk26.default.white(name);
15501
+ const detail = isFinal ? import_chalk26.default.white(step.detail) : import_chalk26.default.gray(step.detail);
15502
+ const arrow = isFinal ? import_chalk26.default.yellow(" \u2190 STOP") : "";
14974
15503
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
14975
15504
  }
14976
15505
  console.log("");
14977
15506
  if (result.decision === "allow") {
14978
- console.log(import_chalk25.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk25.default.gray(" \u2014 no approval needed"));
15507
+ console.log(import_chalk26.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk26.default.gray(" \u2014 no approval needed"));
14979
15508
  } else {
14980
15509
  console.log(
14981
- import_chalk25.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk25.default.gray(" \u2014 human approval required")
15510
+ import_chalk26.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk26.default.gray(" \u2014 human approval required")
14982
15511
  );
14983
15512
  if (result.blockedByLabel) {
14984
- console.log(import_chalk25.default.gray(` Reason: ${result.blockedByLabel}`));
15513
+ console.log(import_chalk26.default.gray(` Reason: ${result.blockedByLabel}`));
14985
15514
  }
14986
15515
  }
14987
15516
  console.log("");
@@ -14996,7 +15525,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
14996
15525
  try {
14997
15526
  await startTail2(options);
14998
15527
  } catch (err2) {
14999
- console.error(import_chalk25.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
15528
+ console.error(import_chalk26.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
15000
15529
  process.exit(1);
15001
15530
  }
15002
15531
  });
@@ -15029,14 +15558,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
15029
15558
  Run "node9 addto claude" to register it as the statusLine.`
15030
15559
  ).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
15031
15560
  if (subcommand === "debug") {
15032
- const flagFile = import_path40.default.join(import_os33.default.homedir(), ".node9", "hud-debug");
15561
+ const flagFile = import_path42.default.join(import_os35.default.homedir(), ".node9", "hud-debug");
15033
15562
  if (state === "on") {
15034
- import_fs37.default.mkdirSync(import_path40.default.dirname(flagFile), { recursive: true });
15035
- import_fs37.default.writeFileSync(flagFile, "");
15563
+ import_fs39.default.mkdirSync(import_path42.default.dirname(flagFile), { recursive: true });
15564
+ import_fs39.default.writeFileSync(flagFile, "");
15036
15565
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
15037
15566
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
15038
15567
  } else if (state === "off") {
15039
- if (import_fs37.default.existsSync(flagFile)) import_fs37.default.unlinkSync(flagFile);
15568
+ if (import_fs39.default.existsSync(flagFile)) import_fs39.default.unlinkSync(flagFile);
15040
15569
  console.log("HUD debug logging disabled.");
15041
15570
  } else {
15042
15571
  console.error("Usage: node9 hud debug on|off");
@@ -15051,7 +15580,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
15051
15580
  const ms = parseDuration(options.duration);
15052
15581
  if (ms === null) {
15053
15582
  console.error(
15054
- import_chalk25.default.red(`
15583
+ import_chalk26.default.red(`
15055
15584
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
15056
15585
  `)
15057
15586
  );
@@ -15059,20 +15588,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
15059
15588
  }
15060
15589
  pauseNode9(ms, options.duration);
15061
15590
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
15062
- console.log(import_chalk25.default.yellow(`
15591
+ console.log(import_chalk26.default.yellow(`
15063
15592
  \u23F8 Node9 paused until ${expiresAt}`));
15064
- console.log(import_chalk25.default.gray(` All tool calls will be allowed without review.`));
15065
- console.log(import_chalk25.default.gray(` Run "node9 resume" to re-enable early.
15593
+ console.log(import_chalk26.default.gray(` All tool calls will be allowed without review.`));
15594
+ console.log(import_chalk26.default.gray(` Run "node9 resume" to re-enable early.
15066
15595
  `));
15067
15596
  });
15068
15597
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
15069
15598
  const { paused } = checkPause();
15070
15599
  if (!paused) {
15071
- console.log(import_chalk25.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
15600
+ console.log(import_chalk26.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
15072
15601
  return;
15073
15602
  }
15074
15603
  resumeNode9();
15075
- console.log(import_chalk25.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
15604
+ console.log(import_chalk26.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
15076
15605
  });
15077
15606
  var HOOK_BASED_AGENTS = {
15078
15607
  claude: "claude",
@@ -15085,15 +15614,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
15085
15614
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
15086
15615
  const target = HOOK_BASED_AGENTS[firstArg2];
15087
15616
  console.error(
15088
- import_chalk25.default.yellow(`
15617
+ import_chalk26.default.yellow(`
15089
15618
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
15090
15619
  );
15091
- console.error(import_chalk25.default.white(`
15620
+ console.error(import_chalk26.default.white(`
15092
15621
  "${target}" uses its own hook system. Use:`));
15093
15622
  console.error(
15094
- import_chalk25.default.green(` node9 addto ${target} `) + import_chalk25.default.gray("# one-time setup")
15623
+ import_chalk26.default.green(` node9 addto ${target} `) + import_chalk26.default.gray("# one-time setup")
15095
15624
  );
15096
- console.error(import_chalk25.default.green(` ${target} `) + import_chalk25.default.gray("# run normally"));
15625
+ console.error(import_chalk26.default.green(` ${target} `) + import_chalk26.default.gray("# run normally"));
15097
15626
  process.exit(1);
15098
15627
  }
15099
15628
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -15110,7 +15639,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
15110
15639
  }
15111
15640
  );
15112
15641
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
15113
- console.error(import_chalk25.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
15642
+ console.error(import_chalk26.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
15114
15643
  const daemonReady = await autoStartDaemonAndWait();
15115
15644
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
15116
15645
  }
@@ -15123,12 +15652,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
15123
15652
  }
15124
15653
  if (!result.approved) {
15125
15654
  console.error(
15126
- import_chalk25.default.red(`
15655
+ import_chalk26.default.red(`
15127
15656
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
15128
15657
  );
15129
15658
  process.exit(1);
15130
15659
  }
15131
- console.error(import_chalk25.default.green("\n\u2705 Approved \u2014 running command...\n"));
15660
+ console.error(import_chalk26.default.green("\n\u2705 Approved \u2014 running command...\n"));
15132
15661
  await runProxy(fullCommand);
15133
15662
  } else {
15134
15663
  program.help();
@@ -15142,14 +15671,15 @@ registerSyncCommand(program);
15142
15671
  registerAgentsCommand(program);
15143
15672
  registerScanCommand(program);
15144
15673
  registerSessionsCommand(program);
15674
+ registerDlpCommand(program);
15145
15675
  if (process.argv[2] !== "daemon") {
15146
15676
  process.on("unhandledRejection", (reason) => {
15147
15677
  const isCheckHook = process.argv[2] === "check";
15148
15678
  if (isCheckHook) {
15149
15679
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
15150
- const logPath = import_path40.default.join(import_os33.default.homedir(), ".node9", "hook-debug.log");
15680
+ const logPath = import_path42.default.join(import_os35.default.homedir(), ".node9", "hook-debug.log");
15151
15681
  const msg = reason instanceof Error ? reason.message : String(reason);
15152
- import_fs37.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
15682
+ import_fs39.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
15153
15683
  `);
15154
15684
  }
15155
15685
  process.exit(0);