@node9/proxy 1.10.0 → 1.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -168,8 +168,8 @@ function sanitizeConfig(raw) {
168
168
  }
169
169
  }
170
170
  const lines = result.error.issues.map((issue) => {
171
- const path34 = issue.path.length > 0 ? issue.path.join(".") : "root";
172
- return ` \u2022 ${path34}: ${issue.message}`;
171
+ const path35 = issue.path.length > 0 ? issue.path.join(".") : "root";
172
+ return ` \u2022 ${path35}: ${issue.message}`;
173
173
  });
174
174
  return {
175
175
  sanitized,
@@ -253,7 +253,8 @@ var init_config_schema = __esm({
253
253
  slackEnabled: import_zod.z.boolean().optional(),
254
254
  enableTrustSessions: import_zod.z.boolean().optional(),
255
255
  allowGlobalPause: import_zod.z.boolean().optional(),
256
- auditHashArgs: import_zod.z.boolean().optional()
256
+ auditHashArgs: import_zod.z.boolean().optional(),
257
+ agentPolicy: import_zod.z.enum(["require_approval", "block_on_rules"]).optional()
257
258
  }).optional(),
258
259
  policy: import_zod.z.object({
259
260
  sandboxPaths: import_zod.z.array(import_zod.z.string()).optional(),
@@ -1726,9 +1727,9 @@ function matchesPattern(text, patterns) {
1726
1727
  const withoutDotSlash = text.replace(/^\.\//, "");
1727
1728
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1728
1729
  }
1729
- function getNestedValue(obj, path34) {
1730
+ function getNestedValue(obj, path35) {
1730
1731
  if (!obj || typeof obj !== "object") return null;
1731
- return path34.split(".").reduce((prev, curr) => prev?.[curr], obj);
1732
+ return path35.split(".").reduce((prev, curr) => prev?.[curr], obj);
1732
1733
  }
1733
1734
  function shouldSnapshot(toolName, args, config) {
1734
1735
  if (!config.settings.enableUndo) return false;
@@ -2456,19 +2457,44 @@ function getInternalToken() {
2456
2457
  function isDaemonRunning() {
2457
2458
  const pidFile = import_path10.default.join(import_os8.default.homedir(), ".node9", "daemon.pid");
2458
2459
  if (import_fs9.default.existsSync(pidFile)) {
2460
+ let pid;
2461
+ let port;
2459
2462
  try {
2460
- const { pid, port } = JSON.parse(import_fs9.default.readFileSync(pidFile, "utf-8"));
2461
- if (port !== DAEMON_PORT) return false;
2462
- process.kill(pid, 0);
2463
- return true;
2463
+ const data = JSON.parse(import_fs9.default.readFileSync(pidFile, "utf-8"));
2464
+ pid = data.pid;
2465
+ port = data.port;
2464
2466
  } catch {
2465
2467
  return false;
2466
2468
  }
2469
+ if (port !== DAEMON_PORT) {
2470
+ return false;
2471
+ }
2472
+ const MAX_PID = 4194304;
2473
+ if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
2474
+ return false;
2475
+ }
2476
+ try {
2477
+ process.kill(pid, 0);
2478
+ } catch (err2) {
2479
+ if (err2 instanceof Error && "code" in err2 && err2.code === "ESRCH") {
2480
+ try {
2481
+ import_fs9.default.unlinkSync(pidFile);
2482
+ } catch {
2483
+ }
2484
+ }
2485
+ return false;
2486
+ }
2487
+ const r = (0, import_child_process.spawnSync)("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
2488
+ encoding: "utf8",
2489
+ timeout: 300
2490
+ });
2491
+ if (r.status === 0 && (r.stdout ?? "").includes(`:${DAEMON_PORT}`)) return true;
2492
+ return false;
2467
2493
  }
2468
2494
  try {
2469
2495
  const r = (0, import_child_process.spawnSync)("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
2470
2496
  encoding: "utf8",
2471
- timeout: 500
2497
+ timeout: 300
2472
2498
  });
2473
2499
  return r.status === 0 && (r.stdout ?? "").includes(`:${DAEMON_PORT}`);
2474
2500
  } catch {
@@ -2811,11 +2837,12 @@ ${smartTruncate(str, 500)}`
2811
2837
  function escapePango(text) {
2812
2838
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2813
2839
  }
2814
- function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
2840
+ function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
2815
2841
  const lines = [];
2816
2842
  if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
2817
2843
  lines.push(`\u{1F916} ${agent || "AI Agent"} | \u{1F527} ${toolName}`);
2818
2844
  lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
2845
+ if (ruleDescription) lines.push(`\u2139 ${ruleDescription}`);
2819
2846
  lines.push("");
2820
2847
  lines.push(formattedArgs);
2821
2848
  if (allowCount >= 3) {
@@ -2828,7 +2855,7 @@ function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, loc
2828
2855
  }
2829
2856
  return lines.join("\n");
2830
2857
  }
2831
- function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
2858
+ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
2832
2859
  const lines = [];
2833
2860
  if (locked) {
2834
2861
  lines.push('<span foreground="red" weight="bold">\u26A0\uFE0F LOCKED BY ADMIN POLICY</span>');
@@ -2838,6 +2865,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
2838
2865
  `<b>\u{1F916} ${escapePango(agent || "AI Agent")}</b> | <b>\u{1F527} <tt>${escapePango(toolName)}</tt></b>`
2839
2866
  );
2840
2867
  lines.push(`<i>\u{1F6E1}\uFE0F ${escapePango(explainableLabel || "Security Policy")}</i>`);
2868
+ if (ruleDescription) lines.push(`<i>\u2139 ${escapePango(ruleDescription)}</i>`);
2841
2869
  lines.push("");
2842
2870
  lines.push(`<tt>${escapePango(formattedArgs)}</tt>`);
2843
2871
  if (allowCount >= 3) {
@@ -2854,7 +2882,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
2854
2882
  }
2855
2883
  return lines.join("\n");
2856
2884
  }
2857
- async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1) {
2885
+ async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1, ruleDescription) {
2858
2886
  if (isTestEnv()) return "deny";
2859
2887
  const { message: formattedArgs, intent } = formatArgs(args, matchedField, matchedWord);
2860
2888
  const intentLabel = intent === "EDIT" ? "Code Edit" : "Action Approval";
@@ -2865,7 +2893,8 @@ async function askNativePopup(toolName, args, agent, explainableLabel, locked =
2865
2893
  agent,
2866
2894
  explainableLabel,
2867
2895
  locked,
2868
- allowCount
2896
+ allowCount,
2897
+ ruleDescription
2869
2898
  );
2870
2899
  return new Promise((resolve) => {
2871
2900
  let childProcess = null;
@@ -2899,7 +2928,8 @@ end run`;
2899
2928
  agent,
2900
2929
  explainableLabel,
2901
2930
  locked,
2902
- allowCount
2931
+ allowCount,
2932
+ ruleDescription
2903
2933
  );
2904
2934
  const argsList = [
2905
2935
  locked ? "--info" : "--question",
@@ -2972,7 +3002,7 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2972
3002
  }).catch(() => {
2973
3003
  });
2974
3004
  }
2975
- async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
3005
+ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPolicy, forceReview) {
2976
3006
  const controller = new AbortController();
2977
3007
  const timeout = setTimeout(() => controller.abort(), 1e4);
2978
3008
  if (!creds.apiKey) throw new Error("Node9 API Key is missing");
@@ -3017,7 +3047,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
3017
3047
  platform: import_os9.default.platform()
3018
3048
  },
3019
3049
  ...riskMetadata && { riskMetadata },
3020
- ...ciContext && { ciContext }
3050
+ ...ciContext && { ciContext },
3051
+ ...agentPolicy && { policy: agentPolicy },
3052
+ ...forceReview && { forceReview: true }
3021
3053
  }),
3022
3054
  signal: controller.signal
3023
3055
  });
@@ -3414,6 +3446,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3414
3446
  policyMatchedWord,
3415
3447
  policyResult.ruleName
3416
3448
  );
3449
+ if (policyRuleDescription) riskMetadata.ruleDescription = policyRuleDescription.slice(0, 200);
3417
3450
  const persistent = policyResult.ruleName ? null : getPersistentDecision(toolName);
3418
3451
  if (persistent === "allow") {
3419
3452
  if (approvers.cloud && creds?.apiKey)
@@ -3448,9 +3481,18 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3448
3481
  }
3449
3482
  let cloudRequestId = null;
3450
3483
  const cloudEnforced = approvers.cloud && !!creds?.apiKey;
3451
- if (cloudEnforced && !localSmartRuleMatched && !options?.localSmartRuleMatched) {
3484
+ const forceReview = localSmartRuleMatched === true || options?.localSmartRuleMatched === true || void 0;
3485
+ if (cloudEnforced) {
3452
3486
  try {
3453
- const initResult = await initNode9SaaS(toolName, args, creds, meta, riskMetadata);
3487
+ const initResult = await initNode9SaaS(
3488
+ toolName,
3489
+ args,
3490
+ creds,
3491
+ meta,
3492
+ riskMetadata,
3493
+ config.settings.agentPolicy,
3494
+ forceReview
3495
+ );
3454
3496
  if (!initResult.pending) {
3455
3497
  if (initResult.shadowMode) {
3456
3498
  return { approved: true, checkedBy: "cloud" };
@@ -3465,9 +3507,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3465
3507
  };
3466
3508
  }
3467
3509
  }
3468
- if (!localSmartRuleMatched && !options?.localSmartRuleMatched) {
3469
- cloudRequestId = initResult.requestId || null;
3470
- }
3510
+ if (initResult.pending) cloudRequestId = initResult.requestId || null;
3471
3511
  if (!taintWarning) explainableLabel = "Organization Policy (SaaS)";
3472
3512
  } catch {
3473
3513
  }
@@ -3524,7 +3564,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3524
3564
  }
3525
3565
  }
3526
3566
  }
3527
- if (cloudEnforced && cloudRequestId && !localSmartRuleMatched && !options?.localSmartRuleMatched) {
3567
+ if (cloudEnforced && cloudRequestId) {
3528
3568
  racePromises.push(
3529
3569
  (async () => {
3530
3570
  try {
@@ -3556,7 +3596,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3556
3596
  signal,
3557
3597
  policyMatchedField,
3558
3598
  policyMatchedWord,
3559
- daemonAllowCount
3599
+ daemonAllowCount,
3600
+ riskMetadata?.ruleDescription
3560
3601
  );
3561
3602
  if (decision === "always_allow") {
3562
3603
  writeTrustSession(toolName, 36e5);
@@ -6059,8 +6100,175 @@ var init_patch = __esm({
6059
6100
  }
6060
6101
  });
6061
6102
 
6103
+ // src/costSync.ts
6104
+ function normalizeModel(raw) {
6105
+ return raw.replace(/-\d{8}$/, "");
6106
+ }
6107
+ function pricingFor(model) {
6108
+ const norm = normalizeModel(model);
6109
+ if (PRICING[norm]) return PRICING[norm];
6110
+ let best = null;
6111
+ for (const key of Object.keys(PRICING)) {
6112
+ if (norm.startsWith(key) && (best === null || key.length > best.length)) best = key;
6113
+ }
6114
+ return best ? PRICING[best] : null;
6115
+ }
6116
+ function parseJSONLFile(filePath) {
6117
+ let content;
6118
+ try {
6119
+ content = import_fs16.default.readFileSync(filePath, "utf8");
6120
+ } catch {
6121
+ return /* @__PURE__ */ new Map();
6122
+ }
6123
+ const daily = /* @__PURE__ */ new Map();
6124
+ for (const line of content.split("\n")) {
6125
+ if (!line.trim()) continue;
6126
+ let row;
6127
+ try {
6128
+ row = JSON.parse(line);
6129
+ } catch {
6130
+ continue;
6131
+ }
6132
+ if (row["type"] !== "assistant") continue;
6133
+ const msg = row["message"];
6134
+ if (!msg?.["usage"] || typeof msg["model"] !== "string") continue;
6135
+ const usage = msg["usage"];
6136
+ const model = msg["model"];
6137
+ const timestamp = row["timestamp"];
6138
+ if (typeof timestamp !== "string" || timestamp.length < 10) continue;
6139
+ const date = timestamp.slice(0, 10);
6140
+ const p = pricingFor(model);
6141
+ if (!p) continue;
6142
+ const inp = Number(usage["input_tokens"] ?? 0);
6143
+ const out = Number(usage["output_tokens"] ?? 0);
6144
+ const cw = Number(usage["cache_creation_input_tokens"] ?? 0);
6145
+ const cr = Number(usage["cache_read_input_tokens"] ?? 0);
6146
+ const cost = inp * p[0] + out * p[1] + cw * p[2] + cr * p[3];
6147
+ const norm = normalizeModel(model);
6148
+ const key = `${date}::${norm}`;
6149
+ const prev = daily.get(key);
6150
+ if (prev) {
6151
+ prev.costUSD += cost;
6152
+ prev.inputTokens += inp;
6153
+ prev.outputTokens += out;
6154
+ prev.cacheWriteTokens += cw;
6155
+ prev.cacheReadTokens += cr;
6156
+ } else {
6157
+ daily.set(key, {
6158
+ date,
6159
+ model: norm,
6160
+ costUSD: cost,
6161
+ inputTokens: inp,
6162
+ outputTokens: out,
6163
+ cacheWriteTokens: cw,
6164
+ cacheReadTokens: cr
6165
+ });
6166
+ }
6167
+ }
6168
+ return daily;
6169
+ }
6170
+ function collectEntries() {
6171
+ const projectsDir = import_path19.default.join(import_os14.default.homedir(), ".claude", "projects");
6172
+ if (!import_fs16.default.existsSync(projectsDir)) return [];
6173
+ const combined = /* @__PURE__ */ new Map();
6174
+ let dirs;
6175
+ try {
6176
+ dirs = import_fs16.default.readdirSync(projectsDir);
6177
+ } catch {
6178
+ return [];
6179
+ }
6180
+ for (const dir of dirs) {
6181
+ const dirPath = import_path19.default.join(projectsDir, dir);
6182
+ try {
6183
+ if (!import_fs16.default.statSync(dirPath).isDirectory()) continue;
6184
+ } catch {
6185
+ continue;
6186
+ }
6187
+ let files;
6188
+ try {
6189
+ files = import_fs16.default.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
6190
+ } catch {
6191
+ continue;
6192
+ }
6193
+ for (const file of files) {
6194
+ const entries = parseJSONLFile(import_path19.default.join(dirPath, file));
6195
+ for (const [key, e] of entries) {
6196
+ const prev = combined.get(key);
6197
+ if (prev) {
6198
+ prev.costUSD += e.costUSD;
6199
+ prev.inputTokens += e.inputTokens;
6200
+ prev.outputTokens += e.outputTokens;
6201
+ prev.cacheWriteTokens += e.cacheWriteTokens;
6202
+ prev.cacheReadTokens += e.cacheReadTokens;
6203
+ } else {
6204
+ combined.set(key, { ...e });
6205
+ }
6206
+ }
6207
+ }
6208
+ }
6209
+ return [...combined.values()];
6210
+ }
6211
+ async function syncCost() {
6212
+ const creds = getCredentials();
6213
+ if (!creds?.apiKey || !creds?.apiUrl) return;
6214
+ const entries = collectEntries();
6215
+ if (entries.length === 0) return;
6216
+ let username = "unknown";
6217
+ try {
6218
+ username = import_os14.default.userInfo().username;
6219
+ } catch {
6220
+ }
6221
+ const machineId = `${import_os14.default.hostname()}:${username}`;
6222
+ try {
6223
+ const res = await fetch(`${creds.apiUrl}/cost-sync`, {
6224
+ method: "POST",
6225
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${creds.apiKey}` },
6226
+ body: JSON.stringify({ machineId, entries }),
6227
+ signal: AbortSignal.timeout(15e3)
6228
+ });
6229
+ if (!res.ok) {
6230
+ import_fs16.default.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] HTTP ${res.status}
6231
+ `);
6232
+ }
6233
+ } catch (err2) {
6234
+ import_fs16.default.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] ${err2.message}
6235
+ `);
6236
+ }
6237
+ }
6238
+ function startCostSync() {
6239
+ syncCost().catch(() => {
6240
+ });
6241
+ const timer = setInterval(() => {
6242
+ syncCost().catch(() => {
6243
+ });
6244
+ }, SYNC_INTERVAL_MS);
6245
+ timer.unref();
6246
+ }
6247
+ var import_fs16, import_path19, import_os14, SYNC_INTERVAL_MS, PRICING;
6248
+ var init_costSync = __esm({
6249
+ "src/costSync.ts"() {
6250
+ "use strict";
6251
+ import_fs16 = __toESM(require("fs"));
6252
+ import_path19 = __toESM(require("path"));
6253
+ import_os14 = __toESM(require("os"));
6254
+ init_config();
6255
+ init_audit();
6256
+ SYNC_INTERVAL_MS = 10 * 60 * 1e3;
6257
+ PRICING = {
6258
+ "claude-opus-4": [5e-6, 25e-6, 625e-8, 5e-7],
6259
+ "claude-sonnet-4": [3e-6, 15e-6, 375e-8, 3e-7],
6260
+ "claude-haiku-4": [8e-7, 4e-6, 1e-6, 8e-8],
6261
+ "claude-3-7-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
6262
+ "claude-3-5-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
6263
+ "claude-3-5-haiku": [8e-7, 4e-6, 1e-6, 8e-8],
6264
+ "claude-3-haiku": [25e-8, 125e-8, 3e-7, 3e-8]
6265
+ };
6266
+ }
6267
+ });
6268
+
6062
6269
  // src/daemon/server.ts
6063
6270
  function startDaemon() {
6271
+ startCostSync();
6064
6272
  loadInsightCounts();
6065
6273
  const csrfToken = (0, import_crypto7.randomUUID)();
6066
6274
  const internalToken = (0, import_crypto7.randomUUID)();
@@ -6076,7 +6284,7 @@ function startDaemon() {
6076
6284
  idleTimer = setTimeout(() => {
6077
6285
  if (autoStarted) {
6078
6286
  try {
6079
- import_fs16.default.unlinkSync(DAEMON_PID_FILE);
6287
+ import_fs17.default.unlinkSync(DAEMON_PID_FILE);
6080
6288
  } catch {
6081
6289
  }
6082
6290
  }
@@ -6239,7 +6447,7 @@ data: ${JSON.stringify(item.data)}
6239
6447
  status: "pending"
6240
6448
  });
6241
6449
  }
6242
- const projectCwd = typeof cwd === "string" && import_path19.default.isAbsolute(cwd) ? cwd : void 0;
6450
+ const projectCwd = typeof cwd === "string" && import_path20.default.isAbsolute(cwd) ? cwd : void 0;
6243
6451
  const projectConfig = getConfig(projectCwd);
6244
6452
  const browserEnabled = projectConfig.settings.approvers?.browser !== false;
6245
6453
  const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
@@ -6629,8 +6837,8 @@ data: ${JSON.stringify(item.data)}
6629
6837
  const body = await readBody(req);
6630
6838
  const data = body ? JSON.parse(body) : {};
6631
6839
  const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
6632
- const node9Dir = import_path19.default.dirname(GLOBAL_CONFIG_PATH);
6633
- if (!import_path19.default.resolve(configPath).startsWith(node9Dir + import_path19.default.sep)) {
6840
+ const node9Dir = import_path20.default.dirname(GLOBAL_CONFIG_PATH);
6841
+ if (!import_path20.default.resolve(configPath).startsWith(node9Dir + import_path20.default.sep)) {
6634
6842
  res.writeHead(400, { "Content-Type": "application/json" });
6635
6843
  return res.end(
6636
6844
  JSON.stringify({ error: "configPath must be within the node9 config directory" })
@@ -6741,14 +6949,14 @@ data: ${JSON.stringify(item.data)}
6741
6949
  server.on("error", (e) => {
6742
6950
  if (e.code === "EADDRINUSE") {
6743
6951
  try {
6744
- if (import_fs16.default.existsSync(DAEMON_PID_FILE)) {
6745
- const { pid } = JSON.parse(import_fs16.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
6952
+ if (import_fs17.default.existsSync(DAEMON_PID_FILE)) {
6953
+ const { pid } = JSON.parse(import_fs17.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
6746
6954
  process.kill(pid, 0);
6747
6955
  return process.exit(0);
6748
6956
  }
6749
6957
  } catch {
6750
6958
  try {
6751
- import_fs16.default.unlinkSync(DAEMON_PID_FILE);
6959
+ import_fs17.default.unlinkSync(DAEMON_PID_FILE);
6752
6960
  } catch {
6753
6961
  }
6754
6962
  server.listen(DAEMON_PORT, DAEMON_HOST);
@@ -6807,13 +7015,13 @@ data: ${JSON.stringify(item.data)}
6807
7015
  }
6808
7016
  startActivitySocket();
6809
7017
  }
6810
- var import_http, import_fs16, import_path19, import_crypto7, import_child_process4, import_chalk2;
7018
+ var import_http, import_fs17, import_path20, import_crypto7, import_child_process4, import_chalk2;
6811
7019
  var init_server = __esm({
6812
7020
  "src/daemon/server.ts"() {
6813
7021
  "use strict";
6814
7022
  import_http = __toESM(require("http"));
6815
- import_fs16 = __toESM(require("fs"));
6816
- import_path19 = __toESM(require("path"));
7023
+ import_fs17 = __toESM(require("fs"));
7024
+ import_path20 = __toESM(require("path"));
6817
7025
  import_crypto7 = require("crypto");
6818
7026
  import_child_process4 = require("child_process");
6819
7027
  import_chalk2 = __toESM(require("chalk"));
@@ -6823,29 +7031,30 @@ var init_server = __esm({
6823
7031
  init_state2();
6824
7032
  init_patch();
6825
7033
  init_config_schema();
7034
+ init_costSync();
6826
7035
  }
6827
7036
  });
6828
7037
 
6829
7038
  // src/daemon/index.ts
6830
7039
  function stopDaemon() {
6831
- if (!import_fs17.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
7040
+ if (!import_fs18.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
6832
7041
  try {
6833
- const { pid } = JSON.parse(import_fs17.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
7042
+ const { pid } = JSON.parse(import_fs18.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
6834
7043
  process.kill(pid, "SIGTERM");
6835
7044
  console.log(import_chalk3.default.green("\u2705 Stopped."));
6836
7045
  } catch {
6837
7046
  console.log(import_chalk3.default.gray("Cleaned up stale PID file."));
6838
7047
  } finally {
6839
7048
  try {
6840
- import_fs17.default.unlinkSync(DAEMON_PID_FILE);
7049
+ import_fs18.default.unlinkSync(DAEMON_PID_FILE);
6841
7050
  } catch {
6842
7051
  }
6843
7052
  }
6844
7053
  }
6845
7054
  function daemonStatus() {
6846
- if (import_fs17.default.existsSync(DAEMON_PID_FILE)) {
7055
+ if (import_fs18.default.existsSync(DAEMON_PID_FILE)) {
6847
7056
  try {
6848
- const { pid } = JSON.parse(import_fs17.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
7057
+ const { pid } = JSON.parse(import_fs18.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
6849
7058
  process.kill(pid, 0);
6850
7059
  console.log(import_chalk3.default.green("Node9 daemon: running"));
6851
7060
  return;
@@ -6864,11 +7073,11 @@ function daemonStatus() {
6864
7073
  console.log(import_chalk3.default.yellow("Node9 daemon: not running"));
6865
7074
  }
6866
7075
  }
6867
- var import_fs17, import_chalk3, import_child_process5;
7076
+ var import_fs18, import_chalk3, import_child_process5;
6868
7077
  var init_daemon2 = __esm({
6869
7078
  "src/daemon/index.ts"() {
6870
7079
  "use strict";
6871
- import_fs17 = __toESM(require("fs"));
7080
+ import_fs18 = __toESM(require("fs"));
6872
7081
  import_chalk3 = __toESM(require("chalk"));
6873
7082
  import_child_process5 = require("child_process");
6874
7083
  init_server();
@@ -6902,7 +7111,7 @@ function formatBase(activity) {
6902
7111
  const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
6903
7112
  const icon = getIcon(activity.tool);
6904
7113
  const toolName = activity.tool.slice(0, 16).padEnd(16);
6905
- const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os24.default.homedir(), "~");
7114
+ const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os25.default.homedir(), "~");
6906
7115
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
6907
7116
  return `${import_chalk19.default.gray(time)} ${icon} ${import_chalk19.default.white.bold(toolName)} ${import_chalk19.default.dim(argsPreview)}`;
6908
7117
  }
@@ -6941,9 +7150,9 @@ function renderPending(activity) {
6941
7150
  }
6942
7151
  async function ensureDaemon() {
6943
7152
  let pidPort = null;
6944
- if (import_fs28.default.existsSync(PID_FILE)) {
7153
+ if (import_fs29.default.existsSync(PID_FILE)) {
6945
7154
  try {
6946
- const { port } = JSON.parse(import_fs28.default.readFileSync(PID_FILE, "utf-8"));
7155
+ const { port } = JSON.parse(import_fs29.default.readFileSync(PID_FILE, "utf-8"));
6947
7156
  pidPort = port;
6948
7157
  } catch {
6949
7158
  console.error(import_chalk19.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
@@ -7020,6 +7229,9 @@ function buildCardLines(req, localCount = 0) {
7020
7229
  `${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}`,
7021
7230
  `${CYAN}\u2551${RESET2} Reason: ${tierLabel} \u2014 ${blockedBy}${RESET2}`
7022
7231
  ];
7232
+ if (req.riskMetadata?.ruleDescription) {
7233
+ lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u2139 ${req.riskMetadata.ruleDescription}${RESET2}`);
7234
+ }
7023
7235
  if (req.riskMetadata?.ruleName && blockedBy.includes("Taint")) {
7024
7236
  lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u26A0 ${req.riskMetadata.ruleName}${RESET2}`);
7025
7237
  }
@@ -7063,9 +7275,9 @@ function buildRecoveryCardLines(req) {
7063
7275
  ];
7064
7276
  }
7065
7277
  function readApproversFromDisk() {
7066
- const configPath = import_path31.default.join(import_os24.default.homedir(), ".node9", "config.json");
7278
+ const configPath = import_path32.default.join(import_os25.default.homedir(), ".node9", "config.json");
7067
7279
  try {
7068
- const raw = JSON.parse(import_fs28.default.readFileSync(configPath, "utf-8"));
7280
+ const raw = JSON.parse(import_fs29.default.readFileSync(configPath, "utf-8"));
7069
7281
  const settings = raw.settings ?? {};
7070
7282
  return settings.approvers ?? {};
7071
7283
  } catch {
@@ -7081,15 +7293,15 @@ function approverStatusLine() {
7081
7293
  return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
7082
7294
  }
7083
7295
  function toggleApprover(channel) {
7084
- const configPath = import_path31.default.join(import_os24.default.homedir(), ".node9", "config.json");
7296
+ const configPath = import_path32.default.join(import_os25.default.homedir(), ".node9", "config.json");
7085
7297
  try {
7086
- const raw = JSON.parse(import_fs28.default.readFileSync(configPath, "utf-8"));
7298
+ const raw = JSON.parse(import_fs29.default.readFileSync(configPath, "utf-8"));
7087
7299
  const settings = raw.settings ?? {};
7088
7300
  const approvers = settings.approvers ?? {};
7089
7301
  approvers[channel] = approvers[channel] === false;
7090
7302
  settings.approvers = approvers;
7091
7303
  raw.settings = settings;
7092
- import_fs28.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
7304
+ import_fs29.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
7093
7305
  } catch (err2) {
7094
7306
  process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
7095
7307
  `);
@@ -7259,8 +7471,8 @@ async function startTail(options = {}) {
7259
7471
  }
7260
7472
  postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
7261
7473
  try {
7262
- import_fs28.default.appendFileSync(
7263
- import_path31.default.join(import_os24.default.homedir(), ".node9", "hook-debug.log"),
7474
+ import_fs29.default.appendFileSync(
7475
+ import_path32.default.join(import_os25.default.homedir(), ".node9", "hook-debug.log"),
7264
7476
  `[tail] POST /decision failed: ${String(err2)}
7265
7477
  `
7266
7478
  );
@@ -7513,21 +7725,21 @@ async function startTail(options = {}) {
7513
7725
  process.exit(1);
7514
7726
  });
7515
7727
  }
7516
- var import_http2, import_chalk19, import_fs28, import_os24, import_path31, import_readline5, import_child_process14, PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
7728
+ var import_http2, import_chalk19, import_fs29, import_os25, import_path32, import_readline5, import_child_process14, PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
7517
7729
  var init_tail = __esm({
7518
7730
  "src/tui/tail.ts"() {
7519
7731
  "use strict";
7520
7732
  import_http2 = __toESM(require("http"));
7521
7733
  import_chalk19 = __toESM(require("chalk"));
7522
- import_fs28 = __toESM(require("fs"));
7523
- import_os24 = __toESM(require("os"));
7524
- import_path31 = __toESM(require("path"));
7734
+ import_fs29 = __toESM(require("fs"));
7735
+ import_os25 = __toESM(require("os"));
7736
+ import_path32 = __toESM(require("path"));
7525
7737
  import_readline5 = __toESM(require("readline"));
7526
7738
  import_child_process14 = require("child_process");
7527
7739
  init_daemon2();
7528
7740
  init_daemon();
7529
7741
  init_core();
7530
- PID_FILE = import_path31.default.join(import_os24.default.homedir(), ".node9", "daemon.pid");
7742
+ PID_FILE = import_path32.default.join(import_os25.default.homedir(), ".node9", "daemon.pid");
7531
7743
  ICONS = {
7532
7744
  bash: "\u{1F4BB}",
7533
7745
  shell: "\u{1F4BB}",
@@ -7642,9 +7854,9 @@ function formatTimeLeft(resetsAt) {
7642
7854
  return ` (${m}m left)`;
7643
7855
  }
7644
7856
  function safeReadJson(filePath) {
7645
- if (!import_fs29.default.existsSync(filePath)) return null;
7857
+ if (!import_fs30.default.existsSync(filePath)) return null;
7646
7858
  try {
7647
- return JSON.parse(import_fs29.default.readFileSync(filePath, "utf-8"));
7859
+ return JSON.parse(import_fs30.default.readFileSync(filePath, "utf-8"));
7648
7860
  } catch {
7649
7861
  return null;
7650
7862
  }
@@ -7665,12 +7877,12 @@ function countHooksInFile(filePath) {
7665
7877
  return Object.keys(cfg.hooks).length;
7666
7878
  }
7667
7879
  function countRulesInDir(rulesDir) {
7668
- if (!import_fs29.default.existsSync(rulesDir)) return 0;
7880
+ if (!import_fs30.default.existsSync(rulesDir)) return 0;
7669
7881
  let count = 0;
7670
7882
  try {
7671
- for (const entry of import_fs29.default.readdirSync(rulesDir, { withFileTypes: true })) {
7883
+ for (const entry of import_fs30.default.readdirSync(rulesDir, { withFileTypes: true })) {
7672
7884
  if (entry.isDirectory()) {
7673
- count += countRulesInDir(import_path32.default.join(rulesDir, entry.name));
7885
+ count += countRulesInDir(import_path33.default.join(rulesDir, entry.name));
7674
7886
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
7675
7887
  count++;
7676
7888
  }
@@ -7681,46 +7893,46 @@ function countRulesInDir(rulesDir) {
7681
7893
  }
7682
7894
  function isSamePath(a, b) {
7683
7895
  try {
7684
- return import_path32.default.resolve(a) === import_path32.default.resolve(b);
7896
+ return import_path33.default.resolve(a) === import_path33.default.resolve(b);
7685
7897
  } catch {
7686
7898
  return false;
7687
7899
  }
7688
7900
  }
7689
7901
  function countConfigs(cwd) {
7690
- const homeDir2 = import_os25.default.homedir();
7691
- const claudeDir = import_path32.default.join(homeDir2, ".claude");
7902
+ const homeDir2 = import_os26.default.homedir();
7903
+ const claudeDir = import_path33.default.join(homeDir2, ".claude");
7692
7904
  let claudeMdCount = 0;
7693
7905
  let rulesCount = 0;
7694
7906
  let hooksCount = 0;
7695
7907
  const userMcpServers = /* @__PURE__ */ new Set();
7696
7908
  const projectMcpServers = /* @__PURE__ */ new Set();
7697
- if (import_fs29.default.existsSync(import_path32.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
7698
- rulesCount += countRulesInDir(import_path32.default.join(claudeDir, "rules"));
7699
- const userSettings = import_path32.default.join(claudeDir, "settings.json");
7909
+ if (import_fs30.default.existsSync(import_path33.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
7910
+ rulesCount += countRulesInDir(import_path33.default.join(claudeDir, "rules"));
7911
+ const userSettings = import_path33.default.join(claudeDir, "settings.json");
7700
7912
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
7701
7913
  hooksCount += countHooksInFile(userSettings);
7702
- const userClaudeJson = import_path32.default.join(homeDir2, ".claude.json");
7914
+ const userClaudeJson = import_path33.default.join(homeDir2, ".claude.json");
7703
7915
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
7704
7916
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
7705
7917
  userMcpServers.delete(name);
7706
7918
  }
7707
7919
  if (cwd) {
7708
- if (import_fs29.default.existsSync(import_path32.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
7709
- if (import_fs29.default.existsSync(import_path32.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
7710
- const projectClaudeDir = import_path32.default.join(cwd, ".claude");
7920
+ if (import_fs30.default.existsSync(import_path33.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
7921
+ if (import_fs30.default.existsSync(import_path33.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
7922
+ const projectClaudeDir = import_path33.default.join(cwd, ".claude");
7711
7923
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
7712
7924
  if (!overlapsUserScope) {
7713
- if (import_fs29.default.existsSync(import_path32.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
7714
- rulesCount += countRulesInDir(import_path32.default.join(projectClaudeDir, "rules"));
7715
- const projSettings = import_path32.default.join(projectClaudeDir, "settings.json");
7925
+ if (import_fs30.default.existsSync(import_path33.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
7926
+ rulesCount += countRulesInDir(import_path33.default.join(projectClaudeDir, "rules"));
7927
+ const projSettings = import_path33.default.join(projectClaudeDir, "settings.json");
7716
7928
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
7717
7929
  hooksCount += countHooksInFile(projSettings);
7718
7930
  }
7719
- if (import_fs29.default.existsSync(import_path32.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
7720
- const localSettings = import_path32.default.join(projectClaudeDir, "settings.local.json");
7931
+ if (import_fs30.default.existsSync(import_path33.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
7932
+ const localSettings = import_path33.default.join(projectClaudeDir, "settings.local.json");
7721
7933
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
7722
7934
  hooksCount += countHooksInFile(localSettings);
7723
- const mcpJsonServers = getMcpServerNames(import_path32.default.join(cwd, ".mcp.json"));
7935
+ const mcpJsonServers = getMcpServerNames(import_path33.default.join(cwd, ".mcp.json"));
7724
7936
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
7725
7937
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
7726
7938
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -7753,12 +7965,12 @@ function readActiveShieldsHud() {
7753
7965
  return shieldsCache.value;
7754
7966
  }
7755
7967
  try {
7756
- const shieldsPath = import_path32.default.join(import_os25.default.homedir(), ".node9", "shields.json");
7757
- if (!import_fs29.default.existsSync(shieldsPath)) {
7968
+ const shieldsPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "shields.json");
7969
+ if (!import_fs30.default.existsSync(shieldsPath)) {
7758
7970
  shieldsCache = { value: [], ts: now };
7759
7971
  return [];
7760
7972
  }
7761
- const parsed = JSON.parse(import_fs29.default.readFileSync(shieldsPath, "utf-8"));
7973
+ const parsed = JSON.parse(import_fs30.default.readFileSync(shieldsPath, "utf-8"));
7762
7974
  if (!Array.isArray(parsed.active)) {
7763
7975
  shieldsCache = { value: [], ts: now };
7764
7976
  return [];
@@ -7860,17 +8072,17 @@ function renderContextLine(stdin) {
7860
8072
  async function main() {
7861
8073
  try {
7862
8074
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
7863
- if (import_fs29.default.existsSync(import_path32.default.join(import_os25.default.homedir(), ".node9", "hud-debug"))) {
8075
+ if (import_fs30.default.existsSync(import_path33.default.join(import_os26.default.homedir(), ".node9", "hud-debug"))) {
7864
8076
  try {
7865
- const logPath = import_path32.default.join(import_os25.default.homedir(), ".node9", "hud-debug.log");
8077
+ const logPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "hud-debug.log");
7866
8078
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
7867
8079
  let size = 0;
7868
8080
  try {
7869
- size = import_fs29.default.statSync(logPath).size;
8081
+ size = import_fs30.default.statSync(logPath).size;
7870
8082
  } catch {
7871
8083
  }
7872
8084
  if (size < MAX_LOG_SIZE) {
7873
- import_fs29.default.appendFileSync(
8085
+ import_fs30.default.appendFileSync(
7874
8086
  logPath,
7875
8087
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
7876
8088
  );
@@ -7891,11 +8103,11 @@ async function main() {
7891
8103
  try {
7892
8104
  const cwd = stdin.cwd ?? process.cwd();
7893
8105
  for (const configPath of [
7894
- import_path32.default.join(cwd, "node9.config.json"),
7895
- import_path32.default.join(import_os25.default.homedir(), ".node9", "config.json")
8106
+ import_path33.default.join(cwd, "node9.config.json"),
8107
+ import_path33.default.join(import_os26.default.homedir(), ".node9", "config.json")
7896
8108
  ]) {
7897
- if (!import_fs29.default.existsSync(configPath)) continue;
7898
- const cfg = JSON.parse(import_fs29.default.readFileSync(configPath, "utf-8"));
8109
+ if (!import_fs30.default.existsSync(configPath)) continue;
8110
+ const cfg = JSON.parse(import_fs30.default.readFileSync(configPath, "utf-8"));
7899
8111
  const hud = cfg.settings?.hud;
7900
8112
  if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
7901
8113
  }
@@ -7913,13 +8125,13 @@ async function main() {
7913
8125
  renderOffline();
7914
8126
  }
7915
8127
  }
7916
- var import_fs29, import_path32, import_os25, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
8128
+ var import_fs30, import_path33, import_os26, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
7917
8129
  var init_hud = __esm({
7918
8130
  "src/cli/hud.ts"() {
7919
8131
  "use strict";
7920
- import_fs29 = __toESM(require("fs"));
7921
- import_path32 = __toESM(require("path"));
7922
- import_os25 = __toESM(require("os"));
8132
+ import_fs30 = __toESM(require("fs"));
8133
+ import_path33 = __toESM(require("path"));
8134
+ import_os26 = __toESM(require("os"));
7923
8135
  import_http3 = __toESM(require("http"));
7924
8136
  init_daemon();
7925
8137
  RESET3 = "\x1B[0m";
@@ -8529,9 +8741,9 @@ function teardownHud() {
8529
8741
  // src/cli.ts
8530
8742
  init_daemon2();
8531
8743
  var import_chalk20 = __toESM(require("chalk"));
8532
- var import_fs30 = __toESM(require("fs"));
8533
- var import_path33 = __toESM(require("path"));
8534
- var import_os26 = __toESM(require("os"));
8744
+ var import_fs31 = __toESM(require("fs"));
8745
+ var import_path34 = __toESM(require("path"));
8746
+ var import_os27 = __toESM(require("os"));
8535
8747
  var import_prompts2 = require("@inquirer/prompts");
8536
8748
 
8537
8749
  // src/utils/duration.ts
@@ -8756,10 +8968,10 @@ async function autoStartDaemonAndWait() {
8756
8968
 
8757
8969
  // src/cli/commands/check.ts
8758
8970
  var import_chalk5 = __toESM(require("chalk"));
8759
- var import_fs19 = __toESM(require("fs"));
8971
+ var import_fs20 = __toESM(require("fs"));
8760
8972
  var import_child_process9 = require("child_process");
8761
- var import_path21 = __toESM(require("path"));
8762
- var import_os15 = __toESM(require("os"));
8973
+ var import_path22 = __toESM(require("path"));
8974
+ var import_os16 = __toESM(require("os"));
8763
8975
  init_orchestrator();
8764
8976
  init_daemon();
8765
8977
  init_config();
@@ -8768,11 +8980,11 @@ init_policy();
8768
8980
  // src/undo.ts
8769
8981
  var import_child_process8 = require("child_process");
8770
8982
  var import_crypto8 = __toESM(require("crypto"));
8771
- var import_fs18 = __toESM(require("fs"));
8983
+ var import_fs19 = __toESM(require("fs"));
8772
8984
  var import_net3 = __toESM(require("net"));
8773
- var import_path20 = __toESM(require("path"));
8774
- var import_os14 = __toESM(require("os"));
8775
- var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path20.default.join(import_os14.default.tmpdir(), "node9-activity.sock");
8985
+ var import_path21 = __toESM(require("path"));
8986
+ var import_os15 = __toESM(require("os"));
8987
+ var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path21.default.join(import_os15.default.tmpdir(), "node9-activity.sock");
8776
8988
  function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
8777
8989
  try {
8778
8990
  const payload = JSON.stringify({
@@ -8792,22 +9004,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
8792
9004
  } catch {
8793
9005
  }
8794
9006
  }
8795
- var SNAPSHOT_STACK_PATH = import_path20.default.join(import_os14.default.homedir(), ".node9", "snapshots.json");
8796
- var UNDO_LATEST_PATH = import_path20.default.join(import_os14.default.homedir(), ".node9", "undo_latest.txt");
9007
+ var SNAPSHOT_STACK_PATH = import_path21.default.join(import_os15.default.homedir(), ".node9", "snapshots.json");
9008
+ var UNDO_LATEST_PATH = import_path21.default.join(import_os15.default.homedir(), ".node9", "undo_latest.txt");
8797
9009
  var MAX_SNAPSHOTS = 10;
8798
9010
  var GIT_TIMEOUT = 15e3;
8799
9011
  function readStack() {
8800
9012
  try {
8801
- if (import_fs18.default.existsSync(SNAPSHOT_STACK_PATH))
8802
- return JSON.parse(import_fs18.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
9013
+ if (import_fs19.default.existsSync(SNAPSHOT_STACK_PATH))
9014
+ return JSON.parse(import_fs19.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
8803
9015
  } catch {
8804
9016
  }
8805
9017
  return [];
8806
9018
  }
8807
9019
  function writeStack(stack) {
8808
- const dir = import_path20.default.dirname(SNAPSHOT_STACK_PATH);
8809
- if (!import_fs18.default.existsSync(dir)) import_fs18.default.mkdirSync(dir, { recursive: true });
8810
- import_fs18.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
9020
+ const dir = import_path21.default.dirname(SNAPSHOT_STACK_PATH);
9021
+ if (!import_fs19.default.existsSync(dir)) import_fs19.default.mkdirSync(dir, { recursive: true });
9022
+ import_fs19.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
8811
9023
  }
8812
9024
  function extractFilePath(args) {
8813
9025
  if (!args || typeof args !== "object") return null;
@@ -8827,12 +9039,12 @@ function buildArgsSummary(tool, args) {
8827
9039
  return "";
8828
9040
  }
8829
9041
  function findProjectRoot(filePath) {
8830
- let dir = import_path20.default.dirname(filePath);
9042
+ let dir = import_path21.default.dirname(filePath);
8831
9043
  while (true) {
8832
- if (import_fs18.default.existsSync(import_path20.default.join(dir, ".git")) || import_fs18.default.existsSync(import_path20.default.join(dir, "package.json"))) {
9044
+ if (import_fs19.default.existsSync(import_path21.default.join(dir, ".git")) || import_fs19.default.existsSync(import_path21.default.join(dir, "package.json"))) {
8833
9045
  return dir;
8834
9046
  }
8835
- const parent = import_path20.default.dirname(dir);
9047
+ const parent = import_path21.default.dirname(dir);
8836
9048
  if (parent === dir) return process.cwd();
8837
9049
  dir = parent;
8838
9050
  }
@@ -8840,7 +9052,7 @@ function findProjectRoot(filePath) {
8840
9052
  function normalizeCwdForHash(cwd) {
8841
9053
  let normalized;
8842
9054
  try {
8843
- normalized = import_fs18.default.realpathSync(cwd);
9055
+ normalized = import_fs19.default.realpathSync(cwd);
8844
9056
  } catch {
8845
9057
  normalized = cwd;
8846
9058
  }
@@ -8850,16 +9062,16 @@ function normalizeCwdForHash(cwd) {
8850
9062
  }
8851
9063
  function getShadowRepoDir(cwd) {
8852
9064
  const hash = import_crypto8.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
8853
- return import_path20.default.join(import_os14.default.homedir(), ".node9", "snapshots", hash);
9065
+ return import_path21.default.join(import_os15.default.homedir(), ".node9", "snapshots", hash);
8854
9066
  }
8855
9067
  function cleanOrphanedIndexFiles(shadowDir) {
8856
9068
  try {
8857
9069
  const cutoff = Date.now() - 6e4;
8858
- for (const f of import_fs18.default.readdirSync(shadowDir)) {
9070
+ for (const f of import_fs19.default.readdirSync(shadowDir)) {
8859
9071
  if (f.startsWith("index_")) {
8860
- const fp = import_path20.default.join(shadowDir, f);
9072
+ const fp = import_path21.default.join(shadowDir, f);
8861
9073
  try {
8862
- if (import_fs18.default.statSync(fp).mtimeMs < cutoff) import_fs18.default.unlinkSync(fp);
9074
+ if (import_fs19.default.statSync(fp).mtimeMs < cutoff) import_fs19.default.unlinkSync(fp);
8863
9075
  } catch {
8864
9076
  }
8865
9077
  }
@@ -8871,7 +9083,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
8871
9083
  const hardcoded = [".git", ".node9"];
8872
9084
  const lines = [...hardcoded, ...ignorePaths].join("\n");
8873
9085
  try {
8874
- import_fs18.default.writeFileSync(import_path20.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
9086
+ import_fs19.default.writeFileSync(import_path21.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
8875
9087
  } catch {
8876
9088
  }
8877
9089
  }
@@ -8884,25 +9096,25 @@ function ensureShadowRepo(shadowDir, cwd) {
8884
9096
  timeout: 3e3
8885
9097
  });
8886
9098
  if (check.status === 0) {
8887
- const ptPath = import_path20.default.join(shadowDir, "project-path.txt");
9099
+ const ptPath = import_path21.default.join(shadowDir, "project-path.txt");
8888
9100
  try {
8889
- const stored = import_fs18.default.readFileSync(ptPath, "utf8").trim();
9101
+ const stored = import_fs19.default.readFileSync(ptPath, "utf8").trim();
8890
9102
  if (stored === normalizedCwd) return true;
8891
9103
  if (process.env.NODE9_DEBUG === "1")
8892
9104
  console.error(
8893
9105
  `[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
8894
9106
  );
8895
- import_fs18.default.rmSync(shadowDir, { recursive: true, force: true });
9107
+ import_fs19.default.rmSync(shadowDir, { recursive: true, force: true });
8896
9108
  } catch {
8897
9109
  try {
8898
- import_fs18.default.writeFileSync(ptPath, normalizedCwd, "utf8");
9110
+ import_fs19.default.writeFileSync(ptPath, normalizedCwd, "utf8");
8899
9111
  } catch {
8900
9112
  }
8901
9113
  return true;
8902
9114
  }
8903
9115
  }
8904
9116
  try {
8905
- import_fs18.default.mkdirSync(shadowDir, { recursive: true });
9117
+ import_fs19.default.mkdirSync(shadowDir, { recursive: true });
8906
9118
  } catch {
8907
9119
  }
8908
9120
  const init = (0, import_child_process8.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
@@ -8911,7 +9123,7 @@ function ensureShadowRepo(shadowDir, cwd) {
8911
9123
  if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
8912
9124
  return false;
8913
9125
  }
8914
- const configFile = import_path20.default.join(shadowDir, "config");
9126
+ const configFile = import_path21.default.join(shadowDir, "config");
8915
9127
  (0, import_child_process8.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
8916
9128
  timeout: 3e3
8917
9129
  });
@@ -8919,7 +9131,7 @@ function ensureShadowRepo(shadowDir, cwd) {
8919
9131
  timeout: 3e3
8920
9132
  });
8921
9133
  try {
8922
- import_fs18.default.writeFileSync(import_path20.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
9134
+ import_fs19.default.writeFileSync(import_path21.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
8923
9135
  } catch {
8924
9136
  }
8925
9137
  return true;
@@ -8939,12 +9151,12 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8939
9151
  let indexFile = null;
8940
9152
  try {
8941
9153
  const rawFilePath = extractFilePath(args);
8942
- const absFilePath = rawFilePath && import_path20.default.isAbsolute(rawFilePath) ? rawFilePath : null;
9154
+ const absFilePath = rawFilePath && import_path21.default.isAbsolute(rawFilePath) ? rawFilePath : null;
8943
9155
  const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
8944
9156
  const shadowDir = getShadowRepoDir(cwd);
8945
9157
  if (!ensureShadowRepo(shadowDir, cwd)) return null;
8946
9158
  writeShadowExcludes(shadowDir, ignorePaths);
8947
- indexFile = import_path20.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
9159
+ indexFile = import_path21.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
8948
9160
  const shadowEnv = {
8949
9161
  ...process.env,
8950
9162
  GIT_DIR: shadowDir,
@@ -9016,7 +9228,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
9016
9228
  writeStack(stack);
9017
9229
  const entry = stack[stack.length - 1];
9018
9230
  notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
9019
- import_fs18.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
9231
+ import_fs19.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
9020
9232
  if (shouldGc) {
9021
9233
  (0, import_child_process8.spawn)("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
9022
9234
  }
@@ -9027,7 +9239,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
9027
9239
  } finally {
9028
9240
  if (indexFile) {
9029
9241
  try {
9030
- import_fs18.default.unlinkSync(indexFile);
9242
+ import_fs19.default.unlinkSync(indexFile);
9031
9243
  } catch {
9032
9244
  }
9033
9245
  }
@@ -9103,9 +9315,9 @@ function applyUndo(hash, cwd) {
9103
9315
  timeout: GIT_TIMEOUT
9104
9316
  }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
9105
9317
  for (const file of [...tracked, ...untracked]) {
9106
- const fullPath = import_path20.default.join(dir, file);
9107
- if (!snapshotFiles.has(file) && import_fs18.default.existsSync(fullPath)) {
9108
- import_fs18.default.unlinkSync(fullPath);
9318
+ const fullPath = import_path21.default.join(dir, file);
9319
+ if (!snapshotFiles.has(file) && import_fs19.default.existsSync(fullPath)) {
9320
+ import_fs19.default.unlinkSync(fullPath);
9109
9321
  }
9110
9322
  }
9111
9323
  return true;
@@ -9129,9 +9341,9 @@ function registerCheckCommand(program2) {
9129
9341
  } catch (err2) {
9130
9342
  const tempConfig = getConfig();
9131
9343
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
9132
- const logPath = import_path21.default.join(import_os15.default.homedir(), ".node9", "hook-debug.log");
9344
+ const logPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "hook-debug.log");
9133
9345
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
9134
- import_fs19.default.appendFileSync(
9346
+ import_fs20.default.appendFileSync(
9135
9347
  logPath,
9136
9348
  `[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
9137
9349
  RAW: ${raw}
@@ -9144,13 +9356,13 @@ RAW: ${raw}
9144
9356
  if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
9145
9357
  try {
9146
9358
  const scriptPath = process.argv[1];
9147
- if (typeof scriptPath !== "string" || !import_path21.default.isAbsolute(scriptPath))
9359
+ if (typeof scriptPath !== "string" || !import_path22.default.isAbsolute(scriptPath))
9148
9360
  throw new Error("node9: argv[1] is not an absolute path");
9149
- const resolvedScript = import_fs19.default.realpathSync(scriptPath);
9150
- const expectedCli = import_fs19.default.realpathSync(import_path21.default.resolve(__dirname, "../../cli.js"));
9151
- if (resolvedScript !== expectedCli)
9361
+ const resolvedScript = import_fs20.default.realpathSync(scriptPath);
9362
+ const packageDist = import_fs20.default.realpathSync(import_path22.default.resolve(__dirname, "../.."));
9363
+ if (!resolvedScript.startsWith(packageDist + import_path22.default.sep) && resolvedScript !== packageDist)
9152
9364
  throw new Error(
9153
- "node9: daemon spawn aborted \u2014 argv[1] does not resolve to the node9 CLI"
9365
+ `node9: daemon spawn aborted \u2014 argv[1] (${resolvedScript}) is outside package dist (${packageDist})`
9154
9366
  );
9155
9367
  const safeEnv = { ...process.env };
9156
9368
  for (const key of [
@@ -9169,14 +9381,24 @@ RAW: ${raw}
9169
9381
  env: { ...safeEnv, NODE9_AUTO_STARTED: "1", NODE9_BROWSER_OPENED: "1" }
9170
9382
  });
9171
9383
  d.unref();
9172
- } catch {
9384
+ } catch (spawnErr) {
9385
+ const logPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "hook-debug.log");
9386
+ const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
9387
+ try {
9388
+ import_fs20.default.appendFileSync(
9389
+ logPath,
9390
+ `[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-failed: ${msg}
9391
+ `
9392
+ );
9393
+ } catch {
9394
+ }
9173
9395
  }
9174
9396
  }
9175
9397
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
9176
- const logPath = import_path21.default.join(import_os15.default.homedir(), ".node9", "hook-debug.log");
9177
- if (!import_fs19.default.existsSync(import_path21.default.dirname(logPath)))
9178
- import_fs19.default.mkdirSync(import_path21.default.dirname(logPath), { recursive: true });
9179
- import_fs19.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
9398
+ const logPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "hook-debug.log");
9399
+ if (!import_fs20.default.existsSync(import_path22.default.dirname(logPath)))
9400
+ import_fs20.default.mkdirSync(import_path22.default.dirname(logPath), { recursive: true });
9401
+ import_fs20.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
9180
9402
  `);
9181
9403
  }
9182
9404
  const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
@@ -9189,8 +9411,8 @@ RAW: ${raw}
9189
9411
  const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
9190
9412
  let ttyFd = null;
9191
9413
  try {
9192
- ttyFd = import_fs19.default.openSync("/dev/tty", "w");
9193
- const writeTty = (line) => import_fs19.default.writeSync(ttyFd, line + "\n");
9414
+ ttyFd = import_fs20.default.openSync("/dev/tty", "w");
9415
+ const writeTty = (line) => import_fs20.default.writeSync(ttyFd, line + "\n");
9194
9416
  if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
9195
9417
  writeTty(import_chalk5.default.bgRed.white.bold(`
9196
9418
  \u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
@@ -9209,7 +9431,7 @@ RAW: ${raw}
9209
9431
  } finally {
9210
9432
  if (ttyFd !== null)
9211
9433
  try {
9212
- import_fs19.default.closeSync(ttyFd);
9434
+ import_fs20.default.closeSync(ttyFd);
9213
9435
  } catch {
9214
9436
  }
9215
9437
  }
@@ -9241,7 +9463,7 @@ RAW: ${raw}
9241
9463
  if (shouldSnapshot(toolName, toolInput, config)) {
9242
9464
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
9243
9465
  }
9244
- const safeCwdForAuth = typeof payload.cwd === "string" && import_path21.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9466
+ const safeCwdForAuth = typeof payload.cwd === "string" && import_path22.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9245
9467
  const result = await authorizeHeadless(toolName, toolInput, meta, {
9246
9468
  cwd: safeCwdForAuth
9247
9469
  });
@@ -9253,12 +9475,12 @@ RAW: ${raw}
9253
9475
  }
9254
9476
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
9255
9477
  try {
9256
- const tty = import_fs19.default.openSync("/dev/tty", "w");
9257
- import_fs19.default.writeSync(
9478
+ const tty = import_fs20.default.openSync("/dev/tty", "w");
9479
+ import_fs20.default.writeSync(
9258
9480
  tty,
9259
9481
  import_chalk5.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
9260
9482
  );
9261
- import_fs19.default.closeSync(tty);
9483
+ import_fs20.default.closeSync(tty);
9262
9484
  } catch {
9263
9485
  }
9264
9486
  const daemonReady = await autoStartDaemonAndWait();
@@ -9285,9 +9507,9 @@ RAW: ${raw}
9285
9507
  });
9286
9508
  } catch (err2) {
9287
9509
  if (process.env.NODE9_DEBUG === "1") {
9288
- const logPath = import_path21.default.join(import_os15.default.homedir(), ".node9", "hook-debug.log");
9510
+ const logPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "hook-debug.log");
9289
9511
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
9290
- import_fs19.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
9512
+ import_fs20.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
9291
9513
  `);
9292
9514
  }
9293
9515
  process.exit(0);
@@ -9321,9 +9543,9 @@ RAW: ${raw}
9321
9543
  }
9322
9544
 
9323
9545
  // src/cli/commands/log.ts
9324
- var import_fs20 = __toESM(require("fs"));
9325
- var import_path22 = __toESM(require("path"));
9326
- var import_os16 = __toESM(require("os"));
9546
+ var import_fs21 = __toESM(require("fs"));
9547
+ var import_path23 = __toESM(require("path"));
9548
+ var import_os17 = __toESM(require("os"));
9327
9549
  init_audit();
9328
9550
  init_config();
9329
9551
  init_policy();
@@ -9399,10 +9621,10 @@ function registerLogCommand(program2) {
9399
9621
  decision: "allowed",
9400
9622
  source: "post-hook"
9401
9623
  };
9402
- const logPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "audit.log");
9403
- if (!import_fs20.default.existsSync(import_path22.default.dirname(logPath)))
9404
- import_fs20.default.mkdirSync(import_path22.default.dirname(logPath), { recursive: true });
9405
- import_fs20.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
9624
+ const logPath = import_path23.default.join(import_os17.default.homedir(), ".node9", "audit.log");
9625
+ if (!import_fs21.default.existsSync(import_path23.default.dirname(logPath)))
9626
+ import_fs21.default.mkdirSync(import_path23.default.dirname(logPath), { recursive: true });
9627
+ import_fs21.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
9406
9628
  if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
9407
9629
  const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
9408
9630
  if (command) {
@@ -9435,7 +9657,7 @@ function registerLogCommand(program2) {
9435
9657
  }
9436
9658
  }
9437
9659
  }
9438
- const safeCwd = typeof payload.cwd === "string" && import_path22.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9660
+ const safeCwd = typeof payload.cwd === "string" && import_path23.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9439
9661
  const config = getConfig(safeCwd);
9440
9662
  if (shouldSnapshot(tool, {}, config)) {
9441
9663
  await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
@@ -9444,9 +9666,9 @@ function registerLogCommand(program2) {
9444
9666
  const msg = err2 instanceof Error ? err2.message : String(err2);
9445
9667
  process.stderr.write(`[Node9] audit log error: ${msg}
9446
9668
  `);
9447
- const debugPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "hook-debug.log");
9669
+ const debugPath = import_path23.default.join(import_os17.default.homedir(), ".node9", "hook-debug.log");
9448
9670
  try {
9449
- import_fs20.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
9671
+ import_fs21.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
9450
9672
  `);
9451
9673
  } catch {
9452
9674
  }
@@ -9846,14 +10068,14 @@ function registerConfigShowCommand(program2) {
9846
10068
 
9847
10069
  // src/cli/commands/doctor.ts
9848
10070
  var import_chalk7 = __toESM(require("chalk"));
9849
- var import_fs21 = __toESM(require("fs"));
9850
- var import_path23 = __toESM(require("path"));
9851
- var import_os17 = __toESM(require("os"));
10071
+ var import_fs22 = __toESM(require("fs"));
10072
+ var import_path24 = __toESM(require("path"));
10073
+ var import_os18 = __toESM(require("os"));
9852
10074
  var import_child_process10 = require("child_process");
9853
10075
  init_daemon();
9854
10076
  function registerDoctorCommand(program2, version2) {
9855
10077
  program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
9856
- const homeDir2 = import_os17.default.homedir();
10078
+ const homeDir2 = import_os18.default.homedir();
9857
10079
  let failures = 0;
9858
10080
  function pass(msg) {
9859
10081
  console.log(import_chalk7.default.green(" \u2705 ") + msg);
@@ -9902,10 +10124,10 @@ function registerDoctorCommand(program2, version2) {
9902
10124
  );
9903
10125
  }
9904
10126
  section("Configuration");
9905
- const globalConfigPath = import_path23.default.join(homeDir2, ".node9", "config.json");
9906
- if (import_fs21.default.existsSync(globalConfigPath)) {
10127
+ const globalConfigPath = import_path24.default.join(homeDir2, ".node9", "config.json");
10128
+ if (import_fs22.default.existsSync(globalConfigPath)) {
9907
10129
  try {
9908
- JSON.parse(import_fs21.default.readFileSync(globalConfigPath, "utf-8"));
10130
+ JSON.parse(import_fs22.default.readFileSync(globalConfigPath, "utf-8"));
9909
10131
  pass("~/.node9/config.json found and valid");
9910
10132
  } catch {
9911
10133
  fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
@@ -9913,10 +10135,10 @@ function registerDoctorCommand(program2, version2) {
9913
10135
  } else {
9914
10136
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
9915
10137
  }
9916
- const projectConfigPath = import_path23.default.join(process.cwd(), "node9.config.json");
9917
- if (import_fs21.default.existsSync(projectConfigPath)) {
10138
+ const projectConfigPath = import_path24.default.join(process.cwd(), "node9.config.json");
10139
+ if (import_fs22.default.existsSync(projectConfigPath)) {
9918
10140
  try {
9919
- JSON.parse(import_fs21.default.readFileSync(projectConfigPath, "utf-8"));
10141
+ JSON.parse(import_fs22.default.readFileSync(projectConfigPath, "utf-8"));
9920
10142
  pass("node9.config.json found and valid (project)");
9921
10143
  } catch {
9922
10144
  fail(
@@ -9925,8 +10147,8 @@ function registerDoctorCommand(program2, version2) {
9925
10147
  );
9926
10148
  }
9927
10149
  }
9928
- const credsPath = import_path23.default.join(homeDir2, ".node9", "credentials.json");
9929
- if (import_fs21.default.existsSync(credsPath)) {
10150
+ const credsPath = import_path24.default.join(homeDir2, ".node9", "credentials.json");
10151
+ if (import_fs22.default.existsSync(credsPath)) {
9930
10152
  pass("Cloud credentials found (~/.node9/credentials.json)");
9931
10153
  } else {
9932
10154
  warn(
@@ -9935,10 +10157,10 @@ function registerDoctorCommand(program2, version2) {
9935
10157
  );
9936
10158
  }
9937
10159
  section("Agent Hooks");
9938
- const claudeSettingsPath = import_path23.default.join(homeDir2, ".claude", "settings.json");
9939
- if (import_fs21.default.existsSync(claudeSettingsPath)) {
10160
+ const claudeSettingsPath = import_path24.default.join(homeDir2, ".claude", "settings.json");
10161
+ if (import_fs22.default.existsSync(claudeSettingsPath)) {
9940
10162
  try {
9941
- const cs = JSON.parse(import_fs21.default.readFileSync(claudeSettingsPath, "utf-8"));
10163
+ const cs = JSON.parse(import_fs22.default.readFileSync(claudeSettingsPath, "utf-8"));
9942
10164
  const hasHook = cs.hooks?.PreToolUse?.some(
9943
10165
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
9944
10166
  );
@@ -9954,10 +10176,10 @@ function registerDoctorCommand(program2, version2) {
9954
10176
  } else {
9955
10177
  warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
9956
10178
  }
9957
- const geminiSettingsPath = import_path23.default.join(homeDir2, ".gemini", "settings.json");
9958
- if (import_fs21.default.existsSync(geminiSettingsPath)) {
10179
+ const geminiSettingsPath = import_path24.default.join(homeDir2, ".gemini", "settings.json");
10180
+ if (import_fs22.default.existsSync(geminiSettingsPath)) {
9959
10181
  try {
9960
- const gs = JSON.parse(import_fs21.default.readFileSync(geminiSettingsPath, "utf-8"));
10182
+ const gs = JSON.parse(import_fs22.default.readFileSync(geminiSettingsPath, "utf-8"));
9961
10183
  const hasHook = gs.hooks?.BeforeTool?.some(
9962
10184
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
9963
10185
  );
@@ -9973,10 +10195,10 @@ function registerDoctorCommand(program2, version2) {
9973
10195
  } else {
9974
10196
  warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
9975
10197
  }
9976
- const cursorHooksPath = import_path23.default.join(homeDir2, ".cursor", "hooks.json");
9977
- if (import_fs21.default.existsSync(cursorHooksPath)) {
10198
+ const cursorHooksPath = import_path24.default.join(homeDir2, ".cursor", "hooks.json");
10199
+ if (import_fs22.default.existsSync(cursorHooksPath)) {
9978
10200
  try {
9979
- const cur = JSON.parse(import_fs21.default.readFileSync(cursorHooksPath, "utf-8"));
10201
+ const cur = JSON.parse(import_fs22.default.readFileSync(cursorHooksPath, "utf-8"));
9980
10202
  const hasHook = cur.hooks?.preToolUse?.some(
9981
10203
  (h) => h.command?.includes("node9") || h.command?.includes("cli.js")
9982
10204
  );
@@ -10014,9 +10236,9 @@ function registerDoctorCommand(program2, version2) {
10014
10236
 
10015
10237
  // src/cli/commands/audit.ts
10016
10238
  var import_chalk8 = __toESM(require("chalk"));
10017
- var import_fs22 = __toESM(require("fs"));
10018
- var import_path24 = __toESM(require("path"));
10019
- var import_os18 = __toESM(require("os"));
10239
+ var import_fs23 = __toESM(require("fs"));
10240
+ var import_path25 = __toESM(require("path"));
10241
+ var import_os19 = __toESM(require("os"));
10020
10242
  function formatRelativeTime(timestamp) {
10021
10243
  const diff = Date.now() - new Date(timestamp).getTime();
10022
10244
  const sec = Math.floor(diff / 1e3);
@@ -10029,14 +10251,14 @@ function formatRelativeTime(timestamp) {
10029
10251
  }
10030
10252
  function registerAuditCommand(program2) {
10031
10253
  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) => {
10032
- const logPath = import_path24.default.join(import_os18.default.homedir(), ".node9", "audit.log");
10033
- if (!import_fs22.default.existsSync(logPath)) {
10254
+ const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "audit.log");
10255
+ if (!import_fs23.default.existsSync(logPath)) {
10034
10256
  console.log(
10035
10257
  import_chalk8.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
10036
10258
  );
10037
10259
  return;
10038
10260
  }
10039
- const raw = import_fs22.default.readFileSync(logPath, "utf-8");
10261
+ const raw = import_fs23.default.readFileSync(logPath, "utf-8");
10040
10262
  const lines = raw.split("\n").filter((l) => l.trim() !== "");
10041
10263
  let entries = lines.flatMap((line) => {
10042
10264
  try {
@@ -10090,9 +10312,9 @@ function registerAuditCommand(program2) {
10090
10312
 
10091
10313
  // src/cli/commands/report.ts
10092
10314
  var import_chalk9 = __toESM(require("chalk"));
10093
- var import_fs23 = __toESM(require("fs"));
10094
- var import_path25 = __toESM(require("path"));
10095
- var import_os19 = __toESM(require("os"));
10315
+ var import_fs24 = __toESM(require("fs"));
10316
+ var import_path26 = __toESM(require("path"));
10317
+ var import_os20 = __toESM(require("os"));
10096
10318
  var TEST_COMMAND_RE3 = /(?:^|\s)(npm\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|yarn\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|vitest|jest|mocha|pytest|py\.test|cargo\s+test|go\s+test|bundle\s+exec\s+rspec|rspec|phpunit|dotnet\s+test)\b/i;
10097
10319
  function buildTestTimestamps(allEntries) {
10098
10320
  const testTs = /* @__PURE__ */ new Set();
@@ -10139,8 +10361,8 @@ function getDateRange(period) {
10139
10361
  }
10140
10362
  }
10141
10363
  function parseAuditLog(logPath) {
10142
- if (!import_fs23.default.existsSync(logPath)) return [];
10143
- const raw = import_fs23.default.readFileSync(logPath, "utf-8");
10364
+ if (!import_fs24.default.existsSync(logPath)) return [];
10365
+ const raw = import_fs24.default.readFileSync(logPath, "utf-8");
10144
10366
  return raw.split("\n").flatMap((line) => {
10145
10367
  if (!line.trim()) return [];
10146
10368
  try {
@@ -10202,29 +10424,39 @@ function claudeModelPrice(model) {
10202
10424
  return null;
10203
10425
  }
10204
10426
  function loadClaudeCost(start, end) {
10205
- const projectsDir = import_path25.default.join(import_os19.default.homedir(), ".claude", "projects");
10206
- if (!import_fs23.default.existsSync(projectsDir)) return { total: 0, byDay: /* @__PURE__ */ new Map() };
10427
+ const empty = {
10428
+ total: 0,
10429
+ byDay: /* @__PURE__ */ new Map(),
10430
+ byModel: /* @__PURE__ */ new Map(),
10431
+ inputTokens: 0,
10432
+ cacheReadTokens: 0
10433
+ };
10434
+ const projectsDir = import_path26.default.join(import_os20.default.homedir(), ".claude", "projects");
10435
+ if (!import_fs24.default.existsSync(projectsDir)) return empty;
10207
10436
  let dirs;
10208
10437
  try {
10209
- dirs = import_fs23.default.readdirSync(projectsDir);
10438
+ dirs = import_fs24.default.readdirSync(projectsDir);
10210
10439
  } catch {
10211
- return { total: 0, byDay: /* @__PURE__ */ new Map() };
10440
+ return empty;
10212
10441
  }
10213
10442
  let total = 0;
10443
+ let inputTokens = 0;
10444
+ let cacheReadTokens = 0;
10214
10445
  const byDay = /* @__PURE__ */ new Map();
10446
+ const byModel = /* @__PURE__ */ new Map();
10215
10447
  for (const proj of dirs) {
10216
- const projPath = import_path25.default.join(projectsDir, proj);
10448
+ const projPath = import_path26.default.join(projectsDir, proj);
10217
10449
  let files;
10218
10450
  try {
10219
- const stat = import_fs23.default.statSync(projPath);
10451
+ const stat = import_fs24.default.statSync(projPath);
10220
10452
  if (!stat.isDirectory()) continue;
10221
- files = import_fs23.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
10453
+ files = import_fs24.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
10222
10454
  } catch {
10223
10455
  continue;
10224
10456
  }
10225
10457
  for (const file of files) {
10226
10458
  try {
10227
- const raw = import_fs23.default.readFileSync(import_path25.default.join(projPath, file), "utf-8");
10459
+ const raw = import_fs24.default.readFileSync(import_path26.default.join(projPath, file), "utf-8");
10228
10460
  for (const line of raw.split("\n")) {
10229
10461
  if (!line.trim()) continue;
10230
10462
  let entry;
@@ -10242,24 +10474,32 @@ function loadClaudeCost(start, end) {
10242
10474
  if (!usage || !model) continue;
10243
10475
  const p = claudeModelPrice(model);
10244
10476
  if (!p) continue;
10245
- const cost = (usage.input_tokens ?? 0) * p.i + (usage.output_tokens ?? 0) * p.o + (usage.cache_creation_input_tokens ?? 0) * p.cw + (usage.cache_read_input_tokens ?? 0) * p.cr;
10477
+ const inp = usage.input_tokens ?? 0;
10478
+ const out = usage.output_tokens ?? 0;
10479
+ const cw = usage.cache_creation_input_tokens ?? 0;
10480
+ const cr = usage.cache_read_input_tokens ?? 0;
10481
+ const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
10246
10482
  total += cost;
10483
+ inputTokens += inp;
10484
+ cacheReadTokens += cr;
10247
10485
  const dateKey = entry.timestamp.slice(0, 10);
10248
10486
  byDay.set(dateKey, (byDay.get(dateKey) ?? 0) + cost);
10487
+ const normModel = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
10488
+ byModel.set(normModel, (byModel.get(normModel) ?? 0) + cost);
10249
10489
  }
10250
10490
  } catch {
10251
10491
  continue;
10252
10492
  }
10253
10493
  }
10254
10494
  }
10255
- return { total, byDay };
10495
+ return { total, byDay, byModel, inputTokens, cacheReadTokens };
10256
10496
  }
10257
10497
  function registerReportCommand(program2) {
10258
10498
  program2.command("report").description("Activity and security report \u2014 what Claude did, what was blocked").option("--period <period>", "today | 7d | 30d | month", "7d").option("--no-tests", "exclude test runner calls (npm test, vitest, pytest\u2026) from stats").action((options) => {
10259
10499
  const period = ["today", "7d", "30d", "month"].includes(
10260
10500
  options.period
10261
10501
  ) ? options.period : "7d";
10262
- const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "audit.log");
10502
+ const logPath = import_path26.default.join(import_os20.default.homedir(), ".node9", "audit.log");
10263
10503
  const allEntries = parseAuditLog(logPath);
10264
10504
  if (allEntries.length === 0) {
10265
10505
  console.log(
@@ -10268,7 +10508,13 @@ function registerReportCommand(program2) {
10268
10508
  return;
10269
10509
  }
10270
10510
  const { start, end } = getDateRange(period);
10271
- const { total: costUSD, byDay: costByDay } = loadClaudeCost(start, end);
10511
+ const {
10512
+ total: costUSD,
10513
+ byDay: costByDay,
10514
+ byModel: costByModel,
10515
+ inputTokens: costInputTokens,
10516
+ cacheReadTokens: costCacheRead
10517
+ } = loadClaudeCost(start, end);
10272
10518
  const periodMs = end.getTime() - start.getTime();
10273
10519
  const priorEnd = new Date(start.getTime() - 1);
10274
10520
  const priorStart = new Date(start.getTime() - periodMs);
@@ -10370,7 +10616,6 @@ function registerReportCommand(program2) {
10370
10616
  const blockLabel = blocked > 0 ? import_chalk9.default.red(`\u{1F6D1} ${num(blocked)} blocked`) : import_chalk9.default.dim("\u{1F6D1} 0 blocked");
10371
10617
  const dlpLabel = dlpHits > 0 ? import_chalk9.default.yellow(`\u{1F6A8} ${dlpHits} DLP hits`) : import_chalk9.default.dim("\u{1F6A8} 0 DLP hits");
10372
10618
  const loopLabel = loopHits > 0 ? import_chalk9.default.yellow(`\u{1F504} ${loopHits} loops`) : import_chalk9.default.dim("\u{1F504} 0 loops");
10373
- const costLabel = costUSD > 0 ? import_chalk9.default.magenta(`\u{1F4B0} ${fmtCost(costUSD)}`) : import_chalk9.default.dim("\u{1F4B0} \u2013");
10374
10619
  const currentRate = total > 0 ? blocked / total : 0;
10375
10620
  const trendLabel = (() => {
10376
10621
  if (priorBlockRate === null) return import_chalk9.default.dim(`${pct(blocked, total)} block rate`);
@@ -10383,7 +10628,7 @@ function registerReportCommand(program2) {
10383
10628
  const ratioLabel = reads > 0 ? import_chalk9.default.dim(`edit/read ${(edits / reads).toFixed(1)}`) : import_chalk9.default.dim("edit/read \u2013");
10384
10629
  const testLabel = testPasses + testFails > 0 ? import_chalk9.default.dim("tests ") + import_chalk9.default.green(`${testPasses}\u2713`) + (testFails > 0 ? " " + import_chalk9.default.red(`${testFails}\u2717`) : "") : import_chalk9.default.dim("tests \u2013");
10385
10630
  console.log(
10386
- " " + import_chalk9.default.green(`\u2705 ${num(allowed)} allowed`) + " " + blockLabel + " " + dlpLabel + " " + loopLabel + " " + trendLabel + " " + costLabel
10631
+ " " + import_chalk9.default.green(`\u2705 ${num(allowed)} allowed`) + " " + blockLabel + " " + dlpLabel + " " + loopLabel + " " + trendLabel
10387
10632
  );
10388
10633
  console.log(" " + ratioLabel + " " + testLabel);
10389
10634
  console.log("");
@@ -10468,6 +10713,30 @@ function registerReportCommand(program2) {
10468
10713
  );
10469
10714
  }
10470
10715
  }
10716
+ if (costUSD > 0) {
10717
+ const periodDays = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 864e5));
10718
+ const avgPerDay = costUSD / periodDays;
10719
+ const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
10720
+ const costHeaderRight = [
10721
+ import_chalk9.default.yellow(fmtCost(costUSD)),
10722
+ import_chalk9.default.dim(`avg ${fmtCost(avgPerDay)}/day`),
10723
+ cacheHitPct > 0 ? import_chalk9.default.dim(`${cacheHitPct}% cache hit`) : null
10724
+ ].filter(Boolean).join(import_chalk9.default.dim(" \xB7 "));
10725
+ console.log("");
10726
+ console.log(" " + import_chalk9.default.bold("Cost") + " " + costHeaderRight);
10727
+ console.log(" " + import_chalk9.default.dim("\u2500".repeat(Math.min(50, W - 4))));
10728
+ const modelList = [...costByModel.entries()].sort((a, b) => b[1] - a[1]);
10729
+ const maxModelCost = Math.max(...modelList.map(([, v]) => v), 1e-9);
10730
+ const MODEL_LABEL = 22;
10731
+ const MODEL_BAR = Math.max(6, Math.min(20, W - MODEL_LABEL - 12));
10732
+ for (const [model, cost] of modelList) {
10733
+ const label = model.length > MODEL_LABEL - 1 ? model.slice(0, MODEL_LABEL - 2) + "\u2026" : model;
10734
+ const b = colorBar(cost, maxModelCost, MODEL_BAR);
10735
+ console.log(
10736
+ " " + import_chalk9.default.white(label.padEnd(MODEL_LABEL)) + b + " " + import_chalk9.default.yellow(fmtCost(cost))
10737
+ );
10738
+ }
10739
+ }
10471
10740
  console.log("");
10472
10741
  console.log(
10473
10742
  " " + import_chalk9.default.dim("node9 audit --deny") + import_chalk9.default.dim(" \xB7 ") + import_chalk9.default.dim("node9 report --period today|7d|30d|month --no-tests")
@@ -10542,14 +10811,14 @@ function registerDaemonCommand(program2) {
10542
10811
 
10543
10812
  // src/cli/commands/status.ts
10544
10813
  var import_chalk11 = __toESM(require("chalk"));
10545
- var import_fs24 = __toESM(require("fs"));
10546
- var import_path26 = __toESM(require("path"));
10547
- var import_os20 = __toESM(require("os"));
10814
+ var import_fs25 = __toESM(require("fs"));
10815
+ var import_path27 = __toESM(require("path"));
10816
+ var import_os21 = __toESM(require("os"));
10548
10817
  init_core();
10549
10818
  init_daemon();
10550
10819
  function readJson2(filePath) {
10551
10820
  try {
10552
- if (import_fs24.default.existsSync(filePath)) return JSON.parse(import_fs24.default.readFileSync(filePath, "utf-8"));
10821
+ if (import_fs25.default.existsSync(filePath)) return JSON.parse(import_fs25.default.readFileSync(filePath, "utf-8"));
10553
10822
  } catch {
10554
10823
  }
10555
10824
  return null;
@@ -10614,28 +10883,28 @@ function registerStatusCommand(program2) {
10614
10883
  console.log("");
10615
10884
  const modeLabel = settings.mode === "audit" ? import_chalk11.default.blue("audit") : settings.mode === "strict" ? import_chalk11.default.red("strict") : import_chalk11.default.white("standard");
10616
10885
  console.log(` Mode: ${modeLabel}`);
10617
- const projectConfig = import_path26.default.join(process.cwd(), "node9.config.json");
10618
- const globalConfig = import_path26.default.join(import_os20.default.homedir(), ".node9", "config.json");
10886
+ const projectConfig = import_path27.default.join(process.cwd(), "node9.config.json");
10887
+ const globalConfig = import_path27.default.join(import_os21.default.homedir(), ".node9", "config.json");
10619
10888
  console.log(
10620
- ` Local: ${import_fs24.default.existsSync(projectConfig) ? import_chalk11.default.green("Active (node9.config.json)") : import_chalk11.default.gray("Not present")}`
10889
+ ` Local: ${import_fs25.default.existsSync(projectConfig) ? import_chalk11.default.green("Active (node9.config.json)") : import_chalk11.default.gray("Not present")}`
10621
10890
  );
10622
10891
  console.log(
10623
- ` Global: ${import_fs24.default.existsSync(globalConfig) ? import_chalk11.default.green("Active (~/.node9/config.json)") : import_chalk11.default.gray("Not present")}`
10892
+ ` Global: ${import_fs25.default.existsSync(globalConfig) ? import_chalk11.default.green("Active (~/.node9/config.json)") : import_chalk11.default.gray("Not present")}`
10624
10893
  );
10625
10894
  if (mergedConfig.policy.sandboxPaths.length > 0) {
10626
10895
  console.log(
10627
10896
  ` Sandbox: ${import_chalk11.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
10628
10897
  );
10629
10898
  }
10630
- const homeDir2 = import_os20.default.homedir();
10899
+ const homeDir2 = import_os21.default.homedir();
10631
10900
  const claudeSettings = readJson2(
10632
- import_path26.default.join(homeDir2, ".claude", "settings.json")
10901
+ import_path27.default.join(homeDir2, ".claude", "settings.json")
10633
10902
  );
10634
- const claudeConfig = readJson2(import_path26.default.join(homeDir2, ".claude.json"));
10903
+ const claudeConfig = readJson2(import_path27.default.join(homeDir2, ".claude.json"));
10635
10904
  const geminiSettings = readJson2(
10636
- import_path26.default.join(homeDir2, ".gemini", "settings.json")
10905
+ import_path27.default.join(homeDir2, ".gemini", "settings.json")
10637
10906
  );
10638
- const cursorConfig = readJson2(import_path26.default.join(homeDir2, ".cursor", "mcp.json"));
10907
+ const cursorConfig = readJson2(import_path27.default.join(homeDir2, ".cursor", "mcp.json"));
10639
10908
  const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
10640
10909
  if (agentFound) {
10641
10910
  console.log("");
@@ -10694,9 +10963,9 @@ function registerStatusCommand(program2) {
10694
10963
 
10695
10964
  // src/cli/commands/init.ts
10696
10965
  var import_chalk12 = __toESM(require("chalk"));
10697
- var import_fs25 = __toESM(require("fs"));
10698
- var import_path27 = __toESM(require("path"));
10699
- var import_os21 = __toESM(require("os"));
10966
+ var import_fs26 = __toESM(require("fs"));
10967
+ var import_path28 = __toESM(require("path"));
10968
+ var import_os22 = __toESM(require("os"));
10700
10969
  var import_https2 = __toESM(require("https"));
10701
10970
  init_core();
10702
10971
  init_shields();
@@ -10756,15 +11025,15 @@ function registerInitCommand(program2) {
10756
11025
  }
10757
11026
  console.log("");
10758
11027
  }
10759
- const configPath = import_path27.default.join(import_os21.default.homedir(), ".node9", "config.json");
10760
- if (import_fs25.default.existsSync(configPath) && !options.force) {
11028
+ const configPath = import_path28.default.join(import_os22.default.homedir(), ".node9", "config.json");
11029
+ if (import_fs26.default.existsSync(configPath) && !options.force) {
10761
11030
  try {
10762
- const existing = JSON.parse(import_fs25.default.readFileSync(configPath, "utf-8"));
11031
+ const existing = JSON.parse(import_fs26.default.readFileSync(configPath, "utf-8"));
10763
11032
  const settings = existing.settings ?? {};
10764
11033
  if (settings.mode !== chosenMode) {
10765
11034
  settings.mode = chosenMode;
10766
11035
  existing.settings = settings;
10767
- import_fs25.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
11036
+ import_fs26.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
10768
11037
  console.log(import_chalk12.default.green(`\u2705 Mode updated: ${chosenMode}`));
10769
11038
  } else {
10770
11039
  console.log(import_chalk12.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
@@ -10777,9 +11046,9 @@ function registerInitCommand(program2) {
10777
11046
  ...DEFAULT_CONFIG,
10778
11047
  settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
10779
11048
  };
10780
- const dir = import_path27.default.dirname(configPath);
10781
- if (!import_fs25.default.existsSync(dir)) import_fs25.default.mkdirSync(dir, { recursive: true });
10782
- import_fs25.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
11049
+ const dir = import_path28.default.dirname(configPath);
11050
+ if (!import_fs26.default.existsSync(dir)) import_fs26.default.mkdirSync(dir, { recursive: true });
11051
+ import_fs26.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
10783
11052
  console.log(import_chalk12.default.green(`\u2705 Config created: ${configPath}`));
10784
11053
  console.log(import_chalk12.default.gray(` Mode: ${chosenMode}`));
10785
11054
  }
@@ -10833,7 +11102,7 @@ function registerInitCommand(program2) {
10833
11102
  }
10834
11103
 
10835
11104
  // src/cli/commands/undo.ts
10836
- var import_path28 = __toESM(require("path"));
11105
+ var import_path29 = __toESM(require("path"));
10837
11106
  var import_chalk14 = __toESM(require("chalk"));
10838
11107
 
10839
11108
  // src/tui/undo-navigator.ts
@@ -10992,7 +11261,7 @@ function findMatchingCwd(startDir, history) {
10992
11261
  let dir = startDir;
10993
11262
  while (true) {
10994
11263
  if (cwds.has(dir)) return dir;
10995
- const parent = import_path28.default.dirname(dir);
11264
+ const parent = import_path29.default.dirname(dir);
10996
11265
  if (parent === dir) return null;
10997
11266
  dir = parent;
10998
11267
  }
@@ -11188,12 +11457,12 @@ init_orchestrator();
11188
11457
  init_provenance();
11189
11458
 
11190
11459
  // src/mcp-pin.ts
11191
- var import_fs26 = __toESM(require("fs"));
11192
- var import_path29 = __toESM(require("path"));
11193
- var import_os22 = __toESM(require("os"));
11460
+ var import_fs27 = __toESM(require("fs"));
11461
+ var import_path30 = __toESM(require("path"));
11462
+ var import_os23 = __toESM(require("os"));
11194
11463
  var import_crypto9 = __toESM(require("crypto"));
11195
11464
  function getPinsFilePath() {
11196
- return import_path29.default.join(import_os22.default.homedir(), ".node9", "mcp-pins.json");
11465
+ return import_path30.default.join(import_os23.default.homedir(), ".node9", "mcp-pins.json");
11197
11466
  }
11198
11467
  function hashToolDefinitions(tools) {
11199
11468
  const sorted = [...tools].sort((a, b) => {
@@ -11210,7 +11479,7 @@ function getServerKey(upstreamCommand) {
11210
11479
  function readMcpPinsSafe() {
11211
11480
  const filePath = getPinsFilePath();
11212
11481
  try {
11213
- const raw = import_fs26.default.readFileSync(filePath, "utf-8");
11482
+ const raw = import_fs27.default.readFileSync(filePath, "utf-8");
11214
11483
  if (!raw.trim()) {
11215
11484
  return { ok: false, reason: "corrupt", detail: "empty file" };
11216
11485
  }
@@ -11234,10 +11503,10 @@ function readMcpPins() {
11234
11503
  }
11235
11504
  function writeMcpPins(data) {
11236
11505
  const filePath = getPinsFilePath();
11237
- import_fs26.default.mkdirSync(import_path29.default.dirname(filePath), { recursive: true });
11506
+ import_fs27.default.mkdirSync(import_path30.default.dirname(filePath), { recursive: true });
11238
11507
  const tmp = `${filePath}.${import_crypto9.default.randomBytes(6).toString("hex")}.tmp`;
11239
- import_fs26.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
11240
- import_fs26.default.renameSync(tmp, filePath);
11508
+ import_fs27.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
11509
+ import_fs27.default.renameSync(tmp, filePath);
11241
11510
  }
11242
11511
  function checkPin(serverKey, currentHash) {
11243
11512
  const result = readMcpPinsSafe();
@@ -11609,9 +11878,9 @@ function registerMcpGatewayCommand(program2) {
11609
11878
 
11610
11879
  // src/mcp-server/index.ts
11611
11880
  var import_readline4 = __toESM(require("readline"));
11612
- var import_fs27 = __toESM(require("fs"));
11613
- var import_os23 = __toESM(require("os"));
11614
- var import_path30 = __toESM(require("path"));
11881
+ var import_fs28 = __toESM(require("fs"));
11882
+ var import_os24 = __toESM(require("os"));
11883
+ var import_path31 = __toESM(require("path"));
11615
11884
  init_core();
11616
11885
  init_daemon();
11617
11886
  init_shields();
@@ -11786,13 +12055,13 @@ function handleStatus() {
11786
12055
  lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
11787
12056
  lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
11788
12057
  lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
11789
- const projectConfig = import_path30.default.join(process.cwd(), "node9.config.json");
11790
- const globalConfig = import_path30.default.join(import_os23.default.homedir(), ".node9", "config.json");
12058
+ const projectConfig = import_path31.default.join(process.cwd(), "node9.config.json");
12059
+ const globalConfig = import_path31.default.join(import_os24.default.homedir(), ".node9", "config.json");
11791
12060
  lines.push(
11792
- `Project config (node9.config.json): ${import_fs27.default.existsSync(projectConfig) ? "present" : "not found"}`
12061
+ `Project config (node9.config.json): ${import_fs28.default.existsSync(projectConfig) ? "present" : "not found"}`
11793
12062
  );
11794
12063
  lines.push(
11795
- `Global config (~/.node9/config.json): ${import_fs27.default.existsSync(globalConfig) ? "present" : "not found"}`
12064
+ `Global config (~/.node9/config.json): ${import_fs28.default.existsSync(globalConfig) ? "present" : "not found"}`
11796
12065
  );
11797
12066
  return lines.join("\n");
11798
12067
  }
@@ -11866,21 +12135,21 @@ function handleShieldDisable(args) {
11866
12135
  writeActiveShields(active.filter((s) => s !== name));
11867
12136
  return `Shield "${name}" disabled.`;
11868
12137
  }
11869
- var GLOBAL_CONFIG_PATH2 = import_path30.default.join(import_os23.default.homedir(), ".node9", "config.json");
12138
+ var GLOBAL_CONFIG_PATH2 = import_path31.default.join(import_os24.default.homedir(), ".node9", "config.json");
11870
12139
  var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
11871
12140
  function readGlobalConfigRaw() {
11872
12141
  try {
11873
- if (import_fs27.default.existsSync(GLOBAL_CONFIG_PATH2)) {
11874
- return JSON.parse(import_fs27.default.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
12142
+ if (import_fs28.default.existsSync(GLOBAL_CONFIG_PATH2)) {
12143
+ return JSON.parse(import_fs28.default.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
11875
12144
  }
11876
12145
  } catch {
11877
12146
  }
11878
12147
  return {};
11879
12148
  }
11880
12149
  function writeGlobalConfigRaw(data) {
11881
- const dir = import_path30.default.dirname(GLOBAL_CONFIG_PATH2);
11882
- if (!import_fs27.default.existsSync(dir)) import_fs27.default.mkdirSync(dir, { recursive: true });
11883
- import_fs27.default.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
12150
+ const dir = import_path31.default.dirname(GLOBAL_CONFIG_PATH2);
12151
+ if (!import_fs28.default.existsSync(dir)) import_fs28.default.mkdirSync(dir, { recursive: true });
12152
+ import_fs28.default.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
11884
12153
  }
11885
12154
  function handleApproverList() {
11886
12155
  const config = getConfig();
@@ -11923,9 +12192,9 @@ function handleApproverSet(args) {
11923
12192
  }
11924
12193
  function handleAuditGet(args) {
11925
12194
  const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
11926
- const auditPath = import_path30.default.join(import_os23.default.homedir(), ".node9", "audit.log");
11927
- if (!import_fs27.default.existsSync(auditPath)) return "No audit log found.";
11928
- const lines = import_fs27.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
12195
+ const auditPath = import_path31.default.join(import_os24.default.homedir(), ".node9", "audit.log");
12196
+ if (!import_fs28.default.existsSync(auditPath)) return "No audit log found.";
12197
+ const lines = import_fs28.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
11929
12198
  const recent = lines.slice(-limit);
11930
12199
  const entries = recent.map((line) => {
11931
12200
  try {
@@ -12245,20 +12514,20 @@ function registerMcpPinCommand(program2) {
12245
12514
 
12246
12515
  // src/cli.ts
12247
12516
  var { version } = JSON.parse(
12248
- import_fs30.default.readFileSync(import_path33.default.join(__dirname, "../package.json"), "utf-8")
12517
+ import_fs31.default.readFileSync(import_path34.default.join(__dirname, "../package.json"), "utf-8")
12249
12518
  );
12250
12519
  var program = new import_commander.Command();
12251
12520
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
12252
12521
  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) => {
12253
12522
  const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
12254
- const credPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "credentials.json");
12255
- if (!import_fs30.default.existsSync(import_path33.default.dirname(credPath)))
12256
- import_fs30.default.mkdirSync(import_path33.default.dirname(credPath), { recursive: true });
12523
+ const credPath = import_path34.default.join(import_os27.default.homedir(), ".node9", "credentials.json");
12524
+ if (!import_fs31.default.existsSync(import_path34.default.dirname(credPath)))
12525
+ import_fs31.default.mkdirSync(import_path34.default.dirname(credPath), { recursive: true });
12257
12526
  const profileName = options.profile || "default";
12258
12527
  let existingCreds = {};
12259
12528
  try {
12260
- if (import_fs30.default.existsSync(credPath)) {
12261
- const raw = JSON.parse(import_fs30.default.readFileSync(credPath, "utf-8"));
12529
+ if (import_fs31.default.existsSync(credPath)) {
12530
+ const raw = JSON.parse(import_fs31.default.readFileSync(credPath, "utf-8"));
12262
12531
  if (raw.apiKey) {
12263
12532
  existingCreds = {
12264
12533
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
@@ -12270,13 +12539,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
12270
12539
  } catch {
12271
12540
  }
12272
12541
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
12273
- import_fs30.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
12542
+ import_fs31.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
12274
12543
  if (profileName === "default") {
12275
- const configPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "config.json");
12544
+ const configPath = import_path34.default.join(import_os27.default.homedir(), ".node9", "config.json");
12276
12545
  let config = {};
12277
12546
  try {
12278
- if (import_fs30.default.existsSync(configPath))
12279
- config = JSON.parse(import_fs30.default.readFileSync(configPath, "utf-8"));
12547
+ if (import_fs31.default.existsSync(configPath))
12548
+ config = JSON.parse(import_fs31.default.readFileSync(configPath, "utf-8"));
12280
12549
  } catch {
12281
12550
  }
12282
12551
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -12291,9 +12560,9 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
12291
12560
  approvers.cloud = false;
12292
12561
  }
12293
12562
  s.approvers = approvers;
12294
- if (!import_fs30.default.existsSync(import_path33.default.dirname(configPath)))
12295
- import_fs30.default.mkdirSync(import_path33.default.dirname(configPath), { recursive: true });
12296
- import_fs30.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
12563
+ if (!import_fs31.default.existsSync(import_path34.default.dirname(configPath)))
12564
+ import_fs31.default.mkdirSync(import_path34.default.dirname(configPath), { recursive: true });
12565
+ import_fs31.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
12297
12566
  }
12298
12567
  if (options.profile && profileName !== "default") {
12299
12568
  console.log(import_chalk20.default.green(`\u2705 Profile "${profileName}" saved`));
@@ -12387,15 +12656,15 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
12387
12656
  }
12388
12657
  }
12389
12658
  if (options.purge) {
12390
- const node9Dir = import_path33.default.join(import_os26.default.homedir(), ".node9");
12391
- if (import_fs30.default.existsSync(node9Dir)) {
12659
+ const node9Dir = import_path34.default.join(import_os27.default.homedir(), ".node9");
12660
+ if (import_fs31.default.existsSync(node9Dir)) {
12392
12661
  const confirmed = await (0, import_prompts2.confirm)({
12393
12662
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
12394
12663
  default: false
12395
12664
  });
12396
12665
  if (confirmed) {
12397
- import_fs30.default.rmSync(node9Dir, { recursive: true });
12398
- if (import_fs30.default.existsSync(node9Dir)) {
12666
+ import_fs31.default.rmSync(node9Dir, { recursive: true });
12667
+ if (import_fs31.default.existsSync(node9Dir)) {
12399
12668
  console.error(
12400
12669
  import_chalk20.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
12401
12670
  );
@@ -12536,14 +12805,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
12536
12805
  Run "node9 addto claude" to register it as the statusLine.`
12537
12806
  ).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
12538
12807
  if (subcommand === "debug") {
12539
- const flagFile = import_path33.default.join(import_os26.default.homedir(), ".node9", "hud-debug");
12808
+ const flagFile = import_path34.default.join(import_os27.default.homedir(), ".node9", "hud-debug");
12540
12809
  if (state === "on") {
12541
- import_fs30.default.mkdirSync(import_path33.default.dirname(flagFile), { recursive: true });
12542
- import_fs30.default.writeFileSync(flagFile, "");
12810
+ import_fs31.default.mkdirSync(import_path34.default.dirname(flagFile), { recursive: true });
12811
+ import_fs31.default.writeFileSync(flagFile, "");
12543
12812
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
12544
12813
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
12545
12814
  } else if (state === "off") {
12546
- if (import_fs30.default.existsSync(flagFile)) import_fs30.default.unlinkSync(flagFile);
12815
+ if (import_fs31.default.existsSync(flagFile)) import_fs31.default.unlinkSync(flagFile);
12547
12816
  console.log("HUD debug logging disabled.");
12548
12817
  } else {
12549
12818
  console.error("Usage: node9 hud debug on|off");
@@ -12650,9 +12919,9 @@ if (process.argv[2] !== "daemon") {
12650
12919
  const isCheckHook = process.argv[2] === "check";
12651
12920
  if (isCheckHook) {
12652
12921
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
12653
- const logPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "hook-debug.log");
12922
+ const logPath = import_path34.default.join(import_os27.default.homedir(), ".node9", "hook-debug.log");
12654
12923
  const msg = reason instanceof Error ? reason.message : String(reason);
12655
- import_fs30.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
12924
+ import_fs31.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
12656
12925
  `);
12657
12926
  }
12658
12927
  process.exit(0);