@node9/proxy 1.10.0 → 1.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -147,8 +147,8 @@ function sanitizeConfig(raw) {
147
147
  }
148
148
  }
149
149
  const lines = result.error.issues.map((issue) => {
150
- const path34 = issue.path.length > 0 ? issue.path.join(".") : "root";
151
- return ` \u2022 ${path34}: ${issue.message}`;
150
+ const path35 = issue.path.length > 0 ? issue.path.join(".") : "root";
151
+ return ` \u2022 ${path35}: ${issue.message}`;
152
152
  });
153
153
  return {
154
154
  sanitized,
@@ -231,7 +231,8 @@ var init_config_schema = __esm({
231
231
  slackEnabled: z.boolean().optional(),
232
232
  enableTrustSessions: z.boolean().optional(),
233
233
  allowGlobalPause: z.boolean().optional(),
234
- auditHashArgs: z.boolean().optional()
234
+ auditHashArgs: z.boolean().optional(),
235
+ agentPolicy: z.enum(["require_approval", "block_on_rules"]).optional()
235
236
  }).optional(),
236
237
  policy: z.object({
237
238
  sandboxPaths: z.array(z.string()).optional(),
@@ -1709,9 +1710,9 @@ function matchesPattern(text, patterns) {
1709
1710
  const withoutDotSlash = text.replace(/^\.\//, "");
1710
1711
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1711
1712
  }
1712
- function getNestedValue(obj, path34) {
1713
+ function getNestedValue(obj, path35) {
1713
1714
  if (!obj || typeof obj !== "object") return null;
1714
- return path34.split(".").reduce((prev, curr) => prev?.[curr], obj);
1715
+ return path35.split(".").reduce((prev, curr) => prev?.[curr], obj);
1715
1716
  }
1716
1717
  function shouldSnapshot(toolName, args, config) {
1717
1718
  if (!config.settings.enableUndo) return false;
@@ -2791,11 +2792,12 @@ ${smartTruncate(str, 500)}`
2791
2792
  function escapePango(text) {
2792
2793
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2793
2794
  }
2794
- function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
2795
+ function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
2795
2796
  const lines = [];
2796
2797
  if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
2797
2798
  lines.push(`\u{1F916} ${agent || "AI Agent"} | \u{1F527} ${toolName}`);
2798
2799
  lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
2800
+ if (ruleDescription) lines.push(`\u2139 ${ruleDescription}`);
2799
2801
  lines.push("");
2800
2802
  lines.push(formattedArgs);
2801
2803
  if (allowCount >= 3) {
@@ -2808,7 +2810,7 @@ function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, loc
2808
2810
  }
2809
2811
  return lines.join("\n");
2810
2812
  }
2811
- function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
2813
+ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
2812
2814
  const lines = [];
2813
2815
  if (locked) {
2814
2816
  lines.push('<span foreground="red" weight="bold">\u26A0\uFE0F LOCKED BY ADMIN POLICY</span>');
@@ -2818,6 +2820,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
2818
2820
  `<b>\u{1F916} ${escapePango(agent || "AI Agent")}</b> | <b>\u{1F527} <tt>${escapePango(toolName)}</tt></b>`
2819
2821
  );
2820
2822
  lines.push(`<i>\u{1F6E1}\uFE0F ${escapePango(explainableLabel || "Security Policy")}</i>`);
2823
+ if (ruleDescription) lines.push(`<i>\u2139 ${escapePango(ruleDescription)}</i>`);
2821
2824
  lines.push("");
2822
2825
  lines.push(`<tt>${escapePango(formattedArgs)}</tt>`);
2823
2826
  if (allowCount >= 3) {
@@ -2834,7 +2837,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
2834
2837
  }
2835
2838
  return lines.join("\n");
2836
2839
  }
2837
- async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1) {
2840
+ async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1, ruleDescription) {
2838
2841
  if (isTestEnv()) return "deny";
2839
2842
  const { message: formattedArgs, intent } = formatArgs(args, matchedField, matchedWord);
2840
2843
  const intentLabel = intent === "EDIT" ? "Code Edit" : "Action Approval";
@@ -2845,7 +2848,8 @@ async function askNativePopup(toolName, args, agent, explainableLabel, locked =
2845
2848
  agent,
2846
2849
  explainableLabel,
2847
2850
  locked,
2848
- allowCount
2851
+ allowCount,
2852
+ ruleDescription
2849
2853
  );
2850
2854
  return new Promise((resolve) => {
2851
2855
  let childProcess = null;
@@ -2879,7 +2883,8 @@ end run`;
2879
2883
  agent,
2880
2884
  explainableLabel,
2881
2885
  locked,
2882
- allowCount
2886
+ allowCount,
2887
+ ruleDescription
2883
2888
  );
2884
2889
  const argsList = [
2885
2890
  locked ? "--info" : "--question",
@@ -2953,7 +2958,7 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2953
2958
  }).catch(() => {
2954
2959
  });
2955
2960
  }
2956
- async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
2961
+ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPolicy, forceReview) {
2957
2962
  const controller = new AbortController();
2958
2963
  const timeout = setTimeout(() => controller.abort(), 1e4);
2959
2964
  if (!creds.apiKey) throw new Error("Node9 API Key is missing");
@@ -2998,7 +3003,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
2998
3003
  platform: os9.platform()
2999
3004
  },
3000
3005
  ...riskMetadata && { riskMetadata },
3001
- ...ciContext && { ciContext }
3006
+ ...ciContext && { ciContext },
3007
+ ...agentPolicy && { policy: agentPolicy },
3008
+ ...forceReview && { forceReview: true }
3002
3009
  }),
3003
3010
  signal: controller.signal
3004
3011
  });
@@ -3392,6 +3399,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3392
3399
  policyMatchedWord,
3393
3400
  policyResult.ruleName
3394
3401
  );
3402
+ if (policyRuleDescription) riskMetadata.ruleDescription = policyRuleDescription.slice(0, 200);
3395
3403
  const persistent = policyResult.ruleName ? null : getPersistentDecision(toolName);
3396
3404
  if (persistent === "allow") {
3397
3405
  if (approvers.cloud && creds?.apiKey)
@@ -3426,9 +3434,18 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3426
3434
  }
3427
3435
  let cloudRequestId = null;
3428
3436
  const cloudEnforced = approvers.cloud && !!creds?.apiKey;
3429
- if (cloudEnforced && !localSmartRuleMatched && !options?.localSmartRuleMatched) {
3437
+ const forceReview = localSmartRuleMatched === true || options?.localSmartRuleMatched === true || void 0;
3438
+ if (cloudEnforced) {
3430
3439
  try {
3431
- const initResult = await initNode9SaaS(toolName, args, creds, meta, riskMetadata);
3440
+ const initResult = await initNode9SaaS(
3441
+ toolName,
3442
+ args,
3443
+ creds,
3444
+ meta,
3445
+ riskMetadata,
3446
+ config.settings.agentPolicy,
3447
+ forceReview
3448
+ );
3432
3449
  if (!initResult.pending) {
3433
3450
  if (initResult.shadowMode) {
3434
3451
  return { approved: true, checkedBy: "cloud" };
@@ -3443,9 +3460,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3443
3460
  };
3444
3461
  }
3445
3462
  }
3446
- if (!localSmartRuleMatched && !options?.localSmartRuleMatched) {
3447
- cloudRequestId = initResult.requestId || null;
3448
- }
3463
+ if (initResult.pending) cloudRequestId = initResult.requestId || null;
3449
3464
  if (!taintWarning) explainableLabel = "Organization Policy (SaaS)";
3450
3465
  } catch {
3451
3466
  }
@@ -3502,7 +3517,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3502
3517
  }
3503
3518
  }
3504
3519
  }
3505
- if (cloudEnforced && cloudRequestId && !localSmartRuleMatched && !options?.localSmartRuleMatched) {
3520
+ if (cloudEnforced && cloudRequestId) {
3506
3521
  racePromises.push(
3507
3522
  (async () => {
3508
3523
  try {
@@ -3534,7 +3549,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3534
3549
  signal,
3535
3550
  policyMatchedField,
3536
3551
  policyMatchedWord,
3537
- daemonAllowCount
3552
+ daemonAllowCount,
3553
+ riskMetadata?.ruleDescription
3538
3554
  );
3539
3555
  if (decision === "always_allow") {
3540
3556
  writeTrustSession(toolName, 36e5);
@@ -6036,14 +6052,181 @@ var init_patch = __esm({
6036
6052
  }
6037
6053
  });
6038
6054
 
6039
- // src/daemon/server.ts
6040
- import http from "http";
6055
+ // src/costSync.ts
6041
6056
  import fs16 from "fs";
6042
6057
  import path19 from "path";
6058
+ import os14 from "os";
6059
+ function normalizeModel(raw) {
6060
+ return raw.replace(/-\d{8}$/, "");
6061
+ }
6062
+ function pricingFor(model) {
6063
+ const norm = normalizeModel(model);
6064
+ if (PRICING[norm]) return PRICING[norm];
6065
+ let best = null;
6066
+ for (const key of Object.keys(PRICING)) {
6067
+ if (norm.startsWith(key) && (best === null || key.length > best.length)) best = key;
6068
+ }
6069
+ return best ? PRICING[best] : null;
6070
+ }
6071
+ function parseJSONLFile(filePath) {
6072
+ let content;
6073
+ try {
6074
+ content = fs16.readFileSync(filePath, "utf8");
6075
+ } catch {
6076
+ return /* @__PURE__ */ new Map();
6077
+ }
6078
+ const daily = /* @__PURE__ */ new Map();
6079
+ for (const line of content.split("\n")) {
6080
+ if (!line.trim()) continue;
6081
+ let row;
6082
+ try {
6083
+ row = JSON.parse(line);
6084
+ } catch {
6085
+ continue;
6086
+ }
6087
+ if (row["type"] !== "assistant") continue;
6088
+ const msg = row["message"];
6089
+ if (!msg?.["usage"] || typeof msg["model"] !== "string") continue;
6090
+ const usage = msg["usage"];
6091
+ const model = msg["model"];
6092
+ const timestamp = row["timestamp"];
6093
+ if (typeof timestamp !== "string" || timestamp.length < 10) continue;
6094
+ const date = timestamp.slice(0, 10);
6095
+ const p = pricingFor(model);
6096
+ if (!p) continue;
6097
+ const inp = Number(usage["input_tokens"] ?? 0);
6098
+ const out = Number(usage["output_tokens"] ?? 0);
6099
+ const cw = Number(usage["cache_creation_input_tokens"] ?? 0);
6100
+ const cr = Number(usage["cache_read_input_tokens"] ?? 0);
6101
+ const cost = inp * p[0] + out * p[1] + cw * p[2] + cr * p[3];
6102
+ const norm = normalizeModel(model);
6103
+ const key = `${date}::${norm}`;
6104
+ const prev = daily.get(key);
6105
+ if (prev) {
6106
+ prev.costUSD += cost;
6107
+ prev.inputTokens += inp;
6108
+ prev.outputTokens += out;
6109
+ prev.cacheWriteTokens += cw;
6110
+ prev.cacheReadTokens += cr;
6111
+ } else {
6112
+ daily.set(key, {
6113
+ date,
6114
+ model: norm,
6115
+ costUSD: cost,
6116
+ inputTokens: inp,
6117
+ outputTokens: out,
6118
+ cacheWriteTokens: cw,
6119
+ cacheReadTokens: cr
6120
+ });
6121
+ }
6122
+ }
6123
+ return daily;
6124
+ }
6125
+ function collectEntries() {
6126
+ const projectsDir = path19.join(os14.homedir(), ".claude", "projects");
6127
+ if (!fs16.existsSync(projectsDir)) return [];
6128
+ const combined = /* @__PURE__ */ new Map();
6129
+ let dirs;
6130
+ try {
6131
+ dirs = fs16.readdirSync(projectsDir);
6132
+ } catch {
6133
+ return [];
6134
+ }
6135
+ for (const dir of dirs) {
6136
+ const dirPath = path19.join(projectsDir, dir);
6137
+ try {
6138
+ if (!fs16.statSync(dirPath).isDirectory()) continue;
6139
+ } catch {
6140
+ continue;
6141
+ }
6142
+ let files;
6143
+ try {
6144
+ files = fs16.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
6145
+ } catch {
6146
+ continue;
6147
+ }
6148
+ for (const file of files) {
6149
+ const entries = parseJSONLFile(path19.join(dirPath, file));
6150
+ for (const [key, e] of entries) {
6151
+ const prev = combined.get(key);
6152
+ if (prev) {
6153
+ prev.costUSD += e.costUSD;
6154
+ prev.inputTokens += e.inputTokens;
6155
+ prev.outputTokens += e.outputTokens;
6156
+ prev.cacheWriteTokens += e.cacheWriteTokens;
6157
+ prev.cacheReadTokens += e.cacheReadTokens;
6158
+ } else {
6159
+ combined.set(key, { ...e });
6160
+ }
6161
+ }
6162
+ }
6163
+ }
6164
+ return [...combined.values()];
6165
+ }
6166
+ async function syncCost() {
6167
+ const creds = getCredentials();
6168
+ if (!creds?.apiKey || !creds?.apiUrl) return;
6169
+ const entries = collectEntries();
6170
+ if (entries.length === 0) return;
6171
+ let username = "unknown";
6172
+ try {
6173
+ username = os14.userInfo().username;
6174
+ } catch {
6175
+ }
6176
+ const machineId = `${os14.hostname()}:${username}`;
6177
+ try {
6178
+ const res = await fetch(`${creds.apiUrl}/cost-sync`, {
6179
+ method: "POST",
6180
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${creds.apiKey}` },
6181
+ body: JSON.stringify({ machineId, entries }),
6182
+ signal: AbortSignal.timeout(15e3)
6183
+ });
6184
+ if (!res.ok) {
6185
+ fs16.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] HTTP ${res.status}
6186
+ `);
6187
+ }
6188
+ } catch (err2) {
6189
+ fs16.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] ${err2.message}
6190
+ `);
6191
+ }
6192
+ }
6193
+ function startCostSync() {
6194
+ syncCost().catch(() => {
6195
+ });
6196
+ const timer = setInterval(() => {
6197
+ syncCost().catch(() => {
6198
+ });
6199
+ }, SYNC_INTERVAL_MS);
6200
+ timer.unref();
6201
+ }
6202
+ var SYNC_INTERVAL_MS, PRICING;
6203
+ var init_costSync = __esm({
6204
+ "src/costSync.ts"() {
6205
+ "use strict";
6206
+ init_config();
6207
+ init_audit();
6208
+ SYNC_INTERVAL_MS = 10 * 60 * 1e3;
6209
+ PRICING = {
6210
+ "claude-opus-4": [5e-6, 25e-6, 625e-8, 5e-7],
6211
+ "claude-sonnet-4": [3e-6, 15e-6, 375e-8, 3e-7],
6212
+ "claude-haiku-4": [8e-7, 4e-6, 1e-6, 8e-8],
6213
+ "claude-3-7-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
6214
+ "claude-3-5-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
6215
+ "claude-3-5-haiku": [8e-7, 4e-6, 1e-6, 8e-8],
6216
+ "claude-3-haiku": [25e-8, 125e-8, 3e-7, 3e-8]
6217
+ };
6218
+ }
6219
+ });
6220
+
6221
+ // src/daemon/server.ts
6222
+ import http from "http";
6223
+ import fs17 from "fs";
6224
+ import path20 from "path";
6043
6225
  import { randomUUID as randomUUID4 } from "crypto";
6044
6226
  import { spawnSync as spawnSync2 } from "child_process";
6045
6227
  import chalk2 from "chalk";
6046
6228
  function startDaemon() {
6229
+ startCostSync();
6047
6230
  loadInsightCounts();
6048
6231
  const csrfToken = randomUUID4();
6049
6232
  const internalToken = randomUUID4();
@@ -6059,7 +6242,7 @@ function startDaemon() {
6059
6242
  idleTimer = setTimeout(() => {
6060
6243
  if (autoStarted) {
6061
6244
  try {
6062
- fs16.unlinkSync(DAEMON_PID_FILE);
6245
+ fs17.unlinkSync(DAEMON_PID_FILE);
6063
6246
  } catch {
6064
6247
  }
6065
6248
  }
@@ -6222,7 +6405,7 @@ data: ${JSON.stringify(item.data)}
6222
6405
  status: "pending"
6223
6406
  });
6224
6407
  }
6225
- const projectCwd = typeof cwd === "string" && path19.isAbsolute(cwd) ? cwd : void 0;
6408
+ const projectCwd = typeof cwd === "string" && path20.isAbsolute(cwd) ? cwd : void 0;
6226
6409
  const projectConfig = getConfig(projectCwd);
6227
6410
  const browserEnabled = projectConfig.settings.approvers?.browser !== false;
6228
6411
  const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
@@ -6612,8 +6795,8 @@ data: ${JSON.stringify(item.data)}
6612
6795
  const body = await readBody(req);
6613
6796
  const data = body ? JSON.parse(body) : {};
6614
6797
  const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
6615
- const node9Dir = path19.dirname(GLOBAL_CONFIG_PATH);
6616
- if (!path19.resolve(configPath).startsWith(node9Dir + path19.sep)) {
6798
+ const node9Dir = path20.dirname(GLOBAL_CONFIG_PATH);
6799
+ if (!path20.resolve(configPath).startsWith(node9Dir + path20.sep)) {
6617
6800
  res.writeHead(400, { "Content-Type": "application/json" });
6618
6801
  return res.end(
6619
6802
  JSON.stringify({ error: "configPath must be within the node9 config directory" })
@@ -6724,14 +6907,14 @@ data: ${JSON.stringify(item.data)}
6724
6907
  server.on("error", (e) => {
6725
6908
  if (e.code === "EADDRINUSE") {
6726
6909
  try {
6727
- if (fs16.existsSync(DAEMON_PID_FILE)) {
6728
- const { pid } = JSON.parse(fs16.readFileSync(DAEMON_PID_FILE, "utf-8"));
6910
+ if (fs17.existsSync(DAEMON_PID_FILE)) {
6911
+ const { pid } = JSON.parse(fs17.readFileSync(DAEMON_PID_FILE, "utf-8"));
6729
6912
  process.kill(pid, 0);
6730
6913
  return process.exit(0);
6731
6914
  }
6732
6915
  } catch {
6733
6916
  try {
6734
- fs16.unlinkSync(DAEMON_PID_FILE);
6917
+ fs17.unlinkSync(DAEMON_PID_FILE);
6735
6918
  } catch {
6736
6919
  }
6737
6920
  server.listen(DAEMON_PORT, DAEMON_HOST);
@@ -6799,32 +6982,33 @@ var init_server = __esm({
6799
6982
  init_state2();
6800
6983
  init_patch();
6801
6984
  init_config_schema();
6985
+ init_costSync();
6802
6986
  }
6803
6987
  });
6804
6988
 
6805
6989
  // src/daemon/index.ts
6806
- import fs17 from "fs";
6990
+ import fs18 from "fs";
6807
6991
  import chalk3 from "chalk";
6808
6992
  import { spawnSync as spawnSync3 } from "child_process";
6809
6993
  function stopDaemon() {
6810
- if (!fs17.existsSync(DAEMON_PID_FILE)) return console.log(chalk3.yellow("Not running."));
6994
+ if (!fs18.existsSync(DAEMON_PID_FILE)) return console.log(chalk3.yellow("Not running."));
6811
6995
  try {
6812
- const { pid } = JSON.parse(fs17.readFileSync(DAEMON_PID_FILE, "utf-8"));
6996
+ const { pid } = JSON.parse(fs18.readFileSync(DAEMON_PID_FILE, "utf-8"));
6813
6997
  process.kill(pid, "SIGTERM");
6814
6998
  console.log(chalk3.green("\u2705 Stopped."));
6815
6999
  } catch {
6816
7000
  console.log(chalk3.gray("Cleaned up stale PID file."));
6817
7001
  } finally {
6818
7002
  try {
6819
- fs17.unlinkSync(DAEMON_PID_FILE);
7003
+ fs18.unlinkSync(DAEMON_PID_FILE);
6820
7004
  } catch {
6821
7005
  }
6822
7006
  }
6823
7007
  }
6824
7008
  function daemonStatus() {
6825
- if (fs17.existsSync(DAEMON_PID_FILE)) {
7009
+ if (fs18.existsSync(DAEMON_PID_FILE)) {
6826
7010
  try {
6827
- const { pid } = JSON.parse(fs17.readFileSync(DAEMON_PID_FILE, "utf-8"));
7011
+ const { pid } = JSON.parse(fs18.readFileSync(DAEMON_PID_FILE, "utf-8"));
6828
7012
  process.kill(pid, 0);
6829
7013
  console.log(chalk3.green("Node9 daemon: running"));
6830
7014
  return;
@@ -6859,9 +7043,9 @@ __export(tail_exports, {
6859
7043
  });
6860
7044
  import http2 from "http";
6861
7045
  import chalk19 from "chalk";
6862
- import fs28 from "fs";
6863
- import os24 from "os";
6864
- import path31 from "path";
7046
+ import fs29 from "fs";
7047
+ import os25 from "os";
7048
+ import path32 from "path";
6865
7049
  import readline5 from "readline";
6866
7050
  import { spawn as spawn10, execSync as execSync3 } from "child_process";
6867
7051
  function getIcon(tool) {
@@ -6884,7 +7068,7 @@ function formatBase(activity) {
6884
7068
  const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
6885
7069
  const icon = getIcon(activity.tool);
6886
7070
  const toolName = activity.tool.slice(0, 16).padEnd(16);
6887
- const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os24.homedir(), "~");
7071
+ const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os25.homedir(), "~");
6888
7072
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
6889
7073
  return `${chalk19.gray(time)} ${icon} ${chalk19.white.bold(toolName)} ${chalk19.dim(argsPreview)}`;
6890
7074
  }
@@ -6923,9 +7107,9 @@ function renderPending(activity) {
6923
7107
  }
6924
7108
  async function ensureDaemon() {
6925
7109
  let pidPort = null;
6926
- if (fs28.existsSync(PID_FILE)) {
7110
+ if (fs29.existsSync(PID_FILE)) {
6927
7111
  try {
6928
- const { port } = JSON.parse(fs28.readFileSync(PID_FILE, "utf-8"));
7112
+ const { port } = JSON.parse(fs29.readFileSync(PID_FILE, "utf-8"));
6929
7113
  pidPort = port;
6930
7114
  } catch {
6931
7115
  console.error(chalk19.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
@@ -7002,6 +7186,9 @@ function buildCardLines(req, localCount = 0) {
7002
7186
  `${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}`,
7003
7187
  `${CYAN}\u2551${RESET2} Reason: ${tierLabel} \u2014 ${blockedBy}${RESET2}`
7004
7188
  ];
7189
+ if (req.riskMetadata?.ruleDescription) {
7190
+ lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u2139 ${req.riskMetadata.ruleDescription}${RESET2}`);
7191
+ }
7005
7192
  if (req.riskMetadata?.ruleName && blockedBy.includes("Taint")) {
7006
7193
  lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u26A0 ${req.riskMetadata.ruleName}${RESET2}`);
7007
7194
  }
@@ -7045,9 +7232,9 @@ function buildRecoveryCardLines(req) {
7045
7232
  ];
7046
7233
  }
7047
7234
  function readApproversFromDisk() {
7048
- const configPath = path31.join(os24.homedir(), ".node9", "config.json");
7235
+ const configPath = path32.join(os25.homedir(), ".node9", "config.json");
7049
7236
  try {
7050
- const raw = JSON.parse(fs28.readFileSync(configPath, "utf-8"));
7237
+ const raw = JSON.parse(fs29.readFileSync(configPath, "utf-8"));
7051
7238
  const settings = raw.settings ?? {};
7052
7239
  return settings.approvers ?? {};
7053
7240
  } catch {
@@ -7063,15 +7250,15 @@ function approverStatusLine() {
7063
7250
  return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
7064
7251
  }
7065
7252
  function toggleApprover(channel) {
7066
- const configPath = path31.join(os24.homedir(), ".node9", "config.json");
7253
+ const configPath = path32.join(os25.homedir(), ".node9", "config.json");
7067
7254
  try {
7068
- const raw = JSON.parse(fs28.readFileSync(configPath, "utf-8"));
7255
+ const raw = JSON.parse(fs29.readFileSync(configPath, "utf-8"));
7069
7256
  const settings = raw.settings ?? {};
7070
7257
  const approvers = settings.approvers ?? {};
7071
7258
  approvers[channel] = approvers[channel] === false;
7072
7259
  settings.approvers = approvers;
7073
7260
  raw.settings = settings;
7074
- fs28.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
7261
+ fs29.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
7075
7262
  } catch (err2) {
7076
7263
  process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
7077
7264
  `);
@@ -7241,8 +7428,8 @@ async function startTail(options = {}) {
7241
7428
  }
7242
7429
  postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
7243
7430
  try {
7244
- fs28.appendFileSync(
7245
- path31.join(os24.homedir(), ".node9", "hook-debug.log"),
7431
+ fs29.appendFileSync(
7432
+ path32.join(os25.homedir(), ".node9", "hook-debug.log"),
7246
7433
  `[tail] POST /decision failed: ${String(err2)}
7247
7434
  `
7248
7435
  );
@@ -7502,7 +7689,7 @@ var init_tail = __esm({
7502
7689
  init_daemon2();
7503
7690
  init_daemon();
7504
7691
  init_core();
7505
- PID_FILE = path31.join(os24.homedir(), ".node9", "daemon.pid");
7692
+ PID_FILE = path32.join(os25.homedir(), ".node9", "daemon.pid");
7506
7693
  ICONS = {
7507
7694
  bash: "\u{1F4BB}",
7508
7695
  shell: "\u{1F4BB}",
@@ -7543,9 +7730,9 @@ __export(hud_exports, {
7543
7730
  main: () => main,
7544
7731
  renderEnvironmentLine: () => renderEnvironmentLine
7545
7732
  });
7546
- import fs29 from "fs";
7547
- import path32 from "path";
7548
- import os25 from "os";
7733
+ import fs30 from "fs";
7734
+ import path33 from "path";
7735
+ import os26 from "os";
7549
7736
  import http3 from "http";
7550
7737
  async function readStdin() {
7551
7738
  const chunks = [];
@@ -7621,9 +7808,9 @@ function formatTimeLeft(resetsAt) {
7621
7808
  return ` (${m}m left)`;
7622
7809
  }
7623
7810
  function safeReadJson(filePath) {
7624
- if (!fs29.existsSync(filePath)) return null;
7811
+ if (!fs30.existsSync(filePath)) return null;
7625
7812
  try {
7626
- return JSON.parse(fs29.readFileSync(filePath, "utf-8"));
7813
+ return JSON.parse(fs30.readFileSync(filePath, "utf-8"));
7627
7814
  } catch {
7628
7815
  return null;
7629
7816
  }
@@ -7644,12 +7831,12 @@ function countHooksInFile(filePath) {
7644
7831
  return Object.keys(cfg.hooks).length;
7645
7832
  }
7646
7833
  function countRulesInDir(rulesDir) {
7647
- if (!fs29.existsSync(rulesDir)) return 0;
7834
+ if (!fs30.existsSync(rulesDir)) return 0;
7648
7835
  let count = 0;
7649
7836
  try {
7650
- for (const entry of fs29.readdirSync(rulesDir, { withFileTypes: true })) {
7837
+ for (const entry of fs30.readdirSync(rulesDir, { withFileTypes: true })) {
7651
7838
  if (entry.isDirectory()) {
7652
- count += countRulesInDir(path32.join(rulesDir, entry.name));
7839
+ count += countRulesInDir(path33.join(rulesDir, entry.name));
7653
7840
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
7654
7841
  count++;
7655
7842
  }
@@ -7660,46 +7847,46 @@ function countRulesInDir(rulesDir) {
7660
7847
  }
7661
7848
  function isSamePath(a, b) {
7662
7849
  try {
7663
- return path32.resolve(a) === path32.resolve(b);
7850
+ return path33.resolve(a) === path33.resolve(b);
7664
7851
  } catch {
7665
7852
  return false;
7666
7853
  }
7667
7854
  }
7668
7855
  function countConfigs(cwd) {
7669
- const homeDir2 = os25.homedir();
7670
- const claudeDir = path32.join(homeDir2, ".claude");
7856
+ const homeDir2 = os26.homedir();
7857
+ const claudeDir = path33.join(homeDir2, ".claude");
7671
7858
  let claudeMdCount = 0;
7672
7859
  let rulesCount = 0;
7673
7860
  let hooksCount = 0;
7674
7861
  const userMcpServers = /* @__PURE__ */ new Set();
7675
7862
  const projectMcpServers = /* @__PURE__ */ new Set();
7676
- if (fs29.existsSync(path32.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
7677
- rulesCount += countRulesInDir(path32.join(claudeDir, "rules"));
7678
- const userSettings = path32.join(claudeDir, "settings.json");
7863
+ if (fs30.existsSync(path33.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
7864
+ rulesCount += countRulesInDir(path33.join(claudeDir, "rules"));
7865
+ const userSettings = path33.join(claudeDir, "settings.json");
7679
7866
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
7680
7867
  hooksCount += countHooksInFile(userSettings);
7681
- const userClaudeJson = path32.join(homeDir2, ".claude.json");
7868
+ const userClaudeJson = path33.join(homeDir2, ".claude.json");
7682
7869
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
7683
7870
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
7684
7871
  userMcpServers.delete(name);
7685
7872
  }
7686
7873
  if (cwd) {
7687
- if (fs29.existsSync(path32.join(cwd, "CLAUDE.md"))) claudeMdCount++;
7688
- if (fs29.existsSync(path32.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
7689
- const projectClaudeDir = path32.join(cwd, ".claude");
7874
+ if (fs30.existsSync(path33.join(cwd, "CLAUDE.md"))) claudeMdCount++;
7875
+ if (fs30.existsSync(path33.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
7876
+ const projectClaudeDir = path33.join(cwd, ".claude");
7690
7877
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
7691
7878
  if (!overlapsUserScope) {
7692
- if (fs29.existsSync(path32.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
7693
- rulesCount += countRulesInDir(path32.join(projectClaudeDir, "rules"));
7694
- const projSettings = path32.join(projectClaudeDir, "settings.json");
7879
+ if (fs30.existsSync(path33.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
7880
+ rulesCount += countRulesInDir(path33.join(projectClaudeDir, "rules"));
7881
+ const projSettings = path33.join(projectClaudeDir, "settings.json");
7695
7882
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
7696
7883
  hooksCount += countHooksInFile(projSettings);
7697
7884
  }
7698
- if (fs29.existsSync(path32.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
7699
- const localSettings = path32.join(projectClaudeDir, "settings.local.json");
7885
+ if (fs30.existsSync(path33.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
7886
+ const localSettings = path33.join(projectClaudeDir, "settings.local.json");
7700
7887
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
7701
7888
  hooksCount += countHooksInFile(localSettings);
7702
- const mcpJsonServers = getMcpServerNames(path32.join(cwd, ".mcp.json"));
7889
+ const mcpJsonServers = getMcpServerNames(path33.join(cwd, ".mcp.json"));
7703
7890
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
7704
7891
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
7705
7892
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -7732,12 +7919,12 @@ function readActiveShieldsHud() {
7732
7919
  return shieldsCache.value;
7733
7920
  }
7734
7921
  try {
7735
- const shieldsPath = path32.join(os25.homedir(), ".node9", "shields.json");
7736
- if (!fs29.existsSync(shieldsPath)) {
7922
+ const shieldsPath = path33.join(os26.homedir(), ".node9", "shields.json");
7923
+ if (!fs30.existsSync(shieldsPath)) {
7737
7924
  shieldsCache = { value: [], ts: now };
7738
7925
  return [];
7739
7926
  }
7740
- const parsed = JSON.parse(fs29.readFileSync(shieldsPath, "utf-8"));
7927
+ const parsed = JSON.parse(fs30.readFileSync(shieldsPath, "utf-8"));
7741
7928
  if (!Array.isArray(parsed.active)) {
7742
7929
  shieldsCache = { value: [], ts: now };
7743
7930
  return [];
@@ -7839,17 +8026,17 @@ function renderContextLine(stdin) {
7839
8026
  async function main() {
7840
8027
  try {
7841
8028
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
7842
- if (fs29.existsSync(path32.join(os25.homedir(), ".node9", "hud-debug"))) {
8029
+ if (fs30.existsSync(path33.join(os26.homedir(), ".node9", "hud-debug"))) {
7843
8030
  try {
7844
- const logPath = path32.join(os25.homedir(), ".node9", "hud-debug.log");
8031
+ const logPath = path33.join(os26.homedir(), ".node9", "hud-debug.log");
7845
8032
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
7846
8033
  let size = 0;
7847
8034
  try {
7848
- size = fs29.statSync(logPath).size;
8035
+ size = fs30.statSync(logPath).size;
7849
8036
  } catch {
7850
8037
  }
7851
8038
  if (size < MAX_LOG_SIZE) {
7852
- fs29.appendFileSync(
8039
+ fs30.appendFileSync(
7853
8040
  logPath,
7854
8041
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
7855
8042
  );
@@ -7870,11 +8057,11 @@ async function main() {
7870
8057
  try {
7871
8058
  const cwd = stdin.cwd ?? process.cwd();
7872
8059
  for (const configPath of [
7873
- path32.join(cwd, "node9.config.json"),
7874
- path32.join(os25.homedir(), ".node9", "config.json")
8060
+ path33.join(cwd, "node9.config.json"),
8061
+ path33.join(os26.homedir(), ".node9", "config.json")
7875
8062
  ]) {
7876
- if (!fs29.existsSync(configPath)) continue;
7877
- const cfg = JSON.parse(fs29.readFileSync(configPath, "utf-8"));
8063
+ if (!fs30.existsSync(configPath)) continue;
8064
+ const cfg = JSON.parse(fs30.readFileSync(configPath, "utf-8"));
7878
8065
  const hud = cfg.settings?.hud;
7879
8066
  if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
7880
8067
  }
@@ -8504,9 +8691,9 @@ function teardownHud() {
8504
8691
  // src/cli.ts
8505
8692
  init_daemon2();
8506
8693
  import chalk20 from "chalk";
8507
- import fs30 from "fs";
8508
- import path33 from "path";
8509
- import os26 from "os";
8694
+ import fs31 from "fs";
8695
+ import path34 from "path";
8696
+ import os27 from "os";
8510
8697
  import { confirm as confirm2 } from "@inquirer/prompts";
8511
8698
 
8512
8699
  // src/utils/duration.ts
@@ -8735,19 +8922,19 @@ init_daemon();
8735
8922
  init_config();
8736
8923
  init_policy();
8737
8924
  import chalk5 from "chalk";
8738
- import fs19 from "fs";
8925
+ import fs20 from "fs";
8739
8926
  import { spawn as spawn6 } from "child_process";
8740
- import path21 from "path";
8741
- import os15 from "os";
8927
+ import path22 from "path";
8928
+ import os16 from "os";
8742
8929
 
8743
8930
  // src/undo.ts
8744
8931
  import { spawnSync as spawnSync4, spawn as spawn5 } from "child_process";
8745
8932
  import crypto3 from "crypto";
8746
- import fs18 from "fs";
8933
+ import fs19 from "fs";
8747
8934
  import net3 from "net";
8748
- import path20 from "path";
8749
- import os14 from "os";
8750
- var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path20.join(os14.tmpdir(), "node9-activity.sock");
8935
+ import path21 from "path";
8936
+ import os15 from "os";
8937
+ var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path21.join(os15.tmpdir(), "node9-activity.sock");
8751
8938
  function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
8752
8939
  try {
8753
8940
  const payload = JSON.stringify({
@@ -8767,22 +8954,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
8767
8954
  } catch {
8768
8955
  }
8769
8956
  }
8770
- var SNAPSHOT_STACK_PATH = path20.join(os14.homedir(), ".node9", "snapshots.json");
8771
- var UNDO_LATEST_PATH = path20.join(os14.homedir(), ".node9", "undo_latest.txt");
8957
+ var SNAPSHOT_STACK_PATH = path21.join(os15.homedir(), ".node9", "snapshots.json");
8958
+ var UNDO_LATEST_PATH = path21.join(os15.homedir(), ".node9", "undo_latest.txt");
8772
8959
  var MAX_SNAPSHOTS = 10;
8773
8960
  var GIT_TIMEOUT = 15e3;
8774
8961
  function readStack() {
8775
8962
  try {
8776
- if (fs18.existsSync(SNAPSHOT_STACK_PATH))
8777
- return JSON.parse(fs18.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
8963
+ if (fs19.existsSync(SNAPSHOT_STACK_PATH))
8964
+ return JSON.parse(fs19.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
8778
8965
  } catch {
8779
8966
  }
8780
8967
  return [];
8781
8968
  }
8782
8969
  function writeStack(stack) {
8783
- const dir = path20.dirname(SNAPSHOT_STACK_PATH);
8784
- if (!fs18.existsSync(dir)) fs18.mkdirSync(dir, { recursive: true });
8785
- fs18.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
8970
+ const dir = path21.dirname(SNAPSHOT_STACK_PATH);
8971
+ if (!fs19.existsSync(dir)) fs19.mkdirSync(dir, { recursive: true });
8972
+ fs19.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
8786
8973
  }
8787
8974
  function extractFilePath(args) {
8788
8975
  if (!args || typeof args !== "object") return null;
@@ -8802,12 +8989,12 @@ function buildArgsSummary(tool, args) {
8802
8989
  return "";
8803
8990
  }
8804
8991
  function findProjectRoot(filePath) {
8805
- let dir = path20.dirname(filePath);
8992
+ let dir = path21.dirname(filePath);
8806
8993
  while (true) {
8807
- if (fs18.existsSync(path20.join(dir, ".git")) || fs18.existsSync(path20.join(dir, "package.json"))) {
8994
+ if (fs19.existsSync(path21.join(dir, ".git")) || fs19.existsSync(path21.join(dir, "package.json"))) {
8808
8995
  return dir;
8809
8996
  }
8810
- const parent = path20.dirname(dir);
8997
+ const parent = path21.dirname(dir);
8811
8998
  if (parent === dir) return process.cwd();
8812
8999
  dir = parent;
8813
9000
  }
@@ -8815,7 +9002,7 @@ function findProjectRoot(filePath) {
8815
9002
  function normalizeCwdForHash(cwd) {
8816
9003
  let normalized;
8817
9004
  try {
8818
- normalized = fs18.realpathSync(cwd);
9005
+ normalized = fs19.realpathSync(cwd);
8819
9006
  } catch {
8820
9007
  normalized = cwd;
8821
9008
  }
@@ -8825,16 +9012,16 @@ function normalizeCwdForHash(cwd) {
8825
9012
  }
8826
9013
  function getShadowRepoDir(cwd) {
8827
9014
  const hash = crypto3.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
8828
- return path20.join(os14.homedir(), ".node9", "snapshots", hash);
9015
+ return path21.join(os15.homedir(), ".node9", "snapshots", hash);
8829
9016
  }
8830
9017
  function cleanOrphanedIndexFiles(shadowDir) {
8831
9018
  try {
8832
9019
  const cutoff = Date.now() - 6e4;
8833
- for (const f of fs18.readdirSync(shadowDir)) {
9020
+ for (const f of fs19.readdirSync(shadowDir)) {
8834
9021
  if (f.startsWith("index_")) {
8835
- const fp = path20.join(shadowDir, f);
9022
+ const fp = path21.join(shadowDir, f);
8836
9023
  try {
8837
- if (fs18.statSync(fp).mtimeMs < cutoff) fs18.unlinkSync(fp);
9024
+ if (fs19.statSync(fp).mtimeMs < cutoff) fs19.unlinkSync(fp);
8838
9025
  } catch {
8839
9026
  }
8840
9027
  }
@@ -8846,7 +9033,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
8846
9033
  const hardcoded = [".git", ".node9"];
8847
9034
  const lines = [...hardcoded, ...ignorePaths].join("\n");
8848
9035
  try {
8849
- fs18.writeFileSync(path20.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
9036
+ fs19.writeFileSync(path21.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
8850
9037
  } catch {
8851
9038
  }
8852
9039
  }
@@ -8859,25 +9046,25 @@ function ensureShadowRepo(shadowDir, cwd) {
8859
9046
  timeout: 3e3
8860
9047
  });
8861
9048
  if (check.status === 0) {
8862
- const ptPath = path20.join(shadowDir, "project-path.txt");
9049
+ const ptPath = path21.join(shadowDir, "project-path.txt");
8863
9050
  try {
8864
- const stored = fs18.readFileSync(ptPath, "utf8").trim();
9051
+ const stored = fs19.readFileSync(ptPath, "utf8").trim();
8865
9052
  if (stored === normalizedCwd) return true;
8866
9053
  if (process.env.NODE9_DEBUG === "1")
8867
9054
  console.error(
8868
9055
  `[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
8869
9056
  );
8870
- fs18.rmSync(shadowDir, { recursive: true, force: true });
9057
+ fs19.rmSync(shadowDir, { recursive: true, force: true });
8871
9058
  } catch {
8872
9059
  try {
8873
- fs18.writeFileSync(ptPath, normalizedCwd, "utf8");
9060
+ fs19.writeFileSync(ptPath, normalizedCwd, "utf8");
8874
9061
  } catch {
8875
9062
  }
8876
9063
  return true;
8877
9064
  }
8878
9065
  }
8879
9066
  try {
8880
- fs18.mkdirSync(shadowDir, { recursive: true });
9067
+ fs19.mkdirSync(shadowDir, { recursive: true });
8881
9068
  } catch {
8882
9069
  }
8883
9070
  const init = spawnSync4("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
@@ -8886,7 +9073,7 @@ function ensureShadowRepo(shadowDir, cwd) {
8886
9073
  if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
8887
9074
  return false;
8888
9075
  }
8889
- const configFile = path20.join(shadowDir, "config");
9076
+ const configFile = path21.join(shadowDir, "config");
8890
9077
  spawnSync4("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
8891
9078
  timeout: 3e3
8892
9079
  });
@@ -8894,7 +9081,7 @@ function ensureShadowRepo(shadowDir, cwd) {
8894
9081
  timeout: 3e3
8895
9082
  });
8896
9083
  try {
8897
- fs18.writeFileSync(path20.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
9084
+ fs19.writeFileSync(path21.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
8898
9085
  } catch {
8899
9086
  }
8900
9087
  return true;
@@ -8914,12 +9101,12 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8914
9101
  let indexFile = null;
8915
9102
  try {
8916
9103
  const rawFilePath = extractFilePath(args);
8917
- const absFilePath = rawFilePath && path20.isAbsolute(rawFilePath) ? rawFilePath : null;
9104
+ const absFilePath = rawFilePath && path21.isAbsolute(rawFilePath) ? rawFilePath : null;
8918
9105
  const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
8919
9106
  const shadowDir = getShadowRepoDir(cwd);
8920
9107
  if (!ensureShadowRepo(shadowDir, cwd)) return null;
8921
9108
  writeShadowExcludes(shadowDir, ignorePaths);
8922
- indexFile = path20.join(shadowDir, `index_${process.pid}_${Date.now()}`);
9109
+ indexFile = path21.join(shadowDir, `index_${process.pid}_${Date.now()}`);
8923
9110
  const shadowEnv = {
8924
9111
  ...process.env,
8925
9112
  GIT_DIR: shadowDir,
@@ -8991,7 +9178,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8991
9178
  writeStack(stack);
8992
9179
  const entry = stack[stack.length - 1];
8993
9180
  notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
8994
- fs18.writeFileSync(UNDO_LATEST_PATH, commitHash);
9181
+ fs19.writeFileSync(UNDO_LATEST_PATH, commitHash);
8995
9182
  if (shouldGc) {
8996
9183
  spawn5("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
8997
9184
  }
@@ -9002,7 +9189,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
9002
9189
  } finally {
9003
9190
  if (indexFile) {
9004
9191
  try {
9005
- fs18.unlinkSync(indexFile);
9192
+ fs19.unlinkSync(indexFile);
9006
9193
  } catch {
9007
9194
  }
9008
9195
  }
@@ -9078,9 +9265,9 @@ function applyUndo(hash, cwd) {
9078
9265
  timeout: GIT_TIMEOUT
9079
9266
  }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
9080
9267
  for (const file of [...tracked, ...untracked]) {
9081
- const fullPath = path20.join(dir, file);
9082
- if (!snapshotFiles.has(file) && fs18.existsSync(fullPath)) {
9083
- fs18.unlinkSync(fullPath);
9268
+ const fullPath = path21.join(dir, file);
9269
+ if (!snapshotFiles.has(file) && fs19.existsSync(fullPath)) {
9270
+ fs19.unlinkSync(fullPath);
9084
9271
  }
9085
9272
  }
9086
9273
  return true;
@@ -9104,9 +9291,9 @@ function registerCheckCommand(program2) {
9104
9291
  } catch (err2) {
9105
9292
  const tempConfig = getConfig();
9106
9293
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
9107
- const logPath = path21.join(os15.homedir(), ".node9", "hook-debug.log");
9294
+ const logPath = path22.join(os16.homedir(), ".node9", "hook-debug.log");
9108
9295
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
9109
- fs19.appendFileSync(
9296
+ fs20.appendFileSync(
9110
9297
  logPath,
9111
9298
  `[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
9112
9299
  RAW: ${raw}
@@ -9119,10 +9306,10 @@ RAW: ${raw}
9119
9306
  if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
9120
9307
  try {
9121
9308
  const scriptPath = process.argv[1];
9122
- if (typeof scriptPath !== "string" || !path21.isAbsolute(scriptPath))
9309
+ if (typeof scriptPath !== "string" || !path22.isAbsolute(scriptPath))
9123
9310
  throw new Error("node9: argv[1] is not an absolute path");
9124
- const resolvedScript = fs19.realpathSync(scriptPath);
9125
- const expectedCli = fs19.realpathSync(path21.resolve(__dirname, "../../cli.js"));
9311
+ const resolvedScript = fs20.realpathSync(scriptPath);
9312
+ const expectedCli = fs20.realpathSync(path22.resolve(__dirname, "../../cli.js"));
9126
9313
  if (resolvedScript !== expectedCli)
9127
9314
  throw new Error(
9128
9315
  "node9: daemon spawn aborted \u2014 argv[1] does not resolve to the node9 CLI"
@@ -9148,10 +9335,10 @@ RAW: ${raw}
9148
9335
  }
9149
9336
  }
9150
9337
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
9151
- const logPath = path21.join(os15.homedir(), ".node9", "hook-debug.log");
9152
- if (!fs19.existsSync(path21.dirname(logPath)))
9153
- fs19.mkdirSync(path21.dirname(logPath), { recursive: true });
9154
- fs19.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
9338
+ const logPath = path22.join(os16.homedir(), ".node9", "hook-debug.log");
9339
+ if (!fs20.existsSync(path22.dirname(logPath)))
9340
+ fs20.mkdirSync(path22.dirname(logPath), { recursive: true });
9341
+ fs20.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
9155
9342
  `);
9156
9343
  }
9157
9344
  const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
@@ -9164,8 +9351,8 @@ RAW: ${raw}
9164
9351
  const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
9165
9352
  let ttyFd = null;
9166
9353
  try {
9167
- ttyFd = fs19.openSync("/dev/tty", "w");
9168
- const writeTty = (line) => fs19.writeSync(ttyFd, line + "\n");
9354
+ ttyFd = fs20.openSync("/dev/tty", "w");
9355
+ const writeTty = (line) => fs20.writeSync(ttyFd, line + "\n");
9169
9356
  if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
9170
9357
  writeTty(chalk5.bgRed.white.bold(`
9171
9358
  \u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
@@ -9184,7 +9371,7 @@ RAW: ${raw}
9184
9371
  } finally {
9185
9372
  if (ttyFd !== null)
9186
9373
  try {
9187
- fs19.closeSync(ttyFd);
9374
+ fs20.closeSync(ttyFd);
9188
9375
  } catch {
9189
9376
  }
9190
9377
  }
@@ -9216,7 +9403,7 @@ RAW: ${raw}
9216
9403
  if (shouldSnapshot(toolName, toolInput, config)) {
9217
9404
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
9218
9405
  }
9219
- const safeCwdForAuth = typeof payload.cwd === "string" && path21.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9406
+ const safeCwdForAuth = typeof payload.cwd === "string" && path22.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9220
9407
  const result = await authorizeHeadless(toolName, toolInput, meta, {
9221
9408
  cwd: safeCwdForAuth
9222
9409
  });
@@ -9228,12 +9415,12 @@ RAW: ${raw}
9228
9415
  }
9229
9416
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
9230
9417
  try {
9231
- const tty = fs19.openSync("/dev/tty", "w");
9232
- fs19.writeSync(
9418
+ const tty = fs20.openSync("/dev/tty", "w");
9419
+ fs20.writeSync(
9233
9420
  tty,
9234
9421
  chalk5.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
9235
9422
  );
9236
- fs19.closeSync(tty);
9423
+ fs20.closeSync(tty);
9237
9424
  } catch {
9238
9425
  }
9239
9426
  const daemonReady = await autoStartDaemonAndWait();
@@ -9260,9 +9447,9 @@ RAW: ${raw}
9260
9447
  });
9261
9448
  } catch (err2) {
9262
9449
  if (process.env.NODE9_DEBUG === "1") {
9263
- const logPath = path21.join(os15.homedir(), ".node9", "hook-debug.log");
9450
+ const logPath = path22.join(os16.homedir(), ".node9", "hook-debug.log");
9264
9451
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
9265
- fs19.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
9452
+ fs20.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
9266
9453
  `);
9267
9454
  }
9268
9455
  process.exit(0);
@@ -9299,9 +9486,9 @@ RAW: ${raw}
9299
9486
  init_audit();
9300
9487
  init_config();
9301
9488
  init_policy();
9302
- import fs20 from "fs";
9303
- import path22 from "path";
9304
- import os16 from "os";
9489
+ import fs21 from "fs";
9490
+ import path23 from "path";
9491
+ import os17 from "os";
9305
9492
  init_daemon();
9306
9493
 
9307
9494
  // src/utils/cp-mv-parser.ts
@@ -9374,10 +9561,10 @@ function registerLogCommand(program2) {
9374
9561
  decision: "allowed",
9375
9562
  source: "post-hook"
9376
9563
  };
9377
- const logPath = path22.join(os16.homedir(), ".node9", "audit.log");
9378
- if (!fs20.existsSync(path22.dirname(logPath)))
9379
- fs20.mkdirSync(path22.dirname(logPath), { recursive: true });
9380
- fs20.appendFileSync(logPath, JSON.stringify(entry) + "\n");
9564
+ const logPath = path23.join(os17.homedir(), ".node9", "audit.log");
9565
+ if (!fs21.existsSync(path23.dirname(logPath)))
9566
+ fs21.mkdirSync(path23.dirname(logPath), { recursive: true });
9567
+ fs21.appendFileSync(logPath, JSON.stringify(entry) + "\n");
9381
9568
  if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
9382
9569
  const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
9383
9570
  if (command) {
@@ -9410,7 +9597,7 @@ function registerLogCommand(program2) {
9410
9597
  }
9411
9598
  }
9412
9599
  }
9413
- const safeCwd = typeof payload.cwd === "string" && path22.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9600
+ const safeCwd = typeof payload.cwd === "string" && path23.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9414
9601
  const config = getConfig(safeCwd);
9415
9602
  if (shouldSnapshot(tool, {}, config)) {
9416
9603
  await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
@@ -9419,9 +9606,9 @@ function registerLogCommand(program2) {
9419
9606
  const msg = err2 instanceof Error ? err2.message : String(err2);
9420
9607
  process.stderr.write(`[Node9] audit log error: ${msg}
9421
9608
  `);
9422
- const debugPath = path22.join(os16.homedir(), ".node9", "hook-debug.log");
9609
+ const debugPath = path23.join(os17.homedir(), ".node9", "hook-debug.log");
9423
9610
  try {
9424
- fs20.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
9611
+ fs21.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
9425
9612
  `);
9426
9613
  } catch {
9427
9614
  }
@@ -9822,13 +10009,13 @@ function registerConfigShowCommand(program2) {
9822
10009
  // src/cli/commands/doctor.ts
9823
10010
  init_daemon();
9824
10011
  import chalk7 from "chalk";
9825
- import fs21 from "fs";
9826
- import path23 from "path";
9827
- import os17 from "os";
10012
+ import fs22 from "fs";
10013
+ import path24 from "path";
10014
+ import os18 from "os";
9828
10015
  import { execSync as execSync2 } from "child_process";
9829
10016
  function registerDoctorCommand(program2, version2) {
9830
10017
  program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
9831
- const homeDir2 = os17.homedir();
10018
+ const homeDir2 = os18.homedir();
9832
10019
  let failures = 0;
9833
10020
  function pass(msg) {
9834
10021
  console.log(chalk7.green(" \u2705 ") + msg);
@@ -9877,10 +10064,10 @@ function registerDoctorCommand(program2, version2) {
9877
10064
  );
9878
10065
  }
9879
10066
  section("Configuration");
9880
- const globalConfigPath = path23.join(homeDir2, ".node9", "config.json");
9881
- if (fs21.existsSync(globalConfigPath)) {
10067
+ const globalConfigPath = path24.join(homeDir2, ".node9", "config.json");
10068
+ if (fs22.existsSync(globalConfigPath)) {
9882
10069
  try {
9883
- JSON.parse(fs21.readFileSync(globalConfigPath, "utf-8"));
10070
+ JSON.parse(fs22.readFileSync(globalConfigPath, "utf-8"));
9884
10071
  pass("~/.node9/config.json found and valid");
9885
10072
  } catch {
9886
10073
  fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
@@ -9888,10 +10075,10 @@ function registerDoctorCommand(program2, version2) {
9888
10075
  } else {
9889
10076
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
9890
10077
  }
9891
- const projectConfigPath = path23.join(process.cwd(), "node9.config.json");
9892
- if (fs21.existsSync(projectConfigPath)) {
10078
+ const projectConfigPath = path24.join(process.cwd(), "node9.config.json");
10079
+ if (fs22.existsSync(projectConfigPath)) {
9893
10080
  try {
9894
- JSON.parse(fs21.readFileSync(projectConfigPath, "utf-8"));
10081
+ JSON.parse(fs22.readFileSync(projectConfigPath, "utf-8"));
9895
10082
  pass("node9.config.json found and valid (project)");
9896
10083
  } catch {
9897
10084
  fail(
@@ -9900,8 +10087,8 @@ function registerDoctorCommand(program2, version2) {
9900
10087
  );
9901
10088
  }
9902
10089
  }
9903
- const credsPath = path23.join(homeDir2, ".node9", "credentials.json");
9904
- if (fs21.existsSync(credsPath)) {
10090
+ const credsPath = path24.join(homeDir2, ".node9", "credentials.json");
10091
+ if (fs22.existsSync(credsPath)) {
9905
10092
  pass("Cloud credentials found (~/.node9/credentials.json)");
9906
10093
  } else {
9907
10094
  warn(
@@ -9910,10 +10097,10 @@ function registerDoctorCommand(program2, version2) {
9910
10097
  );
9911
10098
  }
9912
10099
  section("Agent Hooks");
9913
- const claudeSettingsPath = path23.join(homeDir2, ".claude", "settings.json");
9914
- if (fs21.existsSync(claudeSettingsPath)) {
10100
+ const claudeSettingsPath = path24.join(homeDir2, ".claude", "settings.json");
10101
+ if (fs22.existsSync(claudeSettingsPath)) {
9915
10102
  try {
9916
- const cs = JSON.parse(fs21.readFileSync(claudeSettingsPath, "utf-8"));
10103
+ const cs = JSON.parse(fs22.readFileSync(claudeSettingsPath, "utf-8"));
9917
10104
  const hasHook = cs.hooks?.PreToolUse?.some(
9918
10105
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
9919
10106
  );
@@ -9929,10 +10116,10 @@ function registerDoctorCommand(program2, version2) {
9929
10116
  } else {
9930
10117
  warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
9931
10118
  }
9932
- const geminiSettingsPath = path23.join(homeDir2, ".gemini", "settings.json");
9933
- if (fs21.existsSync(geminiSettingsPath)) {
10119
+ const geminiSettingsPath = path24.join(homeDir2, ".gemini", "settings.json");
10120
+ if (fs22.existsSync(geminiSettingsPath)) {
9934
10121
  try {
9935
- const gs = JSON.parse(fs21.readFileSync(geminiSettingsPath, "utf-8"));
10122
+ const gs = JSON.parse(fs22.readFileSync(geminiSettingsPath, "utf-8"));
9936
10123
  const hasHook = gs.hooks?.BeforeTool?.some(
9937
10124
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
9938
10125
  );
@@ -9948,10 +10135,10 @@ function registerDoctorCommand(program2, version2) {
9948
10135
  } else {
9949
10136
  warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
9950
10137
  }
9951
- const cursorHooksPath = path23.join(homeDir2, ".cursor", "hooks.json");
9952
- if (fs21.existsSync(cursorHooksPath)) {
10138
+ const cursorHooksPath = path24.join(homeDir2, ".cursor", "hooks.json");
10139
+ if (fs22.existsSync(cursorHooksPath)) {
9953
10140
  try {
9954
- const cur = JSON.parse(fs21.readFileSync(cursorHooksPath, "utf-8"));
10141
+ const cur = JSON.parse(fs22.readFileSync(cursorHooksPath, "utf-8"));
9955
10142
  const hasHook = cur.hooks?.preToolUse?.some(
9956
10143
  (h) => h.command?.includes("node9") || h.command?.includes("cli.js")
9957
10144
  );
@@ -9989,9 +10176,9 @@ function registerDoctorCommand(program2, version2) {
9989
10176
 
9990
10177
  // src/cli/commands/audit.ts
9991
10178
  import chalk8 from "chalk";
9992
- import fs22 from "fs";
9993
- import path24 from "path";
9994
- import os18 from "os";
10179
+ import fs23 from "fs";
10180
+ import path25 from "path";
10181
+ import os19 from "os";
9995
10182
  function formatRelativeTime(timestamp) {
9996
10183
  const diff = Date.now() - new Date(timestamp).getTime();
9997
10184
  const sec = Math.floor(diff / 1e3);
@@ -10004,14 +10191,14 @@ function formatRelativeTime(timestamp) {
10004
10191
  }
10005
10192
  function registerAuditCommand(program2) {
10006
10193
  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) => {
10007
- const logPath = path24.join(os18.homedir(), ".node9", "audit.log");
10008
- if (!fs22.existsSync(logPath)) {
10194
+ const logPath = path25.join(os19.homedir(), ".node9", "audit.log");
10195
+ if (!fs23.existsSync(logPath)) {
10009
10196
  console.log(
10010
10197
  chalk8.yellow("No audit logs found. Run node9 with an agent to generate entries.")
10011
10198
  );
10012
10199
  return;
10013
10200
  }
10014
- const raw = fs22.readFileSync(logPath, "utf-8");
10201
+ const raw = fs23.readFileSync(logPath, "utf-8");
10015
10202
  const lines = raw.split("\n").filter((l) => l.trim() !== "");
10016
10203
  let entries = lines.flatMap((line) => {
10017
10204
  try {
@@ -10065,9 +10252,9 @@ function registerAuditCommand(program2) {
10065
10252
 
10066
10253
  // src/cli/commands/report.ts
10067
10254
  import chalk9 from "chalk";
10068
- import fs23 from "fs";
10069
- import path25 from "path";
10070
- import os19 from "os";
10255
+ import fs24 from "fs";
10256
+ import path26 from "path";
10257
+ import os20 from "os";
10071
10258
  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;
10072
10259
  function buildTestTimestamps(allEntries) {
10073
10260
  const testTs = /* @__PURE__ */ new Set();
@@ -10114,8 +10301,8 @@ function getDateRange(period) {
10114
10301
  }
10115
10302
  }
10116
10303
  function parseAuditLog(logPath) {
10117
- if (!fs23.existsSync(logPath)) return [];
10118
- const raw = fs23.readFileSync(logPath, "utf-8");
10304
+ if (!fs24.existsSync(logPath)) return [];
10305
+ const raw = fs24.readFileSync(logPath, "utf-8");
10119
10306
  return raw.split("\n").flatMap((line) => {
10120
10307
  if (!line.trim()) return [];
10121
10308
  try {
@@ -10177,29 +10364,39 @@ function claudeModelPrice(model) {
10177
10364
  return null;
10178
10365
  }
10179
10366
  function loadClaudeCost(start, end) {
10180
- const projectsDir = path25.join(os19.homedir(), ".claude", "projects");
10181
- if (!fs23.existsSync(projectsDir)) return { total: 0, byDay: /* @__PURE__ */ new Map() };
10367
+ const empty = {
10368
+ total: 0,
10369
+ byDay: /* @__PURE__ */ new Map(),
10370
+ byModel: /* @__PURE__ */ new Map(),
10371
+ inputTokens: 0,
10372
+ cacheReadTokens: 0
10373
+ };
10374
+ const projectsDir = path26.join(os20.homedir(), ".claude", "projects");
10375
+ if (!fs24.existsSync(projectsDir)) return empty;
10182
10376
  let dirs;
10183
10377
  try {
10184
- dirs = fs23.readdirSync(projectsDir);
10378
+ dirs = fs24.readdirSync(projectsDir);
10185
10379
  } catch {
10186
- return { total: 0, byDay: /* @__PURE__ */ new Map() };
10380
+ return empty;
10187
10381
  }
10188
10382
  let total = 0;
10383
+ let inputTokens = 0;
10384
+ let cacheReadTokens = 0;
10189
10385
  const byDay = /* @__PURE__ */ new Map();
10386
+ const byModel = /* @__PURE__ */ new Map();
10190
10387
  for (const proj of dirs) {
10191
- const projPath = path25.join(projectsDir, proj);
10388
+ const projPath = path26.join(projectsDir, proj);
10192
10389
  let files;
10193
10390
  try {
10194
- const stat = fs23.statSync(projPath);
10391
+ const stat = fs24.statSync(projPath);
10195
10392
  if (!stat.isDirectory()) continue;
10196
- files = fs23.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
10393
+ files = fs24.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
10197
10394
  } catch {
10198
10395
  continue;
10199
10396
  }
10200
10397
  for (const file of files) {
10201
10398
  try {
10202
- const raw = fs23.readFileSync(path25.join(projPath, file), "utf-8");
10399
+ const raw = fs24.readFileSync(path26.join(projPath, file), "utf-8");
10203
10400
  for (const line of raw.split("\n")) {
10204
10401
  if (!line.trim()) continue;
10205
10402
  let entry;
@@ -10217,24 +10414,32 @@ function loadClaudeCost(start, end) {
10217
10414
  if (!usage || !model) continue;
10218
10415
  const p = claudeModelPrice(model);
10219
10416
  if (!p) continue;
10220
- 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;
10417
+ const inp = usage.input_tokens ?? 0;
10418
+ const out = usage.output_tokens ?? 0;
10419
+ const cw = usage.cache_creation_input_tokens ?? 0;
10420
+ const cr = usage.cache_read_input_tokens ?? 0;
10421
+ const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
10221
10422
  total += cost;
10423
+ inputTokens += inp;
10424
+ cacheReadTokens += cr;
10222
10425
  const dateKey = entry.timestamp.slice(0, 10);
10223
10426
  byDay.set(dateKey, (byDay.get(dateKey) ?? 0) + cost);
10427
+ const normModel = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
10428
+ byModel.set(normModel, (byModel.get(normModel) ?? 0) + cost);
10224
10429
  }
10225
10430
  } catch {
10226
10431
  continue;
10227
10432
  }
10228
10433
  }
10229
10434
  }
10230
- return { total, byDay };
10435
+ return { total, byDay, byModel, inputTokens, cacheReadTokens };
10231
10436
  }
10232
10437
  function registerReportCommand(program2) {
10233
10438
  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) => {
10234
10439
  const period = ["today", "7d", "30d", "month"].includes(
10235
10440
  options.period
10236
10441
  ) ? options.period : "7d";
10237
- const logPath = path25.join(os19.homedir(), ".node9", "audit.log");
10442
+ const logPath = path26.join(os20.homedir(), ".node9", "audit.log");
10238
10443
  const allEntries = parseAuditLog(logPath);
10239
10444
  if (allEntries.length === 0) {
10240
10445
  console.log(
@@ -10243,7 +10448,13 @@ function registerReportCommand(program2) {
10243
10448
  return;
10244
10449
  }
10245
10450
  const { start, end } = getDateRange(period);
10246
- const { total: costUSD, byDay: costByDay } = loadClaudeCost(start, end);
10451
+ const {
10452
+ total: costUSD,
10453
+ byDay: costByDay,
10454
+ byModel: costByModel,
10455
+ inputTokens: costInputTokens,
10456
+ cacheReadTokens: costCacheRead
10457
+ } = loadClaudeCost(start, end);
10247
10458
  const periodMs = end.getTime() - start.getTime();
10248
10459
  const priorEnd = new Date(start.getTime() - 1);
10249
10460
  const priorStart = new Date(start.getTime() - periodMs);
@@ -10345,7 +10556,6 @@ function registerReportCommand(program2) {
10345
10556
  const blockLabel = blocked > 0 ? chalk9.red(`\u{1F6D1} ${num(blocked)} blocked`) : chalk9.dim("\u{1F6D1} 0 blocked");
10346
10557
  const dlpLabel = dlpHits > 0 ? chalk9.yellow(`\u{1F6A8} ${dlpHits} DLP hits`) : chalk9.dim("\u{1F6A8} 0 DLP hits");
10347
10558
  const loopLabel = loopHits > 0 ? chalk9.yellow(`\u{1F504} ${loopHits} loops`) : chalk9.dim("\u{1F504} 0 loops");
10348
- const costLabel = costUSD > 0 ? chalk9.magenta(`\u{1F4B0} ${fmtCost(costUSD)}`) : chalk9.dim("\u{1F4B0} \u2013");
10349
10559
  const currentRate = total > 0 ? blocked / total : 0;
10350
10560
  const trendLabel = (() => {
10351
10561
  if (priorBlockRate === null) return chalk9.dim(`${pct(blocked, total)} block rate`);
@@ -10358,7 +10568,7 @@ function registerReportCommand(program2) {
10358
10568
  const ratioLabel = reads > 0 ? chalk9.dim(`edit/read ${(edits / reads).toFixed(1)}`) : chalk9.dim("edit/read \u2013");
10359
10569
  const testLabel = testPasses + testFails > 0 ? chalk9.dim("tests ") + chalk9.green(`${testPasses}\u2713`) + (testFails > 0 ? " " + chalk9.red(`${testFails}\u2717`) : "") : chalk9.dim("tests \u2013");
10360
10570
  console.log(
10361
- " " + chalk9.green(`\u2705 ${num(allowed)} allowed`) + " " + blockLabel + " " + dlpLabel + " " + loopLabel + " " + trendLabel + " " + costLabel
10571
+ " " + chalk9.green(`\u2705 ${num(allowed)} allowed`) + " " + blockLabel + " " + dlpLabel + " " + loopLabel + " " + trendLabel
10362
10572
  );
10363
10573
  console.log(" " + ratioLabel + " " + testLabel);
10364
10574
  console.log("");
@@ -10443,6 +10653,30 @@ function registerReportCommand(program2) {
10443
10653
  );
10444
10654
  }
10445
10655
  }
10656
+ if (costUSD > 0) {
10657
+ const periodDays = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 864e5));
10658
+ const avgPerDay = costUSD / periodDays;
10659
+ const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
10660
+ const costHeaderRight = [
10661
+ chalk9.yellow(fmtCost(costUSD)),
10662
+ chalk9.dim(`avg ${fmtCost(avgPerDay)}/day`),
10663
+ cacheHitPct > 0 ? chalk9.dim(`${cacheHitPct}% cache hit`) : null
10664
+ ].filter(Boolean).join(chalk9.dim(" \xB7 "));
10665
+ console.log("");
10666
+ console.log(" " + chalk9.bold("Cost") + " " + costHeaderRight);
10667
+ console.log(" " + chalk9.dim("\u2500".repeat(Math.min(50, W - 4))));
10668
+ const modelList = [...costByModel.entries()].sort((a, b) => b[1] - a[1]);
10669
+ const maxModelCost = Math.max(...modelList.map(([, v]) => v), 1e-9);
10670
+ const MODEL_LABEL = 22;
10671
+ const MODEL_BAR = Math.max(6, Math.min(20, W - MODEL_LABEL - 12));
10672
+ for (const [model, cost] of modelList) {
10673
+ const label = model.length > MODEL_LABEL - 1 ? model.slice(0, MODEL_LABEL - 2) + "\u2026" : model;
10674
+ const b = colorBar(cost, maxModelCost, MODEL_BAR);
10675
+ console.log(
10676
+ " " + chalk9.white(label.padEnd(MODEL_LABEL)) + b + " " + chalk9.yellow(fmtCost(cost))
10677
+ );
10678
+ }
10679
+ }
10446
10680
  console.log("");
10447
10681
  console.log(
10448
10682
  " " + chalk9.dim("node9 audit --deny") + chalk9.dim(" \xB7 ") + chalk9.dim("node9 report --period today|7d|30d|month --no-tests")
@@ -10519,12 +10753,12 @@ function registerDaemonCommand(program2) {
10519
10753
  init_core();
10520
10754
  init_daemon();
10521
10755
  import chalk11 from "chalk";
10522
- import fs24 from "fs";
10523
- import path26 from "path";
10524
- import os20 from "os";
10756
+ import fs25 from "fs";
10757
+ import path27 from "path";
10758
+ import os21 from "os";
10525
10759
  function readJson2(filePath) {
10526
10760
  try {
10527
- if (fs24.existsSync(filePath)) return JSON.parse(fs24.readFileSync(filePath, "utf-8"));
10761
+ if (fs25.existsSync(filePath)) return JSON.parse(fs25.readFileSync(filePath, "utf-8"));
10528
10762
  } catch {
10529
10763
  }
10530
10764
  return null;
@@ -10589,28 +10823,28 @@ function registerStatusCommand(program2) {
10589
10823
  console.log("");
10590
10824
  const modeLabel = settings.mode === "audit" ? chalk11.blue("audit") : settings.mode === "strict" ? chalk11.red("strict") : chalk11.white("standard");
10591
10825
  console.log(` Mode: ${modeLabel}`);
10592
- const projectConfig = path26.join(process.cwd(), "node9.config.json");
10593
- const globalConfig = path26.join(os20.homedir(), ".node9", "config.json");
10826
+ const projectConfig = path27.join(process.cwd(), "node9.config.json");
10827
+ const globalConfig = path27.join(os21.homedir(), ".node9", "config.json");
10594
10828
  console.log(
10595
- ` Local: ${fs24.existsSync(projectConfig) ? chalk11.green("Active (node9.config.json)") : chalk11.gray("Not present")}`
10829
+ ` Local: ${fs25.existsSync(projectConfig) ? chalk11.green("Active (node9.config.json)") : chalk11.gray("Not present")}`
10596
10830
  );
10597
10831
  console.log(
10598
- ` Global: ${fs24.existsSync(globalConfig) ? chalk11.green("Active (~/.node9/config.json)") : chalk11.gray("Not present")}`
10832
+ ` Global: ${fs25.existsSync(globalConfig) ? chalk11.green("Active (~/.node9/config.json)") : chalk11.gray("Not present")}`
10599
10833
  );
10600
10834
  if (mergedConfig.policy.sandboxPaths.length > 0) {
10601
10835
  console.log(
10602
10836
  ` Sandbox: ${chalk11.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
10603
10837
  );
10604
10838
  }
10605
- const homeDir2 = os20.homedir();
10839
+ const homeDir2 = os21.homedir();
10606
10840
  const claudeSettings = readJson2(
10607
- path26.join(homeDir2, ".claude", "settings.json")
10841
+ path27.join(homeDir2, ".claude", "settings.json")
10608
10842
  );
10609
- const claudeConfig = readJson2(path26.join(homeDir2, ".claude.json"));
10843
+ const claudeConfig = readJson2(path27.join(homeDir2, ".claude.json"));
10610
10844
  const geminiSettings = readJson2(
10611
- path26.join(homeDir2, ".gemini", "settings.json")
10845
+ path27.join(homeDir2, ".gemini", "settings.json")
10612
10846
  );
10613
- const cursorConfig = readJson2(path26.join(homeDir2, ".cursor", "mcp.json"));
10847
+ const cursorConfig = readJson2(path27.join(homeDir2, ".cursor", "mcp.json"));
10614
10848
  const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
10615
10849
  if (agentFound) {
10616
10850
  console.log("");
@@ -10670,9 +10904,9 @@ function registerStatusCommand(program2) {
10670
10904
  // src/cli/commands/init.ts
10671
10905
  init_core();
10672
10906
  import chalk12 from "chalk";
10673
- import fs25 from "fs";
10674
- import path27 from "path";
10675
- import os21 from "os";
10907
+ import fs26 from "fs";
10908
+ import path28 from "path";
10909
+ import os22 from "os";
10676
10910
  import https2 from "https";
10677
10911
  init_shields();
10678
10912
  var DEFAULT_SHIELDS = ["bash-safe", "filesystem", "postgres"];
@@ -10731,15 +10965,15 @@ function registerInitCommand(program2) {
10731
10965
  }
10732
10966
  console.log("");
10733
10967
  }
10734
- const configPath = path27.join(os21.homedir(), ".node9", "config.json");
10735
- if (fs25.existsSync(configPath) && !options.force) {
10968
+ const configPath = path28.join(os22.homedir(), ".node9", "config.json");
10969
+ if (fs26.existsSync(configPath) && !options.force) {
10736
10970
  try {
10737
- const existing = JSON.parse(fs25.readFileSync(configPath, "utf-8"));
10971
+ const existing = JSON.parse(fs26.readFileSync(configPath, "utf-8"));
10738
10972
  const settings = existing.settings ?? {};
10739
10973
  if (settings.mode !== chosenMode) {
10740
10974
  settings.mode = chosenMode;
10741
10975
  existing.settings = settings;
10742
- fs25.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
10976
+ fs26.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
10743
10977
  console.log(chalk12.green(`\u2705 Mode updated: ${chosenMode}`));
10744
10978
  } else {
10745
10979
  console.log(chalk12.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
@@ -10752,9 +10986,9 @@ function registerInitCommand(program2) {
10752
10986
  ...DEFAULT_CONFIG,
10753
10987
  settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
10754
10988
  };
10755
- const dir = path27.dirname(configPath);
10756
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
10757
- fs25.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
10989
+ const dir = path28.dirname(configPath);
10990
+ if (!fs26.existsSync(dir)) fs26.mkdirSync(dir, { recursive: true });
10991
+ fs26.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
10758
10992
  console.log(chalk12.green(`\u2705 Config created: ${configPath}`));
10759
10993
  console.log(chalk12.gray(` Mode: ${chosenMode}`));
10760
10994
  }
@@ -10808,7 +11042,7 @@ function registerInitCommand(program2) {
10808
11042
  }
10809
11043
 
10810
11044
  // src/cli/commands/undo.ts
10811
- import path28 from "path";
11045
+ import path29 from "path";
10812
11046
  import chalk14 from "chalk";
10813
11047
 
10814
11048
  // src/tui/undo-navigator.ts
@@ -10967,7 +11201,7 @@ function findMatchingCwd(startDir, history) {
10967
11201
  let dir = startDir;
10968
11202
  while (true) {
10969
11203
  if (cwds.has(dir)) return dir;
10970
- const parent = path28.dirname(dir);
11204
+ const parent = path29.dirname(dir);
10971
11205
  if (parent === dir) return null;
10972
11206
  dir = parent;
10973
11207
  }
@@ -11163,12 +11397,12 @@ import { execa as execa2 } from "execa";
11163
11397
  init_provenance();
11164
11398
 
11165
11399
  // src/mcp-pin.ts
11166
- import fs26 from "fs";
11167
- import path29 from "path";
11168
- import os22 from "os";
11400
+ import fs27 from "fs";
11401
+ import path30 from "path";
11402
+ import os23 from "os";
11169
11403
  import crypto4 from "crypto";
11170
11404
  function getPinsFilePath() {
11171
- return path29.join(os22.homedir(), ".node9", "mcp-pins.json");
11405
+ return path30.join(os23.homedir(), ".node9", "mcp-pins.json");
11172
11406
  }
11173
11407
  function hashToolDefinitions(tools) {
11174
11408
  const sorted = [...tools].sort((a, b) => {
@@ -11185,7 +11419,7 @@ function getServerKey(upstreamCommand) {
11185
11419
  function readMcpPinsSafe() {
11186
11420
  const filePath = getPinsFilePath();
11187
11421
  try {
11188
- const raw = fs26.readFileSync(filePath, "utf-8");
11422
+ const raw = fs27.readFileSync(filePath, "utf-8");
11189
11423
  if (!raw.trim()) {
11190
11424
  return { ok: false, reason: "corrupt", detail: "empty file" };
11191
11425
  }
@@ -11209,10 +11443,10 @@ function readMcpPins() {
11209
11443
  }
11210
11444
  function writeMcpPins(data) {
11211
11445
  const filePath = getPinsFilePath();
11212
- fs26.mkdirSync(path29.dirname(filePath), { recursive: true });
11446
+ fs27.mkdirSync(path30.dirname(filePath), { recursive: true });
11213
11447
  const tmp = `${filePath}.${crypto4.randomBytes(6).toString("hex")}.tmp`;
11214
- fs26.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
11215
- fs26.renameSync(tmp, filePath);
11448
+ fs27.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
11449
+ fs27.renameSync(tmp, filePath);
11216
11450
  }
11217
11451
  function checkPin(serverKey, currentHash) {
11218
11452
  const result = readMcpPinsSafe();
@@ -11584,9 +11818,9 @@ function registerMcpGatewayCommand(program2) {
11584
11818
 
11585
11819
  // src/mcp-server/index.ts
11586
11820
  import readline4 from "readline";
11587
- import fs27 from "fs";
11588
- import os23 from "os";
11589
- import path30 from "path";
11821
+ import fs28 from "fs";
11822
+ import os24 from "os";
11823
+ import path31 from "path";
11590
11824
  init_core();
11591
11825
  init_daemon();
11592
11826
  init_shields();
@@ -11761,13 +11995,13 @@ function handleStatus() {
11761
11995
  lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
11762
11996
  lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
11763
11997
  lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
11764
- const projectConfig = path30.join(process.cwd(), "node9.config.json");
11765
- const globalConfig = path30.join(os23.homedir(), ".node9", "config.json");
11998
+ const projectConfig = path31.join(process.cwd(), "node9.config.json");
11999
+ const globalConfig = path31.join(os24.homedir(), ".node9", "config.json");
11766
12000
  lines.push(
11767
- `Project config (node9.config.json): ${fs27.existsSync(projectConfig) ? "present" : "not found"}`
12001
+ `Project config (node9.config.json): ${fs28.existsSync(projectConfig) ? "present" : "not found"}`
11768
12002
  );
11769
12003
  lines.push(
11770
- `Global config (~/.node9/config.json): ${fs27.existsSync(globalConfig) ? "present" : "not found"}`
12004
+ `Global config (~/.node9/config.json): ${fs28.existsSync(globalConfig) ? "present" : "not found"}`
11771
12005
  );
11772
12006
  return lines.join("\n");
11773
12007
  }
@@ -11841,21 +12075,21 @@ function handleShieldDisable(args) {
11841
12075
  writeActiveShields(active.filter((s) => s !== name));
11842
12076
  return `Shield "${name}" disabled.`;
11843
12077
  }
11844
- var GLOBAL_CONFIG_PATH2 = path30.join(os23.homedir(), ".node9", "config.json");
12078
+ var GLOBAL_CONFIG_PATH2 = path31.join(os24.homedir(), ".node9", "config.json");
11845
12079
  var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
11846
12080
  function readGlobalConfigRaw() {
11847
12081
  try {
11848
- if (fs27.existsSync(GLOBAL_CONFIG_PATH2)) {
11849
- return JSON.parse(fs27.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
12082
+ if (fs28.existsSync(GLOBAL_CONFIG_PATH2)) {
12083
+ return JSON.parse(fs28.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
11850
12084
  }
11851
12085
  } catch {
11852
12086
  }
11853
12087
  return {};
11854
12088
  }
11855
12089
  function writeGlobalConfigRaw(data) {
11856
- const dir = path30.dirname(GLOBAL_CONFIG_PATH2);
11857
- if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
11858
- fs27.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
12090
+ const dir = path31.dirname(GLOBAL_CONFIG_PATH2);
12091
+ if (!fs28.existsSync(dir)) fs28.mkdirSync(dir, { recursive: true });
12092
+ fs28.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
11859
12093
  }
11860
12094
  function handleApproverList() {
11861
12095
  const config = getConfig();
@@ -11898,9 +12132,9 @@ function handleApproverSet(args) {
11898
12132
  }
11899
12133
  function handleAuditGet(args) {
11900
12134
  const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
11901
- const auditPath = path30.join(os23.homedir(), ".node9", "audit.log");
11902
- if (!fs27.existsSync(auditPath)) return "No audit log found.";
11903
- const lines = fs27.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
12135
+ const auditPath = path31.join(os24.homedir(), ".node9", "audit.log");
12136
+ if (!fs28.existsSync(auditPath)) return "No audit log found.";
12137
+ const lines = fs28.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
11904
12138
  const recent = lines.slice(-limit);
11905
12139
  const entries = recent.map((line) => {
11906
12140
  try {
@@ -12220,20 +12454,20 @@ function registerMcpPinCommand(program2) {
12220
12454
 
12221
12455
  // src/cli.ts
12222
12456
  var { version } = JSON.parse(
12223
- fs30.readFileSync(path33.join(__dirname, "../package.json"), "utf-8")
12457
+ fs31.readFileSync(path34.join(__dirname, "../package.json"), "utf-8")
12224
12458
  );
12225
12459
  var program = new Command();
12226
12460
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
12227
12461
  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) => {
12228
12462
  const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
12229
- const credPath = path33.join(os26.homedir(), ".node9", "credentials.json");
12230
- if (!fs30.existsSync(path33.dirname(credPath)))
12231
- fs30.mkdirSync(path33.dirname(credPath), { recursive: true });
12463
+ const credPath = path34.join(os27.homedir(), ".node9", "credentials.json");
12464
+ if (!fs31.existsSync(path34.dirname(credPath)))
12465
+ fs31.mkdirSync(path34.dirname(credPath), { recursive: true });
12232
12466
  const profileName = options.profile || "default";
12233
12467
  let existingCreds = {};
12234
12468
  try {
12235
- if (fs30.existsSync(credPath)) {
12236
- const raw = JSON.parse(fs30.readFileSync(credPath, "utf-8"));
12469
+ if (fs31.existsSync(credPath)) {
12470
+ const raw = JSON.parse(fs31.readFileSync(credPath, "utf-8"));
12237
12471
  if (raw.apiKey) {
12238
12472
  existingCreds = {
12239
12473
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
@@ -12245,13 +12479,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
12245
12479
  } catch {
12246
12480
  }
12247
12481
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
12248
- fs30.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
12482
+ fs31.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
12249
12483
  if (profileName === "default") {
12250
- const configPath = path33.join(os26.homedir(), ".node9", "config.json");
12484
+ const configPath = path34.join(os27.homedir(), ".node9", "config.json");
12251
12485
  let config = {};
12252
12486
  try {
12253
- if (fs30.existsSync(configPath))
12254
- config = JSON.parse(fs30.readFileSync(configPath, "utf-8"));
12487
+ if (fs31.existsSync(configPath))
12488
+ config = JSON.parse(fs31.readFileSync(configPath, "utf-8"));
12255
12489
  } catch {
12256
12490
  }
12257
12491
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -12266,9 +12500,9 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
12266
12500
  approvers.cloud = false;
12267
12501
  }
12268
12502
  s.approvers = approvers;
12269
- if (!fs30.existsSync(path33.dirname(configPath)))
12270
- fs30.mkdirSync(path33.dirname(configPath), { recursive: true });
12271
- fs30.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
12503
+ if (!fs31.existsSync(path34.dirname(configPath)))
12504
+ fs31.mkdirSync(path34.dirname(configPath), { recursive: true });
12505
+ fs31.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
12272
12506
  }
12273
12507
  if (options.profile && profileName !== "default") {
12274
12508
  console.log(chalk20.green(`\u2705 Profile "${profileName}" saved`));
@@ -12362,15 +12596,15 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
12362
12596
  }
12363
12597
  }
12364
12598
  if (options.purge) {
12365
- const node9Dir = path33.join(os26.homedir(), ".node9");
12366
- if (fs30.existsSync(node9Dir)) {
12599
+ const node9Dir = path34.join(os27.homedir(), ".node9");
12600
+ if (fs31.existsSync(node9Dir)) {
12367
12601
  const confirmed = await confirm2({
12368
12602
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
12369
12603
  default: false
12370
12604
  });
12371
12605
  if (confirmed) {
12372
- fs30.rmSync(node9Dir, { recursive: true });
12373
- if (fs30.existsSync(node9Dir)) {
12606
+ fs31.rmSync(node9Dir, { recursive: true });
12607
+ if (fs31.existsSync(node9Dir)) {
12374
12608
  console.error(
12375
12609
  chalk20.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
12376
12610
  );
@@ -12511,14 +12745,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
12511
12745
  Run "node9 addto claude" to register it as the statusLine.`
12512
12746
  ).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
12513
12747
  if (subcommand === "debug") {
12514
- const flagFile = path33.join(os26.homedir(), ".node9", "hud-debug");
12748
+ const flagFile = path34.join(os27.homedir(), ".node9", "hud-debug");
12515
12749
  if (state === "on") {
12516
- fs30.mkdirSync(path33.dirname(flagFile), { recursive: true });
12517
- fs30.writeFileSync(flagFile, "");
12750
+ fs31.mkdirSync(path34.dirname(flagFile), { recursive: true });
12751
+ fs31.writeFileSync(flagFile, "");
12518
12752
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
12519
12753
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
12520
12754
  } else if (state === "off") {
12521
- if (fs30.existsSync(flagFile)) fs30.unlinkSync(flagFile);
12755
+ if (fs31.existsSync(flagFile)) fs31.unlinkSync(flagFile);
12522
12756
  console.log("HUD debug logging disabled.");
12523
12757
  } else {
12524
12758
  console.error("Usage: node9 hud debug on|off");
@@ -12625,9 +12859,9 @@ if (process.argv[2] !== "daemon") {
12625
12859
  const isCheckHook = process.argv[2] === "check";
12626
12860
  if (isCheckHook) {
12627
12861
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
12628
- const logPath = path33.join(os26.homedir(), ".node9", "hook-debug.log");
12862
+ const logPath = path34.join(os27.homedir(), ".node9", "hook-debug.log");
12629
12863
  const msg = reason instanceof Error ? reason.message : String(reason);
12630
- fs30.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
12864
+ fs31.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
12631
12865
  `);
12632
12866
  }
12633
12867
  process.exit(0);