@node9/proxy 1.5.3 → 1.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -139,8 +139,8 @@ function sanitizeConfig(raw) {
139
139
  }
140
140
  }
141
141
  const lines = result.error.issues.map((issue) => {
142
- const path28 = issue.path.length > 0 ? issue.path.join(".") : "root";
143
- return ` \u2022 ${path28}: ${issue.message}`;
142
+ const path30 = issue.path.length > 0 ? issue.path.join(".") : "root";
143
+ return ` \u2022 ${path30}: ${issue.message}`;
144
144
  });
145
145
  return {
146
146
  sanitized,
@@ -841,7 +841,9 @@ var init_config = __esm({
841
841
  {
842
842
  field: "command",
843
843
  op: "matches",
844
- value: "rm\\b.*(-[rRfF]*[rR][rRfF]*|--recursive)"
844
+ // Require the recursive flag to be preceded by whitespace so that
845
+ // filenames containing "-r" (e.g. "ai-review.yml") don't false-positive.
846
+ value: "rm\\b.*\\s(-[rRfF]*[rR][rRfF]*|--recursive)(\\s|$)"
845
847
  },
846
848
  {
847
849
  field: "command",
@@ -1755,9 +1757,9 @@ function matchesPattern(text, patterns) {
1755
1757
  const withoutDotSlash = text.replace(/^\.\//, "");
1756
1758
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1757
1759
  }
1758
- function getNestedValue(obj, path28) {
1760
+ function getNestedValue(obj, path30) {
1759
1761
  if (!obj || typeof obj !== "object") return null;
1760
- return path28.split(".").reduce((prev, curr) => prev?.[curr], obj);
1762
+ return path30.split(".").reduce((prev, curr) => prev?.[curr], obj);
1761
1763
  }
1762
1764
  function shouldSnapshot(toolName, args, config) {
1763
1765
  if (!config.settings.enableUndo) return false;
@@ -2975,6 +2977,7 @@ var init_native = __esm({
2975
2977
  // src/auth/cloud.ts
2976
2978
  import fs10 from "fs";
2977
2979
  import os9 from "os";
2980
+ import path13 from "path";
2978
2981
  function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2979
2982
  return fetch(`${creds.apiUrl}/audit`, {
2980
2983
  method: "POST",
@@ -2999,6 +3002,33 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2999
3002
  async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
3000
3003
  const controller = new AbortController();
3001
3004
  const timeout = setTimeout(() => controller.abort(), 1e4);
3005
+ if (!creds.apiKey) throw new Error("Node9 API Key is missing");
3006
+ let ciContext;
3007
+ if (process.env.CI) {
3008
+ try {
3009
+ const ciContextPath = path13.join(os9.homedir(), ".node9", "ci-context.json");
3010
+ const stats = fs10.statSync(ciContextPath);
3011
+ if (stats.size > 1e4) throw new Error("ci-context.json exceeds 10 KB");
3012
+ const raw = fs10.readFileSync(ciContextPath, "utf8");
3013
+ const parsed = JSON.parse(raw);
3014
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
3015
+ throw new Error("ci-context.json is not a plain object");
3016
+ }
3017
+ const p = parsed;
3018
+ ciContext = {
3019
+ tests_after: p["tests_after"],
3020
+ files_changed: p["files_changed"],
3021
+ issues_found: p["issues_found"],
3022
+ issues_fixed: p["issues_fixed"],
3023
+ github_repository: p["github_repository"],
3024
+ github_head_ref: p["github_head_ref"],
3025
+ iteration: p["iteration"],
3026
+ draft_pr_number: p["draft_pr_number"],
3027
+ draft_pr_url: p["draft_pr_url"]
3028
+ };
3029
+ } catch {
3030
+ }
3031
+ }
3002
3032
  try {
3003
3033
  const response = await fetch(creds.apiUrl, {
3004
3034
  method: "POST",
@@ -3013,7 +3043,8 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
3013
3043
  cwd: process.cwd(),
3014
3044
  platform: os9.platform()
3015
3045
  },
3016
- ...riskMetadata && { riskMetadata }
3046
+ ...riskMetadata && { riskMetadata },
3047
+ ...ciContext && { ciContext }
3017
3048
  }),
3018
3049
  signal: controller.signal
3019
3050
  });
@@ -3039,12 +3070,17 @@ async function pollNode9SaaS(requestId, creds, signal) {
3039
3070
  });
3040
3071
  clearTimeout(pollTimer);
3041
3072
  if (!statusRes.ok) continue;
3042
- const { status, reason } = await statusRes.json();
3073
+ const statusBody = await statusRes.json();
3074
+ const { status } = statusBody;
3043
3075
  if (status === "APPROVED") {
3044
- return { approved: true, reason };
3076
+ return { approved: true, reason: statusBody.reason };
3045
3077
  }
3046
3078
  if (status === "DENIED" || status === "AUTO_BLOCKED" || status === "TIMED_OUT") {
3047
- return { approved: false, reason };
3079
+ return { approved: false, reason: statusBody.reason };
3080
+ }
3081
+ if (status === "FIX") {
3082
+ const feedbackText = statusBody.feedbackText ?? statusBody.reason ?? "Run again with feedback.";
3083
+ return { approved: false, reason: feedbackText };
3048
3084
  }
3049
3085
  } catch {
3050
3086
  }
@@ -3276,7 +3312,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3276
3312
  const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
3277
3313
  if (policyResult.decision === "allow") {
3278
3314
  if (approvers.cloud && creds?.apiKey)
3279
- auditLocalAllow(toolName, args, "local-policy", creds, meta);
3315
+ await auditLocalAllow(toolName, args, "local-policy", creds, meta);
3280
3316
  if (!isManual) appendLocalAudit(toolName, args, "allow", "local-policy", meta, hashAuditArgs);
3281
3317
  return { approved: true, checkedBy: "local-policy" };
3282
3318
  }
@@ -3297,9 +3333,16 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3297
3333
  if (predicatesMet && policyResult.recoveryCommand) {
3298
3334
  statefulRecoveryCommand = policyResult.recoveryCommand;
3299
3335
  }
3336
+ } else if (isDaemonRunning() && !isTestEnv2) {
3337
+ if (!isManual)
3338
+ appendLocalAudit(toolName, args, "deny", "smart-rule-block", meta, hashAuditArgs);
3339
+ if (approvers.cloud && creds?.apiKey)
3340
+ auditLocalAllow(toolName, args, "smart-rule-block", creds, meta);
3300
3341
  } else {
3301
3342
  if (!isManual)
3302
3343
  appendLocalAudit(toolName, args, "deny", "smart-rule-block", meta, hashAuditArgs);
3344
+ if (approvers.cloud && creds?.apiKey)
3345
+ auditLocalAllow(toolName, args, "smart-rule-block", creds, meta);
3303
3346
  return {
3304
3347
  approved: false,
3305
3348
  reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
@@ -5322,7 +5365,7 @@ var init_suggestion_tracker = __esm({
5322
5365
 
5323
5366
  // src/daemon/taint-store.ts
5324
5367
  import fs12 from "fs";
5325
- import path14 from "path";
5368
+ import path15 from "path";
5326
5369
  var DEFAULT_TTL_MS, TaintStore;
5327
5370
  var init_taint_store = __esm({
5328
5371
  "src/daemon/taint-store.ts"() {
@@ -5391,9 +5434,9 @@ var init_taint_store = __esm({
5391
5434
  /** Resolve to absolute path, falling back to path.resolve if file doesn't exist yet. */
5392
5435
  _resolve(filePath) {
5393
5436
  try {
5394
- return fs12.realpathSync.native(path14.resolve(filePath));
5437
+ return fs12.realpathSync.native(path15.resolve(filePath));
5395
5438
  } catch {
5396
- return path14.resolve(filePath);
5439
+ return path15.resolve(filePath);
5397
5440
  }
5398
5441
  }
5399
5442
  };
@@ -5504,7 +5547,7 @@ var init_session_history = __esm({
5504
5547
  // src/daemon/state.ts
5505
5548
  import net2 from "net";
5506
5549
  import fs13 from "fs";
5507
- import path15 from "path";
5550
+ import path16 from "path";
5508
5551
  import os11 from "os";
5509
5552
  import { spawn as spawn2 } from "child_process";
5510
5553
  import { randomUUID as randomUUID3 } from "crypto";
@@ -5550,7 +5593,7 @@ function markRejectionHandlerRegistered() {
5550
5593
  daemonRejectionHandlerRegistered = true;
5551
5594
  }
5552
5595
  function atomicWriteSync2(filePath, data, options) {
5553
- const dir = path15.dirname(filePath);
5596
+ const dir = path16.dirname(filePath);
5554
5597
  if (!fs13.existsSync(dir)) fs13.mkdirSync(dir, { recursive: true });
5555
5598
  const tmpPath = `${filePath}.${randomUUID3()}.tmp`;
5556
5599
  try {
@@ -5590,7 +5633,7 @@ function appendAuditLog(data) {
5590
5633
  decision: data.decision,
5591
5634
  source: "daemon"
5592
5635
  };
5593
- const dir = path15.dirname(AUDIT_LOG_FILE);
5636
+ const dir = path16.dirname(AUDIT_LOG_FILE);
5594
5637
  if (!fs13.existsSync(dir)) fs13.mkdirSync(dir, { recursive: true });
5595
5638
  fs13.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
5596
5639
  } catch {
@@ -5668,6 +5711,7 @@ function readBody(req) {
5668
5711
  });
5669
5712
  }
5670
5713
  function openBrowser(url) {
5714
+ if (process.env.NODE9_TESTING === "1") return;
5671
5715
  try {
5672
5716
  const args = process.platform === "darwin" ? ["open", url] : process.platform === "win32" ? ["cmd", "/c", "start", "", url] : ["xdg-open", url];
5673
5717
  spawn2(args[0], args.slice(1), { detached: true, stdio: "ignore" }).unref();
@@ -5804,13 +5848,13 @@ var init_state2 = __esm({
5804
5848
  init_session_counters();
5805
5849
  init_session_history();
5806
5850
  homeDir = os11.homedir();
5807
- DAEMON_PID_FILE = path15.join(homeDir, ".node9", "daemon.pid");
5808
- DECISIONS_FILE = path15.join(homeDir, ".node9", "decisions.json");
5809
- AUDIT_LOG_FILE = path15.join(homeDir, ".node9", "audit.log");
5810
- TRUST_FILE2 = path15.join(homeDir, ".node9", "trust.json");
5811
- GLOBAL_CONFIG_FILE = path15.join(homeDir, ".node9", "config.json");
5812
- CREDENTIALS_FILE = path15.join(homeDir, ".node9", "credentials.json");
5813
- INSIGHT_COUNTS_FILE = path15.join(homeDir, ".node9", "insight-counts.json");
5851
+ DAEMON_PID_FILE = path16.join(homeDir, ".node9", "daemon.pid");
5852
+ DECISIONS_FILE = path16.join(homeDir, ".node9", "decisions.json");
5853
+ AUDIT_LOG_FILE = path16.join(homeDir, ".node9", "audit.log");
5854
+ TRUST_FILE2 = path16.join(homeDir, ".node9", "trust.json");
5855
+ GLOBAL_CONFIG_FILE = path16.join(homeDir, ".node9", "config.json");
5856
+ CREDENTIALS_FILE = path16.join(homeDir, ".node9", "credentials.json");
5857
+ INSIGHT_COUNTS_FILE = path16.join(homeDir, ".node9", "insight-counts.json");
5814
5858
  pending = /* @__PURE__ */ new Map();
5815
5859
  sseClients = /* @__PURE__ */ new Set();
5816
5860
  suggestionTracker = new SuggestionTracker(3);
@@ -5828,7 +5872,7 @@ var init_state2 = __esm({
5828
5872
  "2h": 2 * 60 * 6e4
5829
5873
  };
5830
5874
  autoStarted = process.env.NODE9_AUTO_STARTED === "1";
5831
- ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path15.join(os11.tmpdir(), "node9-activity.sock");
5875
+ ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path16.join(os11.tmpdir(), "node9-activity.sock");
5832
5876
  ACTIVITY_RING_SIZE = 100;
5833
5877
  activityRing = [];
5834
5878
  SECRET_KEY_RE = /password|secret|token|key|apikey|credential|auth/i;
@@ -5848,7 +5892,7 @@ var init_state2 = __esm({
5848
5892
 
5849
5893
  // src/config/patch.ts
5850
5894
  import fs14 from "fs";
5851
- import path16 from "path";
5895
+ import path17 from "path";
5852
5896
  import os12 from "os";
5853
5897
  function patchConfig(configPath, patch) {
5854
5898
  let config = {};
@@ -5873,7 +5917,7 @@ function patchConfig(configPath, patch) {
5873
5917
  ignored.push(patch.toolName);
5874
5918
  }
5875
5919
  }
5876
- const dir = path16.dirname(configPath);
5920
+ const dir = path17.dirname(configPath);
5877
5921
  fs14.mkdirSync(dir, { recursive: true });
5878
5922
  const tmp = configPath + ".node9-tmp";
5879
5923
  try {
@@ -5899,14 +5943,14 @@ var GLOBAL_CONFIG_PATH;
5899
5943
  var init_patch = __esm({
5900
5944
  "src/config/patch.ts"() {
5901
5945
  "use strict";
5902
- GLOBAL_CONFIG_PATH = path16.join(os12.homedir(), ".node9", "config.json");
5946
+ GLOBAL_CONFIG_PATH = path17.join(os12.homedir(), ".node9", "config.json");
5903
5947
  }
5904
5948
  });
5905
5949
 
5906
5950
  // src/daemon/server.ts
5907
5951
  import http from "http";
5908
5952
  import fs15 from "fs";
5909
- import path17 from "path";
5953
+ import path18 from "path";
5910
5954
  import { randomUUID as randomUUID4 } from "crypto";
5911
5955
  import { spawnSync as spawnSync2 } from "child_process";
5912
5956
  import chalk2 from "chalk";
@@ -6088,7 +6132,7 @@ data: ${JSON.stringify(item.data)}
6088
6132
  status: "pending"
6089
6133
  });
6090
6134
  }
6091
- const projectCwd = typeof cwd === "string" && path17.isAbsolute(cwd) ? cwd : void 0;
6135
+ const projectCwd = typeof cwd === "string" && path18.isAbsolute(cwd) ? cwd : void 0;
6092
6136
  const projectConfig = getConfig(projectCwd);
6093
6137
  const browserEnabled = projectConfig.settings.approvers?.browser !== false;
6094
6138
  const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
@@ -6476,8 +6520,8 @@ data: ${JSON.stringify(item.data)}
6476
6520
  const body = await readBody(req);
6477
6521
  const data = body ? JSON.parse(body) : {};
6478
6522
  const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
6479
- const node9Dir = path17.dirname(GLOBAL_CONFIG_PATH);
6480
- if (!path17.resolve(configPath).startsWith(node9Dir + path17.sep)) {
6523
+ const node9Dir = path18.dirname(GLOBAL_CONFIG_PATH);
6524
+ if (!path18.resolve(configPath).startsWith(node9Dir + path18.sep)) {
6481
6525
  res.writeHead(400, { "Content-Type": "application/json" });
6482
6526
  return res.end(
6483
6527
  JSON.stringify({ error: "configPath must be within the node9 config directory" })
@@ -6722,11 +6766,11 @@ __export(tail_exports, {
6722
6766
  startTail: () => startTail
6723
6767
  });
6724
6768
  import http2 from "http";
6725
- import chalk16 from "chalk";
6769
+ import chalk17 from "chalk";
6726
6770
  import fs24 from "fs";
6727
6771
  import os20 from "os";
6728
- import path25 from "path";
6729
- import readline3 from "readline";
6772
+ import path27 from "path";
6773
+ import readline4 from "readline";
6730
6774
  import { spawn as spawn9, execSync as execSync3 } from "child_process";
6731
6775
  function getIcon(tool) {
6732
6776
  const t = tool.toLowerCase();
@@ -6741,27 +6785,27 @@ function formatBase(activity) {
6741
6785
  const toolName = activity.tool.slice(0, 16).padEnd(16);
6742
6786
  const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ");
6743
6787
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
6744
- return `${chalk16.gray(time)} ${icon} ${chalk16.white.bold(toolName)} ${chalk16.dim(argsPreview)}`;
6788
+ return `${chalk17.gray(time)} ${icon} ${chalk17.white.bold(toolName)} ${chalk17.dim(argsPreview)}`;
6745
6789
  }
6746
6790
  function renderResult(activity, result) {
6747
6791
  const base = formatBase(activity);
6748
6792
  let status;
6749
6793
  if (result.status === "allow") {
6750
- status = chalk16.green("\u2713 ALLOW");
6794
+ status = chalk17.green("\u2713 ALLOW");
6751
6795
  } else if (result.status === "dlp") {
6752
- status = chalk16.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
6796
+ status = chalk17.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
6753
6797
  } else {
6754
- status = chalk16.red("\u2717 BLOCK");
6798
+ status = chalk17.red("\u2717 BLOCK");
6755
6799
  }
6756
6800
  if (process.stdout.isTTY) {
6757
- readline3.clearLine(process.stdout, 0);
6758
- readline3.cursorTo(process.stdout, 0);
6801
+ readline4.clearLine(process.stdout, 0);
6802
+ readline4.cursorTo(process.stdout, 0);
6759
6803
  }
6760
6804
  console.log(`${base} ${status}`);
6761
6805
  }
6762
6806
  function renderPending(activity) {
6763
6807
  if (!process.stdout.isTTY) return;
6764
- process.stdout.write(`${formatBase(activity)} ${chalk16.yellow("\u25CF \u2026")}\r`);
6808
+ process.stdout.write(`${formatBase(activity)} ${chalk17.yellow("\u25CF \u2026")}\r`);
6765
6809
  }
6766
6810
  async function ensureDaemon() {
6767
6811
  let pidPort = null;
@@ -6770,7 +6814,7 @@ async function ensureDaemon() {
6770
6814
  const { port } = JSON.parse(fs24.readFileSync(PID_FILE, "utf-8"));
6771
6815
  pidPort = port;
6772
6816
  } catch {
6773
- console.error(chalk16.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
6817
+ console.error(chalk17.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
6774
6818
  }
6775
6819
  }
6776
6820
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -6781,7 +6825,7 @@ async function ensureDaemon() {
6781
6825
  if (res.ok) return checkPort;
6782
6826
  } catch {
6783
6827
  }
6784
- console.log(chalk16.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
6828
+ console.log(chalk17.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
6785
6829
  const child = spawn9(process.execPath, [process.argv[1], "daemon"], {
6786
6830
  detached: true,
6787
6831
  stdio: "ignore",
@@ -6798,7 +6842,7 @@ async function ensureDaemon() {
6798
6842
  } catch {
6799
6843
  }
6800
6844
  }
6801
- console.error(chalk16.red("\u274C Daemon failed to start. Try: node9 daemon start"));
6845
+ console.error(chalk17.red("\u274C Daemon failed to start. Try: node9 daemon start"));
6802
6846
  process.exit(1);
6803
6847
  }
6804
6848
  function postDecisionHttp(id, decision, csrfToken, port, opts) {
@@ -6840,23 +6884,23 @@ function buildCardLines(req, localCount = 0) {
6840
6884
  const blockedBy = req.riskMetadata?.blockedByLabel ?? "Policy rule";
6841
6885
  const lines = [
6842
6886
  ``,
6843
- `${BOLD}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET}`,
6844
- `${CYAN}\u2551${RESET} Tool: ${BOLD}${req.toolName}${RESET}`,
6845
- `${CYAN}\u2551${RESET} Reason: ${tierLabel} \u2014 ${blockedBy}${RESET}`
6887
+ `${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
6888
+ `${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}`,
6889
+ `${CYAN}\u2551${RESET2} Reason: ${tierLabel} \u2014 ${blockedBy}${RESET2}`
6846
6890
  ];
6847
6891
  if (req.riskMetadata?.ruleName && blockedBy.includes("Taint")) {
6848
- lines.push(`${CYAN}\u2551${RESET} ${YELLOW}\u26A0 ${req.riskMetadata.ruleName}${RESET}`);
6892
+ lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u26A0 ${req.riskMetadata.ruleName}${RESET2}`);
6849
6893
  }
6850
- lines.push(`${CYAN}\u2551${RESET} Args: ${GRAY}${argsPreview}${RESET}`);
6894
+ lines.push(`${CYAN}\u2551${RESET2} Args: ${GRAY}${argsPreview}${RESET2}`);
6851
6895
  if (localCount >= 2) {
6852
6896
  lines.push(
6853
- `${CYAN}\u2551${RESET} ${YELLOW}\u{1F4A1}${RESET} Approved ${localCount}\xD7 before \u2014 ${BOLD}[a]${RESET}${YELLOW} creates a permanent rule${RESET}`
6897
+ `${CYAN}\u2551${RESET2} ${YELLOW}\u{1F4A1}${RESET2} Approved ${localCount}\xD7 before \u2014 ${BOLD2}[a]${RESET2}${YELLOW} creates a permanent rule${RESET2}`
6854
6898
  );
6855
6899
  }
6856
6900
  lines.push(
6857
- `${CYAN}\u255A${RESET}`,
6901
+ `${CYAN}\u255A${RESET2}`,
6858
6902
  ``,
6859
- ` ${BOLD}${GREEN}[\u21B5/y]${RESET} Allow ${BOLD}${RED}[n]${RESET} Deny ${BOLD}${YELLOW}[a]${RESET} Always Allow ${BOLD}${CYAN}[t]${RESET} Trust 30m`,
6903
+ ` ${BOLD2}${GREEN}[\u21B5/y]${RESET2} Allow ${BOLD2}${RED}[n]${RESET2} Deny ${BOLD2}${YELLOW}[a]${RESET2} Always Allow ${BOLD2}${CYAN}[t]${RESET2} Trust 30m`,
6860
6904
  ``
6861
6905
  );
6862
6906
  return lines;
@@ -6866,23 +6910,23 @@ function buildRecoveryCardLines(req) {
6866
6910
  const command = typeof argsObj?.command === "string" ? argsObj.command : JSON.stringify(req.args ?? {}).replace(/\s+/g, " ").slice(0, 60);
6867
6911
  const ruleName = req.riskMetadata?.ruleName?.replace(/^Smart Rule:\s*/i, "") ?? "policy rule";
6868
6912
  const recoveryCommand = req.recoveryCommand;
6869
- const interactiveLines = req.viewOnly ? [` ${GRAY}\u2192 Awaiting decision from interactive terminal...${RESET}`] : [
6870
- ` ${BOLD}${GREEN}[1]${RESET} Allow anyway ${GRAY}(override policy)${RESET}`,
6871
- ` ${BOLD}${YELLOW}[2]${RESET} Redirect AI: "Run '${recoveryCommand}' first, then retry"`,
6872
- ` ${BOLD}${RED}[3]${RESET} Deny & stop ${GRAY}(hard block)${RESET}`,
6913
+ const interactiveLines = req.viewOnly ? [` ${GRAY}\u2192 Awaiting decision from interactive terminal...${RESET2}`] : [
6914
+ ` ${BOLD2}${GREEN}[1]${RESET2} Allow anyway ${GRAY}(override policy)${RESET2}`,
6915
+ ` ${BOLD2}${YELLOW}[2]${RESET2} Redirect AI: "Run '${recoveryCommand}' first, then retry"`,
6916
+ ` ${BOLD2}${RED}[3]${RESET2} Deny & stop ${GRAY}(hard block)${RESET2}`,
6873
6917
  ``,
6874
- ` ${GRAY}[Timeout: auto-deny]${RESET}`,
6918
+ ` ${GRAY}[Timeout: auto-deny]${RESET2}`,
6875
6919
  ` Select [1-3]: `
6876
6920
  ];
6877
6921
  return [
6878
6922
  ``,
6879
- `${BOLD}${CYAN}${DIVIDER}${RESET}`,
6880
- `\u{1F6E1}\uFE0F ${BOLD}NODE9 STATE GUARD:${RESET} '${BOLD}${command}${RESET}'`,
6881
- `${YELLOW}\u26A0\uFE0F Rule: ${ruleName}${RESET}`,
6882
- `${CYAN}${DIVIDER}${RESET}`,
6883
- ...!req.viewOnly ? [`${BOLD}What would you like to do?${RESET}`, ``] : [],
6923
+ `${BOLD2}${CYAN}${DIVIDER}${RESET2}`,
6924
+ `\u{1F6E1}\uFE0F ${BOLD2}NODE9 STATE GUARD:${RESET2} '${BOLD2}${command}${RESET2}'`,
6925
+ `${YELLOW}\u26A0\uFE0F Rule: ${ruleName}${RESET2}`,
6926
+ `${CYAN}${DIVIDER}${RESET2}`,
6927
+ ...!req.viewOnly ? [`${BOLD2}What would you like to do?${RESET2}`, ``] : [],
6884
6928
  ...interactiveLines,
6885
- `${CYAN}${DIVIDER}${RESET}`,
6929
+ `${CYAN}${DIVIDER}${RESET2}`,
6886
6930
  ``
6887
6931
  ];
6888
6932
  }
@@ -6912,7 +6956,7 @@ async function startTail(options = {}) {
6912
6956
  req2.end();
6913
6957
  });
6914
6958
  if (result.ok) {
6915
- console.log(chalk16.green("\u2713 Flight Recorder buffer cleared."));
6959
+ console.log(chalk17.green("\u2713 Flight Recorder buffer cleared."));
6916
6960
  } else if (result.code === "ECONNREFUSED") {
6917
6961
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
6918
6962
  } else if (result.code === "ETIMEDOUT") {
@@ -6931,10 +6975,10 @@ async function startTail(options = {}) {
6931
6975
  let cancelActiveCard = null;
6932
6976
  const localAllowCounts = /* @__PURE__ */ new Map();
6933
6977
  const canApprove = process.stdout.isTTY && process.stdin.isTTY;
6934
- if (canApprove) readline3.emitKeypressEvents(process.stdin);
6978
+ if (canApprove) readline4.emitKeypressEvents(process.stdin);
6935
6979
  function clearCard() {
6936
6980
  if (cardLineCount > 0) {
6937
- readline3.moveCursor(process.stdout, 0, -cardLineCount);
6981
+ readline4.moveCursor(process.stdout, 0, -cardLineCount);
6938
6982
  process.stdout.write(ERASE_DOWN);
6939
6983
  cardLineCount = 0;
6940
6984
  }
@@ -6984,8 +7028,8 @@ async function startTail(options = {}) {
6984
7028
  localAllowCounts.get(req2.toolName) ?? 0
6985
7029
  )
6986
7030
  );
6987
- const decisionStamp = action === "always-allow" ? chalk16.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? chalk16.cyan("\u23F1 TRUST 30m") : action === "allow" ? chalk16.green("\u2713 ALLOWED") : action === "redirect" ? chalk16.yellow("\u21A9 REDIRECT AI") : chalk16.red("\u2717 DENIED");
6988
- stampedLines.push(` ${BOLD}\u2192${RESET} ${decisionStamp} ${GRAY}(terminal)${RESET}`, ``);
7031
+ const decisionStamp = action === "always-allow" ? chalk17.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? chalk17.cyan("\u23F1 TRUST 30m") : action === "allow" ? chalk17.green("\u2713 ALLOWED") : action === "redirect" ? chalk17.yellow("\u21A9 REDIRECT AI") : chalk17.red("\u2717 DENIED");
7032
+ stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
6989
7033
  for (const line of stampedLines) process.stdout.write(line + "\n");
6990
7034
  process.stdout.write(SHOW_CURSOR);
6991
7035
  cardLineCount = 0;
@@ -7013,7 +7057,7 @@ async function startTail(options = {}) {
7013
7057
  postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err) => {
7014
7058
  try {
7015
7059
  fs24.appendFileSync(
7016
- path25.join(os20.homedir(), ".node9", "hook-debug.log"),
7060
+ path27.join(os20.homedir(), ".node9", "hook-debug.log"),
7017
7061
  `[tail] POST /decision failed: ${String(err)}
7018
7062
  `
7019
7063
  );
@@ -7035,8 +7079,8 @@ async function startTail(options = {}) {
7035
7079
  );
7036
7080
  const stampedLines = buildCardLines(req2, priorCount);
7037
7081
  if (externalDecision) {
7038
- const source = externalDecision === "allow" ? chalk16.green("\u2713 ALLOWED") : chalk16.red("\u2717 DENIED");
7039
- stampedLines.push(` ${BOLD}\u2192${RESET} ${source} ${GRAY}(external)${RESET}`, ``);
7082
+ const source = externalDecision === "allow" ? chalk17.green("\u2713 ALLOWED") : chalk17.red("\u2717 DENIED");
7083
+ stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
7040
7084
  }
7041
7085
  for (const line of stampedLines) process.stdout.write(line + "\n");
7042
7086
  process.stdout.write(SHOW_CURSOR);
@@ -7094,41 +7138,41 @@ async function startTail(options = {}) {
7094
7138
  }
7095
7139
  } catch {
7096
7140
  }
7097
- console.log(chalk16.cyan.bold(`
7098
- \u{1F6F0}\uFE0F Node9 tail `) + chalk16.dim(`\u2192 ${dashboardUrl}`));
7141
+ console.log(chalk17.cyan.bold(`
7142
+ \u{1F6F0}\uFE0F Node9 tail `) + chalk17.dim(`\u2192 ${dashboardUrl}`));
7099
7143
  if (canApprove) {
7100
7144
  console.log(
7101
- chalk16.dim("Interactive approvals: [\u21B5/y] Allow [n] Deny [a] Always Allow [t] Trust 30m")
7145
+ chalk17.dim("Interactive approvals: [\u21B5/y] Allow [n] Deny [a] Always Allow [t] Trust 30m")
7102
7146
  );
7103
7147
  }
7104
7148
  if (options.history) {
7105
- console.log(chalk16.dim("Showing history + live events. Press Ctrl+C to exit.\n"));
7149
+ console.log(chalk17.dim("Showing history + live events. Press Ctrl+C to exit.\n"));
7106
7150
  } else {
7107
7151
  console.log(
7108
- chalk16.dim("Showing live events only. Use --history to include past. Press Ctrl+C to exit.\n")
7152
+ chalk17.dim("Showing live events only. Use --history to include past. Press Ctrl+C to exit.\n")
7109
7153
  );
7110
7154
  }
7111
7155
  process.on("SIGINT", () => {
7112
7156
  clearCard();
7113
7157
  process.stdout.write(SHOW_CURSOR);
7114
7158
  if (process.stdout.isTTY) {
7115
- readline3.clearLine(process.stdout, 0);
7116
- readline3.cursorTo(process.stdout, 0);
7159
+ readline4.clearLine(process.stdout, 0);
7160
+ readline4.cursorTo(process.stdout, 0);
7117
7161
  }
7118
- console.log(chalk16.dim("\n\u{1F6F0}\uFE0F Disconnected."));
7162
+ console.log(chalk17.dim("\n\u{1F6F0}\uFE0F Disconnected."));
7119
7163
  process.exit(0);
7120
7164
  });
7121
7165
  const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
7122
7166
  const req = http2.get(sseUrl, (res) => {
7123
7167
  if (res.statusCode !== 200) {
7124
- console.error(chalk16.red(`Failed to connect: HTTP ${res.statusCode}`));
7168
+ console.error(chalk17.red(`Failed to connect: HTTP ${res.statusCode}`));
7125
7169
  process.exit(1);
7126
7170
  }
7127
7171
  let currentEvent = "";
7128
7172
  let currentData = "";
7129
7173
  res.on("error", () => {
7130
7174
  });
7131
- const rl = readline3.createInterface({ input: res, crlfDelay: Infinity });
7175
+ const rl = readline4.createInterface({ input: res, crlfDelay: Infinity });
7132
7176
  rl.on("error", () => {
7133
7177
  });
7134
7178
  rl.on("line", (line) => {
@@ -7148,10 +7192,10 @@ async function startTail(options = {}) {
7148
7192
  clearCard();
7149
7193
  process.stdout.write(SHOW_CURSOR);
7150
7194
  if (process.stdout.isTTY) {
7151
- readline3.clearLine(process.stdout, 0);
7152
- readline3.cursorTo(process.stdout, 0);
7195
+ readline4.clearLine(process.stdout, 0);
7196
+ readline4.cursorTo(process.stdout, 0);
7153
7197
  }
7154
- console.log(chalk16.red("\n\u274C Daemon disconnected."));
7198
+ console.log(chalk17.red("\n\u274C Daemon disconnected."));
7155
7199
  process.exit(1);
7156
7200
  });
7157
7201
  });
@@ -7237,19 +7281,19 @@ async function startTail(options = {}) {
7237
7281
  }
7238
7282
  req.on("error", (err) => {
7239
7283
  const msg = err.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err.message;
7240
- console.error(chalk16.red(`
7284
+ console.error(chalk17.red(`
7241
7285
  \u274C ${msg}`));
7242
7286
  process.exit(1);
7243
7287
  });
7244
7288
  }
7245
- var PID_FILE, ICONS, RESET, BOLD, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, DIVIDER;
7289
+ var PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, DIVIDER;
7246
7290
  var init_tail = __esm({
7247
7291
  "src/tui/tail.ts"() {
7248
7292
  "use strict";
7249
7293
  init_daemon2();
7250
7294
  init_daemon();
7251
7295
  init_core();
7252
- PID_FILE = path25.join(os20.homedir(), ".node9", "daemon.pid");
7296
+ PID_FILE = path27.join(os20.homedir(), ".node9", "daemon.pid");
7253
7297
  ICONS = {
7254
7298
  bash: "\u{1F4BB}",
7255
7299
  shell: "\u{1F4BB}",
@@ -7267,8 +7311,8 @@ var init_tail = __esm({
7267
7311
  delete: "\u{1F5D1}\uFE0F",
7268
7312
  web: "\u{1F310}"
7269
7313
  };
7270
- RESET = "\x1B[0m";
7271
- BOLD = "\x1B[1m";
7314
+ RESET2 = "\x1B[0m";
7315
+ BOLD2 = "\x1B[1m";
7272
7316
  RED = "\x1B[31m";
7273
7317
  YELLOW = "\x1B[33m";
7274
7318
  CYAN = "\x1B[36m";
@@ -7289,7 +7333,7 @@ __export(hud_exports, {
7289
7333
  renderEnvironmentLine: () => renderEnvironmentLine
7290
7334
  });
7291
7335
  import fs25 from "fs";
7292
- import path26 from "path";
7336
+ import path28 from "path";
7293
7337
  import os21 from "os";
7294
7338
  import http3 from "http";
7295
7339
  async function readStdin() {
@@ -7341,19 +7385,19 @@ function queryDaemon() {
7341
7385
  });
7342
7386
  }
7343
7387
  function dim(s) {
7344
- return `${DIM}${s}${RESET2}`;
7388
+ return `${DIM}${s}${RESET3}`;
7345
7389
  }
7346
7390
  function bold(s) {
7347
- return `${BOLD2}${s}${RESET2}`;
7391
+ return `${BOLD3}${s}${RESET3}`;
7348
7392
  }
7349
7393
  function color(c, s) {
7350
- return `${c}${s}${RESET2}`;
7394
+ return `${c}${s}${RESET3}`;
7351
7395
  }
7352
7396
  function progressBar(pct, warnAt = 70, critAt = 85) {
7353
7397
  const filled = Math.round(Math.min(pct, 100) / 100 * BAR_WIDTH);
7354
7398
  const bar = BAR_FILLED.repeat(filled) + BAR_EMPTY.repeat(BAR_WIDTH - filled);
7355
7399
  const c = pct >= critAt ? RED2 : pct >= warnAt ? YELLOW2 : GREEN2;
7356
- return `${c}${bar}${RESET2}`;
7400
+ return `${c}${bar}${RESET3}`;
7357
7401
  }
7358
7402
  function formatTimeLeft(resetsAt) {
7359
7403
  if (!resetsAt) return "";
@@ -7394,7 +7438,7 @@ function countRulesInDir(rulesDir) {
7394
7438
  try {
7395
7439
  for (const entry of fs25.readdirSync(rulesDir, { withFileTypes: true })) {
7396
7440
  if (entry.isDirectory()) {
7397
- count += countRulesInDir(path26.join(rulesDir, entry.name));
7441
+ count += countRulesInDir(path28.join(rulesDir, entry.name));
7398
7442
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
7399
7443
  count++;
7400
7444
  }
@@ -7405,46 +7449,46 @@ function countRulesInDir(rulesDir) {
7405
7449
  }
7406
7450
  function isSamePath(a, b) {
7407
7451
  try {
7408
- return path26.resolve(a) === path26.resolve(b);
7452
+ return path28.resolve(a) === path28.resolve(b);
7409
7453
  } catch {
7410
7454
  return false;
7411
7455
  }
7412
7456
  }
7413
7457
  function countConfigs(cwd) {
7414
7458
  const homeDir2 = os21.homedir();
7415
- const claudeDir = path26.join(homeDir2, ".claude");
7459
+ const claudeDir = path28.join(homeDir2, ".claude");
7416
7460
  let claudeMdCount = 0;
7417
7461
  let rulesCount = 0;
7418
7462
  let hooksCount = 0;
7419
7463
  const userMcpServers = /* @__PURE__ */ new Set();
7420
7464
  const projectMcpServers = /* @__PURE__ */ new Set();
7421
- if (fs25.existsSync(path26.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
7422
- rulesCount += countRulesInDir(path26.join(claudeDir, "rules"));
7423
- const userSettings = path26.join(claudeDir, "settings.json");
7465
+ if (fs25.existsSync(path28.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
7466
+ rulesCount += countRulesInDir(path28.join(claudeDir, "rules"));
7467
+ const userSettings = path28.join(claudeDir, "settings.json");
7424
7468
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
7425
7469
  hooksCount += countHooksInFile(userSettings);
7426
- const userClaudeJson = path26.join(homeDir2, ".claude.json");
7470
+ const userClaudeJson = path28.join(homeDir2, ".claude.json");
7427
7471
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
7428
7472
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
7429
7473
  userMcpServers.delete(name);
7430
7474
  }
7431
7475
  if (cwd) {
7432
- if (fs25.existsSync(path26.join(cwd, "CLAUDE.md"))) claudeMdCount++;
7433
- if (fs25.existsSync(path26.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
7434
- const projectClaudeDir = path26.join(cwd, ".claude");
7476
+ if (fs25.existsSync(path28.join(cwd, "CLAUDE.md"))) claudeMdCount++;
7477
+ if (fs25.existsSync(path28.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
7478
+ const projectClaudeDir = path28.join(cwd, ".claude");
7435
7479
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
7436
7480
  if (!overlapsUserScope) {
7437
- if (fs25.existsSync(path26.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
7438
- rulesCount += countRulesInDir(path26.join(projectClaudeDir, "rules"));
7439
- const projSettings = path26.join(projectClaudeDir, "settings.json");
7481
+ if (fs25.existsSync(path28.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
7482
+ rulesCount += countRulesInDir(path28.join(projectClaudeDir, "rules"));
7483
+ const projSettings = path28.join(projectClaudeDir, "settings.json");
7440
7484
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
7441
7485
  hooksCount += countHooksInFile(projSettings);
7442
7486
  }
7443
- if (fs25.existsSync(path26.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
7444
- const localSettings = path26.join(projectClaudeDir, "settings.local.json");
7487
+ if (fs25.existsSync(path28.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
7488
+ const localSettings = path28.join(projectClaudeDir, "settings.local.json");
7445
7489
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
7446
7490
  hooksCount += countHooksInFile(localSettings);
7447
- const mcpJsonServers = getMcpServerNames(path26.join(cwd, ".mcp.json"));
7491
+ const mcpJsonServers = getMcpServerNames(path28.join(cwd, ".mcp.json"));
7448
7492
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
7449
7493
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
7450
7494
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -7557,8 +7601,8 @@ async function main() {
7557
7601
  try {
7558
7602
  const cwd = stdin.cwd ?? process.cwd();
7559
7603
  for (const configPath of [
7560
- path26.join(cwd, "node9.config.json"),
7561
- path26.join(os21.homedir(), ".node9", "config.json")
7604
+ path28.join(cwd, "node9.config.json"),
7605
+ path28.join(os21.homedir(), ".node9", "config.json")
7562
7606
  ]) {
7563
7607
  if (!fs25.existsSync(configPath)) continue;
7564
7608
  const cfg = JSON.parse(fs25.readFileSync(configPath, "utf-8"));
@@ -7579,13 +7623,13 @@ async function main() {
7579
7623
  renderOffline();
7580
7624
  }
7581
7625
  }
7582
- var RESET2, BOLD2, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH;
7626
+ var RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH;
7583
7627
  var init_hud = __esm({
7584
7628
  "src/cli/hud.ts"() {
7585
7629
  "use strict";
7586
7630
  init_daemon();
7587
- RESET2 = "\x1B[0m";
7588
- BOLD2 = "\x1B[1m";
7631
+ RESET3 = "\x1B[0m";
7632
+ BOLD3 = "\x1B[1m";
7589
7633
  DIM = "\x1B[2m";
7590
7634
  RED2 = "\x1B[31m";
7591
7635
  GREEN2 = "\x1B[32m";
@@ -7606,7 +7650,7 @@ import { Command } from "commander";
7606
7650
 
7607
7651
  // src/setup.ts
7608
7652
  import fs11 from "fs";
7609
- import path13 from "path";
7653
+ import path14 from "path";
7610
7654
  import os10 from "os";
7611
7655
  import chalk from "chalk";
7612
7656
  import { confirm } from "@inquirer/prompts";
@@ -7632,7 +7676,7 @@ function readJson(filePath) {
7632
7676
  return null;
7633
7677
  }
7634
7678
  function writeJson(filePath, data) {
7635
- const dir = path13.dirname(filePath);
7679
+ const dir = path14.dirname(filePath);
7636
7680
  if (!fs11.existsSync(dir)) fs11.mkdirSync(dir, { recursive: true });
7637
7681
  fs11.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
7638
7682
  }
@@ -7642,8 +7686,8 @@ function isNode9Hook(cmd) {
7642
7686
  }
7643
7687
  function teardownClaude() {
7644
7688
  const homeDir2 = os10.homedir();
7645
- const hooksPath = path13.join(homeDir2, ".claude", "settings.json");
7646
- const mcpPath = path13.join(homeDir2, ".claude.json");
7689
+ const hooksPath = path14.join(homeDir2, ".claude", "settings.json");
7690
+ const mcpPath = path14.join(homeDir2, ".claude.json");
7647
7691
  let changed = false;
7648
7692
  const settings = readJson(hooksPath);
7649
7693
  if (settings?.hooks) {
@@ -7692,7 +7736,7 @@ function teardownClaude() {
7692
7736
  }
7693
7737
  function teardownGemini() {
7694
7738
  const homeDir2 = os10.homedir();
7695
- const settingsPath = path13.join(homeDir2, ".gemini", "settings.json");
7739
+ const settingsPath = path14.join(homeDir2, ".gemini", "settings.json");
7696
7740
  const settings = readJson(settingsPath);
7697
7741
  if (!settings) {
7698
7742
  console.log(chalk.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
@@ -7731,7 +7775,7 @@ function teardownGemini() {
7731
7775
  }
7732
7776
  function teardownCursor() {
7733
7777
  const homeDir2 = os10.homedir();
7734
- const mcpPath = path13.join(homeDir2, ".cursor", "mcp.json");
7778
+ const mcpPath = path14.join(homeDir2, ".cursor", "mcp.json");
7735
7779
  const mcpConfig = readJson(mcpPath);
7736
7780
  if (!mcpConfig?.mcpServers) {
7737
7781
  console.log(chalk.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
@@ -7758,8 +7802,8 @@ function teardownCursor() {
7758
7802
  }
7759
7803
  async function setupClaude() {
7760
7804
  const homeDir2 = os10.homedir();
7761
- const mcpPath = path13.join(homeDir2, ".claude.json");
7762
- const hooksPath = path13.join(homeDir2, ".claude", "settings.json");
7805
+ const mcpPath = path14.join(homeDir2, ".claude.json");
7806
+ const hooksPath = path14.join(homeDir2, ".claude", "settings.json");
7763
7807
  const claudeConfig = readJson(mcpPath) ?? {};
7764
7808
  const settings = readJson(hooksPath) ?? {};
7765
7809
  const servers = claudeConfig.mcpServers ?? {};
@@ -7834,7 +7878,7 @@ async function setupClaude() {
7834
7878
  }
7835
7879
  async function setupGemini() {
7836
7880
  const homeDir2 = os10.homedir();
7837
- const settingsPath = path13.join(homeDir2, ".gemini", "settings.json");
7881
+ const settingsPath = path14.join(homeDir2, ".gemini", "settings.json");
7838
7882
  const settings = readJson(settingsPath) ?? {};
7839
7883
  const servers = settings.mcpServers ?? {};
7840
7884
  let anythingChanged = false;
@@ -7929,14 +7973,14 @@ function detectAgents(homeDir2 = os10.homedir()) {
7929
7973
  }
7930
7974
  };
7931
7975
  return {
7932
- claude: exists(path13.join(homeDir2, ".claude")) || exists(path13.join(homeDir2, ".claude.json")),
7933
- gemini: exists(path13.join(homeDir2, ".gemini")),
7934
- cursor: exists(path13.join(homeDir2, ".cursor"))
7976
+ claude: exists(path14.join(homeDir2, ".claude")) || exists(path14.join(homeDir2, ".claude.json")),
7977
+ gemini: exists(path14.join(homeDir2, ".gemini")),
7978
+ cursor: exists(path14.join(homeDir2, ".cursor"))
7935
7979
  };
7936
7980
  }
7937
7981
  async function setupCursor() {
7938
7982
  const homeDir2 = os10.homedir();
7939
- const mcpPath = path13.join(homeDir2, ".cursor", "mcp.json");
7983
+ const mcpPath = path14.join(homeDir2, ".cursor", "mcp.json");
7940
7984
  const mcpConfig = readJson(mcpPath) ?? {};
7941
7985
  const servers = mcpConfig.mcpServers ?? {};
7942
7986
  let anythingChanged = false;
@@ -7991,7 +8035,7 @@ async function setupCursor() {
7991
8035
  }
7992
8036
  function setupHud() {
7993
8037
  const homeDir2 = os10.homedir();
7994
- const hooksPath = path13.join(homeDir2, ".claude", "settings.json");
8038
+ const hooksPath = path14.join(homeDir2, ".claude", "settings.json");
7995
8039
  const settings = readJson(hooksPath) ?? {};
7996
8040
  const hudCommand = fullPathCommand("hud");
7997
8041
  const statusLineObj = { type: "command", command: hudCommand };
@@ -8018,7 +8062,7 @@ function setupHud() {
8018
8062
  }
8019
8063
  function teardownHud() {
8020
8064
  const homeDir2 = os10.homedir();
8021
- const hooksPath = path13.join(homeDir2, ".claude", "settings.json");
8065
+ const hooksPath = path14.join(homeDir2, ".claude", "settings.json");
8022
8066
  const settings = readJson(hooksPath);
8023
8067
  if (!settings) {
8024
8068
  console.log(chalk.blue(" \u2139\uFE0F ~/.claude/settings.json not found \u2014 nothing to remove"));
@@ -8038,11 +8082,11 @@ function teardownHud() {
8038
8082
 
8039
8083
  // src/cli.ts
8040
8084
  init_daemon2();
8041
- import chalk17 from "chalk";
8085
+ import chalk18 from "chalk";
8042
8086
  import fs26 from "fs";
8043
- import path27 from "path";
8087
+ import path29 from "path";
8044
8088
  import os22 from "os";
8045
- import { confirm as confirm3 } from "@inquirer/prompts";
8089
+ import { confirm as confirm2 } from "@inquirer/prompts";
8046
8090
 
8047
8091
  // src/utils/duration.ts
8048
8092
  function parseDuration(str) {
@@ -8233,6 +8277,7 @@ function openBrowserLocal() {
8233
8277
  }
8234
8278
  }
8235
8279
  async function autoStartDaemonAndWait() {
8280
+ if (process.env.NODE9_TESTING === "1") return false;
8236
8281
  try {
8237
8282
  const child = spawn4(process.execPath, [process.argv[1], "daemon"], {
8238
8283
  detached: true,
@@ -8268,17 +8313,17 @@ init_config();
8268
8313
  init_policy();
8269
8314
  import chalk5 from "chalk";
8270
8315
  import fs18 from "fs";
8271
- import path19 from "path";
8316
+ import path20 from "path";
8272
8317
  import os14 from "os";
8273
8318
 
8274
8319
  // src/undo.ts
8275
8320
  import { spawnSync as spawnSync4, spawn as spawn5 } from "child_process";
8276
8321
  import crypto2 from "crypto";
8277
8322
  import fs17 from "fs";
8278
- import path18 from "path";
8323
+ import path19 from "path";
8279
8324
  import os13 from "os";
8280
- var SNAPSHOT_STACK_PATH = path18.join(os13.homedir(), ".node9", "snapshots.json");
8281
- var UNDO_LATEST_PATH = path18.join(os13.homedir(), ".node9", "undo_latest.txt");
8325
+ var SNAPSHOT_STACK_PATH = path19.join(os13.homedir(), ".node9", "snapshots.json");
8326
+ var UNDO_LATEST_PATH = path19.join(os13.homedir(), ".node9", "undo_latest.txt");
8282
8327
  var MAX_SNAPSHOTS = 10;
8283
8328
  var GIT_TIMEOUT = 15e3;
8284
8329
  function readStack() {
@@ -8290,20 +8335,37 @@ function readStack() {
8290
8335
  return [];
8291
8336
  }
8292
8337
  function writeStack(stack) {
8293
- const dir = path18.dirname(SNAPSHOT_STACK_PATH);
8338
+ const dir = path19.dirname(SNAPSHOT_STACK_PATH);
8294
8339
  if (!fs17.existsSync(dir)) fs17.mkdirSync(dir, { recursive: true });
8295
8340
  fs17.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
8296
8341
  }
8342
+ function extractFilePath(args) {
8343
+ if (!args || typeof args !== "object") return null;
8344
+ const a = args;
8345
+ const fp = a.file_path ?? a.path ?? a.filename;
8346
+ return typeof fp === "string" ? fp : null;
8347
+ }
8297
8348
  function buildArgsSummary(tool, args) {
8349
+ const filePath = extractFilePath(args);
8350
+ if (filePath) return filePath;
8298
8351
  if (!args || typeof args !== "object") return "";
8299
8352
  const a = args;
8300
- const filePath = a.file_path ?? a.path ?? a.filename;
8301
- if (typeof filePath === "string") return filePath;
8302
8353
  const cmd = a.command ?? a.cmd;
8303
8354
  if (typeof cmd === "string") return cmd.slice(0, 80);
8304
8355
  const sql = a.sql ?? a.query;
8305
8356
  if (typeof sql === "string") return sql.slice(0, 80);
8306
- return tool;
8357
+ return "";
8358
+ }
8359
+ function findProjectRoot(filePath) {
8360
+ let dir = path19.dirname(filePath);
8361
+ while (true) {
8362
+ if (fs17.existsSync(path19.join(dir, ".git")) || fs17.existsSync(path19.join(dir, "package.json"))) {
8363
+ return dir;
8364
+ }
8365
+ const parent = path19.dirname(dir);
8366
+ if (parent === dir) return process.cwd();
8367
+ dir = parent;
8368
+ }
8307
8369
  }
8308
8370
  function normalizeCwdForHash(cwd) {
8309
8371
  let normalized;
@@ -8318,14 +8380,14 @@ function normalizeCwdForHash(cwd) {
8318
8380
  }
8319
8381
  function getShadowRepoDir(cwd) {
8320
8382
  const hash = crypto2.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
8321
- return path18.join(os13.homedir(), ".node9", "snapshots", hash);
8383
+ return path19.join(os13.homedir(), ".node9", "snapshots", hash);
8322
8384
  }
8323
8385
  function cleanOrphanedIndexFiles(shadowDir) {
8324
8386
  try {
8325
8387
  const cutoff = Date.now() - 6e4;
8326
8388
  for (const f of fs17.readdirSync(shadowDir)) {
8327
8389
  if (f.startsWith("index_")) {
8328
- const fp = path18.join(shadowDir, f);
8390
+ const fp = path19.join(shadowDir, f);
8329
8391
  try {
8330
8392
  if (fs17.statSync(fp).mtimeMs < cutoff) fs17.unlinkSync(fp);
8331
8393
  } catch {
@@ -8339,7 +8401,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
8339
8401
  const hardcoded = [".git", ".node9"];
8340
8402
  const lines = [...hardcoded, ...ignorePaths].join("\n");
8341
8403
  try {
8342
- fs17.writeFileSync(path18.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
8404
+ fs17.writeFileSync(path19.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
8343
8405
  } catch {
8344
8406
  }
8345
8407
  }
@@ -8352,7 +8414,7 @@ function ensureShadowRepo(shadowDir, cwd) {
8352
8414
  timeout: 3e3
8353
8415
  });
8354
8416
  if (check.status === 0) {
8355
- const ptPath = path18.join(shadowDir, "project-path.txt");
8417
+ const ptPath = path19.join(shadowDir, "project-path.txt");
8356
8418
  try {
8357
8419
  const stored = fs17.readFileSync(ptPath, "utf8").trim();
8358
8420
  if (stored === normalizedCwd) return true;
@@ -8374,12 +8436,12 @@ function ensureShadowRepo(shadowDir, cwd) {
8374
8436
  } catch {
8375
8437
  }
8376
8438
  const init = spawnSync4("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
8377
- if (init.status !== 0) {
8378
- if (process.env.NODE9_DEBUG === "1")
8379
- console.error("[Node9] git init --bare failed:", init.stderr?.toString());
8439
+ if (init.status !== 0 || init.error) {
8440
+ const reason = init.error ? init.error.message : init.stderr?.toString();
8441
+ if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
8380
8442
  return false;
8381
8443
  }
8382
- const configFile = path18.join(shadowDir, "config");
8444
+ const configFile = path19.join(shadowDir, "config");
8383
8445
  spawnSync4("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
8384
8446
  timeout: 3e3
8385
8447
  });
@@ -8387,7 +8449,7 @@ function ensureShadowRepo(shadowDir, cwd) {
8387
8449
  timeout: 3e3
8388
8450
  });
8389
8451
  try {
8390
- fs17.writeFileSync(path18.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
8452
+ fs17.writeFileSync(path19.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
8391
8453
  } catch {
8392
8454
  }
8393
8455
  return true;
@@ -8406,11 +8468,13 @@ function buildGitEnv(cwd) {
8406
8468
  async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = []) {
8407
8469
  let indexFile = null;
8408
8470
  try {
8409
- const cwd = process.cwd();
8471
+ const rawFilePath = extractFilePath(args);
8472
+ const absFilePath = rawFilePath && path19.isAbsolute(rawFilePath) ? rawFilePath : null;
8473
+ const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
8410
8474
  const shadowDir = getShadowRepoDir(cwd);
8411
8475
  if (!ensureShadowRepo(shadowDir, cwd)) return null;
8412
8476
  writeShadowExcludes(shadowDir, ignorePaths);
8413
- indexFile = path18.join(shadowDir, `index_${process.pid}_${Date.now()}`);
8477
+ indexFile = path19.join(shadowDir, `index_${process.pid}_${Date.now()}`);
8414
8478
  const shadowEnv = {
8415
8479
  ...process.env,
8416
8480
  GIT_DIR: shadowDir,
@@ -8429,15 +8493,53 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8429
8493
  const commitHash = commitRes.stdout?.toString().trim();
8430
8494
  if (!commitHash || commitRes.status !== 0) return null;
8431
8495
  const stack = readStack();
8496
+ const prevEntry = [...stack].reverse().find((e) => e.cwd === cwd);
8497
+ let capturedFiles = [];
8498
+ let capturedDiff = null;
8499
+ if (prevEntry) {
8500
+ const filesRes = spawnSync4("git", ["diff", "--name-only", prevEntry.hash, commitHash], {
8501
+ env: shadowEnv,
8502
+ timeout: GIT_TIMEOUT
8503
+ });
8504
+ if (filesRes.status === 0) {
8505
+ capturedFiles = filesRes.stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
8506
+ }
8507
+ const diffRes = spawnSync4("git", ["diff", prevEntry.hash, commitHash], {
8508
+ env: shadowEnv,
8509
+ timeout: GIT_TIMEOUT
8510
+ });
8511
+ if (diffRes.status === 0) {
8512
+ capturedDiff = diffRes.stdout?.toString() || null;
8513
+ }
8514
+ } else {
8515
+ const filesRes = spawnSync4("git", ["ls-tree", "-r", "--name-only", commitHash], {
8516
+ env: shadowEnv,
8517
+ timeout: GIT_TIMEOUT
8518
+ });
8519
+ if (filesRes.status === 0) {
8520
+ capturedFiles = filesRes.stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
8521
+ }
8522
+ capturedDiff = null;
8523
+ }
8432
8524
  stack.push({
8433
8525
  hash: commitHash,
8434
8526
  tool,
8435
8527
  argsSummary: buildArgsSummary(tool, args),
8528
+ files: capturedFiles,
8529
+ diff: capturedDiff,
8436
8530
  cwd,
8437
8531
  timestamp: Date.now()
8438
8532
  });
8439
8533
  const shouldGc = stack.length % 5 === 0;
8440
- if (stack.length > MAX_SNAPSHOTS) stack.splice(0, stack.length - MAX_SNAPSHOTS);
8534
+ let cwdCount = 0;
8535
+ let oldestCwdIdx = -1;
8536
+ for (let i = 0; i < stack.length; i++) {
8537
+ if (stack[i].cwd === cwd) {
8538
+ if (oldestCwdIdx === -1) oldestCwdIdx = i;
8539
+ cwdCount++;
8540
+ }
8541
+ }
8542
+ if (cwdCount > MAX_SNAPSHOTS) stack.splice(oldestCwdIdx, 1);
8441
8543
  writeStack(stack);
8442
8544
  fs17.writeFileSync(UNDO_LATEST_PATH, commitHash);
8443
8545
  if (shouldGc) {
@@ -8493,14 +8595,21 @@ function applyUndo(hash, cwd) {
8493
8595
  env,
8494
8596
  timeout: GIT_TIMEOUT
8495
8597
  });
8496
- if (restore.status !== 0) return false;
8598
+ if (restore.status !== 0 || restore.error) {
8599
+ if (process.env.NODE9_DEBUG === "1") {
8600
+ const msg = restore.error ? restore.error.message : restore.stderr?.toString();
8601
+ console.error("[Node9] git restore failed:", msg);
8602
+ }
8603
+ return false;
8604
+ }
8497
8605
  const lsTree = spawnSync4("git", ["ls-tree", "-r", "--name-only", hash], {
8498
8606
  cwd: dir,
8499
8607
  env,
8500
8608
  timeout: GIT_TIMEOUT
8501
8609
  });
8502
8610
  if (lsTree.status !== 0) {
8503
- process.stderr.write(`[Node9] applyUndo: git ls-tree failed for hash ${hash}
8611
+ const errorMsg = lsTree.stderr?.toString() || "Unknown git error";
8612
+ process.stderr.write(`[Node9] applyUndo: git ls-tree failed for hash ${hash}: ${errorMsg}
8504
8613
  `);
8505
8614
  return false;
8506
8615
  }
@@ -8519,7 +8628,7 @@ function applyUndo(hash, cwd) {
8519
8628
  timeout: GIT_TIMEOUT
8520
8629
  }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
8521
8630
  for (const file of [...tracked, ...untracked]) {
8522
- const fullPath = path18.join(dir, file);
8631
+ const fullPath = path19.join(dir, file);
8523
8632
  if (!snapshotFiles.has(file) && fs17.existsSync(fullPath)) {
8524
8633
  fs17.unlinkSync(fullPath);
8525
8634
  }
@@ -8545,7 +8654,7 @@ function registerCheckCommand(program2) {
8545
8654
  } catch (err) {
8546
8655
  const tempConfig = getConfig();
8547
8656
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
8548
- const logPath = path19.join(os14.homedir(), ".node9", "hook-debug.log");
8657
+ const logPath = path20.join(os14.homedir(), ".node9", "hook-debug.log");
8549
8658
  const errMsg = err instanceof Error ? err.message : String(err);
8550
8659
  fs18.appendFileSync(
8551
8660
  logPath,
@@ -8558,9 +8667,9 @@ RAW: ${raw}
8558
8667
  }
8559
8668
  const config = getConfig(payload.cwd || void 0);
8560
8669
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
8561
- const logPath = path19.join(os14.homedir(), ".node9", "hook-debug.log");
8562
- if (!fs18.existsSync(path19.dirname(logPath)))
8563
- fs18.mkdirSync(path19.dirname(logPath), { recursive: true });
8670
+ const logPath = path20.join(os14.homedir(), ".node9", "hook-debug.log");
8671
+ if (!fs18.existsSync(path20.dirname(logPath)))
8672
+ fs18.mkdirSync(path20.dirname(logPath), { recursive: true });
8564
8673
  fs18.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
8565
8674
  `);
8566
8675
  }
@@ -8625,7 +8734,7 @@ RAW: ${raw}
8625
8734
  if (shouldSnapshot(toolName, toolInput, config)) {
8626
8735
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
8627
8736
  }
8628
- const safeCwdForAuth = typeof payload.cwd === "string" && path19.isAbsolute(payload.cwd) ? payload.cwd : void 0;
8737
+ const safeCwdForAuth = typeof payload.cwd === "string" && path20.isAbsolute(payload.cwd) ? payload.cwd : void 0;
8629
8738
  const result = await authorizeHeadless(toolName, toolInput, meta, {
8630
8739
  cwd: safeCwdForAuth
8631
8740
  });
@@ -8669,7 +8778,7 @@ RAW: ${raw}
8669
8778
  });
8670
8779
  } catch (err) {
8671
8780
  if (process.env.NODE9_DEBUG === "1") {
8672
- const logPath = path19.join(os14.homedir(), ".node9", "hook-debug.log");
8781
+ const logPath = path20.join(os14.homedir(), ".node9", "hook-debug.log");
8673
8782
  const errMsg = err instanceof Error ? err.message : String(err);
8674
8783
  fs18.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
8675
8784
  `);
@@ -8709,7 +8818,7 @@ init_audit();
8709
8818
  init_config();
8710
8819
  init_policy();
8711
8820
  import fs19 from "fs";
8712
- import path20 from "path";
8821
+ import path21 from "path";
8713
8822
  import os15 from "os";
8714
8823
  init_daemon();
8715
8824
 
@@ -8783,9 +8892,9 @@ function registerLogCommand(program2) {
8783
8892
  decision: "allowed",
8784
8893
  source: "post-hook"
8785
8894
  };
8786
- const logPath = path20.join(os15.homedir(), ".node9", "audit.log");
8787
- if (!fs19.existsSync(path20.dirname(logPath)))
8788
- fs19.mkdirSync(path20.dirname(logPath), { recursive: true });
8895
+ const logPath = path21.join(os15.homedir(), ".node9", "audit.log");
8896
+ if (!fs19.existsSync(path21.dirname(logPath)))
8897
+ fs19.mkdirSync(path21.dirname(logPath), { recursive: true });
8789
8898
  fs19.appendFileSync(logPath, JSON.stringify(entry) + "\n");
8790
8899
  if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
8791
8900
  const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
@@ -8811,7 +8920,7 @@ function registerLogCommand(program2) {
8811
8920
  }
8812
8921
  }
8813
8922
  }
8814
- const safeCwd = typeof payload.cwd === "string" && path20.isAbsolute(payload.cwd) ? payload.cwd : void 0;
8923
+ const safeCwd = typeof payload.cwd === "string" && path21.isAbsolute(payload.cwd) ? payload.cwd : void 0;
8815
8924
  const config = getConfig(safeCwd);
8816
8925
  if (shouldSnapshot(tool, {}, config)) {
8817
8926
  await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
@@ -8820,7 +8929,7 @@ function registerLogCommand(program2) {
8820
8929
  const msg = err instanceof Error ? err.message : String(err);
8821
8930
  process.stderr.write(`[Node9] audit log error: ${msg}
8822
8931
  `);
8823
- const debugPath = path20.join(os15.homedir(), ".node9", "hook-debug.log");
8932
+ const debugPath = path21.join(os15.homedir(), ".node9", "hook-debug.log");
8824
8933
  try {
8825
8934
  fs19.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
8826
8935
  `);
@@ -9128,7 +9237,7 @@ function registerConfigShowCommand(program2) {
9128
9237
  init_daemon();
9129
9238
  import chalk7 from "chalk";
9130
9239
  import fs20 from "fs";
9131
- import path21 from "path";
9240
+ import path22 from "path";
9132
9241
  import os16 from "os";
9133
9242
  import { execSync as execSync2 } from "child_process";
9134
9243
  function registerDoctorCommand(program2, version2) {
@@ -9182,7 +9291,7 @@ function registerDoctorCommand(program2, version2) {
9182
9291
  );
9183
9292
  }
9184
9293
  section("Configuration");
9185
- const globalConfigPath = path21.join(homeDir2, ".node9", "config.json");
9294
+ const globalConfigPath = path22.join(homeDir2, ".node9", "config.json");
9186
9295
  if (fs20.existsSync(globalConfigPath)) {
9187
9296
  try {
9188
9297
  JSON.parse(fs20.readFileSync(globalConfigPath, "utf-8"));
@@ -9193,7 +9302,7 @@ function registerDoctorCommand(program2, version2) {
9193
9302
  } else {
9194
9303
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
9195
9304
  }
9196
- const projectConfigPath = path21.join(process.cwd(), "node9.config.json");
9305
+ const projectConfigPath = path22.join(process.cwd(), "node9.config.json");
9197
9306
  if (fs20.existsSync(projectConfigPath)) {
9198
9307
  try {
9199
9308
  JSON.parse(fs20.readFileSync(projectConfigPath, "utf-8"));
@@ -9205,7 +9314,7 @@ function registerDoctorCommand(program2, version2) {
9205
9314
  );
9206
9315
  }
9207
9316
  }
9208
- const credsPath = path21.join(homeDir2, ".node9", "credentials.json");
9317
+ const credsPath = path22.join(homeDir2, ".node9", "credentials.json");
9209
9318
  if (fs20.existsSync(credsPath)) {
9210
9319
  pass("Cloud credentials found (~/.node9/credentials.json)");
9211
9320
  } else {
@@ -9215,7 +9324,7 @@ function registerDoctorCommand(program2, version2) {
9215
9324
  );
9216
9325
  }
9217
9326
  section("Agent Hooks");
9218
- const claudeSettingsPath = path21.join(homeDir2, ".claude", "settings.json");
9327
+ const claudeSettingsPath = path22.join(homeDir2, ".claude", "settings.json");
9219
9328
  if (fs20.existsSync(claudeSettingsPath)) {
9220
9329
  try {
9221
9330
  const cs = JSON.parse(fs20.readFileSync(claudeSettingsPath, "utf-8"));
@@ -9234,7 +9343,7 @@ function registerDoctorCommand(program2, version2) {
9234
9343
  } else {
9235
9344
  warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
9236
9345
  }
9237
- const geminiSettingsPath = path21.join(homeDir2, ".gemini", "settings.json");
9346
+ const geminiSettingsPath = path22.join(homeDir2, ".gemini", "settings.json");
9238
9347
  if (fs20.existsSync(geminiSettingsPath)) {
9239
9348
  try {
9240
9349
  const gs = JSON.parse(fs20.readFileSync(geminiSettingsPath, "utf-8"));
@@ -9253,7 +9362,7 @@ function registerDoctorCommand(program2, version2) {
9253
9362
  } else {
9254
9363
  warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
9255
9364
  }
9256
- const cursorHooksPath = path21.join(homeDir2, ".cursor", "hooks.json");
9365
+ const cursorHooksPath = path22.join(homeDir2, ".cursor", "hooks.json");
9257
9366
  if (fs20.existsSync(cursorHooksPath)) {
9258
9367
  try {
9259
9368
  const cur = JSON.parse(fs20.readFileSync(cursorHooksPath, "utf-8"));
@@ -9295,7 +9404,7 @@ function registerDoctorCommand(program2, version2) {
9295
9404
  // src/cli/commands/audit.ts
9296
9405
  import chalk8 from "chalk";
9297
9406
  import fs21 from "fs";
9298
- import path22 from "path";
9407
+ import path23 from "path";
9299
9408
  import os17 from "os";
9300
9409
  function formatRelativeTime(timestamp) {
9301
9410
  const diff = Date.now() - new Date(timestamp).getTime();
@@ -9309,7 +9418,7 @@ function formatRelativeTime(timestamp) {
9309
9418
  }
9310
9419
  function registerAuditCommand(program2) {
9311
9420
  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) => {
9312
- const logPath = path22.join(os17.homedir(), ".node9", "audit.log");
9421
+ const logPath = path23.join(os17.homedir(), ".node9", "audit.log");
9313
9422
  if (!fs21.existsSync(logPath)) {
9314
9423
  console.log(
9315
9424
  chalk8.yellow("No audit logs found. Run node9 with an agent to generate entries.")
@@ -9437,7 +9546,7 @@ init_core();
9437
9546
  init_daemon();
9438
9547
  import chalk10 from "chalk";
9439
9548
  import fs22 from "fs";
9440
- import path23 from "path";
9549
+ import path24 from "path";
9441
9550
  import os18 from "os";
9442
9551
  function readJson2(filePath) {
9443
9552
  try {
@@ -9506,8 +9615,8 @@ function registerStatusCommand(program2) {
9506
9615
  console.log("");
9507
9616
  const modeLabel = settings.mode === "audit" ? chalk10.blue("audit") : settings.mode === "strict" ? chalk10.red("strict") : chalk10.white("standard");
9508
9617
  console.log(` Mode: ${modeLabel}`);
9509
- const projectConfig = path23.join(process.cwd(), "node9.config.json");
9510
- const globalConfig = path23.join(os18.homedir(), ".node9", "config.json");
9618
+ const projectConfig = path24.join(process.cwd(), "node9.config.json");
9619
+ const globalConfig = path24.join(os18.homedir(), ".node9", "config.json");
9511
9620
  console.log(
9512
9621
  ` Local: ${fs22.existsSync(projectConfig) ? chalk10.green("Active (node9.config.json)") : chalk10.gray("Not present")}`
9513
9622
  );
@@ -9521,13 +9630,13 @@ function registerStatusCommand(program2) {
9521
9630
  }
9522
9631
  const homeDir2 = os18.homedir();
9523
9632
  const claudeSettings = readJson2(
9524
- path23.join(homeDir2, ".claude", "settings.json")
9633
+ path24.join(homeDir2, ".claude", "settings.json")
9525
9634
  );
9526
- const claudeConfig = readJson2(path23.join(homeDir2, ".claude.json"));
9635
+ const claudeConfig = readJson2(path24.join(homeDir2, ".claude.json"));
9527
9636
  const geminiSettings = readJson2(
9528
- path23.join(homeDir2, ".gemini", "settings.json")
9637
+ path24.join(homeDir2, ".gemini", "settings.json")
9529
9638
  );
9530
- const cursorConfig = readJson2(path23.join(homeDir2, ".cursor", "mcp.json"));
9639
+ const cursorConfig = readJson2(path24.join(homeDir2, ".cursor", "mcp.json"));
9531
9640
  const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
9532
9641
  if (agentFound) {
9533
9642
  console.log("");
@@ -9588,12 +9697,12 @@ function registerStatusCommand(program2) {
9588
9697
  init_core();
9589
9698
  import chalk11 from "chalk";
9590
9699
  import fs23 from "fs";
9591
- import path24 from "path";
9700
+ import path25 from "path";
9592
9701
  import os19 from "os";
9593
9702
  function registerInitCommand(program2) {
9594
9703
  program2.command("init").description("Set up Node9: create config and wire all detected AI agents").option("--force", "Overwrite existing config").option("-m, --mode <mode>", "Set initial security mode (standard, strict, audit)", "standard").option("--skip-setup", "Only create config \u2014 do not wire AI agents").action(async (options) => {
9595
9704
  console.log(chalk11.cyan.bold("\n\u{1F6E1}\uFE0F Node9 Init\n"));
9596
- const configPath = path24.join(os19.homedir(), ".node9", "config.json");
9705
+ const configPath = path25.join(os19.homedir(), ".node9", "config.json");
9597
9706
  if (fs23.existsSync(configPath) && !options.force) {
9598
9707
  console.log(chalk11.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
9599
9708
  } else {
@@ -9603,7 +9712,7 @@ function registerInitCommand(program2) {
9603
9712
  ...DEFAULT_CONFIG,
9604
9713
  settings: { ...DEFAULT_CONFIG.settings, mode: safeMode }
9605
9714
  };
9606
- const dir = path24.dirname(configPath);
9715
+ const dir = path25.dirname(configPath);
9607
9716
  if (!fs23.existsSync(dir)) fs23.mkdirSync(dir, { recursive: true });
9608
9717
  fs23.writeFileSync(configPath, JSON.stringify(configToSave, null, 2));
9609
9718
  console.log(chalk11.green(`\u2705 Config created: ${configPath}`));
@@ -9634,107 +9743,306 @@ function registerInitCommand(program2) {
9634
9743
  else if (agent === "cursor") await setupCursor();
9635
9744
  console.log("");
9636
9745
  }
9746
+ if (detected.claude) {
9747
+ setupHud();
9748
+ console.log(chalk11.green("\u2705 node9 HUD added to Claude Code statusline"));
9749
+ console.log(chalk11.gray(" Restart Claude Code to activate the security statusline."));
9750
+ console.log("");
9751
+ }
9637
9752
  console.log(chalk11.green.bold("\u{1F6E1}\uFE0F Node9 is ready!"));
9638
9753
  console.log(chalk11.gray(" Run: node9 daemon start"));
9639
9754
  });
9640
9755
  }
9641
9756
 
9642
9757
  // src/cli/commands/undo.ts
9758
+ import path26 from "path";
9759
+ import chalk13 from "chalk";
9760
+
9761
+ // src/tui/undo-navigator.ts
9762
+ import readline2 from "readline";
9643
9763
  import chalk12 from "chalk";
9644
- import { confirm as confirm2 } from "@inquirer/prompts";
9764
+ var RESET = "\x1B[0m";
9765
+ var BOLD = "\x1B[1m";
9766
+ var CLEAR_SCREEN = "\x1B[2J\x1B[H";
9767
+ var SESSION_GAP_MS = 6e4;
9768
+ function formatAge(timestamp) {
9769
+ const age = Math.round((Date.now() - timestamp) / 1e3);
9770
+ if (age < 60) return `${age}s ago`;
9771
+ if (age < 3600) return `${Math.round(age / 60)}m ago`;
9772
+ if (age < 86400) return `${Math.round(age / 3600)}h ago`;
9773
+ return `${Math.round(age / 86400)}d ago`;
9774
+ }
9775
+ function renderDiff(raw) {
9776
+ const lines = raw.split("\n").filter(
9777
+ (l) => !l.startsWith("diff --git") && !l.startsWith("index ") && !l.startsWith("Binary")
9778
+ );
9779
+ for (const line of lines) {
9780
+ if (line.startsWith("+++") || line.startsWith("---")) {
9781
+ process.stdout.write(chalk12.bold(line) + "\n");
9782
+ } else if (line.startsWith("+")) {
9783
+ process.stdout.write(chalk12.green(line) + "\n");
9784
+ } else if (line.startsWith("-")) {
9785
+ process.stdout.write(chalk12.red(line) + "\n");
9786
+ } else if (line.startsWith("@@")) {
9787
+ process.stdout.write(chalk12.cyan(line) + "\n");
9788
+ } else {
9789
+ process.stdout.write(chalk12.gray(line) + "\n");
9790
+ }
9791
+ }
9792
+ }
9793
+ function isSessionBoundary(entries, idx) {
9794
+ if (idx <= 0) return false;
9795
+ return entries[idx - 1].timestamp - entries[idx].timestamp > SESSION_GAP_MS;
9796
+ }
9797
+ function sessionStart(entries, idx) {
9798
+ let i = idx;
9799
+ while (i > 0 && !isSessionBoundary(entries, i)) i--;
9800
+ return i;
9801
+ }
9802
+ function render(entries, idx) {
9803
+ const entry = entries[idx];
9804
+ const total = entries.length;
9805
+ const step = idx + 1;
9806
+ process.stdout.write(CLEAR_SCREEN);
9807
+ process.stdout.write(
9808
+ chalk12.magenta.bold(`\u23EA Node9 Undo`) + chalk12.gray(` \u2500\u2500 step ${step} of ${total}`) + (entry.files?.length ? chalk12.gray(
9809
+ ` \u2500\u2500 ${entry.files.slice(0, 2).join(", ")}${entry.files.length > 2 ? ` +${entry.files.length - 2} more` : ""}`
9810
+ ) : "") + "\n\n"
9811
+ );
9812
+ process.stdout.write(
9813
+ ` ${BOLD}Tool:${RESET} ${chalk12.cyan(entry.tool)}` + (entry.argsSummary ? chalk12.gray(" \u2192 " + entry.argsSummary) : "") + "\n"
9814
+ );
9815
+ process.stdout.write(` ${BOLD}When:${RESET} ${chalk12.gray(formatAge(entry.timestamp))}
9816
+ `);
9817
+ process.stdout.write(` ${BOLD}Dir: ${RESET} ${chalk12.gray(entry.cwd)}
9818
+ `);
9819
+ if (entry.files && entry.files.length > 0) {
9820
+ process.stdout.write(` ${BOLD}Files:${RESET} ${chalk12.gray(entry.files.join(", "))}
9821
+ `);
9822
+ }
9823
+ if (idx < total - 1 && isSessionBoundary(entries, idx + 1)) {
9824
+ process.stdout.write(chalk12.gray("\n \u2500\u2500 session boundary above \u2500\u2500\n"));
9825
+ }
9826
+ process.stdout.write("\n");
9827
+ const diff = entry.diff ?? computeUndoDiff(entry.hash, entry.cwd);
9828
+ if (diff) {
9829
+ renderDiff(diff);
9830
+ } else {
9831
+ process.stdout.write(
9832
+ chalk12.gray(" (no diff \u2014 working tree may already match this snapshot)\n")
9833
+ );
9834
+ }
9835
+ process.stdout.write("\n");
9836
+ process.stdout.write(
9837
+ chalk12.gray(" ") + (idx < total - 1 ? chalk12.white("[\u2190] older") : chalk12.gray("[\u2190] older")) + chalk12.gray(" ") + (idx > 0 ? chalk12.white("[\u2192] newer") : chalk12.gray("[\u2192] newer")) + chalk12.gray(" ") + chalk12.green("[\u21B5] restore here") + chalk12.gray(" ") + chalk12.yellow("[s] session start") + chalk12.gray(" ") + chalk12.gray("[q] quit") + "\n"
9838
+ );
9839
+ }
9840
+ async function runUndoNavigator(entries) {
9841
+ if (entries.length === 0) return { restored: false };
9842
+ const display = [...entries].reverse();
9843
+ let idx = 0;
9844
+ if (!process.stdout.isTTY || !process.stdin.isTTY) {
9845
+ render(display, idx);
9846
+ return { restored: false };
9847
+ }
9848
+ readline2.emitKeypressEvents(process.stdin);
9849
+ return new Promise((resolve) => {
9850
+ let done = false;
9851
+ render(display, idx);
9852
+ try {
9853
+ process.stdin.setRawMode(true);
9854
+ } catch {
9855
+ resolve({ restored: false });
9856
+ return;
9857
+ }
9858
+ process.stdin.resume();
9859
+ const cleanup = () => {
9860
+ process.stdin.removeListener("keypress", onKeypress);
9861
+ try {
9862
+ process.stdin.setRawMode(false);
9863
+ } catch {
9864
+ }
9865
+ process.stdin.pause();
9866
+ };
9867
+ const onKeypress = (_str, key) => {
9868
+ if (done) return;
9869
+ const name = key?.name ?? "";
9870
+ if (name === "left" || name === "h") {
9871
+ if (idx < display.length - 1) {
9872
+ idx++;
9873
+ render(display, idx);
9874
+ }
9875
+ } else if (name === "right" || name === "l") {
9876
+ if (idx > 0) {
9877
+ idx--;
9878
+ render(display, idx);
9879
+ }
9880
+ } else if (name === "s") {
9881
+ const start = sessionStart(display, idx);
9882
+ if (start !== idx) {
9883
+ idx = start;
9884
+ render(display, idx);
9885
+ }
9886
+ } else if (name === "return" || name === "y") {
9887
+ done = true;
9888
+ cleanup();
9889
+ process.stdout.write(CLEAR_SCREEN);
9890
+ const entry = display[idx];
9891
+ process.stdout.write(chalk12.magenta.bold("\n\u23EA Restoring snapshot...\n\n"));
9892
+ if (applyUndo(entry.hash, entry.cwd)) {
9893
+ process.stdout.write(chalk12.green("\u2705 Reverted successfully.\n\n"));
9894
+ resolve({ restored: true });
9895
+ } else {
9896
+ process.stdout.write(chalk12.red("\u274C Undo failed.\n\n"));
9897
+ resolve({ restored: false });
9898
+ }
9899
+ } else if (name === "q" || key?.ctrl && name === "c") {
9900
+ done = true;
9901
+ cleanup();
9902
+ process.stdout.write(CLEAR_SCREEN);
9903
+ process.stdout.write(chalk12.gray("\nCancelled.\n\n"));
9904
+ resolve({ restored: false });
9905
+ }
9906
+ };
9907
+ process.stdin.on("keypress", onKeypress);
9908
+ });
9909
+ }
9910
+
9911
+ // src/cli/commands/undo.ts
9912
+ function findMatchingCwd(startDir, history) {
9913
+ const cwds = new Set(history.map((e) => e.cwd));
9914
+ let dir = startDir;
9915
+ while (true) {
9916
+ if (cwds.has(dir)) return dir;
9917
+ const parent = path26.dirname(dir);
9918
+ if (parent === dir) return null;
9919
+ dir = parent;
9920
+ }
9921
+ }
9922
+ function formatAge2(timestamp) {
9923
+ const age = Math.round((Date.now() - timestamp) / 1e3);
9924
+ if (age < 60) return `${age}s ago`;
9925
+ if (age < 3600) return `${Math.round(age / 60)}m ago`;
9926
+ if (age < 86400) return `${Math.round(age / 3600)}h ago`;
9927
+ return `${Math.round(age / 86400)}d ago`;
9928
+ }
9645
9929
  function registerUndoCommand(program2) {
9646
9930
  program2.command("undo").description(
9647
- "Revert files to a pre-AI snapshot. Shows a diff and asks for confirmation before reverting. Use --steps N to go back N actions, --all to include snapshots from other directories."
9648
- ).option("--steps <n>", "Number of snapshots to go back (default: 1)", "1").option("--all", "Show snapshots from all directories, not just the current one").action(async (options) => {
9649
- const steps = Math.max(1, parseInt(options.steps, 10) || 1);
9931
+ "Browse and restore pre-AI snapshots. Arrow keys to navigate, Enter to restore. Use --steps N to go back N actions non-interactively, --list to print history."
9932
+ ).option("--steps <n>", "Non-interactive: restore N steps back (default: 1)").option("--list", "Print snapshot history as a table and exit").option("--all", "Include snapshots from all directories, not just the current one").action(async (options) => {
9650
9933
  const allHistory = getSnapshotHistory();
9651
- const history = options.all ? allHistory : allHistory.filter((s) => s.cwd === process.cwd());
9934
+ const matchedCwd = options.all ? null : findMatchingCwd(process.cwd(), allHistory);
9935
+ const history = options.all ? allHistory : allHistory.filter((s) => s.cwd === matchedCwd);
9652
9936
  if (history.length === 0) {
9653
9937
  if (!options.all && allHistory.length > 0) {
9654
9938
  console.log(
9655
- chalk12.yellow(
9939
+ chalk13.yellow(
9656
9940
  `
9657
9941
  \u2139\uFE0F No snapshots found for the current directory (${process.cwd()}).
9658
- Run ${chalk12.cyan("node9 undo --all")} to see snapshots from all projects.
9942
+ Run ${chalk13.cyan("node9 undo --all")} to see snapshots from all projects.
9659
9943
  `
9660
9944
  )
9661
9945
  );
9662
9946
  } else {
9663
- console.log(chalk12.yellow("\n\u2139\uFE0F No undo snapshots found.\n"));
9947
+ console.log(chalk13.yellow("\n\u2139\uFE0F No undo snapshots found.\n"));
9664
9948
  }
9665
9949
  return;
9666
9950
  }
9667
- const idx = history.length - steps;
9668
- if (idx < 0) {
9951
+ if (options.list) {
9952
+ console.log(chalk13.magenta.bold("\n\u23EA Snapshot History\n"));
9669
9953
  console.log(
9670
- chalk12.yellow(
9671
- `
9672
- \u2139\uFE0F Only ${history.length} snapshot(s) available, cannot go back ${steps}.
9673
- `
9954
+ chalk13.gray(
9955
+ ` ${"#".padEnd(3)} ${"File / Command".padEnd(30)} ${"Tool".padEnd(8)} ${"When".padEnd(10)} Dir`
9674
9956
  )
9675
9957
  );
9958
+ console.log(chalk13.gray(" " + "\u2500".repeat(80)));
9959
+ const display = [...history].reverse();
9960
+ let prevTs = null;
9961
+ for (let i = 0; i < display.length; i++) {
9962
+ const e = display[i];
9963
+ const isGap = prevTs !== null && prevTs - e.timestamp > 6e4;
9964
+ if (isGap) console.log(chalk13.gray(" \u2500\u2500 earlier \u2500\u2500"));
9965
+ const label = (e.argsSummary || e.files?.[0] || "\u2014").slice(0, 30).padEnd(30);
9966
+ const tool = e.tool.slice(0, 8).padEnd(8);
9967
+ const when = formatAge2(e.timestamp).padEnd(10);
9968
+ const dir = e.cwd.length > 30 ? "\u2026" + e.cwd.slice(-29) : e.cwd;
9969
+ console.log(
9970
+ chalk13.white(
9971
+ ` ${String(i + 1).padEnd(3)} ${label} ${chalk13.cyan(tool)} ${chalk13.gray(when)} ${chalk13.gray(dir)}`
9972
+ )
9973
+ );
9974
+ prevTs = e.timestamp;
9975
+ }
9976
+ console.log("");
9676
9977
  return;
9677
9978
  }
9678
- const snapshot = history[idx];
9679
- const age = Math.round((Date.now() - snapshot.timestamp) / 1e3);
9680
- const ageStr = age < 60 ? `${age}s ago` : age < 3600 ? `${Math.round(age / 60)}m ago` : `${Math.round(age / 3600)}h ago`;
9681
- console.log(
9682
- chalk12.magenta.bold(`
9979
+ if (options.steps !== void 0) {
9980
+ const steps = Math.max(1, parseInt(options.steps, 10) || 1);
9981
+ const idx = history.length - steps;
9982
+ if (idx < 0) {
9983
+ console.log(
9984
+ chalk13.yellow(
9985
+ `
9986
+ \u2139\uFE0F Only ${history.length} snapshot(s) available, cannot go back ${steps}.
9987
+ `
9988
+ )
9989
+ );
9990
+ return;
9991
+ }
9992
+ const snapshot = history[idx];
9993
+ const ageStr = formatAge2(snapshot.timestamp);
9994
+ console.log(
9995
+ chalk13.magenta.bold(`
9683
9996
  \u23EA Node9 Undo${steps > 1 ? ` (${steps} steps back)` : ""}`)
9684
- );
9685
- console.log(
9686
- chalk12.white(
9687
- ` Tool: ${chalk12.cyan(snapshot.tool)}${snapshot.argsSummary ? chalk12.gray(" \u2192 " + snapshot.argsSummary) : ""}`
9688
- )
9689
- );
9690
- console.log(chalk12.white(` When: ${chalk12.gray(ageStr)}`));
9691
- console.log(chalk12.white(` Dir: ${chalk12.gray(snapshot.cwd)}`));
9692
- if (steps > 1)
9997
+ );
9693
9998
  console.log(
9694
- chalk12.yellow(` Note: This will also undo the ${steps - 1} action(s) after it.`)
9999
+ chalk13.white(
10000
+ ` Tool: ${chalk13.cyan(snapshot.tool)}${snapshot.argsSummary ? chalk13.gray(" \u2192 " + snapshot.argsSummary) : ""}`
10001
+ )
9695
10002
  );
9696
- console.log("");
9697
- const diff = computeUndoDiff(snapshot.hash, snapshot.cwd);
9698
- if (diff) {
9699
- const lines = diff.split("\n");
9700
- for (const line of lines) {
9701
- if (line.startsWith("+++") || line.startsWith("---")) {
9702
- console.log(chalk12.bold(line));
9703
- } else if (line.startsWith("+")) {
9704
- console.log(chalk12.green(line));
9705
- } else if (line.startsWith("-")) {
9706
- console.log(chalk12.red(line));
9707
- } else if (line.startsWith("@@")) {
9708
- console.log(chalk12.cyan(line));
9709
- } else {
9710
- console.log(chalk12.gray(line));
10003
+ console.log(chalk13.white(` When: ${chalk13.gray(ageStr)}`));
10004
+ console.log(chalk13.white(` Dir: ${chalk13.gray(snapshot.cwd)}`));
10005
+ if (steps > 1)
10006
+ console.log(
10007
+ chalk13.yellow(` Note: This will also undo the ${steps - 1} action(s) after it.`)
10008
+ );
10009
+ console.log("");
10010
+ const diff = snapshot.diff ?? computeUndoDiff(snapshot.hash, snapshot.cwd);
10011
+ if (diff) {
10012
+ const lines = diff.split("\n").filter((l) => !l.startsWith("diff --git") && !l.startsWith("index "));
10013
+ for (const line of lines) {
10014
+ if (line.startsWith("+++") || line.startsWith("---")) console.log(chalk13.bold(line));
10015
+ else if (line.startsWith("+")) console.log(chalk13.green(line));
10016
+ else if (line.startsWith("-")) console.log(chalk13.red(line));
10017
+ else if (line.startsWith("@@")) console.log(chalk13.cyan(line));
10018
+ else console.log(chalk13.gray(line));
9711
10019
  }
10020
+ console.log("");
10021
+ } else {
10022
+ console.log(
10023
+ chalk13.gray(" (no diff available \u2014 working tree may already match snapshot)\n")
10024
+ );
9712
10025
  }
9713
- console.log("");
9714
- } else {
9715
- console.log(
9716
- chalk12.gray(" (no diff available \u2014 working tree may already match snapshot)\n")
9717
- );
9718
- }
9719
- const proceed = await confirm2({
9720
- message: `Revert to this snapshot?`,
9721
- default: false
9722
- });
9723
- if (proceed) {
9724
- if (applyUndo(snapshot.hash, snapshot.cwd)) {
9725
- console.log(chalk12.green("\n\u2705 Reverted successfully.\n"));
10026
+ const { confirm: confirm3 } = await import("@inquirer/prompts");
10027
+ const proceed = await confirm3({ message: `Revert to this snapshot?`, default: false });
10028
+ if (proceed) {
10029
+ if (applyUndo(snapshot.hash, snapshot.cwd)) {
10030
+ console.log(chalk13.green("\n\u2705 Reverted successfully.\n"));
10031
+ } else {
10032
+ console.error(chalk13.red("\n\u274C Undo failed. Ensure you are in a Git repository.\n"));
10033
+ }
9726
10034
  } else {
9727
- console.error(chalk12.red("\n\u274C Undo failed. Ensure you are in a Git repository.\n"));
10035
+ console.log(chalk13.gray("\nCancelled.\n"));
9728
10036
  }
9729
- } else {
9730
- console.log(chalk12.gray("\nCancelled.\n"));
10037
+ return;
9731
10038
  }
10039
+ await runUndoNavigator(history);
9732
10040
  });
9733
10041
  }
9734
10042
 
9735
10043
  // src/cli/commands/watch.ts
9736
10044
  init_daemon();
9737
- import chalk13 from "chalk";
10045
+ import chalk14 from "chalk";
9738
10046
  import { spawn as spawn7, spawnSync as spawnSync5 } from "child_process";
9739
10047
  function registerWatchCommand(program2) {
9740
10048
  program2.command("watch").description("Run a command under Node9 watch mode (daemon stays alive for the session)").argument("<command>", "Command to run").argument("[args...]", "Arguments for the command").action(async (cmd, args) => {
@@ -9750,7 +10058,7 @@ function registerWatchCommand(program2) {
9750
10058
  throw new Error("not running");
9751
10059
  }
9752
10060
  } catch {
9753
- console.error(chalk13.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
10061
+ console.error(chalk14.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
9754
10062
  const child = spawn7(process.execPath, [process.argv[1], "daemon"], {
9755
10063
  detached: true,
9756
10064
  stdio: "ignore",
@@ -9772,12 +10080,12 @@ function registerWatchCommand(program2) {
9772
10080
  }
9773
10081
  }
9774
10082
  if (!ready) {
9775
- console.error(chalk13.red("\u274C Daemon failed to start. Try: node9 daemon start"));
10083
+ console.error(chalk14.red("\u274C Daemon failed to start. Try: node9 daemon start"));
9776
10084
  process.exit(1);
9777
10085
  }
9778
10086
  }
9779
10087
  console.error(
9780
- chalk13.cyan.bold("\u{1F6E1}\uFE0F Node9 watch") + chalk13.dim(` \u2192 localhost:${port}`) + chalk13.dim(
10088
+ chalk14.cyan.bold("\u{1F6E1}\uFE0F Node9 watch") + chalk14.dim(` \u2192 localhost:${port}`) + chalk14.dim(
9781
10089
  "\n Tip: run `node9 tail` in another terminal to review and approve AI actions.\n"
9782
10090
  )
9783
10091
  );
@@ -9786,7 +10094,7 @@ function registerWatchCommand(program2) {
9786
10094
  env: { ...process.env, NODE9_WATCH_MODE: "1" }
9787
10095
  });
9788
10096
  if (result.error) {
9789
- console.error(chalk13.red(`\u274C Failed to run command: ${result.error.message}`));
10097
+ console.error(chalk14.red(`\u274C Failed to run command: ${result.error.message}`));
9790
10098
  process.exit(1);
9791
10099
  }
9792
10100
  process.exit(result.status ?? 0);
@@ -9795,8 +10103,8 @@ function registerWatchCommand(program2) {
9795
10103
 
9796
10104
  // src/mcp-gateway/index.ts
9797
10105
  init_orchestrator();
9798
- import readline2 from "readline";
9799
- import chalk14 from "chalk";
10106
+ import readline3 from "readline";
10107
+ import chalk15 from "chalk";
9800
10108
  import { spawn as spawn8 } from "child_process";
9801
10109
  import { execa as execa2 } from "execa";
9802
10110
  init_provenance();
@@ -9859,13 +10167,13 @@ async function runMcpGateway(upstreamCommand) {
9859
10167
  const prov = checkProvenance(executable);
9860
10168
  if (prov.trustLevel === "suspect") {
9861
10169
  console.error(
9862
- chalk14.red(
10170
+ chalk15.red(
9863
10171
  `\u26A0\uFE0F Node9: Upstream MCP server binary is suspect \u2014 ${prov.reason} (${prov.resolvedPath})`
9864
10172
  )
9865
10173
  );
9866
- console.error(chalk14.red(" Verify this binary is trusted before proceeding."));
10174
+ console.error(chalk15.red(" Verify this binary is trusted before proceeding."));
9867
10175
  }
9868
- console.error(chalk14.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
10176
+ console.error(chalk15.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
9869
10177
  const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
9870
10178
  "NODE_OPTIONS",
9871
10179
  "NODE_PATH",
@@ -9893,7 +10201,7 @@ async function runMcpGateway(upstreamCommand) {
9893
10201
  let authPending = false;
9894
10202
  let deferredExitCode = null;
9895
10203
  let deferredStdinEnd = false;
9896
- const agentIn = readline2.createInterface({ input: process.stdin, terminal: false });
10204
+ const agentIn = readline3.createInterface({ input: process.stdin, terminal: false });
9897
10205
  agentIn.on("line", async (line) => {
9898
10206
  let message;
9899
10207
  try {
@@ -9929,10 +10237,10 @@ async function runMcpGateway(upstreamCommand) {
9929
10237
  mcpServer
9930
10238
  });
9931
10239
  if (!result.approved) {
9932
- console.error(chalk14.red(`
10240
+ console.error(chalk15.red(`
9933
10241
  \u{1F6D1} Node9 MCP Gateway: Action Blocked`));
9934
- console.error(chalk14.gray(` Tool: ${toolName}`));
9935
- console.error(chalk14.gray(` Reason: ${result.reason ?? "Security Policy"}
10242
+ console.error(chalk15.gray(` Tool: ${toolName}`));
10243
+ console.error(chalk15.gray(` Reason: ${result.reason ?? "Security Policy"}
9936
10244
  `));
9937
10245
  const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
9938
10246
  const isHumanDecision = blockedByLabel.toLowerCase().includes("user") || blockedByLabel.toLowerCase().includes("daemon") || blockedByLabel.toLowerCase().includes("decision");
@@ -10006,7 +10314,7 @@ function registerMcpGatewayCommand(program2) {
10006
10314
 
10007
10315
  // src/cli/commands/trust.ts
10008
10316
  init_trusted_hosts();
10009
- import chalk15 from "chalk";
10317
+ import chalk16 from "chalk";
10010
10318
  function isValidHost(host) {
10011
10319
  return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host);
10012
10320
  }
@@ -10016,44 +10324,44 @@ function registerTrustCommand(program2) {
10016
10324
  const normalized = normalizeHost(host.trim());
10017
10325
  if (!isValidHost(normalized)) {
10018
10326
  console.error(
10019
- chalk15.red(`
10327
+ chalk16.red(`
10020
10328
  \u274C Invalid host: "${host}"
10021
- `) + chalk15.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
10329
+ `) + chalk16.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
10022
10330
  );
10023
10331
  process.exit(1);
10024
10332
  }
10025
10333
  addTrustedHost(normalized);
10026
- console.log(chalk15.green(`
10334
+ console.log(chalk16.green(`
10027
10335
  \u2705 ${normalized} added to trusted hosts.`));
10028
10336
  console.log(
10029
- chalk15.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
10337
+ chalk16.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
10030
10338
  );
10031
10339
  });
10032
10340
  trustCmd.command("remove <host>").description("Remove a trusted host").action((host) => {
10033
10341
  const normalized = normalizeHost(host.trim());
10034
10342
  const removed = removeTrustedHost(normalized);
10035
10343
  if (!removed) {
10036
- console.error(chalk15.yellow(`
10344
+ console.error(chalk16.yellow(`
10037
10345
  \u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
10038
10346
  `));
10039
10347
  process.exit(1);
10040
10348
  }
10041
- console.log(chalk15.green(`
10349
+ console.log(chalk16.green(`
10042
10350
  \u2705 ${normalized} removed from trusted hosts.
10043
10351
  `));
10044
10352
  });
10045
10353
  trustCmd.command("list").description("Show all trusted hosts").action(() => {
10046
10354
  const hosts = readTrustedHosts();
10047
10355
  if (hosts.length === 0) {
10048
- console.log(chalk15.gray("\n No trusted hosts configured.\n"));
10049
- console.log(` Add one: ${chalk15.cyan("node9 trust add api.mycompany.com")}
10356
+ console.log(chalk16.gray("\n No trusted hosts configured.\n"));
10357
+ console.log(` Add one: ${chalk16.cyan("node9 trust add api.mycompany.com")}
10050
10358
  `);
10051
10359
  return;
10052
10360
  }
10053
- console.log(chalk15.bold("\n\u{1F513} Trusted Hosts\n"));
10361
+ console.log(chalk16.bold("\n\u{1F513} Trusted Hosts\n"));
10054
10362
  for (const entry of hosts) {
10055
10363
  const date = new Date(entry.addedAt).toLocaleDateString();
10056
- console.log(` ${chalk15.cyan(entry.host.padEnd(40))} ${chalk15.gray(`added ${date}`)}`);
10364
+ console.log(` ${chalk16.cyan(entry.host.padEnd(40))} ${chalk16.gray(`added ${date}`)}`);
10057
10365
  }
10058
10366
  console.log("");
10059
10367
  });
@@ -10061,15 +10369,15 @@ function registerTrustCommand(program2) {
10061
10369
 
10062
10370
  // src/cli.ts
10063
10371
  var { version } = JSON.parse(
10064
- fs26.readFileSync(path27.join(__dirname, "../package.json"), "utf-8")
10372
+ fs26.readFileSync(path29.join(__dirname, "../package.json"), "utf-8")
10065
10373
  );
10066
10374
  var program = new Command();
10067
10375
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
10068
10376
  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) => {
10069
10377
  const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
10070
- const credPath = path27.join(os22.homedir(), ".node9", "credentials.json");
10071
- if (!fs26.existsSync(path27.dirname(credPath)))
10072
- fs26.mkdirSync(path27.dirname(credPath), { recursive: true });
10378
+ const credPath = path29.join(os22.homedir(), ".node9", "credentials.json");
10379
+ if (!fs26.existsSync(path29.dirname(credPath)))
10380
+ fs26.mkdirSync(path29.dirname(credPath), { recursive: true });
10073
10381
  const profileName = options.profile || "default";
10074
10382
  let existingCreds = {};
10075
10383
  try {
@@ -10088,7 +10396,7 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
10088
10396
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
10089
10397
  fs26.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
10090
10398
  if (profileName === "default") {
10091
- const configPath = path27.join(os22.homedir(), ".node9", "config.json");
10399
+ const configPath = path29.join(os22.homedir(), ".node9", "config.json");
10092
10400
  let config = {};
10093
10401
  try {
10094
10402
  if (fs26.existsSync(configPath))
@@ -10107,19 +10415,19 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
10107
10415
  approvers.cloud = false;
10108
10416
  }
10109
10417
  s.approvers = approvers;
10110
- if (!fs26.existsSync(path27.dirname(configPath)))
10111
- fs26.mkdirSync(path27.dirname(configPath), { recursive: true });
10418
+ if (!fs26.existsSync(path29.dirname(configPath)))
10419
+ fs26.mkdirSync(path29.dirname(configPath), { recursive: true });
10112
10420
  fs26.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
10113
10421
  }
10114
10422
  if (options.profile && profileName !== "default") {
10115
- console.log(chalk17.green(`\u2705 Profile "${profileName}" saved`));
10116
- console.log(chalk17.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
10423
+ console.log(chalk18.green(`\u2705 Profile "${profileName}" saved`));
10424
+ console.log(chalk18.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
10117
10425
  } else if (options.local) {
10118
- console.log(chalk17.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
10119
- console.log(chalk17.gray(` All decisions stay on this machine.`));
10426
+ console.log(chalk18.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
10427
+ console.log(chalk18.gray(` All decisions stay on this machine.`));
10120
10428
  } else {
10121
- console.log(chalk17.green(`\u2705 Logged in \u2014 agent mode`));
10122
- console.log(chalk17.gray(` Team policy enforced for all calls via Node9 cloud.`));
10429
+ console.log(chalk18.green(`\u2705 Logged in \u2014 agent mode`));
10430
+ console.log(chalk18.gray(` Team policy enforced for all calls via Node9 cloud.`));
10123
10431
  }
10124
10432
  });
10125
10433
  program.command("addto").description("Integrate Node9 with an AI agent").addHelpText("after", "\n Supported targets: claude gemini cursor hud").argument("<target>", "The agent to protect: claude | gemini | cursor | hud").action(async (target) => {
@@ -10127,19 +10435,19 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
10127
10435
  if (target === "claude") return await setupClaude();
10128
10436
  if (target === "cursor") return await setupCursor();
10129
10437
  if (target === "hud") return setupHud();
10130
- console.error(chalk17.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
10438
+ console.error(chalk18.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
10131
10439
  process.exit(1);
10132
10440
  });
10133
10441
  program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText("after", "\n Supported targets: claude gemini cursor hud").argument("[target]", "The agent to protect: claude | gemini | cursor | hud").action(async (target) => {
10134
10442
  if (!target) {
10135
- console.log(chalk17.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
10136
- console.log(" Usage: " + chalk17.white("node9 setup <target>") + "\n");
10443
+ console.log(chalk18.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
10444
+ console.log(" Usage: " + chalk18.white("node9 setup <target>") + "\n");
10137
10445
  console.log(" Targets:");
10138
- console.log(" " + chalk17.green("claude") + " \u2014 Claude Code (hook mode)");
10139
- console.log(" " + chalk17.green("gemini") + " \u2014 Gemini CLI (hook mode)");
10140
- console.log(" " + chalk17.green("cursor") + " \u2014 Cursor (hook mode)");
10446
+ console.log(" " + chalk18.green("claude") + " \u2014 Claude Code (hook mode)");
10447
+ console.log(" " + chalk18.green("gemini") + " \u2014 Gemini CLI (hook mode)");
10448
+ console.log(" " + chalk18.green("cursor") + " \u2014 Cursor (hook mode)");
10141
10449
  process.stdout.write(
10142
- " " + chalk17.green("hud") + " \u2014 Claude Code security statusline\n"
10450
+ " " + chalk18.green("hud") + " \u2014 Claude Code security statusline\n"
10143
10451
  );
10144
10452
  console.log("");
10145
10453
  return;
@@ -10149,7 +10457,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
10149
10457
  if (t === "claude") return await setupClaude();
10150
10458
  if (t === "cursor") return await setupCursor();
10151
10459
  if (t === "hud") return setupHud();
10152
- console.error(chalk17.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
10460
+ console.error(chalk18.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
10153
10461
  process.exit(1);
10154
10462
  });
10155
10463
  program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText("after", "\n Supported targets: claude gemini cursor").argument("<target>", "The agent to remove from: claude | gemini | cursor").action((target) => {
@@ -10160,31 +10468,31 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
10160
10468
  else if (target === "hud") fn = teardownHud;
10161
10469
  else {
10162
10470
  console.error(
10163
- chalk17.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`)
10471
+ chalk18.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`)
10164
10472
  );
10165
10473
  process.exit(1);
10166
10474
  }
10167
- console.log(chalk17.cyan(`
10475
+ console.log(chalk18.cyan(`
10168
10476
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
10169
10477
  `));
10170
10478
  try {
10171
10479
  fn();
10172
10480
  } catch (err) {
10173
- console.error(chalk17.red(` \u26A0\uFE0F Failed: ${err instanceof Error ? err.message : String(err)}`));
10481
+ console.error(chalk18.red(` \u26A0\uFE0F Failed: ${err instanceof Error ? err.message : String(err)}`));
10174
10482
  process.exit(1);
10175
10483
  }
10176
- console.log(chalk17.gray("\n Restart the agent for changes to take effect."));
10484
+ console.log(chalk18.gray("\n Restart the agent for changes to take effect."));
10177
10485
  });
10178
10486
  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) => {
10179
- console.log(chalk17.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
10180
- console.log(chalk17.bold("Stopping daemon..."));
10487
+ console.log(chalk18.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
10488
+ console.log(chalk18.bold("Stopping daemon..."));
10181
10489
  try {
10182
10490
  stopDaemon();
10183
- console.log(chalk17.green(" \u2705 Daemon stopped"));
10491
+ console.log(chalk18.green(" \u2705 Daemon stopped"));
10184
10492
  } catch {
10185
- console.log(chalk17.blue(" \u2139\uFE0F Daemon was not running"));
10493
+ console.log(chalk18.blue(" \u2139\uFE0F Daemon was not running"));
10186
10494
  }
10187
- console.log(chalk17.bold("\nRemoving hooks..."));
10495
+ console.log(chalk18.bold("\nRemoving hooks..."));
10188
10496
  let teardownFailed = false;
10189
10497
  for (const [label, fn] of [
10190
10498
  ["Claude", teardownClaude],
@@ -10196,16 +10504,16 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
10196
10504
  } catch (err) {
10197
10505
  teardownFailed = true;
10198
10506
  console.error(
10199
- chalk17.red(
10507
+ chalk18.red(
10200
10508
  ` \u26A0\uFE0F Failed to remove ${label} hooks: ${err instanceof Error ? err.message : String(err)}`
10201
10509
  )
10202
10510
  );
10203
10511
  }
10204
10512
  }
10205
10513
  if (options.purge) {
10206
- const node9Dir = path27.join(os22.homedir(), ".node9");
10514
+ const node9Dir = path29.join(os22.homedir(), ".node9");
10207
10515
  if (fs26.existsSync(node9Dir)) {
10208
- const confirmed = await confirm3({
10516
+ const confirmed = await confirm2({
10209
10517
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
10210
10518
  default: false
10211
10519
  });
@@ -10213,28 +10521,28 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
10213
10521
  fs26.rmSync(node9Dir, { recursive: true });
10214
10522
  if (fs26.existsSync(node9Dir)) {
10215
10523
  console.error(
10216
- chalk17.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
10524
+ chalk18.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
10217
10525
  );
10218
10526
  } else {
10219
- console.log(chalk17.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
10527
+ console.log(chalk18.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
10220
10528
  }
10221
10529
  } else {
10222
- console.log(chalk17.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
10530
+ console.log(chalk18.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
10223
10531
  }
10224
10532
  } else {
10225
- console.log(chalk17.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
10533
+ console.log(chalk18.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
10226
10534
  }
10227
10535
  } else {
10228
10536
  console.log(
10229
- chalk17.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
10537
+ chalk18.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
10230
10538
  );
10231
10539
  }
10232
10540
  if (teardownFailed) {
10233
- console.error(chalk17.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
10541
+ console.error(chalk18.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
10234
10542
  process.exit(1);
10235
10543
  }
10236
- console.log(chalk17.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
10237
- console.log(chalk17.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
10544
+ console.log(chalk18.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
10545
+ console.log(chalk18.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
10238
10546
  });
10239
10547
  registerDoctorCommand(program, version);
10240
10548
  program.command("explain").description(
@@ -10247,7 +10555,7 @@ program.command("explain").description(
10247
10555
  try {
10248
10556
  args = JSON.parse(trimmed);
10249
10557
  } catch {
10250
- console.error(chalk17.red(`
10558
+ console.error(chalk18.red(`
10251
10559
  \u274C Invalid JSON: ${trimmed}
10252
10560
  `));
10253
10561
  process.exit(1);
@@ -10258,54 +10566,54 @@ program.command("explain").description(
10258
10566
  }
10259
10567
  const result = await explainPolicy(tool, args);
10260
10568
  console.log("");
10261
- console.log(chalk17.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
10569
+ console.log(chalk18.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
10262
10570
  console.log("");
10263
- console.log(` ${chalk17.bold("Tool:")} ${chalk17.white(result.tool)}`);
10571
+ console.log(` ${chalk18.bold("Tool:")} ${chalk18.white(result.tool)}`);
10264
10572
  if (argsRaw) {
10265
10573
  const preview = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
10266
- console.log(` ${chalk17.bold("Input:")} ${chalk17.gray(preview)}`);
10574
+ console.log(` ${chalk18.bold("Input:")} ${chalk18.gray(preview)}`);
10267
10575
  }
10268
10576
  console.log("");
10269
- console.log(chalk17.bold("Config Sources (Waterfall):"));
10577
+ console.log(chalk18.bold("Config Sources (Waterfall):"));
10270
10578
  for (const tier of result.waterfall) {
10271
- const num = chalk17.gray(` ${tier.tier}.`);
10579
+ const num = chalk18.gray(` ${tier.tier}.`);
10272
10580
  const label = tier.label.padEnd(16);
10273
10581
  let statusStr;
10274
10582
  if (tier.tier === 1) {
10275
- statusStr = chalk17.gray(tier.note ?? "");
10583
+ statusStr = chalk18.gray(tier.note ?? "");
10276
10584
  } else if (tier.status === "active") {
10277
- const loc = tier.path ? chalk17.gray(tier.path) : "";
10278
- const note = tier.note ? chalk17.gray(`(${tier.note})`) : "";
10279
- statusStr = chalk17.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
10585
+ const loc = tier.path ? chalk18.gray(tier.path) : "";
10586
+ const note = tier.note ? chalk18.gray(`(${tier.note})`) : "";
10587
+ statusStr = chalk18.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
10280
10588
  } else {
10281
- statusStr = chalk17.gray("\u25CB " + (tier.note ?? "not found"));
10589
+ statusStr = chalk18.gray("\u25CB " + (tier.note ?? "not found"));
10282
10590
  }
10283
- console.log(`${num} ${chalk17.white(label)} ${statusStr}`);
10591
+ console.log(`${num} ${chalk18.white(label)} ${statusStr}`);
10284
10592
  }
10285
10593
  console.log("");
10286
- console.log(chalk17.bold("Policy Evaluation:"));
10594
+ console.log(chalk18.bold("Policy Evaluation:"));
10287
10595
  for (const step of result.steps) {
10288
10596
  const isFinal = step.isFinal;
10289
10597
  let icon;
10290
- if (step.outcome === "allow") icon = chalk17.green(" \u2705");
10291
- else if (step.outcome === "review") icon = chalk17.red(" \u{1F534}");
10292
- else if (step.outcome === "skip") icon = chalk17.gray(" \u2500 ");
10293
- else icon = chalk17.gray(" \u25CB ");
10598
+ if (step.outcome === "allow") icon = chalk18.green(" \u2705");
10599
+ else if (step.outcome === "review") icon = chalk18.red(" \u{1F534}");
10600
+ else if (step.outcome === "skip") icon = chalk18.gray(" \u2500 ");
10601
+ else icon = chalk18.gray(" \u25CB ");
10294
10602
  const name = step.name.padEnd(18);
10295
- const nameStr = isFinal ? chalk17.white.bold(name) : chalk17.white(name);
10296
- const detail = isFinal ? chalk17.white(step.detail) : chalk17.gray(step.detail);
10297
- const arrow = isFinal ? chalk17.yellow(" \u2190 STOP") : "";
10603
+ const nameStr = isFinal ? chalk18.white.bold(name) : chalk18.white(name);
10604
+ const detail = isFinal ? chalk18.white(step.detail) : chalk18.gray(step.detail);
10605
+ const arrow = isFinal ? chalk18.yellow(" \u2190 STOP") : "";
10298
10606
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
10299
10607
  }
10300
10608
  console.log("");
10301
10609
  if (result.decision === "allow") {
10302
- console.log(chalk17.green.bold(" Decision: \u2705 ALLOW") + chalk17.gray(" \u2014 no approval needed"));
10610
+ console.log(chalk18.green.bold(" Decision: \u2705 ALLOW") + chalk18.gray(" \u2014 no approval needed"));
10303
10611
  } else {
10304
10612
  console.log(
10305
- chalk17.red.bold(" Decision: \u{1F534} REVIEW") + chalk17.gray(" \u2014 human approval required")
10613
+ chalk18.red.bold(" Decision: \u{1F534} REVIEW") + chalk18.gray(" \u2014 human approval required")
10306
10614
  );
10307
10615
  if (result.blockedByLabel) {
10308
- console.log(chalk17.gray(` Reason: ${result.blockedByLabel}`));
10616
+ console.log(chalk18.gray(` Reason: ${result.blockedByLabel}`));
10309
10617
  }
10310
10618
  }
10311
10619
  console.log("");
@@ -10319,7 +10627,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
10319
10627
  try {
10320
10628
  await startTail2(options);
10321
10629
  } catch (err) {
10322
- console.error(chalk17.red(`\u274C ${err instanceof Error ? err.message : String(err)}`));
10630
+ console.error(chalk18.red(`\u274C ${err instanceof Error ? err.message : String(err)}`));
10323
10631
  process.exit(1);
10324
10632
  }
10325
10633
  });
@@ -10335,7 +10643,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
10335
10643
  const ms = parseDuration(options.duration);
10336
10644
  if (ms === null) {
10337
10645
  console.error(
10338
- chalk17.red(`
10646
+ chalk18.red(`
10339
10647
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
10340
10648
  `)
10341
10649
  );
@@ -10343,20 +10651,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
10343
10651
  }
10344
10652
  pauseNode9(ms, options.duration);
10345
10653
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
10346
- console.log(chalk17.yellow(`
10654
+ console.log(chalk18.yellow(`
10347
10655
  \u23F8 Node9 paused until ${expiresAt}`));
10348
- console.log(chalk17.gray(` All tool calls will be allowed without review.`));
10349
- console.log(chalk17.gray(` Run "node9 resume" to re-enable early.
10656
+ console.log(chalk18.gray(` All tool calls will be allowed without review.`));
10657
+ console.log(chalk18.gray(` Run "node9 resume" to re-enable early.
10350
10658
  `));
10351
10659
  });
10352
10660
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
10353
10661
  const { paused } = checkPause();
10354
10662
  if (!paused) {
10355
- console.log(chalk17.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
10663
+ console.log(chalk18.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
10356
10664
  return;
10357
10665
  }
10358
10666
  resumeNode9();
10359
- console.log(chalk17.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
10667
+ console.log(chalk18.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
10360
10668
  });
10361
10669
  var HOOK_BASED_AGENTS = {
10362
10670
  claude: "claude",
@@ -10369,15 +10677,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
10369
10677
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
10370
10678
  const target = HOOK_BASED_AGENTS[firstArg2];
10371
10679
  console.error(
10372
- chalk17.yellow(`
10680
+ chalk18.yellow(`
10373
10681
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
10374
10682
  );
10375
- console.error(chalk17.white(`
10683
+ console.error(chalk18.white(`
10376
10684
  "${target}" uses its own hook system. Use:`));
10377
10685
  console.error(
10378
- chalk17.green(` node9 addto ${target} `) + chalk17.gray("# one-time setup")
10686
+ chalk18.green(` node9 addto ${target} `) + chalk18.gray("# one-time setup")
10379
10687
  );
10380
- console.error(chalk17.green(` ${target} `) + chalk17.gray("# run normally"));
10688
+ console.error(chalk18.green(` ${target} `) + chalk18.gray("# run normally"));
10381
10689
  process.exit(1);
10382
10690
  }
10383
10691
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -10394,12 +10702,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
10394
10702
  }
10395
10703
  );
10396
10704
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
10397
- console.error(chalk17.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
10705
+ console.error(chalk18.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
10398
10706
  const daemonReady = await autoStartDaemonAndWait();
10399
10707
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
10400
10708
  }
10401
10709
  if (result.noApprovalMechanism && process.stdout.isTTY) {
10402
- const approved = await confirm3({
10710
+ const approved = await confirm2({
10403
10711
  message: `\u{1F6E1}\uFE0F Node9: Allow "${fullCommand}"?`,
10404
10712
  default: false
10405
10713
  });
@@ -10407,12 +10715,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
10407
10715
  }
10408
10716
  if (!result.approved) {
10409
10717
  console.error(
10410
- chalk17.red(`
10718
+ chalk18.red(`
10411
10719
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
10412
10720
  );
10413
10721
  process.exit(1);
10414
10722
  }
10415
- console.error(chalk17.green("\n\u2705 Approved \u2014 running command...\n"));
10723
+ console.error(chalk18.green("\n\u2705 Approved \u2014 running command...\n"));
10416
10724
  await runProxy(fullCommand);
10417
10725
  } else {
10418
10726
  program.help();
@@ -10427,7 +10735,7 @@ if (process.argv[2] !== "daemon") {
10427
10735
  const isCheckHook = process.argv[2] === "check";
10428
10736
  if (isCheckHook) {
10429
10737
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
10430
- const logPath = path27.join(os22.homedir(), ".node9", "hook-debug.log");
10738
+ const logPath = path29.join(os22.homedir(), ".node9", "hook-debug.log");
10431
10739
  const msg = reason instanceof Error ? reason.message : String(reason);
10432
10740
  fs26.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
10433
10741
  `);