@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.js CHANGED
@@ -160,8 +160,8 @@ function sanitizeConfig(raw) {
160
160
  }
161
161
  }
162
162
  const lines = result.error.issues.map((issue) => {
163
- const path28 = issue.path.length > 0 ? issue.path.join(".") : "root";
164
- return ` \u2022 ${path28}: ${issue.message}`;
163
+ const path30 = issue.path.length > 0 ? issue.path.join(".") : "root";
164
+ return ` \u2022 ${path30}: ${issue.message}`;
165
165
  });
166
166
  return {
167
167
  sanitized,
@@ -863,7 +863,9 @@ var init_config = __esm({
863
863
  {
864
864
  field: "command",
865
865
  op: "matches",
866
- value: "rm\\b.*(-[rRfF]*[rR][rRfF]*|--recursive)"
866
+ // Require the recursive flag to be preceded by whitespace so that
867
+ // filenames containing "-r" (e.g. "ai-review.yml") don't false-positive.
868
+ value: "rm\\b.*\\s(-[rRfF]*[rR][rRfF]*|--recursive)(\\s|$)"
867
869
  },
868
870
  {
869
871
  field: "command",
@@ -1772,9 +1774,9 @@ function matchesPattern(text, patterns) {
1772
1774
  const withoutDotSlash = text.replace(/^\.\//, "");
1773
1775
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1774
1776
  }
1775
- function getNestedValue(obj, path28) {
1777
+ function getNestedValue(obj, path30) {
1776
1778
  if (!obj || typeof obj !== "object") return null;
1777
- return path28.split(".").reduce((prev, curr) => prev?.[curr], obj);
1779
+ return path30.split(".").reduce((prev, curr) => prev?.[curr], obj);
1778
1780
  }
1779
1781
  function shouldSnapshot(toolName, args, config) {
1780
1782
  if (!config.settings.enableUndo) return false;
@@ -3019,6 +3021,33 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
3019
3021
  async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
3020
3022
  const controller = new AbortController();
3021
3023
  const timeout = setTimeout(() => controller.abort(), 1e4);
3024
+ if (!creds.apiKey) throw new Error("Node9 API Key is missing");
3025
+ let ciContext;
3026
+ if (process.env.CI) {
3027
+ try {
3028
+ const ciContextPath = import_path13.default.join(import_os9.default.homedir(), ".node9", "ci-context.json");
3029
+ const stats = import_fs10.default.statSync(ciContextPath);
3030
+ if (stats.size > 1e4) throw new Error("ci-context.json exceeds 10 KB");
3031
+ const raw = import_fs10.default.readFileSync(ciContextPath, "utf8");
3032
+ const parsed = JSON.parse(raw);
3033
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
3034
+ throw new Error("ci-context.json is not a plain object");
3035
+ }
3036
+ const p = parsed;
3037
+ ciContext = {
3038
+ tests_after: p["tests_after"],
3039
+ files_changed: p["files_changed"],
3040
+ issues_found: p["issues_found"],
3041
+ issues_fixed: p["issues_fixed"],
3042
+ github_repository: p["github_repository"],
3043
+ github_head_ref: p["github_head_ref"],
3044
+ iteration: p["iteration"],
3045
+ draft_pr_number: p["draft_pr_number"],
3046
+ draft_pr_url: p["draft_pr_url"]
3047
+ };
3048
+ } catch {
3049
+ }
3050
+ }
3022
3051
  try {
3023
3052
  const response = await fetch(creds.apiUrl, {
3024
3053
  method: "POST",
@@ -3033,7 +3062,8 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
3033
3062
  cwd: process.cwd(),
3034
3063
  platform: import_os9.default.platform()
3035
3064
  },
3036
- ...riskMetadata && { riskMetadata }
3065
+ ...riskMetadata && { riskMetadata },
3066
+ ...ciContext && { ciContext }
3037
3067
  }),
3038
3068
  signal: controller.signal
3039
3069
  });
@@ -3059,12 +3089,17 @@ async function pollNode9SaaS(requestId, creds, signal) {
3059
3089
  });
3060
3090
  clearTimeout(pollTimer);
3061
3091
  if (!statusRes.ok) continue;
3062
- const { status, reason } = await statusRes.json();
3092
+ const statusBody = await statusRes.json();
3093
+ const { status } = statusBody;
3063
3094
  if (status === "APPROVED") {
3064
- return { approved: true, reason };
3095
+ return { approved: true, reason: statusBody.reason };
3065
3096
  }
3066
3097
  if (status === "DENIED" || status === "AUTO_BLOCKED" || status === "TIMED_OUT") {
3067
- return { approved: false, reason };
3098
+ return { approved: false, reason: statusBody.reason };
3099
+ }
3100
+ if (status === "FIX") {
3101
+ const feedbackText = statusBody.feedbackText ?? statusBody.reason ?? "Run again with feedback.";
3102
+ return { approved: false, reason: feedbackText };
3068
3103
  }
3069
3104
  } catch {
3070
3105
  }
@@ -3101,12 +3136,13 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
3101
3136
  );
3102
3137
  }
3103
3138
  }
3104
- var import_fs10, import_os9;
3139
+ var import_fs10, import_os9, import_path13;
3105
3140
  var init_cloud = __esm({
3106
3141
  "src/auth/cloud.ts"() {
3107
3142
  "use strict";
3108
3143
  import_fs10 = __toESM(require("fs"));
3109
3144
  import_os9 = __toESM(require("os"));
3145
+ import_path13 = __toESM(require("path"));
3110
3146
  init_audit();
3111
3147
  }
3112
3148
  });
@@ -3298,7 +3334,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3298
3334
  const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
3299
3335
  if (policyResult.decision === "allow") {
3300
3336
  if (approvers.cloud && creds?.apiKey)
3301
- auditLocalAllow(toolName, args, "local-policy", creds, meta);
3337
+ await auditLocalAllow(toolName, args, "local-policy", creds, meta);
3302
3338
  if (!isManual) appendLocalAudit(toolName, args, "allow", "local-policy", meta, hashAuditArgs);
3303
3339
  return { approved: true, checkedBy: "local-policy" };
3304
3340
  }
@@ -3319,9 +3355,16 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3319
3355
  if (predicatesMet && policyResult.recoveryCommand) {
3320
3356
  statefulRecoveryCommand = policyResult.recoveryCommand;
3321
3357
  }
3358
+ } else if (isDaemonRunning() && !isTestEnv2) {
3359
+ if (!isManual)
3360
+ appendLocalAudit(toolName, args, "deny", "smart-rule-block", meta, hashAuditArgs);
3361
+ if (approvers.cloud && creds?.apiKey)
3362
+ auditLocalAllow(toolName, args, "smart-rule-block", creds, meta);
3322
3363
  } else {
3323
3364
  if (!isManual)
3324
3365
  appendLocalAudit(toolName, args, "deny", "smart-rule-block", meta, hashAuditArgs);
3366
+ if (approvers.cloud && creds?.apiKey)
3367
+ auditLocalAllow(toolName, args, "smart-rule-block", creds, meta);
3325
3368
  return {
3326
3369
  approved: false,
3327
3370
  reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
@@ -5344,12 +5387,12 @@ var init_suggestion_tracker = __esm({
5344
5387
  });
5345
5388
 
5346
5389
  // src/daemon/taint-store.ts
5347
- var import_fs12, import_path14, DEFAULT_TTL_MS, TaintStore;
5390
+ var import_fs12, import_path15, DEFAULT_TTL_MS, TaintStore;
5348
5391
  var init_taint_store = __esm({
5349
5392
  "src/daemon/taint-store.ts"() {
5350
5393
  "use strict";
5351
5394
  import_fs12 = __toESM(require("fs"));
5352
- import_path14 = __toESM(require("path"));
5395
+ import_path15 = __toESM(require("path"));
5353
5396
  DEFAULT_TTL_MS = 60 * 60 * 1e3;
5354
5397
  TaintStore = class {
5355
5398
  records = /* @__PURE__ */ new Map();
@@ -5414,9 +5457,9 @@ var init_taint_store = __esm({
5414
5457
  /** Resolve to absolute path, falling back to path.resolve if file doesn't exist yet. */
5415
5458
  _resolve(filePath) {
5416
5459
  try {
5417
- return import_fs12.default.realpathSync.native(import_path14.default.resolve(filePath));
5460
+ return import_fs12.default.realpathSync.native(import_path15.default.resolve(filePath));
5418
5461
  } catch {
5419
- return import_path14.default.resolve(filePath);
5462
+ return import_path15.default.resolve(filePath);
5420
5463
  }
5421
5464
  }
5422
5465
  };
@@ -5567,7 +5610,7 @@ function markRejectionHandlerRegistered() {
5567
5610
  daemonRejectionHandlerRegistered = true;
5568
5611
  }
5569
5612
  function atomicWriteSync2(filePath, data, options) {
5570
- const dir = import_path15.default.dirname(filePath);
5613
+ const dir = import_path16.default.dirname(filePath);
5571
5614
  if (!import_fs13.default.existsSync(dir)) import_fs13.default.mkdirSync(dir, { recursive: true });
5572
5615
  const tmpPath = `${filePath}.${(0, import_crypto5.randomUUID)()}.tmp`;
5573
5616
  try {
@@ -5607,7 +5650,7 @@ function appendAuditLog(data) {
5607
5650
  decision: data.decision,
5608
5651
  source: "daemon"
5609
5652
  };
5610
- const dir = import_path15.default.dirname(AUDIT_LOG_FILE);
5653
+ const dir = import_path16.default.dirname(AUDIT_LOG_FILE);
5611
5654
  if (!import_fs13.default.existsSync(dir)) import_fs13.default.mkdirSync(dir, { recursive: true });
5612
5655
  import_fs13.default.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
5613
5656
  } catch {
@@ -5685,6 +5728,7 @@ function readBody(req) {
5685
5728
  });
5686
5729
  }
5687
5730
  function openBrowser(url) {
5731
+ if (process.env.NODE9_TESTING === "1") return;
5688
5732
  try {
5689
5733
  const args = process.platform === "darwin" ? ["open", url] : process.platform === "win32" ? ["cmd", "/c", "start", "", url] : ["xdg-open", url];
5690
5734
  (0, import_child_process3.spawn)(args[0], args.slice(1), { detached: true, stdio: "ignore" }).unref();
@@ -5811,13 +5855,13 @@ function startActivitySocket() {
5811
5855
  }
5812
5856
  });
5813
5857
  }
5814
- var import_net2, import_fs13, import_path15, import_os11, import_child_process3, import_crypto5, homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, pending, sseClients, suggestionTracker, suggestions, taintStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, SECRET_KEY_RE, WRITE_TOOL_NAMES;
5858
+ var import_net2, import_fs13, import_path16, import_os11, import_child_process3, import_crypto5, homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, pending, sseClients, suggestionTracker, suggestions, taintStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, SECRET_KEY_RE, WRITE_TOOL_NAMES;
5815
5859
  var init_state2 = __esm({
5816
5860
  "src/daemon/state.ts"() {
5817
5861
  "use strict";
5818
5862
  import_net2 = __toESM(require("net"));
5819
5863
  import_fs13 = __toESM(require("fs"));
5820
- import_path15 = __toESM(require("path"));
5864
+ import_path16 = __toESM(require("path"));
5821
5865
  import_os11 = __toESM(require("os"));
5822
5866
  import_child_process3 = require("child_process");
5823
5867
  import_crypto5 = require("crypto");
@@ -5827,13 +5871,13 @@ var init_state2 = __esm({
5827
5871
  init_session_counters();
5828
5872
  init_session_history();
5829
5873
  homeDir = import_os11.default.homedir();
5830
- DAEMON_PID_FILE = import_path15.default.join(homeDir, ".node9", "daemon.pid");
5831
- DECISIONS_FILE = import_path15.default.join(homeDir, ".node9", "decisions.json");
5832
- AUDIT_LOG_FILE = import_path15.default.join(homeDir, ".node9", "audit.log");
5833
- TRUST_FILE2 = import_path15.default.join(homeDir, ".node9", "trust.json");
5834
- GLOBAL_CONFIG_FILE = import_path15.default.join(homeDir, ".node9", "config.json");
5835
- CREDENTIALS_FILE = import_path15.default.join(homeDir, ".node9", "credentials.json");
5836
- INSIGHT_COUNTS_FILE = import_path15.default.join(homeDir, ".node9", "insight-counts.json");
5874
+ DAEMON_PID_FILE = import_path16.default.join(homeDir, ".node9", "daemon.pid");
5875
+ DECISIONS_FILE = import_path16.default.join(homeDir, ".node9", "decisions.json");
5876
+ AUDIT_LOG_FILE = import_path16.default.join(homeDir, ".node9", "audit.log");
5877
+ TRUST_FILE2 = import_path16.default.join(homeDir, ".node9", "trust.json");
5878
+ GLOBAL_CONFIG_FILE = import_path16.default.join(homeDir, ".node9", "config.json");
5879
+ CREDENTIALS_FILE = import_path16.default.join(homeDir, ".node9", "credentials.json");
5880
+ INSIGHT_COUNTS_FILE = import_path16.default.join(homeDir, ".node9", "insight-counts.json");
5837
5881
  pending = /* @__PURE__ */ new Map();
5838
5882
  sseClients = /* @__PURE__ */ new Set();
5839
5883
  suggestionTracker = new SuggestionTracker(3);
@@ -5851,7 +5895,7 @@ var init_state2 = __esm({
5851
5895
  "2h": 2 * 60 * 6e4
5852
5896
  };
5853
5897
  autoStarted = process.env.NODE9_AUTO_STARTED === "1";
5854
- ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path15.default.join(import_os11.default.tmpdir(), "node9-activity.sock");
5898
+ ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path16.default.join(import_os11.default.tmpdir(), "node9-activity.sock");
5855
5899
  ACTIVITY_RING_SIZE = 100;
5856
5900
  activityRing = [];
5857
5901
  SECRET_KEY_RE = /password|secret|token|key|apikey|credential|auth/i;
@@ -5893,7 +5937,7 @@ function patchConfig(configPath, patch) {
5893
5937
  ignored.push(patch.toolName);
5894
5938
  }
5895
5939
  }
5896
- const dir = import_path16.default.dirname(configPath);
5940
+ const dir = import_path17.default.dirname(configPath);
5897
5941
  import_fs14.default.mkdirSync(dir, { recursive: true });
5898
5942
  const tmp = configPath + ".node9-tmp";
5899
5943
  try {
@@ -5915,14 +5959,14 @@ function patchConfig(configPath, patch) {
5915
5959
  throw err;
5916
5960
  }
5917
5961
  }
5918
- var import_fs14, import_path16, import_os12, GLOBAL_CONFIG_PATH;
5962
+ var import_fs14, import_path17, import_os12, GLOBAL_CONFIG_PATH;
5919
5963
  var init_patch = __esm({
5920
5964
  "src/config/patch.ts"() {
5921
5965
  "use strict";
5922
5966
  import_fs14 = __toESM(require("fs"));
5923
- import_path16 = __toESM(require("path"));
5967
+ import_path17 = __toESM(require("path"));
5924
5968
  import_os12 = __toESM(require("os"));
5925
- GLOBAL_CONFIG_PATH = import_path16.default.join(import_os12.default.homedir(), ".node9", "config.json");
5969
+ GLOBAL_CONFIG_PATH = import_path17.default.join(import_os12.default.homedir(), ".node9", "config.json");
5926
5970
  }
5927
5971
  });
5928
5972
 
@@ -6105,7 +6149,7 @@ data: ${JSON.stringify(item.data)}
6105
6149
  status: "pending"
6106
6150
  });
6107
6151
  }
6108
- const projectCwd = typeof cwd === "string" && import_path17.default.isAbsolute(cwd) ? cwd : void 0;
6152
+ const projectCwd = typeof cwd === "string" && import_path18.default.isAbsolute(cwd) ? cwd : void 0;
6109
6153
  const projectConfig = getConfig(projectCwd);
6110
6154
  const browserEnabled = projectConfig.settings.approvers?.browser !== false;
6111
6155
  const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
@@ -6493,8 +6537,8 @@ data: ${JSON.stringify(item.data)}
6493
6537
  const body = await readBody(req);
6494
6538
  const data = body ? JSON.parse(body) : {};
6495
6539
  const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
6496
- const node9Dir = import_path17.default.dirname(GLOBAL_CONFIG_PATH);
6497
- if (!import_path17.default.resolve(configPath).startsWith(node9Dir + import_path17.default.sep)) {
6540
+ const node9Dir = import_path18.default.dirname(GLOBAL_CONFIG_PATH);
6541
+ if (!import_path18.default.resolve(configPath).startsWith(node9Dir + import_path18.default.sep)) {
6498
6542
  res.writeHead(400, { "Content-Type": "application/json" });
6499
6543
  return res.end(
6500
6544
  JSON.stringify({ error: "configPath must be within the node9 config directory" })
@@ -6671,13 +6715,13 @@ data: ${JSON.stringify(item.data)}
6671
6715
  }
6672
6716
  startActivitySocket();
6673
6717
  }
6674
- var import_http, import_fs15, import_path17, import_crypto6, import_child_process4, import_chalk2;
6718
+ var import_http, import_fs15, import_path18, import_crypto6, import_child_process4, import_chalk2;
6675
6719
  var init_server = __esm({
6676
6720
  "src/daemon/server.ts"() {
6677
6721
  "use strict";
6678
6722
  import_http = __toESM(require("http"));
6679
6723
  import_fs15 = __toESM(require("fs"));
6680
- import_path17 = __toESM(require("path"));
6724
+ import_path18 = __toESM(require("path"));
6681
6725
  import_crypto6 = require("crypto");
6682
6726
  import_child_process4 = require("child_process");
6683
6727
  import_chalk2 = __toESM(require("chalk"));
@@ -6759,27 +6803,27 @@ function formatBase(activity) {
6759
6803
  const toolName = activity.tool.slice(0, 16).padEnd(16);
6760
6804
  const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ");
6761
6805
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
6762
- return `${import_chalk16.default.gray(time)} ${icon} ${import_chalk16.default.white.bold(toolName)} ${import_chalk16.default.dim(argsPreview)}`;
6806
+ return `${import_chalk17.default.gray(time)} ${icon} ${import_chalk17.default.white.bold(toolName)} ${import_chalk17.default.dim(argsPreview)}`;
6763
6807
  }
6764
6808
  function renderResult(activity, result) {
6765
6809
  const base = formatBase(activity);
6766
6810
  let status;
6767
6811
  if (result.status === "allow") {
6768
- status = import_chalk16.default.green("\u2713 ALLOW");
6812
+ status = import_chalk17.default.green("\u2713 ALLOW");
6769
6813
  } else if (result.status === "dlp") {
6770
- status = import_chalk16.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
6814
+ status = import_chalk17.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
6771
6815
  } else {
6772
- status = import_chalk16.default.red("\u2717 BLOCK");
6816
+ status = import_chalk17.default.red("\u2717 BLOCK");
6773
6817
  }
6774
6818
  if (process.stdout.isTTY) {
6775
- import_readline3.default.clearLine(process.stdout, 0);
6776
- import_readline3.default.cursorTo(process.stdout, 0);
6819
+ import_readline4.default.clearLine(process.stdout, 0);
6820
+ import_readline4.default.cursorTo(process.stdout, 0);
6777
6821
  }
6778
6822
  console.log(`${base} ${status}`);
6779
6823
  }
6780
6824
  function renderPending(activity) {
6781
6825
  if (!process.stdout.isTTY) return;
6782
- process.stdout.write(`${formatBase(activity)} ${import_chalk16.default.yellow("\u25CF \u2026")}\r`);
6826
+ process.stdout.write(`${formatBase(activity)} ${import_chalk17.default.yellow("\u25CF \u2026")}\r`);
6783
6827
  }
6784
6828
  async function ensureDaemon() {
6785
6829
  let pidPort = null;
@@ -6788,7 +6832,7 @@ async function ensureDaemon() {
6788
6832
  const { port } = JSON.parse(import_fs24.default.readFileSync(PID_FILE, "utf-8"));
6789
6833
  pidPort = port;
6790
6834
  } catch {
6791
- console.error(import_chalk16.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
6835
+ console.error(import_chalk17.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
6792
6836
  }
6793
6837
  }
6794
6838
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -6799,7 +6843,7 @@ async function ensureDaemon() {
6799
6843
  if (res.ok) return checkPort;
6800
6844
  } catch {
6801
6845
  }
6802
- console.log(import_chalk16.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
6846
+ console.log(import_chalk17.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
6803
6847
  const child = (0, import_child_process13.spawn)(process.execPath, [process.argv[1], "daemon"], {
6804
6848
  detached: true,
6805
6849
  stdio: "ignore",
@@ -6816,7 +6860,7 @@ async function ensureDaemon() {
6816
6860
  } catch {
6817
6861
  }
6818
6862
  }
6819
- console.error(import_chalk16.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
6863
+ console.error(import_chalk17.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
6820
6864
  process.exit(1);
6821
6865
  }
6822
6866
  function postDecisionHttp(id, decision, csrfToken, port, opts) {
@@ -6858,23 +6902,23 @@ function buildCardLines(req, localCount = 0) {
6858
6902
  const blockedBy = req.riskMetadata?.blockedByLabel ?? "Policy rule";
6859
6903
  const lines = [
6860
6904
  ``,
6861
- `${BOLD}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET}`,
6862
- `${CYAN}\u2551${RESET} Tool: ${BOLD}${req.toolName}${RESET}`,
6863
- `${CYAN}\u2551${RESET} Reason: ${tierLabel} \u2014 ${blockedBy}${RESET}`
6905
+ `${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
6906
+ `${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}`,
6907
+ `${CYAN}\u2551${RESET2} Reason: ${tierLabel} \u2014 ${blockedBy}${RESET2}`
6864
6908
  ];
6865
6909
  if (req.riskMetadata?.ruleName && blockedBy.includes("Taint")) {
6866
- lines.push(`${CYAN}\u2551${RESET} ${YELLOW}\u26A0 ${req.riskMetadata.ruleName}${RESET}`);
6910
+ lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u26A0 ${req.riskMetadata.ruleName}${RESET2}`);
6867
6911
  }
6868
- lines.push(`${CYAN}\u2551${RESET} Args: ${GRAY}${argsPreview}${RESET}`);
6912
+ lines.push(`${CYAN}\u2551${RESET2} Args: ${GRAY}${argsPreview}${RESET2}`);
6869
6913
  if (localCount >= 2) {
6870
6914
  lines.push(
6871
- `${CYAN}\u2551${RESET} ${YELLOW}\u{1F4A1}${RESET} Approved ${localCount}\xD7 before \u2014 ${BOLD}[a]${RESET}${YELLOW} creates a permanent rule${RESET}`
6915
+ `${CYAN}\u2551${RESET2} ${YELLOW}\u{1F4A1}${RESET2} Approved ${localCount}\xD7 before \u2014 ${BOLD2}[a]${RESET2}${YELLOW} creates a permanent rule${RESET2}`
6872
6916
  );
6873
6917
  }
6874
6918
  lines.push(
6875
- `${CYAN}\u255A${RESET}`,
6919
+ `${CYAN}\u255A${RESET2}`,
6876
6920
  ``,
6877
- ` ${BOLD}${GREEN}[\u21B5/y]${RESET} Allow ${BOLD}${RED}[n]${RESET} Deny ${BOLD}${YELLOW}[a]${RESET} Always Allow ${BOLD}${CYAN}[t]${RESET} Trust 30m`,
6921
+ ` ${BOLD2}${GREEN}[\u21B5/y]${RESET2} Allow ${BOLD2}${RED}[n]${RESET2} Deny ${BOLD2}${YELLOW}[a]${RESET2} Always Allow ${BOLD2}${CYAN}[t]${RESET2} Trust 30m`,
6878
6922
  ``
6879
6923
  );
6880
6924
  return lines;
@@ -6884,23 +6928,23 @@ function buildRecoveryCardLines(req) {
6884
6928
  const command = typeof argsObj?.command === "string" ? argsObj.command : JSON.stringify(req.args ?? {}).replace(/\s+/g, " ").slice(0, 60);
6885
6929
  const ruleName = req.riskMetadata?.ruleName?.replace(/^Smart Rule:\s*/i, "") ?? "policy rule";
6886
6930
  const recoveryCommand = req.recoveryCommand;
6887
- const interactiveLines = req.viewOnly ? [` ${GRAY}\u2192 Awaiting decision from interactive terminal...${RESET}`] : [
6888
- ` ${BOLD}${GREEN}[1]${RESET} Allow anyway ${GRAY}(override policy)${RESET}`,
6889
- ` ${BOLD}${YELLOW}[2]${RESET} Redirect AI: "Run '${recoveryCommand}' first, then retry"`,
6890
- ` ${BOLD}${RED}[3]${RESET} Deny & stop ${GRAY}(hard block)${RESET}`,
6931
+ const interactiveLines = req.viewOnly ? [` ${GRAY}\u2192 Awaiting decision from interactive terminal...${RESET2}`] : [
6932
+ ` ${BOLD2}${GREEN}[1]${RESET2} Allow anyway ${GRAY}(override policy)${RESET2}`,
6933
+ ` ${BOLD2}${YELLOW}[2]${RESET2} Redirect AI: "Run '${recoveryCommand}' first, then retry"`,
6934
+ ` ${BOLD2}${RED}[3]${RESET2} Deny & stop ${GRAY}(hard block)${RESET2}`,
6891
6935
  ``,
6892
- ` ${GRAY}[Timeout: auto-deny]${RESET}`,
6936
+ ` ${GRAY}[Timeout: auto-deny]${RESET2}`,
6893
6937
  ` Select [1-3]: `
6894
6938
  ];
6895
6939
  return [
6896
6940
  ``,
6897
- `${BOLD}${CYAN}${DIVIDER}${RESET}`,
6898
- `\u{1F6E1}\uFE0F ${BOLD}NODE9 STATE GUARD:${RESET} '${BOLD}${command}${RESET}'`,
6899
- `${YELLOW}\u26A0\uFE0F Rule: ${ruleName}${RESET}`,
6900
- `${CYAN}${DIVIDER}${RESET}`,
6901
- ...!req.viewOnly ? [`${BOLD}What would you like to do?${RESET}`, ``] : [],
6941
+ `${BOLD2}${CYAN}${DIVIDER}${RESET2}`,
6942
+ `\u{1F6E1}\uFE0F ${BOLD2}NODE9 STATE GUARD:${RESET2} '${BOLD2}${command}${RESET2}'`,
6943
+ `${YELLOW}\u26A0\uFE0F Rule: ${ruleName}${RESET2}`,
6944
+ `${CYAN}${DIVIDER}${RESET2}`,
6945
+ ...!req.viewOnly ? [`${BOLD2}What would you like to do?${RESET2}`, ``] : [],
6902
6946
  ...interactiveLines,
6903
- `${CYAN}${DIVIDER}${RESET}`,
6947
+ `${CYAN}${DIVIDER}${RESET2}`,
6904
6948
  ``
6905
6949
  ];
6906
6950
  }
@@ -6930,7 +6974,7 @@ async function startTail(options = {}) {
6930
6974
  req2.end();
6931
6975
  });
6932
6976
  if (result.ok) {
6933
- console.log(import_chalk16.default.green("\u2713 Flight Recorder buffer cleared."));
6977
+ console.log(import_chalk17.default.green("\u2713 Flight Recorder buffer cleared."));
6934
6978
  } else if (result.code === "ECONNREFUSED") {
6935
6979
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
6936
6980
  } else if (result.code === "ETIMEDOUT") {
@@ -6949,10 +6993,10 @@ async function startTail(options = {}) {
6949
6993
  let cancelActiveCard = null;
6950
6994
  const localAllowCounts = /* @__PURE__ */ new Map();
6951
6995
  const canApprove = process.stdout.isTTY && process.stdin.isTTY;
6952
- if (canApprove) import_readline3.default.emitKeypressEvents(process.stdin);
6996
+ if (canApprove) import_readline4.default.emitKeypressEvents(process.stdin);
6953
6997
  function clearCard() {
6954
6998
  if (cardLineCount > 0) {
6955
- import_readline3.default.moveCursor(process.stdout, 0, -cardLineCount);
6999
+ import_readline4.default.moveCursor(process.stdout, 0, -cardLineCount);
6956
7000
  process.stdout.write(ERASE_DOWN);
6957
7001
  cardLineCount = 0;
6958
7002
  }
@@ -7002,8 +7046,8 @@ async function startTail(options = {}) {
7002
7046
  localAllowCounts.get(req2.toolName) ?? 0
7003
7047
  )
7004
7048
  );
7005
- const decisionStamp = action === "always-allow" ? import_chalk16.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk16.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk16.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk16.default.yellow("\u21A9 REDIRECT AI") : import_chalk16.default.red("\u2717 DENIED");
7006
- stampedLines.push(` ${BOLD}\u2192${RESET} ${decisionStamp} ${GRAY}(terminal)${RESET}`, ``);
7049
+ const decisionStamp = action === "always-allow" ? import_chalk17.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk17.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk17.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk17.default.yellow("\u21A9 REDIRECT AI") : import_chalk17.default.red("\u2717 DENIED");
7050
+ stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
7007
7051
  for (const line of stampedLines) process.stdout.write(line + "\n");
7008
7052
  process.stdout.write(SHOW_CURSOR);
7009
7053
  cardLineCount = 0;
@@ -7031,7 +7075,7 @@ async function startTail(options = {}) {
7031
7075
  postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err) => {
7032
7076
  try {
7033
7077
  import_fs24.default.appendFileSync(
7034
- import_path25.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log"),
7078
+ import_path27.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log"),
7035
7079
  `[tail] POST /decision failed: ${String(err)}
7036
7080
  `
7037
7081
  );
@@ -7053,8 +7097,8 @@ async function startTail(options = {}) {
7053
7097
  );
7054
7098
  const stampedLines = buildCardLines(req2, priorCount);
7055
7099
  if (externalDecision) {
7056
- const source = externalDecision === "allow" ? import_chalk16.default.green("\u2713 ALLOWED") : import_chalk16.default.red("\u2717 DENIED");
7057
- stampedLines.push(` ${BOLD}\u2192${RESET} ${source} ${GRAY}(external)${RESET}`, ``);
7100
+ const source = externalDecision === "allow" ? import_chalk17.default.green("\u2713 ALLOWED") : import_chalk17.default.red("\u2717 DENIED");
7101
+ stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
7058
7102
  }
7059
7103
  for (const line of stampedLines) process.stdout.write(line + "\n");
7060
7104
  process.stdout.write(SHOW_CURSOR);
@@ -7112,41 +7156,41 @@ async function startTail(options = {}) {
7112
7156
  }
7113
7157
  } catch {
7114
7158
  }
7115
- console.log(import_chalk16.default.cyan.bold(`
7116
- \u{1F6F0}\uFE0F Node9 tail `) + import_chalk16.default.dim(`\u2192 ${dashboardUrl}`));
7159
+ console.log(import_chalk17.default.cyan.bold(`
7160
+ \u{1F6F0}\uFE0F Node9 tail `) + import_chalk17.default.dim(`\u2192 ${dashboardUrl}`));
7117
7161
  if (canApprove) {
7118
7162
  console.log(
7119
- import_chalk16.default.dim("Interactive approvals: [\u21B5/y] Allow [n] Deny [a] Always Allow [t] Trust 30m")
7163
+ import_chalk17.default.dim("Interactive approvals: [\u21B5/y] Allow [n] Deny [a] Always Allow [t] Trust 30m")
7120
7164
  );
7121
7165
  }
7122
7166
  if (options.history) {
7123
- console.log(import_chalk16.default.dim("Showing history + live events. Press Ctrl+C to exit.\n"));
7167
+ console.log(import_chalk17.default.dim("Showing history + live events. Press Ctrl+C to exit.\n"));
7124
7168
  } else {
7125
7169
  console.log(
7126
- import_chalk16.default.dim("Showing live events only. Use --history to include past. Press Ctrl+C to exit.\n")
7170
+ import_chalk17.default.dim("Showing live events only. Use --history to include past. Press Ctrl+C to exit.\n")
7127
7171
  );
7128
7172
  }
7129
7173
  process.on("SIGINT", () => {
7130
7174
  clearCard();
7131
7175
  process.stdout.write(SHOW_CURSOR);
7132
7176
  if (process.stdout.isTTY) {
7133
- import_readline3.default.clearLine(process.stdout, 0);
7134
- import_readline3.default.cursorTo(process.stdout, 0);
7177
+ import_readline4.default.clearLine(process.stdout, 0);
7178
+ import_readline4.default.cursorTo(process.stdout, 0);
7135
7179
  }
7136
- console.log(import_chalk16.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
7180
+ console.log(import_chalk17.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
7137
7181
  process.exit(0);
7138
7182
  });
7139
7183
  const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
7140
7184
  const req = import_http2.default.get(sseUrl, (res) => {
7141
7185
  if (res.statusCode !== 200) {
7142
- console.error(import_chalk16.default.red(`Failed to connect: HTTP ${res.statusCode}`));
7186
+ console.error(import_chalk17.default.red(`Failed to connect: HTTP ${res.statusCode}`));
7143
7187
  process.exit(1);
7144
7188
  }
7145
7189
  let currentEvent = "";
7146
7190
  let currentData = "";
7147
7191
  res.on("error", () => {
7148
7192
  });
7149
- const rl = import_readline3.default.createInterface({ input: res, crlfDelay: Infinity });
7193
+ const rl = import_readline4.default.createInterface({ input: res, crlfDelay: Infinity });
7150
7194
  rl.on("error", () => {
7151
7195
  });
7152
7196
  rl.on("line", (line) => {
@@ -7166,10 +7210,10 @@ async function startTail(options = {}) {
7166
7210
  clearCard();
7167
7211
  process.stdout.write(SHOW_CURSOR);
7168
7212
  if (process.stdout.isTTY) {
7169
- import_readline3.default.clearLine(process.stdout, 0);
7170
- import_readline3.default.cursorTo(process.stdout, 0);
7213
+ import_readline4.default.clearLine(process.stdout, 0);
7214
+ import_readline4.default.cursorTo(process.stdout, 0);
7171
7215
  }
7172
- console.log(import_chalk16.default.red("\n\u274C Daemon disconnected."));
7216
+ console.log(import_chalk17.default.red("\n\u274C Daemon disconnected."));
7173
7217
  process.exit(1);
7174
7218
  });
7175
7219
  });
@@ -7255,26 +7299,26 @@ async function startTail(options = {}) {
7255
7299
  }
7256
7300
  req.on("error", (err) => {
7257
7301
  const msg = err.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err.message;
7258
- console.error(import_chalk16.default.red(`
7302
+ console.error(import_chalk17.default.red(`
7259
7303
  \u274C ${msg}`));
7260
7304
  process.exit(1);
7261
7305
  });
7262
7306
  }
7263
- var import_http2, import_chalk16, import_fs24, import_os20, import_path25, import_readline3, import_child_process13, PID_FILE, ICONS, RESET, BOLD, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, DIVIDER;
7307
+ var import_http2, import_chalk17, import_fs24, import_os20, import_path27, import_readline4, import_child_process13, PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, DIVIDER;
7264
7308
  var init_tail = __esm({
7265
7309
  "src/tui/tail.ts"() {
7266
7310
  "use strict";
7267
7311
  import_http2 = __toESM(require("http"));
7268
- import_chalk16 = __toESM(require("chalk"));
7312
+ import_chalk17 = __toESM(require("chalk"));
7269
7313
  import_fs24 = __toESM(require("fs"));
7270
7314
  import_os20 = __toESM(require("os"));
7271
- import_path25 = __toESM(require("path"));
7272
- import_readline3 = __toESM(require("readline"));
7315
+ import_path27 = __toESM(require("path"));
7316
+ import_readline4 = __toESM(require("readline"));
7273
7317
  import_child_process13 = require("child_process");
7274
7318
  init_daemon2();
7275
7319
  init_daemon();
7276
7320
  init_core();
7277
- PID_FILE = import_path25.default.join(import_os20.default.homedir(), ".node9", "daemon.pid");
7321
+ PID_FILE = import_path27.default.join(import_os20.default.homedir(), ".node9", "daemon.pid");
7278
7322
  ICONS = {
7279
7323
  bash: "\u{1F4BB}",
7280
7324
  shell: "\u{1F4BB}",
@@ -7292,8 +7336,8 @@ var init_tail = __esm({
7292
7336
  delete: "\u{1F5D1}\uFE0F",
7293
7337
  web: "\u{1F310}"
7294
7338
  };
7295
- RESET = "\x1B[0m";
7296
- BOLD = "\x1B[1m";
7339
+ RESET2 = "\x1B[0m";
7340
+ BOLD2 = "\x1B[1m";
7297
7341
  RED = "\x1B[31m";
7298
7342
  YELLOW = "\x1B[33m";
7299
7343
  CYAN = "\x1B[36m";
@@ -7362,19 +7406,19 @@ function queryDaemon() {
7362
7406
  });
7363
7407
  }
7364
7408
  function dim(s) {
7365
- return `${DIM}${s}${RESET2}`;
7409
+ return `${DIM}${s}${RESET3}`;
7366
7410
  }
7367
7411
  function bold(s) {
7368
- return `${BOLD2}${s}${RESET2}`;
7412
+ return `${BOLD3}${s}${RESET3}`;
7369
7413
  }
7370
7414
  function color(c, s) {
7371
- return `${c}${s}${RESET2}`;
7415
+ return `${c}${s}${RESET3}`;
7372
7416
  }
7373
7417
  function progressBar(pct, warnAt = 70, critAt = 85) {
7374
7418
  const filled = Math.round(Math.min(pct, 100) / 100 * BAR_WIDTH);
7375
7419
  const bar = BAR_FILLED.repeat(filled) + BAR_EMPTY.repeat(BAR_WIDTH - filled);
7376
7420
  const c = pct >= critAt ? RED2 : pct >= warnAt ? YELLOW2 : GREEN2;
7377
- return `${c}${bar}${RESET2}`;
7421
+ return `${c}${bar}${RESET3}`;
7378
7422
  }
7379
7423
  function formatTimeLeft(resetsAt) {
7380
7424
  if (!resetsAt) return "";
@@ -7415,7 +7459,7 @@ function countRulesInDir(rulesDir) {
7415
7459
  try {
7416
7460
  for (const entry of import_fs25.default.readdirSync(rulesDir, { withFileTypes: true })) {
7417
7461
  if (entry.isDirectory()) {
7418
- count += countRulesInDir(import_path26.default.join(rulesDir, entry.name));
7462
+ count += countRulesInDir(import_path28.default.join(rulesDir, entry.name));
7419
7463
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
7420
7464
  count++;
7421
7465
  }
@@ -7426,46 +7470,46 @@ function countRulesInDir(rulesDir) {
7426
7470
  }
7427
7471
  function isSamePath(a, b) {
7428
7472
  try {
7429
- return import_path26.default.resolve(a) === import_path26.default.resolve(b);
7473
+ return import_path28.default.resolve(a) === import_path28.default.resolve(b);
7430
7474
  } catch {
7431
7475
  return false;
7432
7476
  }
7433
7477
  }
7434
7478
  function countConfigs(cwd) {
7435
7479
  const homeDir2 = import_os21.default.homedir();
7436
- const claudeDir = import_path26.default.join(homeDir2, ".claude");
7480
+ const claudeDir = import_path28.default.join(homeDir2, ".claude");
7437
7481
  let claudeMdCount = 0;
7438
7482
  let rulesCount = 0;
7439
7483
  let hooksCount = 0;
7440
7484
  const userMcpServers = /* @__PURE__ */ new Set();
7441
7485
  const projectMcpServers = /* @__PURE__ */ new Set();
7442
- if (import_fs25.default.existsSync(import_path26.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
7443
- rulesCount += countRulesInDir(import_path26.default.join(claudeDir, "rules"));
7444
- const userSettings = import_path26.default.join(claudeDir, "settings.json");
7486
+ if (import_fs25.default.existsSync(import_path28.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
7487
+ rulesCount += countRulesInDir(import_path28.default.join(claudeDir, "rules"));
7488
+ const userSettings = import_path28.default.join(claudeDir, "settings.json");
7445
7489
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
7446
7490
  hooksCount += countHooksInFile(userSettings);
7447
- const userClaudeJson = import_path26.default.join(homeDir2, ".claude.json");
7491
+ const userClaudeJson = import_path28.default.join(homeDir2, ".claude.json");
7448
7492
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
7449
7493
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
7450
7494
  userMcpServers.delete(name);
7451
7495
  }
7452
7496
  if (cwd) {
7453
- if (import_fs25.default.existsSync(import_path26.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
7454
- if (import_fs25.default.existsSync(import_path26.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
7455
- const projectClaudeDir = import_path26.default.join(cwd, ".claude");
7497
+ if (import_fs25.default.existsSync(import_path28.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
7498
+ if (import_fs25.default.existsSync(import_path28.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
7499
+ const projectClaudeDir = import_path28.default.join(cwd, ".claude");
7456
7500
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
7457
7501
  if (!overlapsUserScope) {
7458
- if (import_fs25.default.existsSync(import_path26.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
7459
- rulesCount += countRulesInDir(import_path26.default.join(projectClaudeDir, "rules"));
7460
- const projSettings = import_path26.default.join(projectClaudeDir, "settings.json");
7502
+ if (import_fs25.default.existsSync(import_path28.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
7503
+ rulesCount += countRulesInDir(import_path28.default.join(projectClaudeDir, "rules"));
7504
+ const projSettings = import_path28.default.join(projectClaudeDir, "settings.json");
7461
7505
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
7462
7506
  hooksCount += countHooksInFile(projSettings);
7463
7507
  }
7464
- if (import_fs25.default.existsSync(import_path26.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
7465
- const localSettings = import_path26.default.join(projectClaudeDir, "settings.local.json");
7508
+ if (import_fs25.default.existsSync(import_path28.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
7509
+ const localSettings = import_path28.default.join(projectClaudeDir, "settings.local.json");
7466
7510
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
7467
7511
  hooksCount += countHooksInFile(localSettings);
7468
- const mcpJsonServers = getMcpServerNames(import_path26.default.join(cwd, ".mcp.json"));
7512
+ const mcpJsonServers = getMcpServerNames(import_path28.default.join(cwd, ".mcp.json"));
7469
7513
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
7470
7514
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
7471
7515
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -7578,8 +7622,8 @@ async function main() {
7578
7622
  try {
7579
7623
  const cwd = stdin.cwd ?? process.cwd();
7580
7624
  for (const configPath of [
7581
- import_path26.default.join(cwd, "node9.config.json"),
7582
- import_path26.default.join(import_os21.default.homedir(), ".node9", "config.json")
7625
+ import_path28.default.join(cwd, "node9.config.json"),
7626
+ import_path28.default.join(import_os21.default.homedir(), ".node9", "config.json")
7583
7627
  ]) {
7584
7628
  if (!import_fs25.default.existsSync(configPath)) continue;
7585
7629
  const cfg = JSON.parse(import_fs25.default.readFileSync(configPath, "utf-8"));
@@ -7600,17 +7644,17 @@ async function main() {
7600
7644
  renderOffline();
7601
7645
  }
7602
7646
  }
7603
- var import_fs25, import_path26, import_os21, import_http3, RESET2, BOLD2, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH;
7647
+ var import_fs25, import_path28, import_os21, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH;
7604
7648
  var init_hud = __esm({
7605
7649
  "src/cli/hud.ts"() {
7606
7650
  "use strict";
7607
7651
  import_fs25 = __toESM(require("fs"));
7608
- import_path26 = __toESM(require("path"));
7652
+ import_path28 = __toESM(require("path"));
7609
7653
  import_os21 = __toESM(require("os"));
7610
7654
  import_http3 = __toESM(require("http"));
7611
7655
  init_daemon();
7612
- RESET2 = "\x1B[0m";
7613
- BOLD2 = "\x1B[1m";
7656
+ RESET3 = "\x1B[0m";
7657
+ BOLD3 = "\x1B[1m";
7614
7658
  DIM = "\x1B[2m";
7615
7659
  RED2 = "\x1B[31m";
7616
7660
  GREEN2 = "\x1B[32m";
@@ -7631,7 +7675,7 @@ init_core();
7631
7675
 
7632
7676
  // src/setup.ts
7633
7677
  var import_fs11 = __toESM(require("fs"));
7634
- var import_path13 = __toESM(require("path"));
7678
+ var import_path14 = __toESM(require("path"));
7635
7679
  var import_os10 = __toESM(require("os"));
7636
7680
  var import_chalk = __toESM(require("chalk"));
7637
7681
  var import_prompts = require("@inquirer/prompts");
@@ -7657,7 +7701,7 @@ function readJson(filePath) {
7657
7701
  return null;
7658
7702
  }
7659
7703
  function writeJson(filePath, data) {
7660
- const dir = import_path13.default.dirname(filePath);
7704
+ const dir = import_path14.default.dirname(filePath);
7661
7705
  if (!import_fs11.default.existsSync(dir)) import_fs11.default.mkdirSync(dir, { recursive: true });
7662
7706
  import_fs11.default.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
7663
7707
  }
@@ -7667,8 +7711,8 @@ function isNode9Hook(cmd) {
7667
7711
  }
7668
7712
  function teardownClaude() {
7669
7713
  const homeDir2 = import_os10.default.homedir();
7670
- const hooksPath = import_path13.default.join(homeDir2, ".claude", "settings.json");
7671
- const mcpPath = import_path13.default.join(homeDir2, ".claude.json");
7714
+ const hooksPath = import_path14.default.join(homeDir2, ".claude", "settings.json");
7715
+ const mcpPath = import_path14.default.join(homeDir2, ".claude.json");
7672
7716
  let changed = false;
7673
7717
  const settings = readJson(hooksPath);
7674
7718
  if (settings?.hooks) {
@@ -7717,7 +7761,7 @@ function teardownClaude() {
7717
7761
  }
7718
7762
  function teardownGemini() {
7719
7763
  const homeDir2 = import_os10.default.homedir();
7720
- const settingsPath = import_path13.default.join(homeDir2, ".gemini", "settings.json");
7764
+ const settingsPath = import_path14.default.join(homeDir2, ".gemini", "settings.json");
7721
7765
  const settings = readJson(settingsPath);
7722
7766
  if (!settings) {
7723
7767
  console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
@@ -7756,7 +7800,7 @@ function teardownGemini() {
7756
7800
  }
7757
7801
  function teardownCursor() {
7758
7802
  const homeDir2 = import_os10.default.homedir();
7759
- const mcpPath = import_path13.default.join(homeDir2, ".cursor", "mcp.json");
7803
+ const mcpPath = import_path14.default.join(homeDir2, ".cursor", "mcp.json");
7760
7804
  const mcpConfig = readJson(mcpPath);
7761
7805
  if (!mcpConfig?.mcpServers) {
7762
7806
  console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
@@ -7783,8 +7827,8 @@ function teardownCursor() {
7783
7827
  }
7784
7828
  async function setupClaude() {
7785
7829
  const homeDir2 = import_os10.default.homedir();
7786
- const mcpPath = import_path13.default.join(homeDir2, ".claude.json");
7787
- const hooksPath = import_path13.default.join(homeDir2, ".claude", "settings.json");
7830
+ const mcpPath = import_path14.default.join(homeDir2, ".claude.json");
7831
+ const hooksPath = import_path14.default.join(homeDir2, ".claude", "settings.json");
7788
7832
  const claudeConfig = readJson(mcpPath) ?? {};
7789
7833
  const settings = readJson(hooksPath) ?? {};
7790
7834
  const servers = claudeConfig.mcpServers ?? {};
@@ -7859,7 +7903,7 @@ async function setupClaude() {
7859
7903
  }
7860
7904
  async function setupGemini() {
7861
7905
  const homeDir2 = import_os10.default.homedir();
7862
- const settingsPath = import_path13.default.join(homeDir2, ".gemini", "settings.json");
7906
+ const settingsPath = import_path14.default.join(homeDir2, ".gemini", "settings.json");
7863
7907
  const settings = readJson(settingsPath) ?? {};
7864
7908
  const servers = settings.mcpServers ?? {};
7865
7909
  let anythingChanged = false;
@@ -7954,14 +7998,14 @@ function detectAgents(homeDir2 = import_os10.default.homedir()) {
7954
7998
  }
7955
7999
  };
7956
8000
  return {
7957
- claude: exists(import_path13.default.join(homeDir2, ".claude")) || exists(import_path13.default.join(homeDir2, ".claude.json")),
7958
- gemini: exists(import_path13.default.join(homeDir2, ".gemini")),
7959
- cursor: exists(import_path13.default.join(homeDir2, ".cursor"))
8001
+ claude: exists(import_path14.default.join(homeDir2, ".claude")) || exists(import_path14.default.join(homeDir2, ".claude.json")),
8002
+ gemini: exists(import_path14.default.join(homeDir2, ".gemini")),
8003
+ cursor: exists(import_path14.default.join(homeDir2, ".cursor"))
7960
8004
  };
7961
8005
  }
7962
8006
  async function setupCursor() {
7963
8007
  const homeDir2 = import_os10.default.homedir();
7964
- const mcpPath = import_path13.default.join(homeDir2, ".cursor", "mcp.json");
8008
+ const mcpPath = import_path14.default.join(homeDir2, ".cursor", "mcp.json");
7965
8009
  const mcpConfig = readJson(mcpPath) ?? {};
7966
8010
  const servers = mcpConfig.mcpServers ?? {};
7967
8011
  let anythingChanged = false;
@@ -8016,7 +8060,7 @@ async function setupCursor() {
8016
8060
  }
8017
8061
  function setupHud() {
8018
8062
  const homeDir2 = import_os10.default.homedir();
8019
- const hooksPath = import_path13.default.join(homeDir2, ".claude", "settings.json");
8063
+ const hooksPath = import_path14.default.join(homeDir2, ".claude", "settings.json");
8020
8064
  const settings = readJson(hooksPath) ?? {};
8021
8065
  const hudCommand = fullPathCommand("hud");
8022
8066
  const statusLineObj = { type: "command", command: hudCommand };
@@ -8043,7 +8087,7 @@ function setupHud() {
8043
8087
  }
8044
8088
  function teardownHud() {
8045
8089
  const homeDir2 = import_os10.default.homedir();
8046
- const hooksPath = import_path13.default.join(homeDir2, ".claude", "settings.json");
8090
+ const hooksPath = import_path14.default.join(homeDir2, ".claude", "settings.json");
8047
8091
  const settings = readJson(hooksPath);
8048
8092
  if (!settings) {
8049
8093
  console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.claude/settings.json not found \u2014 nothing to remove"));
@@ -8063,11 +8107,11 @@ function teardownHud() {
8063
8107
 
8064
8108
  // src/cli.ts
8065
8109
  init_daemon2();
8066
- var import_chalk17 = __toESM(require("chalk"));
8110
+ var import_chalk18 = __toESM(require("chalk"));
8067
8111
  var import_fs26 = __toESM(require("fs"));
8068
- var import_path27 = __toESM(require("path"));
8112
+ var import_path29 = __toESM(require("path"));
8069
8113
  var import_os22 = __toESM(require("os"));
8070
- var import_prompts3 = require("@inquirer/prompts");
8114
+ var import_prompts2 = require("@inquirer/prompts");
8071
8115
 
8072
8116
  // src/utils/duration.ts
8073
8117
  function parseDuration(str) {
@@ -8258,6 +8302,7 @@ function openBrowserLocal() {
8258
8302
  }
8259
8303
  }
8260
8304
  async function autoStartDaemonAndWait() {
8305
+ if (process.env.NODE9_TESTING === "1") return false;
8261
8306
  try {
8262
8307
  const child = (0, import_child_process7.spawn)(process.execPath, [process.argv[1], "daemon"], {
8263
8308
  detached: true,
@@ -8289,7 +8334,7 @@ async function autoStartDaemonAndWait() {
8289
8334
  // src/cli/commands/check.ts
8290
8335
  var import_chalk5 = __toESM(require("chalk"));
8291
8336
  var import_fs18 = __toESM(require("fs"));
8292
- var import_path19 = __toESM(require("path"));
8337
+ var import_path20 = __toESM(require("path"));
8293
8338
  var import_os14 = __toESM(require("os"));
8294
8339
  init_orchestrator();
8295
8340
  init_daemon();
@@ -8300,10 +8345,10 @@ init_policy();
8300
8345
  var import_child_process8 = require("child_process");
8301
8346
  var import_crypto7 = __toESM(require("crypto"));
8302
8347
  var import_fs17 = __toESM(require("fs"));
8303
- var import_path18 = __toESM(require("path"));
8348
+ var import_path19 = __toESM(require("path"));
8304
8349
  var import_os13 = __toESM(require("os"));
8305
- var SNAPSHOT_STACK_PATH = import_path18.default.join(import_os13.default.homedir(), ".node9", "snapshots.json");
8306
- var UNDO_LATEST_PATH = import_path18.default.join(import_os13.default.homedir(), ".node9", "undo_latest.txt");
8350
+ var SNAPSHOT_STACK_PATH = import_path19.default.join(import_os13.default.homedir(), ".node9", "snapshots.json");
8351
+ var UNDO_LATEST_PATH = import_path19.default.join(import_os13.default.homedir(), ".node9", "undo_latest.txt");
8307
8352
  var MAX_SNAPSHOTS = 10;
8308
8353
  var GIT_TIMEOUT = 15e3;
8309
8354
  function readStack() {
@@ -8315,20 +8360,37 @@ function readStack() {
8315
8360
  return [];
8316
8361
  }
8317
8362
  function writeStack(stack) {
8318
- const dir = import_path18.default.dirname(SNAPSHOT_STACK_PATH);
8363
+ const dir = import_path19.default.dirname(SNAPSHOT_STACK_PATH);
8319
8364
  if (!import_fs17.default.existsSync(dir)) import_fs17.default.mkdirSync(dir, { recursive: true });
8320
8365
  import_fs17.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
8321
8366
  }
8367
+ function extractFilePath(args) {
8368
+ if (!args || typeof args !== "object") return null;
8369
+ const a = args;
8370
+ const fp = a.file_path ?? a.path ?? a.filename;
8371
+ return typeof fp === "string" ? fp : null;
8372
+ }
8322
8373
  function buildArgsSummary(tool, args) {
8374
+ const filePath = extractFilePath(args);
8375
+ if (filePath) return filePath;
8323
8376
  if (!args || typeof args !== "object") return "";
8324
8377
  const a = args;
8325
- const filePath = a.file_path ?? a.path ?? a.filename;
8326
- if (typeof filePath === "string") return filePath;
8327
8378
  const cmd = a.command ?? a.cmd;
8328
8379
  if (typeof cmd === "string") return cmd.slice(0, 80);
8329
8380
  const sql = a.sql ?? a.query;
8330
8381
  if (typeof sql === "string") return sql.slice(0, 80);
8331
- return tool;
8382
+ return "";
8383
+ }
8384
+ function findProjectRoot(filePath) {
8385
+ let dir = import_path19.default.dirname(filePath);
8386
+ while (true) {
8387
+ if (import_fs17.default.existsSync(import_path19.default.join(dir, ".git")) || import_fs17.default.existsSync(import_path19.default.join(dir, "package.json"))) {
8388
+ return dir;
8389
+ }
8390
+ const parent = import_path19.default.dirname(dir);
8391
+ if (parent === dir) return process.cwd();
8392
+ dir = parent;
8393
+ }
8332
8394
  }
8333
8395
  function normalizeCwdForHash(cwd) {
8334
8396
  let normalized;
@@ -8343,14 +8405,14 @@ function normalizeCwdForHash(cwd) {
8343
8405
  }
8344
8406
  function getShadowRepoDir(cwd) {
8345
8407
  const hash = import_crypto7.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
8346
- return import_path18.default.join(import_os13.default.homedir(), ".node9", "snapshots", hash);
8408
+ return import_path19.default.join(import_os13.default.homedir(), ".node9", "snapshots", hash);
8347
8409
  }
8348
8410
  function cleanOrphanedIndexFiles(shadowDir) {
8349
8411
  try {
8350
8412
  const cutoff = Date.now() - 6e4;
8351
8413
  for (const f of import_fs17.default.readdirSync(shadowDir)) {
8352
8414
  if (f.startsWith("index_")) {
8353
- const fp = import_path18.default.join(shadowDir, f);
8415
+ const fp = import_path19.default.join(shadowDir, f);
8354
8416
  try {
8355
8417
  if (import_fs17.default.statSync(fp).mtimeMs < cutoff) import_fs17.default.unlinkSync(fp);
8356
8418
  } catch {
@@ -8364,7 +8426,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
8364
8426
  const hardcoded = [".git", ".node9"];
8365
8427
  const lines = [...hardcoded, ...ignorePaths].join("\n");
8366
8428
  try {
8367
- import_fs17.default.writeFileSync(import_path18.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
8429
+ import_fs17.default.writeFileSync(import_path19.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
8368
8430
  } catch {
8369
8431
  }
8370
8432
  }
@@ -8377,7 +8439,7 @@ function ensureShadowRepo(shadowDir, cwd) {
8377
8439
  timeout: 3e3
8378
8440
  });
8379
8441
  if (check.status === 0) {
8380
- const ptPath = import_path18.default.join(shadowDir, "project-path.txt");
8442
+ const ptPath = import_path19.default.join(shadowDir, "project-path.txt");
8381
8443
  try {
8382
8444
  const stored = import_fs17.default.readFileSync(ptPath, "utf8").trim();
8383
8445
  if (stored === normalizedCwd) return true;
@@ -8399,12 +8461,12 @@ function ensureShadowRepo(shadowDir, cwd) {
8399
8461
  } catch {
8400
8462
  }
8401
8463
  const init = (0, import_child_process8.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
8402
- if (init.status !== 0) {
8403
- if (process.env.NODE9_DEBUG === "1")
8404
- console.error("[Node9] git init --bare failed:", init.stderr?.toString());
8464
+ if (init.status !== 0 || init.error) {
8465
+ const reason = init.error ? init.error.message : init.stderr?.toString();
8466
+ if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
8405
8467
  return false;
8406
8468
  }
8407
- const configFile = import_path18.default.join(shadowDir, "config");
8469
+ const configFile = import_path19.default.join(shadowDir, "config");
8408
8470
  (0, import_child_process8.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
8409
8471
  timeout: 3e3
8410
8472
  });
@@ -8412,7 +8474,7 @@ function ensureShadowRepo(shadowDir, cwd) {
8412
8474
  timeout: 3e3
8413
8475
  });
8414
8476
  try {
8415
- import_fs17.default.writeFileSync(import_path18.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
8477
+ import_fs17.default.writeFileSync(import_path19.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
8416
8478
  } catch {
8417
8479
  }
8418
8480
  return true;
@@ -8431,11 +8493,13 @@ function buildGitEnv(cwd) {
8431
8493
  async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = []) {
8432
8494
  let indexFile = null;
8433
8495
  try {
8434
- const cwd = process.cwd();
8496
+ const rawFilePath = extractFilePath(args);
8497
+ const absFilePath = rawFilePath && import_path19.default.isAbsolute(rawFilePath) ? rawFilePath : null;
8498
+ const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
8435
8499
  const shadowDir = getShadowRepoDir(cwd);
8436
8500
  if (!ensureShadowRepo(shadowDir, cwd)) return null;
8437
8501
  writeShadowExcludes(shadowDir, ignorePaths);
8438
- indexFile = import_path18.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
8502
+ indexFile = import_path19.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
8439
8503
  const shadowEnv = {
8440
8504
  ...process.env,
8441
8505
  GIT_DIR: shadowDir,
@@ -8454,15 +8518,53 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8454
8518
  const commitHash = commitRes.stdout?.toString().trim();
8455
8519
  if (!commitHash || commitRes.status !== 0) return null;
8456
8520
  const stack = readStack();
8521
+ const prevEntry = [...stack].reverse().find((e) => e.cwd === cwd);
8522
+ let capturedFiles = [];
8523
+ let capturedDiff = null;
8524
+ if (prevEntry) {
8525
+ const filesRes = (0, import_child_process8.spawnSync)("git", ["diff", "--name-only", prevEntry.hash, commitHash], {
8526
+ env: shadowEnv,
8527
+ timeout: GIT_TIMEOUT
8528
+ });
8529
+ if (filesRes.status === 0) {
8530
+ capturedFiles = filesRes.stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
8531
+ }
8532
+ const diffRes = (0, import_child_process8.spawnSync)("git", ["diff", prevEntry.hash, commitHash], {
8533
+ env: shadowEnv,
8534
+ timeout: GIT_TIMEOUT
8535
+ });
8536
+ if (diffRes.status === 0) {
8537
+ capturedDiff = diffRes.stdout?.toString() || null;
8538
+ }
8539
+ } else {
8540
+ const filesRes = (0, import_child_process8.spawnSync)("git", ["ls-tree", "-r", "--name-only", commitHash], {
8541
+ env: shadowEnv,
8542
+ timeout: GIT_TIMEOUT
8543
+ });
8544
+ if (filesRes.status === 0) {
8545
+ capturedFiles = filesRes.stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
8546
+ }
8547
+ capturedDiff = null;
8548
+ }
8457
8549
  stack.push({
8458
8550
  hash: commitHash,
8459
8551
  tool,
8460
8552
  argsSummary: buildArgsSummary(tool, args),
8553
+ files: capturedFiles,
8554
+ diff: capturedDiff,
8461
8555
  cwd,
8462
8556
  timestamp: Date.now()
8463
8557
  });
8464
8558
  const shouldGc = stack.length % 5 === 0;
8465
- if (stack.length > MAX_SNAPSHOTS) stack.splice(0, stack.length - MAX_SNAPSHOTS);
8559
+ let cwdCount = 0;
8560
+ let oldestCwdIdx = -1;
8561
+ for (let i = 0; i < stack.length; i++) {
8562
+ if (stack[i].cwd === cwd) {
8563
+ if (oldestCwdIdx === -1) oldestCwdIdx = i;
8564
+ cwdCount++;
8565
+ }
8566
+ }
8567
+ if (cwdCount > MAX_SNAPSHOTS) stack.splice(oldestCwdIdx, 1);
8466
8568
  writeStack(stack);
8467
8569
  import_fs17.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
8468
8570
  if (shouldGc) {
@@ -8518,14 +8620,21 @@ function applyUndo(hash, cwd) {
8518
8620
  env,
8519
8621
  timeout: GIT_TIMEOUT
8520
8622
  });
8521
- if (restore.status !== 0) return false;
8623
+ if (restore.status !== 0 || restore.error) {
8624
+ if (process.env.NODE9_DEBUG === "1") {
8625
+ const msg = restore.error ? restore.error.message : restore.stderr?.toString();
8626
+ console.error("[Node9] git restore failed:", msg);
8627
+ }
8628
+ return false;
8629
+ }
8522
8630
  const lsTree = (0, import_child_process8.spawnSync)("git", ["ls-tree", "-r", "--name-only", hash], {
8523
8631
  cwd: dir,
8524
8632
  env,
8525
8633
  timeout: GIT_TIMEOUT
8526
8634
  });
8527
8635
  if (lsTree.status !== 0) {
8528
- process.stderr.write(`[Node9] applyUndo: git ls-tree failed for hash ${hash}
8636
+ const errorMsg = lsTree.stderr?.toString() || "Unknown git error";
8637
+ process.stderr.write(`[Node9] applyUndo: git ls-tree failed for hash ${hash}: ${errorMsg}
8529
8638
  `);
8530
8639
  return false;
8531
8640
  }
@@ -8544,7 +8653,7 @@ function applyUndo(hash, cwd) {
8544
8653
  timeout: GIT_TIMEOUT
8545
8654
  }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
8546
8655
  for (const file of [...tracked, ...untracked]) {
8547
- const fullPath = import_path18.default.join(dir, file);
8656
+ const fullPath = import_path19.default.join(dir, file);
8548
8657
  if (!snapshotFiles.has(file) && import_fs17.default.existsSync(fullPath)) {
8549
8658
  import_fs17.default.unlinkSync(fullPath);
8550
8659
  }
@@ -8570,7 +8679,7 @@ function registerCheckCommand(program2) {
8570
8679
  } catch (err) {
8571
8680
  const tempConfig = getConfig();
8572
8681
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
8573
- const logPath = import_path19.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
8682
+ const logPath = import_path20.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
8574
8683
  const errMsg = err instanceof Error ? err.message : String(err);
8575
8684
  import_fs18.default.appendFileSync(
8576
8685
  logPath,
@@ -8583,9 +8692,9 @@ RAW: ${raw}
8583
8692
  }
8584
8693
  const config = getConfig(payload.cwd || void 0);
8585
8694
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
8586
- const logPath = import_path19.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
8587
- if (!import_fs18.default.existsSync(import_path19.default.dirname(logPath)))
8588
- import_fs18.default.mkdirSync(import_path19.default.dirname(logPath), { recursive: true });
8695
+ const logPath = import_path20.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
8696
+ if (!import_fs18.default.existsSync(import_path20.default.dirname(logPath)))
8697
+ import_fs18.default.mkdirSync(import_path20.default.dirname(logPath), { recursive: true });
8589
8698
  import_fs18.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
8590
8699
  `);
8591
8700
  }
@@ -8650,7 +8759,7 @@ RAW: ${raw}
8650
8759
  if (shouldSnapshot(toolName, toolInput, config)) {
8651
8760
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
8652
8761
  }
8653
- const safeCwdForAuth = typeof payload.cwd === "string" && import_path19.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
8762
+ const safeCwdForAuth = typeof payload.cwd === "string" && import_path20.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
8654
8763
  const result = await authorizeHeadless(toolName, toolInput, meta, {
8655
8764
  cwd: safeCwdForAuth
8656
8765
  });
@@ -8694,7 +8803,7 @@ RAW: ${raw}
8694
8803
  });
8695
8804
  } catch (err) {
8696
8805
  if (process.env.NODE9_DEBUG === "1") {
8697
- const logPath = import_path19.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
8806
+ const logPath = import_path20.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
8698
8807
  const errMsg = err instanceof Error ? err.message : String(err);
8699
8808
  import_fs18.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
8700
8809
  `);
@@ -8731,7 +8840,7 @@ RAW: ${raw}
8731
8840
 
8732
8841
  // src/cli/commands/log.ts
8733
8842
  var import_fs19 = __toESM(require("fs"));
8734
- var import_path20 = __toESM(require("path"));
8843
+ var import_path21 = __toESM(require("path"));
8735
8844
  var import_os15 = __toESM(require("os"));
8736
8845
  init_audit();
8737
8846
  init_config();
@@ -8808,9 +8917,9 @@ function registerLogCommand(program2) {
8808
8917
  decision: "allowed",
8809
8918
  source: "post-hook"
8810
8919
  };
8811
- const logPath = import_path20.default.join(import_os15.default.homedir(), ".node9", "audit.log");
8812
- if (!import_fs19.default.existsSync(import_path20.default.dirname(logPath)))
8813
- import_fs19.default.mkdirSync(import_path20.default.dirname(logPath), { recursive: true });
8920
+ const logPath = import_path21.default.join(import_os15.default.homedir(), ".node9", "audit.log");
8921
+ if (!import_fs19.default.existsSync(import_path21.default.dirname(logPath)))
8922
+ import_fs19.default.mkdirSync(import_path21.default.dirname(logPath), { recursive: true });
8814
8923
  import_fs19.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
8815
8924
  if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
8816
8925
  const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
@@ -8836,7 +8945,7 @@ function registerLogCommand(program2) {
8836
8945
  }
8837
8946
  }
8838
8947
  }
8839
- const safeCwd = typeof payload.cwd === "string" && import_path20.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
8948
+ const safeCwd = typeof payload.cwd === "string" && import_path21.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
8840
8949
  const config = getConfig(safeCwd);
8841
8950
  if (shouldSnapshot(tool, {}, config)) {
8842
8951
  await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
@@ -8845,7 +8954,7 @@ function registerLogCommand(program2) {
8845
8954
  const msg = err instanceof Error ? err.message : String(err);
8846
8955
  process.stderr.write(`[Node9] audit log error: ${msg}
8847
8956
  `);
8848
- const debugPath = import_path20.default.join(import_os15.default.homedir(), ".node9", "hook-debug.log");
8957
+ const debugPath = import_path21.default.join(import_os15.default.homedir(), ".node9", "hook-debug.log");
8849
8958
  try {
8850
8959
  import_fs19.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
8851
8960
  `);
@@ -9152,7 +9261,7 @@ function registerConfigShowCommand(program2) {
9152
9261
  // src/cli/commands/doctor.ts
9153
9262
  var import_chalk7 = __toESM(require("chalk"));
9154
9263
  var import_fs20 = __toESM(require("fs"));
9155
- var import_path21 = __toESM(require("path"));
9264
+ var import_path22 = __toESM(require("path"));
9156
9265
  var import_os16 = __toESM(require("os"));
9157
9266
  var import_child_process9 = require("child_process");
9158
9267
  init_daemon();
@@ -9207,7 +9316,7 @@ function registerDoctorCommand(program2, version2) {
9207
9316
  );
9208
9317
  }
9209
9318
  section("Configuration");
9210
- const globalConfigPath = import_path21.default.join(homeDir2, ".node9", "config.json");
9319
+ const globalConfigPath = import_path22.default.join(homeDir2, ".node9", "config.json");
9211
9320
  if (import_fs20.default.existsSync(globalConfigPath)) {
9212
9321
  try {
9213
9322
  JSON.parse(import_fs20.default.readFileSync(globalConfigPath, "utf-8"));
@@ -9218,7 +9327,7 @@ function registerDoctorCommand(program2, version2) {
9218
9327
  } else {
9219
9328
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
9220
9329
  }
9221
- const projectConfigPath = import_path21.default.join(process.cwd(), "node9.config.json");
9330
+ const projectConfigPath = import_path22.default.join(process.cwd(), "node9.config.json");
9222
9331
  if (import_fs20.default.existsSync(projectConfigPath)) {
9223
9332
  try {
9224
9333
  JSON.parse(import_fs20.default.readFileSync(projectConfigPath, "utf-8"));
@@ -9230,7 +9339,7 @@ function registerDoctorCommand(program2, version2) {
9230
9339
  );
9231
9340
  }
9232
9341
  }
9233
- const credsPath = import_path21.default.join(homeDir2, ".node9", "credentials.json");
9342
+ const credsPath = import_path22.default.join(homeDir2, ".node9", "credentials.json");
9234
9343
  if (import_fs20.default.existsSync(credsPath)) {
9235
9344
  pass("Cloud credentials found (~/.node9/credentials.json)");
9236
9345
  } else {
@@ -9240,7 +9349,7 @@ function registerDoctorCommand(program2, version2) {
9240
9349
  );
9241
9350
  }
9242
9351
  section("Agent Hooks");
9243
- const claudeSettingsPath = import_path21.default.join(homeDir2, ".claude", "settings.json");
9352
+ const claudeSettingsPath = import_path22.default.join(homeDir2, ".claude", "settings.json");
9244
9353
  if (import_fs20.default.existsSync(claudeSettingsPath)) {
9245
9354
  try {
9246
9355
  const cs = JSON.parse(import_fs20.default.readFileSync(claudeSettingsPath, "utf-8"));
@@ -9259,7 +9368,7 @@ function registerDoctorCommand(program2, version2) {
9259
9368
  } else {
9260
9369
  warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
9261
9370
  }
9262
- const geminiSettingsPath = import_path21.default.join(homeDir2, ".gemini", "settings.json");
9371
+ const geminiSettingsPath = import_path22.default.join(homeDir2, ".gemini", "settings.json");
9263
9372
  if (import_fs20.default.existsSync(geminiSettingsPath)) {
9264
9373
  try {
9265
9374
  const gs = JSON.parse(import_fs20.default.readFileSync(geminiSettingsPath, "utf-8"));
@@ -9278,7 +9387,7 @@ function registerDoctorCommand(program2, version2) {
9278
9387
  } else {
9279
9388
  warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
9280
9389
  }
9281
- const cursorHooksPath = import_path21.default.join(homeDir2, ".cursor", "hooks.json");
9390
+ const cursorHooksPath = import_path22.default.join(homeDir2, ".cursor", "hooks.json");
9282
9391
  if (import_fs20.default.existsSync(cursorHooksPath)) {
9283
9392
  try {
9284
9393
  const cur = JSON.parse(import_fs20.default.readFileSync(cursorHooksPath, "utf-8"));
@@ -9320,7 +9429,7 @@ function registerDoctorCommand(program2, version2) {
9320
9429
  // src/cli/commands/audit.ts
9321
9430
  var import_chalk8 = __toESM(require("chalk"));
9322
9431
  var import_fs21 = __toESM(require("fs"));
9323
- var import_path22 = __toESM(require("path"));
9432
+ var import_path23 = __toESM(require("path"));
9324
9433
  var import_os17 = __toESM(require("os"));
9325
9434
  function formatRelativeTime(timestamp) {
9326
9435
  const diff = Date.now() - new Date(timestamp).getTime();
@@ -9334,7 +9443,7 @@ function formatRelativeTime(timestamp) {
9334
9443
  }
9335
9444
  function registerAuditCommand(program2) {
9336
9445
  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) => {
9337
- const logPath = import_path22.default.join(import_os17.default.homedir(), ".node9", "audit.log");
9446
+ const logPath = import_path23.default.join(import_os17.default.homedir(), ".node9", "audit.log");
9338
9447
  if (!import_fs21.default.existsSync(logPath)) {
9339
9448
  console.log(
9340
9449
  import_chalk8.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
@@ -9460,7 +9569,7 @@ function registerDaemonCommand(program2) {
9460
9569
  // src/cli/commands/status.ts
9461
9570
  var import_chalk10 = __toESM(require("chalk"));
9462
9571
  var import_fs22 = __toESM(require("fs"));
9463
- var import_path23 = __toESM(require("path"));
9572
+ var import_path24 = __toESM(require("path"));
9464
9573
  var import_os18 = __toESM(require("os"));
9465
9574
  init_core();
9466
9575
  init_daemon();
@@ -9531,8 +9640,8 @@ function registerStatusCommand(program2) {
9531
9640
  console.log("");
9532
9641
  const modeLabel = settings.mode === "audit" ? import_chalk10.default.blue("audit") : settings.mode === "strict" ? import_chalk10.default.red("strict") : import_chalk10.default.white("standard");
9533
9642
  console.log(` Mode: ${modeLabel}`);
9534
- const projectConfig = import_path23.default.join(process.cwd(), "node9.config.json");
9535
- const globalConfig = import_path23.default.join(import_os18.default.homedir(), ".node9", "config.json");
9643
+ const projectConfig = import_path24.default.join(process.cwd(), "node9.config.json");
9644
+ const globalConfig = import_path24.default.join(import_os18.default.homedir(), ".node9", "config.json");
9536
9645
  console.log(
9537
9646
  ` Local: ${import_fs22.default.existsSync(projectConfig) ? import_chalk10.default.green("Active (node9.config.json)") : import_chalk10.default.gray("Not present")}`
9538
9647
  );
@@ -9546,13 +9655,13 @@ function registerStatusCommand(program2) {
9546
9655
  }
9547
9656
  const homeDir2 = import_os18.default.homedir();
9548
9657
  const claudeSettings = readJson2(
9549
- import_path23.default.join(homeDir2, ".claude", "settings.json")
9658
+ import_path24.default.join(homeDir2, ".claude", "settings.json")
9550
9659
  );
9551
- const claudeConfig = readJson2(import_path23.default.join(homeDir2, ".claude.json"));
9660
+ const claudeConfig = readJson2(import_path24.default.join(homeDir2, ".claude.json"));
9552
9661
  const geminiSettings = readJson2(
9553
- import_path23.default.join(homeDir2, ".gemini", "settings.json")
9662
+ import_path24.default.join(homeDir2, ".gemini", "settings.json")
9554
9663
  );
9555
- const cursorConfig = readJson2(import_path23.default.join(homeDir2, ".cursor", "mcp.json"));
9664
+ const cursorConfig = readJson2(import_path24.default.join(homeDir2, ".cursor", "mcp.json"));
9556
9665
  const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
9557
9666
  if (agentFound) {
9558
9667
  console.log("");
@@ -9612,13 +9721,13 @@ function registerStatusCommand(program2) {
9612
9721
  // src/cli/commands/init.ts
9613
9722
  var import_chalk11 = __toESM(require("chalk"));
9614
9723
  var import_fs23 = __toESM(require("fs"));
9615
- var import_path24 = __toESM(require("path"));
9724
+ var import_path25 = __toESM(require("path"));
9616
9725
  var import_os19 = __toESM(require("os"));
9617
9726
  init_core();
9618
9727
  function registerInitCommand(program2) {
9619
9728
  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) => {
9620
9729
  console.log(import_chalk11.default.cyan.bold("\n\u{1F6E1}\uFE0F Node9 Init\n"));
9621
- const configPath = import_path24.default.join(import_os19.default.homedir(), ".node9", "config.json");
9730
+ const configPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "config.json");
9622
9731
  if (import_fs23.default.existsSync(configPath) && !options.force) {
9623
9732
  console.log(import_chalk11.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
9624
9733
  } else {
@@ -9628,7 +9737,7 @@ function registerInitCommand(program2) {
9628
9737
  ...DEFAULT_CONFIG,
9629
9738
  settings: { ...DEFAULT_CONFIG.settings, mode: safeMode }
9630
9739
  };
9631
- const dir = import_path24.default.dirname(configPath);
9740
+ const dir = import_path25.default.dirname(configPath);
9632
9741
  if (!import_fs23.default.existsSync(dir)) import_fs23.default.mkdirSync(dir, { recursive: true });
9633
9742
  import_fs23.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2));
9634
9743
  console.log(import_chalk11.default.green(`\u2705 Config created: ${configPath}`));
@@ -9659,106 +9768,305 @@ function registerInitCommand(program2) {
9659
9768
  else if (agent === "cursor") await setupCursor();
9660
9769
  console.log("");
9661
9770
  }
9771
+ if (detected.claude) {
9772
+ setupHud();
9773
+ console.log(import_chalk11.default.green("\u2705 node9 HUD added to Claude Code statusline"));
9774
+ console.log(import_chalk11.default.gray(" Restart Claude Code to activate the security statusline."));
9775
+ console.log("");
9776
+ }
9662
9777
  console.log(import_chalk11.default.green.bold("\u{1F6E1}\uFE0F Node9 is ready!"));
9663
9778
  console.log(import_chalk11.default.gray(" Run: node9 daemon start"));
9664
9779
  });
9665
9780
  }
9666
9781
 
9667
9782
  // src/cli/commands/undo.ts
9783
+ var import_path26 = __toESM(require("path"));
9784
+ var import_chalk13 = __toESM(require("chalk"));
9785
+
9786
+ // src/tui/undo-navigator.ts
9787
+ var import_readline2 = __toESM(require("readline"));
9668
9788
  var import_chalk12 = __toESM(require("chalk"));
9669
- var import_prompts2 = require("@inquirer/prompts");
9789
+ var RESET = "\x1B[0m";
9790
+ var BOLD = "\x1B[1m";
9791
+ var CLEAR_SCREEN = "\x1B[2J\x1B[H";
9792
+ var SESSION_GAP_MS = 6e4;
9793
+ function formatAge(timestamp) {
9794
+ const age = Math.round((Date.now() - timestamp) / 1e3);
9795
+ if (age < 60) return `${age}s ago`;
9796
+ if (age < 3600) return `${Math.round(age / 60)}m ago`;
9797
+ if (age < 86400) return `${Math.round(age / 3600)}h ago`;
9798
+ return `${Math.round(age / 86400)}d ago`;
9799
+ }
9800
+ function renderDiff(raw) {
9801
+ const lines = raw.split("\n").filter(
9802
+ (l) => !l.startsWith("diff --git") && !l.startsWith("index ") && !l.startsWith("Binary")
9803
+ );
9804
+ for (const line of lines) {
9805
+ if (line.startsWith("+++") || line.startsWith("---")) {
9806
+ process.stdout.write(import_chalk12.default.bold(line) + "\n");
9807
+ } else if (line.startsWith("+")) {
9808
+ process.stdout.write(import_chalk12.default.green(line) + "\n");
9809
+ } else if (line.startsWith("-")) {
9810
+ process.stdout.write(import_chalk12.default.red(line) + "\n");
9811
+ } else if (line.startsWith("@@")) {
9812
+ process.stdout.write(import_chalk12.default.cyan(line) + "\n");
9813
+ } else {
9814
+ process.stdout.write(import_chalk12.default.gray(line) + "\n");
9815
+ }
9816
+ }
9817
+ }
9818
+ function isSessionBoundary(entries, idx) {
9819
+ if (idx <= 0) return false;
9820
+ return entries[idx - 1].timestamp - entries[idx].timestamp > SESSION_GAP_MS;
9821
+ }
9822
+ function sessionStart(entries, idx) {
9823
+ let i = idx;
9824
+ while (i > 0 && !isSessionBoundary(entries, i)) i--;
9825
+ return i;
9826
+ }
9827
+ function render(entries, idx) {
9828
+ const entry = entries[idx];
9829
+ const total = entries.length;
9830
+ const step = idx + 1;
9831
+ process.stdout.write(CLEAR_SCREEN);
9832
+ process.stdout.write(
9833
+ import_chalk12.default.magenta.bold(`\u23EA Node9 Undo`) + import_chalk12.default.gray(` \u2500\u2500 step ${step} of ${total}`) + (entry.files?.length ? import_chalk12.default.gray(
9834
+ ` \u2500\u2500 ${entry.files.slice(0, 2).join(", ")}${entry.files.length > 2 ? ` +${entry.files.length - 2} more` : ""}`
9835
+ ) : "") + "\n\n"
9836
+ );
9837
+ process.stdout.write(
9838
+ ` ${BOLD}Tool:${RESET} ${import_chalk12.default.cyan(entry.tool)}` + (entry.argsSummary ? import_chalk12.default.gray(" \u2192 " + entry.argsSummary) : "") + "\n"
9839
+ );
9840
+ process.stdout.write(` ${BOLD}When:${RESET} ${import_chalk12.default.gray(formatAge(entry.timestamp))}
9841
+ `);
9842
+ process.stdout.write(` ${BOLD}Dir: ${RESET} ${import_chalk12.default.gray(entry.cwd)}
9843
+ `);
9844
+ if (entry.files && entry.files.length > 0) {
9845
+ process.stdout.write(` ${BOLD}Files:${RESET} ${import_chalk12.default.gray(entry.files.join(", "))}
9846
+ `);
9847
+ }
9848
+ if (idx < total - 1 && isSessionBoundary(entries, idx + 1)) {
9849
+ process.stdout.write(import_chalk12.default.gray("\n \u2500\u2500 session boundary above \u2500\u2500\n"));
9850
+ }
9851
+ process.stdout.write("\n");
9852
+ const diff = entry.diff ?? computeUndoDiff(entry.hash, entry.cwd);
9853
+ if (diff) {
9854
+ renderDiff(diff);
9855
+ } else {
9856
+ process.stdout.write(
9857
+ import_chalk12.default.gray(" (no diff \u2014 working tree may already match this snapshot)\n")
9858
+ );
9859
+ }
9860
+ process.stdout.write("\n");
9861
+ process.stdout.write(
9862
+ import_chalk12.default.gray(" ") + (idx < total - 1 ? import_chalk12.default.white("[\u2190] older") : import_chalk12.default.gray("[\u2190] older")) + import_chalk12.default.gray(" ") + (idx > 0 ? import_chalk12.default.white("[\u2192] newer") : import_chalk12.default.gray("[\u2192] newer")) + import_chalk12.default.gray(" ") + import_chalk12.default.green("[\u21B5] restore here") + import_chalk12.default.gray(" ") + import_chalk12.default.yellow("[s] session start") + import_chalk12.default.gray(" ") + import_chalk12.default.gray("[q] quit") + "\n"
9863
+ );
9864
+ }
9865
+ async function runUndoNavigator(entries) {
9866
+ if (entries.length === 0) return { restored: false };
9867
+ const display = [...entries].reverse();
9868
+ let idx = 0;
9869
+ if (!process.stdout.isTTY || !process.stdin.isTTY) {
9870
+ render(display, idx);
9871
+ return { restored: false };
9872
+ }
9873
+ import_readline2.default.emitKeypressEvents(process.stdin);
9874
+ return new Promise((resolve) => {
9875
+ let done = false;
9876
+ render(display, idx);
9877
+ try {
9878
+ process.stdin.setRawMode(true);
9879
+ } catch {
9880
+ resolve({ restored: false });
9881
+ return;
9882
+ }
9883
+ process.stdin.resume();
9884
+ const cleanup = () => {
9885
+ process.stdin.removeListener("keypress", onKeypress);
9886
+ try {
9887
+ process.stdin.setRawMode(false);
9888
+ } catch {
9889
+ }
9890
+ process.stdin.pause();
9891
+ };
9892
+ const onKeypress = (_str, key) => {
9893
+ if (done) return;
9894
+ const name = key?.name ?? "";
9895
+ if (name === "left" || name === "h") {
9896
+ if (idx < display.length - 1) {
9897
+ idx++;
9898
+ render(display, idx);
9899
+ }
9900
+ } else if (name === "right" || name === "l") {
9901
+ if (idx > 0) {
9902
+ idx--;
9903
+ render(display, idx);
9904
+ }
9905
+ } else if (name === "s") {
9906
+ const start = sessionStart(display, idx);
9907
+ if (start !== idx) {
9908
+ idx = start;
9909
+ render(display, idx);
9910
+ }
9911
+ } else if (name === "return" || name === "y") {
9912
+ done = true;
9913
+ cleanup();
9914
+ process.stdout.write(CLEAR_SCREEN);
9915
+ const entry = display[idx];
9916
+ process.stdout.write(import_chalk12.default.magenta.bold("\n\u23EA Restoring snapshot...\n\n"));
9917
+ if (applyUndo(entry.hash, entry.cwd)) {
9918
+ process.stdout.write(import_chalk12.default.green("\u2705 Reverted successfully.\n\n"));
9919
+ resolve({ restored: true });
9920
+ } else {
9921
+ process.stdout.write(import_chalk12.default.red("\u274C Undo failed.\n\n"));
9922
+ resolve({ restored: false });
9923
+ }
9924
+ } else if (name === "q" || key?.ctrl && name === "c") {
9925
+ done = true;
9926
+ cleanup();
9927
+ process.stdout.write(CLEAR_SCREEN);
9928
+ process.stdout.write(import_chalk12.default.gray("\nCancelled.\n\n"));
9929
+ resolve({ restored: false });
9930
+ }
9931
+ };
9932
+ process.stdin.on("keypress", onKeypress);
9933
+ });
9934
+ }
9935
+
9936
+ // src/cli/commands/undo.ts
9937
+ function findMatchingCwd(startDir, history) {
9938
+ const cwds = new Set(history.map((e) => e.cwd));
9939
+ let dir = startDir;
9940
+ while (true) {
9941
+ if (cwds.has(dir)) return dir;
9942
+ const parent = import_path26.default.dirname(dir);
9943
+ if (parent === dir) return null;
9944
+ dir = parent;
9945
+ }
9946
+ }
9947
+ function formatAge2(timestamp) {
9948
+ const age = Math.round((Date.now() - timestamp) / 1e3);
9949
+ if (age < 60) return `${age}s ago`;
9950
+ if (age < 3600) return `${Math.round(age / 60)}m ago`;
9951
+ if (age < 86400) return `${Math.round(age / 3600)}h ago`;
9952
+ return `${Math.round(age / 86400)}d ago`;
9953
+ }
9670
9954
  function registerUndoCommand(program2) {
9671
9955
  program2.command("undo").description(
9672
- "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."
9673
- ).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) => {
9674
- const steps = Math.max(1, parseInt(options.steps, 10) || 1);
9956
+ "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."
9957
+ ).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) => {
9675
9958
  const allHistory = getSnapshotHistory();
9676
- const history = options.all ? allHistory : allHistory.filter((s) => s.cwd === process.cwd());
9959
+ const matchedCwd = options.all ? null : findMatchingCwd(process.cwd(), allHistory);
9960
+ const history = options.all ? allHistory : allHistory.filter((s) => s.cwd === matchedCwd);
9677
9961
  if (history.length === 0) {
9678
9962
  if (!options.all && allHistory.length > 0) {
9679
9963
  console.log(
9680
- import_chalk12.default.yellow(
9964
+ import_chalk13.default.yellow(
9681
9965
  `
9682
9966
  \u2139\uFE0F No snapshots found for the current directory (${process.cwd()}).
9683
- Run ${import_chalk12.default.cyan("node9 undo --all")} to see snapshots from all projects.
9967
+ Run ${import_chalk13.default.cyan("node9 undo --all")} to see snapshots from all projects.
9684
9968
  `
9685
9969
  )
9686
9970
  );
9687
9971
  } else {
9688
- console.log(import_chalk12.default.yellow("\n\u2139\uFE0F No undo snapshots found.\n"));
9972
+ console.log(import_chalk13.default.yellow("\n\u2139\uFE0F No undo snapshots found.\n"));
9689
9973
  }
9690
9974
  return;
9691
9975
  }
9692
- const idx = history.length - steps;
9693
- if (idx < 0) {
9976
+ if (options.list) {
9977
+ console.log(import_chalk13.default.magenta.bold("\n\u23EA Snapshot History\n"));
9694
9978
  console.log(
9695
- import_chalk12.default.yellow(
9696
- `
9697
- \u2139\uFE0F Only ${history.length} snapshot(s) available, cannot go back ${steps}.
9698
- `
9979
+ import_chalk13.default.gray(
9980
+ ` ${"#".padEnd(3)} ${"File / Command".padEnd(30)} ${"Tool".padEnd(8)} ${"When".padEnd(10)} Dir`
9699
9981
  )
9700
9982
  );
9983
+ console.log(import_chalk13.default.gray(" " + "\u2500".repeat(80)));
9984
+ const display = [...history].reverse();
9985
+ let prevTs = null;
9986
+ for (let i = 0; i < display.length; i++) {
9987
+ const e = display[i];
9988
+ const isGap = prevTs !== null && prevTs - e.timestamp > 6e4;
9989
+ if (isGap) console.log(import_chalk13.default.gray(" \u2500\u2500 earlier \u2500\u2500"));
9990
+ const label = (e.argsSummary || e.files?.[0] || "\u2014").slice(0, 30).padEnd(30);
9991
+ const tool = e.tool.slice(0, 8).padEnd(8);
9992
+ const when = formatAge2(e.timestamp).padEnd(10);
9993
+ const dir = e.cwd.length > 30 ? "\u2026" + e.cwd.slice(-29) : e.cwd;
9994
+ console.log(
9995
+ import_chalk13.default.white(
9996
+ ` ${String(i + 1).padEnd(3)} ${label} ${import_chalk13.default.cyan(tool)} ${import_chalk13.default.gray(when)} ${import_chalk13.default.gray(dir)}`
9997
+ )
9998
+ );
9999
+ prevTs = e.timestamp;
10000
+ }
10001
+ console.log("");
9701
10002
  return;
9702
10003
  }
9703
- const snapshot = history[idx];
9704
- const age = Math.round((Date.now() - snapshot.timestamp) / 1e3);
9705
- const ageStr = age < 60 ? `${age}s ago` : age < 3600 ? `${Math.round(age / 60)}m ago` : `${Math.round(age / 3600)}h ago`;
9706
- console.log(
9707
- import_chalk12.default.magenta.bold(`
10004
+ if (options.steps !== void 0) {
10005
+ const steps = Math.max(1, parseInt(options.steps, 10) || 1);
10006
+ const idx = history.length - steps;
10007
+ if (idx < 0) {
10008
+ console.log(
10009
+ import_chalk13.default.yellow(
10010
+ `
10011
+ \u2139\uFE0F Only ${history.length} snapshot(s) available, cannot go back ${steps}.
10012
+ `
10013
+ )
10014
+ );
10015
+ return;
10016
+ }
10017
+ const snapshot = history[idx];
10018
+ const ageStr = formatAge2(snapshot.timestamp);
10019
+ console.log(
10020
+ import_chalk13.default.magenta.bold(`
9708
10021
  \u23EA Node9 Undo${steps > 1 ? ` (${steps} steps back)` : ""}`)
9709
- );
9710
- console.log(
9711
- import_chalk12.default.white(
9712
- ` Tool: ${import_chalk12.default.cyan(snapshot.tool)}${snapshot.argsSummary ? import_chalk12.default.gray(" \u2192 " + snapshot.argsSummary) : ""}`
9713
- )
9714
- );
9715
- console.log(import_chalk12.default.white(` When: ${import_chalk12.default.gray(ageStr)}`));
9716
- console.log(import_chalk12.default.white(` Dir: ${import_chalk12.default.gray(snapshot.cwd)}`));
9717
- if (steps > 1)
10022
+ );
9718
10023
  console.log(
9719
- import_chalk12.default.yellow(` Note: This will also undo the ${steps - 1} action(s) after it.`)
10024
+ import_chalk13.default.white(
10025
+ ` Tool: ${import_chalk13.default.cyan(snapshot.tool)}${snapshot.argsSummary ? import_chalk13.default.gray(" \u2192 " + snapshot.argsSummary) : ""}`
10026
+ )
9720
10027
  );
9721
- console.log("");
9722
- const diff = computeUndoDiff(snapshot.hash, snapshot.cwd);
9723
- if (diff) {
9724
- const lines = diff.split("\n");
9725
- for (const line of lines) {
9726
- if (line.startsWith("+++") || line.startsWith("---")) {
9727
- console.log(import_chalk12.default.bold(line));
9728
- } else if (line.startsWith("+")) {
9729
- console.log(import_chalk12.default.green(line));
9730
- } else if (line.startsWith("-")) {
9731
- console.log(import_chalk12.default.red(line));
9732
- } else if (line.startsWith("@@")) {
9733
- console.log(import_chalk12.default.cyan(line));
9734
- } else {
9735
- console.log(import_chalk12.default.gray(line));
10028
+ console.log(import_chalk13.default.white(` When: ${import_chalk13.default.gray(ageStr)}`));
10029
+ console.log(import_chalk13.default.white(` Dir: ${import_chalk13.default.gray(snapshot.cwd)}`));
10030
+ if (steps > 1)
10031
+ console.log(
10032
+ import_chalk13.default.yellow(` Note: This will also undo the ${steps - 1} action(s) after it.`)
10033
+ );
10034
+ console.log("");
10035
+ const diff = snapshot.diff ?? computeUndoDiff(snapshot.hash, snapshot.cwd);
10036
+ if (diff) {
10037
+ const lines = diff.split("\n").filter((l) => !l.startsWith("diff --git") && !l.startsWith("index "));
10038
+ for (const line of lines) {
10039
+ if (line.startsWith("+++") || line.startsWith("---")) console.log(import_chalk13.default.bold(line));
10040
+ else if (line.startsWith("+")) console.log(import_chalk13.default.green(line));
10041
+ else if (line.startsWith("-")) console.log(import_chalk13.default.red(line));
10042
+ else if (line.startsWith("@@")) console.log(import_chalk13.default.cyan(line));
10043
+ else console.log(import_chalk13.default.gray(line));
9736
10044
  }
10045
+ console.log("");
10046
+ } else {
10047
+ console.log(
10048
+ import_chalk13.default.gray(" (no diff available \u2014 working tree may already match snapshot)\n")
10049
+ );
9737
10050
  }
9738
- console.log("");
9739
- } else {
9740
- console.log(
9741
- import_chalk12.default.gray(" (no diff available \u2014 working tree may already match snapshot)\n")
9742
- );
9743
- }
9744
- const proceed = await (0, import_prompts2.confirm)({
9745
- message: `Revert to this snapshot?`,
9746
- default: false
9747
- });
9748
- if (proceed) {
9749
- if (applyUndo(snapshot.hash, snapshot.cwd)) {
9750
- console.log(import_chalk12.default.green("\n\u2705 Reverted successfully.\n"));
10051
+ const { confirm: confirm3 } = await import("@inquirer/prompts");
10052
+ const proceed = await confirm3({ message: `Revert to this snapshot?`, default: false });
10053
+ if (proceed) {
10054
+ if (applyUndo(snapshot.hash, snapshot.cwd)) {
10055
+ console.log(import_chalk13.default.green("\n\u2705 Reverted successfully.\n"));
10056
+ } else {
10057
+ console.error(import_chalk13.default.red("\n\u274C Undo failed. Ensure you are in a Git repository.\n"));
10058
+ }
9751
10059
  } else {
9752
- console.error(import_chalk12.default.red("\n\u274C Undo failed. Ensure you are in a Git repository.\n"));
10060
+ console.log(import_chalk13.default.gray("\nCancelled.\n"));
9753
10061
  }
9754
- } else {
9755
- console.log(import_chalk12.default.gray("\nCancelled.\n"));
10062
+ return;
9756
10063
  }
10064
+ await runUndoNavigator(history);
9757
10065
  });
9758
10066
  }
9759
10067
 
9760
10068
  // src/cli/commands/watch.ts
9761
- var import_chalk13 = __toESM(require("chalk"));
10069
+ var import_chalk14 = __toESM(require("chalk"));
9762
10070
  var import_child_process11 = require("child_process");
9763
10071
  init_daemon();
9764
10072
  function registerWatchCommand(program2) {
@@ -9775,7 +10083,7 @@ function registerWatchCommand(program2) {
9775
10083
  throw new Error("not running");
9776
10084
  }
9777
10085
  } catch {
9778
- console.error(import_chalk13.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
10086
+ console.error(import_chalk14.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
9779
10087
  const child = (0, import_child_process11.spawn)(process.execPath, [process.argv[1], "daemon"], {
9780
10088
  detached: true,
9781
10089
  stdio: "ignore",
@@ -9797,12 +10105,12 @@ function registerWatchCommand(program2) {
9797
10105
  }
9798
10106
  }
9799
10107
  if (!ready) {
9800
- console.error(import_chalk13.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
10108
+ console.error(import_chalk14.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
9801
10109
  process.exit(1);
9802
10110
  }
9803
10111
  }
9804
10112
  console.error(
9805
- import_chalk13.default.cyan.bold("\u{1F6E1}\uFE0F Node9 watch") + import_chalk13.default.dim(` \u2192 localhost:${port}`) + import_chalk13.default.dim(
10113
+ import_chalk14.default.cyan.bold("\u{1F6E1}\uFE0F Node9 watch") + import_chalk14.default.dim(` \u2192 localhost:${port}`) + import_chalk14.default.dim(
9806
10114
  "\n Tip: run `node9 tail` in another terminal to review and approve AI actions.\n"
9807
10115
  )
9808
10116
  );
@@ -9811,7 +10119,7 @@ function registerWatchCommand(program2) {
9811
10119
  env: { ...process.env, NODE9_WATCH_MODE: "1" }
9812
10120
  });
9813
10121
  if (result.error) {
9814
- console.error(import_chalk13.default.red(`\u274C Failed to run command: ${result.error.message}`));
10122
+ console.error(import_chalk14.default.red(`\u274C Failed to run command: ${result.error.message}`));
9815
10123
  process.exit(1);
9816
10124
  }
9817
10125
  process.exit(result.status ?? 0);
@@ -9819,8 +10127,8 @@ function registerWatchCommand(program2) {
9819
10127
  }
9820
10128
 
9821
10129
  // src/mcp-gateway/index.ts
9822
- var import_readline2 = __toESM(require("readline"));
9823
- var import_chalk14 = __toESM(require("chalk"));
10130
+ var import_readline3 = __toESM(require("readline"));
10131
+ var import_chalk15 = __toESM(require("chalk"));
9824
10132
  var import_child_process12 = require("child_process");
9825
10133
  var import_execa3 = require("execa");
9826
10134
  init_orchestrator();
@@ -9884,13 +10192,13 @@ async function runMcpGateway(upstreamCommand) {
9884
10192
  const prov = checkProvenance(executable);
9885
10193
  if (prov.trustLevel === "suspect") {
9886
10194
  console.error(
9887
- import_chalk14.default.red(
10195
+ import_chalk15.default.red(
9888
10196
  `\u26A0\uFE0F Node9: Upstream MCP server binary is suspect \u2014 ${prov.reason} (${prov.resolvedPath})`
9889
10197
  )
9890
10198
  );
9891
- console.error(import_chalk14.default.red(" Verify this binary is trusted before proceeding."));
10199
+ console.error(import_chalk15.default.red(" Verify this binary is trusted before proceeding."));
9892
10200
  }
9893
- console.error(import_chalk14.default.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
10201
+ console.error(import_chalk15.default.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
9894
10202
  const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
9895
10203
  "NODE_OPTIONS",
9896
10204
  "NODE_PATH",
@@ -9918,7 +10226,7 @@ async function runMcpGateway(upstreamCommand) {
9918
10226
  let authPending = false;
9919
10227
  let deferredExitCode = null;
9920
10228
  let deferredStdinEnd = false;
9921
- const agentIn = import_readline2.default.createInterface({ input: process.stdin, terminal: false });
10229
+ const agentIn = import_readline3.default.createInterface({ input: process.stdin, terminal: false });
9922
10230
  agentIn.on("line", async (line) => {
9923
10231
  let message;
9924
10232
  try {
@@ -9954,10 +10262,10 @@ async function runMcpGateway(upstreamCommand) {
9954
10262
  mcpServer
9955
10263
  });
9956
10264
  if (!result.approved) {
9957
- console.error(import_chalk14.default.red(`
10265
+ console.error(import_chalk15.default.red(`
9958
10266
  \u{1F6D1} Node9 MCP Gateway: Action Blocked`));
9959
- console.error(import_chalk14.default.gray(` Tool: ${toolName}`));
9960
- console.error(import_chalk14.default.gray(` Reason: ${result.reason ?? "Security Policy"}
10267
+ console.error(import_chalk15.default.gray(` Tool: ${toolName}`));
10268
+ console.error(import_chalk15.default.gray(` Reason: ${result.reason ?? "Security Policy"}
9961
10269
  `));
9962
10270
  const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
9963
10271
  const isHumanDecision = blockedByLabel.toLowerCase().includes("user") || blockedByLabel.toLowerCase().includes("daemon") || blockedByLabel.toLowerCase().includes("decision");
@@ -10030,7 +10338,7 @@ function registerMcpGatewayCommand(program2) {
10030
10338
  }
10031
10339
 
10032
10340
  // src/cli/commands/trust.ts
10033
- var import_chalk15 = __toESM(require("chalk"));
10341
+ var import_chalk16 = __toESM(require("chalk"));
10034
10342
  init_trusted_hosts();
10035
10343
  function isValidHost(host) {
10036
10344
  return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host);
@@ -10041,44 +10349,44 @@ function registerTrustCommand(program2) {
10041
10349
  const normalized = normalizeHost(host.trim());
10042
10350
  if (!isValidHost(normalized)) {
10043
10351
  console.error(
10044
- import_chalk15.default.red(`
10352
+ import_chalk16.default.red(`
10045
10353
  \u274C Invalid host: "${host}"
10046
- `) + import_chalk15.default.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
10354
+ `) + import_chalk16.default.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
10047
10355
  );
10048
10356
  process.exit(1);
10049
10357
  }
10050
10358
  addTrustedHost(normalized);
10051
- console.log(import_chalk15.default.green(`
10359
+ console.log(import_chalk16.default.green(`
10052
10360
  \u2705 ${normalized} added to trusted hosts.`));
10053
10361
  console.log(
10054
- import_chalk15.default.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
10362
+ import_chalk16.default.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
10055
10363
  );
10056
10364
  });
10057
10365
  trustCmd.command("remove <host>").description("Remove a trusted host").action((host) => {
10058
10366
  const normalized = normalizeHost(host.trim());
10059
10367
  const removed = removeTrustedHost(normalized);
10060
10368
  if (!removed) {
10061
- console.error(import_chalk15.default.yellow(`
10369
+ console.error(import_chalk16.default.yellow(`
10062
10370
  \u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
10063
10371
  `));
10064
10372
  process.exit(1);
10065
10373
  }
10066
- console.log(import_chalk15.default.green(`
10374
+ console.log(import_chalk16.default.green(`
10067
10375
  \u2705 ${normalized} removed from trusted hosts.
10068
10376
  `));
10069
10377
  });
10070
10378
  trustCmd.command("list").description("Show all trusted hosts").action(() => {
10071
10379
  const hosts = readTrustedHosts();
10072
10380
  if (hosts.length === 0) {
10073
- console.log(import_chalk15.default.gray("\n No trusted hosts configured.\n"));
10074
- console.log(` Add one: ${import_chalk15.default.cyan("node9 trust add api.mycompany.com")}
10381
+ console.log(import_chalk16.default.gray("\n No trusted hosts configured.\n"));
10382
+ console.log(` Add one: ${import_chalk16.default.cyan("node9 trust add api.mycompany.com")}
10075
10383
  `);
10076
10384
  return;
10077
10385
  }
10078
- console.log(import_chalk15.default.bold("\n\u{1F513} Trusted Hosts\n"));
10386
+ console.log(import_chalk16.default.bold("\n\u{1F513} Trusted Hosts\n"));
10079
10387
  for (const entry of hosts) {
10080
10388
  const date = new Date(entry.addedAt).toLocaleDateString();
10081
- console.log(` ${import_chalk15.default.cyan(entry.host.padEnd(40))} ${import_chalk15.default.gray(`added ${date}`)}`);
10389
+ console.log(` ${import_chalk16.default.cyan(entry.host.padEnd(40))} ${import_chalk16.default.gray(`added ${date}`)}`);
10082
10390
  }
10083
10391
  console.log("");
10084
10392
  });
@@ -10086,15 +10394,15 @@ function registerTrustCommand(program2) {
10086
10394
 
10087
10395
  // src/cli.ts
10088
10396
  var { version } = JSON.parse(
10089
- import_fs26.default.readFileSync(import_path27.default.join(__dirname, "../package.json"), "utf-8")
10397
+ import_fs26.default.readFileSync(import_path29.default.join(__dirname, "../package.json"), "utf-8")
10090
10398
  );
10091
10399
  var program = new import_commander.Command();
10092
10400
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
10093
10401
  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) => {
10094
10402
  const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
10095
- const credPath = import_path27.default.join(import_os22.default.homedir(), ".node9", "credentials.json");
10096
- if (!import_fs26.default.existsSync(import_path27.default.dirname(credPath)))
10097
- import_fs26.default.mkdirSync(import_path27.default.dirname(credPath), { recursive: true });
10403
+ const credPath = import_path29.default.join(import_os22.default.homedir(), ".node9", "credentials.json");
10404
+ if (!import_fs26.default.existsSync(import_path29.default.dirname(credPath)))
10405
+ import_fs26.default.mkdirSync(import_path29.default.dirname(credPath), { recursive: true });
10098
10406
  const profileName = options.profile || "default";
10099
10407
  let existingCreds = {};
10100
10408
  try {
@@ -10113,7 +10421,7 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
10113
10421
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
10114
10422
  import_fs26.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
10115
10423
  if (profileName === "default") {
10116
- const configPath = import_path27.default.join(import_os22.default.homedir(), ".node9", "config.json");
10424
+ const configPath = import_path29.default.join(import_os22.default.homedir(), ".node9", "config.json");
10117
10425
  let config = {};
10118
10426
  try {
10119
10427
  if (import_fs26.default.existsSync(configPath))
@@ -10132,19 +10440,19 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
10132
10440
  approvers.cloud = false;
10133
10441
  }
10134
10442
  s.approvers = approvers;
10135
- if (!import_fs26.default.existsSync(import_path27.default.dirname(configPath)))
10136
- import_fs26.default.mkdirSync(import_path27.default.dirname(configPath), { recursive: true });
10443
+ if (!import_fs26.default.existsSync(import_path29.default.dirname(configPath)))
10444
+ import_fs26.default.mkdirSync(import_path29.default.dirname(configPath), { recursive: true });
10137
10445
  import_fs26.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
10138
10446
  }
10139
10447
  if (options.profile && profileName !== "default") {
10140
- console.log(import_chalk17.default.green(`\u2705 Profile "${profileName}" saved`));
10141
- console.log(import_chalk17.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
10448
+ console.log(import_chalk18.default.green(`\u2705 Profile "${profileName}" saved`));
10449
+ console.log(import_chalk18.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
10142
10450
  } else if (options.local) {
10143
- console.log(import_chalk17.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
10144
- console.log(import_chalk17.default.gray(` All decisions stay on this machine.`));
10451
+ console.log(import_chalk18.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
10452
+ console.log(import_chalk18.default.gray(` All decisions stay on this machine.`));
10145
10453
  } else {
10146
- console.log(import_chalk17.default.green(`\u2705 Logged in \u2014 agent mode`));
10147
- console.log(import_chalk17.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
10454
+ console.log(import_chalk18.default.green(`\u2705 Logged in \u2014 agent mode`));
10455
+ console.log(import_chalk18.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
10148
10456
  }
10149
10457
  });
10150
10458
  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) => {
@@ -10152,19 +10460,19 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
10152
10460
  if (target === "claude") return await setupClaude();
10153
10461
  if (target === "cursor") return await setupCursor();
10154
10462
  if (target === "hud") return setupHud();
10155
- console.error(import_chalk17.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
10463
+ console.error(import_chalk18.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
10156
10464
  process.exit(1);
10157
10465
  });
10158
10466
  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) => {
10159
10467
  if (!target) {
10160
- console.log(import_chalk17.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
10161
- console.log(" Usage: " + import_chalk17.default.white("node9 setup <target>") + "\n");
10468
+ console.log(import_chalk18.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
10469
+ console.log(" Usage: " + import_chalk18.default.white("node9 setup <target>") + "\n");
10162
10470
  console.log(" Targets:");
10163
- console.log(" " + import_chalk17.default.green("claude") + " \u2014 Claude Code (hook mode)");
10164
- console.log(" " + import_chalk17.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
10165
- console.log(" " + import_chalk17.default.green("cursor") + " \u2014 Cursor (hook mode)");
10471
+ console.log(" " + import_chalk18.default.green("claude") + " \u2014 Claude Code (hook mode)");
10472
+ console.log(" " + import_chalk18.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
10473
+ console.log(" " + import_chalk18.default.green("cursor") + " \u2014 Cursor (hook mode)");
10166
10474
  process.stdout.write(
10167
- " " + import_chalk17.default.green("hud") + " \u2014 Claude Code security statusline\n"
10475
+ " " + import_chalk18.default.green("hud") + " \u2014 Claude Code security statusline\n"
10168
10476
  );
10169
10477
  console.log("");
10170
10478
  return;
@@ -10174,7 +10482,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
10174
10482
  if (t === "claude") return await setupClaude();
10175
10483
  if (t === "cursor") return await setupCursor();
10176
10484
  if (t === "hud") return setupHud();
10177
- console.error(import_chalk17.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
10485
+ console.error(import_chalk18.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
10178
10486
  process.exit(1);
10179
10487
  });
10180
10488
  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) => {
@@ -10185,31 +10493,31 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
10185
10493
  else if (target === "hud") fn = teardownHud;
10186
10494
  else {
10187
10495
  console.error(
10188
- import_chalk17.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`)
10496
+ import_chalk18.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`)
10189
10497
  );
10190
10498
  process.exit(1);
10191
10499
  }
10192
- console.log(import_chalk17.default.cyan(`
10500
+ console.log(import_chalk18.default.cyan(`
10193
10501
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
10194
10502
  `));
10195
10503
  try {
10196
10504
  fn();
10197
10505
  } catch (err) {
10198
- console.error(import_chalk17.default.red(` \u26A0\uFE0F Failed: ${err instanceof Error ? err.message : String(err)}`));
10506
+ console.error(import_chalk18.default.red(` \u26A0\uFE0F Failed: ${err instanceof Error ? err.message : String(err)}`));
10199
10507
  process.exit(1);
10200
10508
  }
10201
- console.log(import_chalk17.default.gray("\n Restart the agent for changes to take effect."));
10509
+ console.log(import_chalk18.default.gray("\n Restart the agent for changes to take effect."));
10202
10510
  });
10203
10511
  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) => {
10204
- console.log(import_chalk17.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
10205
- console.log(import_chalk17.default.bold("Stopping daemon..."));
10512
+ console.log(import_chalk18.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
10513
+ console.log(import_chalk18.default.bold("Stopping daemon..."));
10206
10514
  try {
10207
10515
  stopDaemon();
10208
- console.log(import_chalk17.default.green(" \u2705 Daemon stopped"));
10516
+ console.log(import_chalk18.default.green(" \u2705 Daemon stopped"));
10209
10517
  } catch {
10210
- console.log(import_chalk17.default.blue(" \u2139\uFE0F Daemon was not running"));
10518
+ console.log(import_chalk18.default.blue(" \u2139\uFE0F Daemon was not running"));
10211
10519
  }
10212
- console.log(import_chalk17.default.bold("\nRemoving hooks..."));
10520
+ console.log(import_chalk18.default.bold("\nRemoving hooks..."));
10213
10521
  let teardownFailed = false;
10214
10522
  for (const [label, fn] of [
10215
10523
  ["Claude", teardownClaude],
@@ -10221,16 +10529,16 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
10221
10529
  } catch (err) {
10222
10530
  teardownFailed = true;
10223
10531
  console.error(
10224
- import_chalk17.default.red(
10532
+ import_chalk18.default.red(
10225
10533
  ` \u26A0\uFE0F Failed to remove ${label} hooks: ${err instanceof Error ? err.message : String(err)}`
10226
10534
  )
10227
10535
  );
10228
10536
  }
10229
10537
  }
10230
10538
  if (options.purge) {
10231
- const node9Dir = import_path27.default.join(import_os22.default.homedir(), ".node9");
10539
+ const node9Dir = import_path29.default.join(import_os22.default.homedir(), ".node9");
10232
10540
  if (import_fs26.default.existsSync(node9Dir)) {
10233
- const confirmed = await (0, import_prompts3.confirm)({
10541
+ const confirmed = await (0, import_prompts2.confirm)({
10234
10542
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
10235
10543
  default: false
10236
10544
  });
@@ -10238,28 +10546,28 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
10238
10546
  import_fs26.default.rmSync(node9Dir, { recursive: true });
10239
10547
  if (import_fs26.default.existsSync(node9Dir)) {
10240
10548
  console.error(
10241
- import_chalk17.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
10549
+ import_chalk18.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
10242
10550
  );
10243
10551
  } else {
10244
- console.log(import_chalk17.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
10552
+ console.log(import_chalk18.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
10245
10553
  }
10246
10554
  } else {
10247
- console.log(import_chalk17.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
10555
+ console.log(import_chalk18.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
10248
10556
  }
10249
10557
  } else {
10250
- console.log(import_chalk17.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
10558
+ console.log(import_chalk18.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
10251
10559
  }
10252
10560
  } else {
10253
10561
  console.log(
10254
- import_chalk17.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
10562
+ import_chalk18.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
10255
10563
  );
10256
10564
  }
10257
10565
  if (teardownFailed) {
10258
- console.error(import_chalk17.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
10566
+ console.error(import_chalk18.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
10259
10567
  process.exit(1);
10260
10568
  }
10261
- console.log(import_chalk17.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
10262
- console.log(import_chalk17.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
10569
+ console.log(import_chalk18.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
10570
+ console.log(import_chalk18.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
10263
10571
  });
10264
10572
  registerDoctorCommand(program, version);
10265
10573
  program.command("explain").description(
@@ -10272,7 +10580,7 @@ program.command("explain").description(
10272
10580
  try {
10273
10581
  args = JSON.parse(trimmed);
10274
10582
  } catch {
10275
- console.error(import_chalk17.default.red(`
10583
+ console.error(import_chalk18.default.red(`
10276
10584
  \u274C Invalid JSON: ${trimmed}
10277
10585
  `));
10278
10586
  process.exit(1);
@@ -10283,54 +10591,54 @@ program.command("explain").description(
10283
10591
  }
10284
10592
  const result = await explainPolicy(tool, args);
10285
10593
  console.log("");
10286
- console.log(import_chalk17.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
10594
+ console.log(import_chalk18.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
10287
10595
  console.log("");
10288
- console.log(` ${import_chalk17.default.bold("Tool:")} ${import_chalk17.default.white(result.tool)}`);
10596
+ console.log(` ${import_chalk18.default.bold("Tool:")} ${import_chalk18.default.white(result.tool)}`);
10289
10597
  if (argsRaw) {
10290
10598
  const preview = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
10291
- console.log(` ${import_chalk17.default.bold("Input:")} ${import_chalk17.default.gray(preview)}`);
10599
+ console.log(` ${import_chalk18.default.bold("Input:")} ${import_chalk18.default.gray(preview)}`);
10292
10600
  }
10293
10601
  console.log("");
10294
- console.log(import_chalk17.default.bold("Config Sources (Waterfall):"));
10602
+ console.log(import_chalk18.default.bold("Config Sources (Waterfall):"));
10295
10603
  for (const tier of result.waterfall) {
10296
- const num = import_chalk17.default.gray(` ${tier.tier}.`);
10604
+ const num = import_chalk18.default.gray(` ${tier.tier}.`);
10297
10605
  const label = tier.label.padEnd(16);
10298
10606
  let statusStr;
10299
10607
  if (tier.tier === 1) {
10300
- statusStr = import_chalk17.default.gray(tier.note ?? "");
10608
+ statusStr = import_chalk18.default.gray(tier.note ?? "");
10301
10609
  } else if (tier.status === "active") {
10302
- const loc = tier.path ? import_chalk17.default.gray(tier.path) : "";
10303
- const note = tier.note ? import_chalk17.default.gray(`(${tier.note})`) : "";
10304
- statusStr = import_chalk17.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
10610
+ const loc = tier.path ? import_chalk18.default.gray(tier.path) : "";
10611
+ const note = tier.note ? import_chalk18.default.gray(`(${tier.note})`) : "";
10612
+ statusStr = import_chalk18.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
10305
10613
  } else {
10306
- statusStr = import_chalk17.default.gray("\u25CB " + (tier.note ?? "not found"));
10614
+ statusStr = import_chalk18.default.gray("\u25CB " + (tier.note ?? "not found"));
10307
10615
  }
10308
- console.log(`${num} ${import_chalk17.default.white(label)} ${statusStr}`);
10616
+ console.log(`${num} ${import_chalk18.default.white(label)} ${statusStr}`);
10309
10617
  }
10310
10618
  console.log("");
10311
- console.log(import_chalk17.default.bold("Policy Evaluation:"));
10619
+ console.log(import_chalk18.default.bold("Policy Evaluation:"));
10312
10620
  for (const step of result.steps) {
10313
10621
  const isFinal = step.isFinal;
10314
10622
  let icon;
10315
- if (step.outcome === "allow") icon = import_chalk17.default.green(" \u2705");
10316
- else if (step.outcome === "review") icon = import_chalk17.default.red(" \u{1F534}");
10317
- else if (step.outcome === "skip") icon = import_chalk17.default.gray(" \u2500 ");
10318
- else icon = import_chalk17.default.gray(" \u25CB ");
10623
+ if (step.outcome === "allow") icon = import_chalk18.default.green(" \u2705");
10624
+ else if (step.outcome === "review") icon = import_chalk18.default.red(" \u{1F534}");
10625
+ else if (step.outcome === "skip") icon = import_chalk18.default.gray(" \u2500 ");
10626
+ else icon = import_chalk18.default.gray(" \u25CB ");
10319
10627
  const name = step.name.padEnd(18);
10320
- const nameStr = isFinal ? import_chalk17.default.white.bold(name) : import_chalk17.default.white(name);
10321
- const detail = isFinal ? import_chalk17.default.white(step.detail) : import_chalk17.default.gray(step.detail);
10322
- const arrow = isFinal ? import_chalk17.default.yellow(" \u2190 STOP") : "";
10628
+ const nameStr = isFinal ? import_chalk18.default.white.bold(name) : import_chalk18.default.white(name);
10629
+ const detail = isFinal ? import_chalk18.default.white(step.detail) : import_chalk18.default.gray(step.detail);
10630
+ const arrow = isFinal ? import_chalk18.default.yellow(" \u2190 STOP") : "";
10323
10631
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
10324
10632
  }
10325
10633
  console.log("");
10326
10634
  if (result.decision === "allow") {
10327
- console.log(import_chalk17.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk17.default.gray(" \u2014 no approval needed"));
10635
+ console.log(import_chalk18.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk18.default.gray(" \u2014 no approval needed"));
10328
10636
  } else {
10329
10637
  console.log(
10330
- import_chalk17.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk17.default.gray(" \u2014 human approval required")
10638
+ import_chalk18.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk18.default.gray(" \u2014 human approval required")
10331
10639
  );
10332
10640
  if (result.blockedByLabel) {
10333
- console.log(import_chalk17.default.gray(` Reason: ${result.blockedByLabel}`));
10641
+ console.log(import_chalk18.default.gray(` Reason: ${result.blockedByLabel}`));
10334
10642
  }
10335
10643
  }
10336
10644
  console.log("");
@@ -10344,7 +10652,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
10344
10652
  try {
10345
10653
  await startTail2(options);
10346
10654
  } catch (err) {
10347
- console.error(import_chalk17.default.red(`\u274C ${err instanceof Error ? err.message : String(err)}`));
10655
+ console.error(import_chalk18.default.red(`\u274C ${err instanceof Error ? err.message : String(err)}`));
10348
10656
  process.exit(1);
10349
10657
  }
10350
10658
  });
@@ -10360,7 +10668,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
10360
10668
  const ms = parseDuration(options.duration);
10361
10669
  if (ms === null) {
10362
10670
  console.error(
10363
- import_chalk17.default.red(`
10671
+ import_chalk18.default.red(`
10364
10672
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
10365
10673
  `)
10366
10674
  );
@@ -10368,20 +10676,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
10368
10676
  }
10369
10677
  pauseNode9(ms, options.duration);
10370
10678
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
10371
- console.log(import_chalk17.default.yellow(`
10679
+ console.log(import_chalk18.default.yellow(`
10372
10680
  \u23F8 Node9 paused until ${expiresAt}`));
10373
- console.log(import_chalk17.default.gray(` All tool calls will be allowed without review.`));
10374
- console.log(import_chalk17.default.gray(` Run "node9 resume" to re-enable early.
10681
+ console.log(import_chalk18.default.gray(` All tool calls will be allowed without review.`));
10682
+ console.log(import_chalk18.default.gray(` Run "node9 resume" to re-enable early.
10375
10683
  `));
10376
10684
  });
10377
10685
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
10378
10686
  const { paused } = checkPause();
10379
10687
  if (!paused) {
10380
- console.log(import_chalk17.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
10688
+ console.log(import_chalk18.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
10381
10689
  return;
10382
10690
  }
10383
10691
  resumeNode9();
10384
- console.log(import_chalk17.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
10692
+ console.log(import_chalk18.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
10385
10693
  });
10386
10694
  var HOOK_BASED_AGENTS = {
10387
10695
  claude: "claude",
@@ -10394,15 +10702,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
10394
10702
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
10395
10703
  const target = HOOK_BASED_AGENTS[firstArg2];
10396
10704
  console.error(
10397
- import_chalk17.default.yellow(`
10705
+ import_chalk18.default.yellow(`
10398
10706
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
10399
10707
  );
10400
- console.error(import_chalk17.default.white(`
10708
+ console.error(import_chalk18.default.white(`
10401
10709
  "${target}" uses its own hook system. Use:`));
10402
10710
  console.error(
10403
- import_chalk17.default.green(` node9 addto ${target} `) + import_chalk17.default.gray("# one-time setup")
10711
+ import_chalk18.default.green(` node9 addto ${target} `) + import_chalk18.default.gray("# one-time setup")
10404
10712
  );
10405
- console.error(import_chalk17.default.green(` ${target} `) + import_chalk17.default.gray("# run normally"));
10713
+ console.error(import_chalk18.default.green(` ${target} `) + import_chalk18.default.gray("# run normally"));
10406
10714
  process.exit(1);
10407
10715
  }
10408
10716
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -10419,12 +10727,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
10419
10727
  }
10420
10728
  );
10421
10729
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
10422
- console.error(import_chalk17.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
10730
+ console.error(import_chalk18.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
10423
10731
  const daemonReady = await autoStartDaemonAndWait();
10424
10732
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
10425
10733
  }
10426
10734
  if (result.noApprovalMechanism && process.stdout.isTTY) {
10427
- const approved = await (0, import_prompts3.confirm)({
10735
+ const approved = await (0, import_prompts2.confirm)({
10428
10736
  message: `\u{1F6E1}\uFE0F Node9: Allow "${fullCommand}"?`,
10429
10737
  default: false
10430
10738
  });
@@ -10432,12 +10740,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
10432
10740
  }
10433
10741
  if (!result.approved) {
10434
10742
  console.error(
10435
- import_chalk17.default.red(`
10743
+ import_chalk18.default.red(`
10436
10744
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
10437
10745
  );
10438
10746
  process.exit(1);
10439
10747
  }
10440
- console.error(import_chalk17.default.green("\n\u2705 Approved \u2014 running command...\n"));
10748
+ console.error(import_chalk18.default.green("\n\u2705 Approved \u2014 running command...\n"));
10441
10749
  await runProxy(fullCommand);
10442
10750
  } else {
10443
10751
  program.help();
@@ -10452,7 +10760,7 @@ if (process.argv[2] !== "daemon") {
10452
10760
  const isCheckHook = process.argv[2] === "check";
10453
10761
  if (isCheckHook) {
10454
10762
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
10455
- const logPath = import_path27.default.join(import_os22.default.homedir(), ".node9", "hook-debug.log");
10763
+ const logPath = import_path29.default.join(import_os22.default.homedir(), ".node9", "hook-debug.log");
10456
10764
  const msg = reason instanceof Error ? reason.message : String(reason);
10457
10765
  import_fs26.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
10458
10766
  `);