@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.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;
@@ -2439,19 +2440,44 @@ function getInternalToken() {
2439
2440
  function isDaemonRunning() {
2440
2441
  const pidFile = path10.join(os8.homedir(), ".node9", "daemon.pid");
2441
2442
  if (fs9.existsSync(pidFile)) {
2443
+ let pid;
2444
+ let port;
2442
2445
  try {
2443
- const { pid, port } = JSON.parse(fs9.readFileSync(pidFile, "utf-8"));
2444
- if (port !== DAEMON_PORT) return false;
2445
- process.kill(pid, 0);
2446
- return true;
2446
+ const data = JSON.parse(fs9.readFileSync(pidFile, "utf-8"));
2447
+ pid = data.pid;
2448
+ port = data.port;
2447
2449
  } catch {
2448
2450
  return false;
2449
2451
  }
2452
+ if (port !== DAEMON_PORT) {
2453
+ return false;
2454
+ }
2455
+ const MAX_PID = 4194304;
2456
+ if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
2457
+ return false;
2458
+ }
2459
+ try {
2460
+ process.kill(pid, 0);
2461
+ } catch (err2) {
2462
+ if (err2 instanceof Error && "code" in err2 && err2.code === "ESRCH") {
2463
+ try {
2464
+ fs9.unlinkSync(pidFile);
2465
+ } catch {
2466
+ }
2467
+ }
2468
+ return false;
2469
+ }
2470
+ const r = spawnSync("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
2471
+ encoding: "utf8",
2472
+ timeout: 300
2473
+ });
2474
+ if (r.status === 0 && (r.stdout ?? "").includes(`:${DAEMON_PORT}`)) return true;
2475
+ return false;
2450
2476
  }
2451
2477
  try {
2452
2478
  const r = spawnSync("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
2453
2479
  encoding: "utf8",
2454
- timeout: 500
2480
+ timeout: 300
2455
2481
  });
2456
2482
  return r.status === 0 && (r.stdout ?? "").includes(`:${DAEMON_PORT}`);
2457
2483
  } catch {
@@ -2791,11 +2817,12 @@ ${smartTruncate(str, 500)}`
2791
2817
  function escapePango(text) {
2792
2818
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2793
2819
  }
2794
- function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
2820
+ function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
2795
2821
  const lines = [];
2796
2822
  if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
2797
2823
  lines.push(`\u{1F916} ${agent || "AI Agent"} | \u{1F527} ${toolName}`);
2798
2824
  lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
2825
+ if (ruleDescription) lines.push(`\u2139 ${ruleDescription}`);
2799
2826
  lines.push("");
2800
2827
  lines.push(formattedArgs);
2801
2828
  if (allowCount >= 3) {
@@ -2808,7 +2835,7 @@ function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, loc
2808
2835
  }
2809
2836
  return lines.join("\n");
2810
2837
  }
2811
- function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
2838
+ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
2812
2839
  const lines = [];
2813
2840
  if (locked) {
2814
2841
  lines.push('<span foreground="red" weight="bold">\u26A0\uFE0F LOCKED BY ADMIN POLICY</span>');
@@ -2818,6 +2845,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
2818
2845
  `<b>\u{1F916} ${escapePango(agent || "AI Agent")}</b> | <b>\u{1F527} <tt>${escapePango(toolName)}</tt></b>`
2819
2846
  );
2820
2847
  lines.push(`<i>\u{1F6E1}\uFE0F ${escapePango(explainableLabel || "Security Policy")}</i>`);
2848
+ if (ruleDescription) lines.push(`<i>\u2139 ${escapePango(ruleDescription)}</i>`);
2821
2849
  lines.push("");
2822
2850
  lines.push(`<tt>${escapePango(formattedArgs)}</tt>`);
2823
2851
  if (allowCount >= 3) {
@@ -2834,7 +2862,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
2834
2862
  }
2835
2863
  return lines.join("\n");
2836
2864
  }
2837
- async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1) {
2865
+ async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1, ruleDescription) {
2838
2866
  if (isTestEnv()) return "deny";
2839
2867
  const { message: formattedArgs, intent } = formatArgs(args, matchedField, matchedWord);
2840
2868
  const intentLabel = intent === "EDIT" ? "Code Edit" : "Action Approval";
@@ -2845,7 +2873,8 @@ async function askNativePopup(toolName, args, agent, explainableLabel, locked =
2845
2873
  agent,
2846
2874
  explainableLabel,
2847
2875
  locked,
2848
- allowCount
2876
+ allowCount,
2877
+ ruleDescription
2849
2878
  );
2850
2879
  return new Promise((resolve) => {
2851
2880
  let childProcess = null;
@@ -2879,7 +2908,8 @@ end run`;
2879
2908
  agent,
2880
2909
  explainableLabel,
2881
2910
  locked,
2882
- allowCount
2911
+ allowCount,
2912
+ ruleDescription
2883
2913
  );
2884
2914
  const argsList = [
2885
2915
  locked ? "--info" : "--question",
@@ -2953,7 +2983,7 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2953
2983
  }).catch(() => {
2954
2984
  });
2955
2985
  }
2956
- async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
2986
+ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPolicy, forceReview) {
2957
2987
  const controller = new AbortController();
2958
2988
  const timeout = setTimeout(() => controller.abort(), 1e4);
2959
2989
  if (!creds.apiKey) throw new Error("Node9 API Key is missing");
@@ -2998,7 +3028,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
2998
3028
  platform: os9.platform()
2999
3029
  },
3000
3030
  ...riskMetadata && { riskMetadata },
3001
- ...ciContext && { ciContext }
3031
+ ...ciContext && { ciContext },
3032
+ ...agentPolicy && { policy: agentPolicy },
3033
+ ...forceReview && { forceReview: true }
3002
3034
  }),
3003
3035
  signal: controller.signal
3004
3036
  });
@@ -3392,6 +3424,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3392
3424
  policyMatchedWord,
3393
3425
  policyResult.ruleName
3394
3426
  );
3427
+ if (policyRuleDescription) riskMetadata.ruleDescription = policyRuleDescription.slice(0, 200);
3395
3428
  const persistent = policyResult.ruleName ? null : getPersistentDecision(toolName);
3396
3429
  if (persistent === "allow") {
3397
3430
  if (approvers.cloud && creds?.apiKey)
@@ -3426,9 +3459,18 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3426
3459
  }
3427
3460
  let cloudRequestId = null;
3428
3461
  const cloudEnforced = approvers.cloud && !!creds?.apiKey;
3429
- if (cloudEnforced && !localSmartRuleMatched && !options?.localSmartRuleMatched) {
3462
+ const forceReview = localSmartRuleMatched === true || options?.localSmartRuleMatched === true || void 0;
3463
+ if (cloudEnforced) {
3430
3464
  try {
3431
- const initResult = await initNode9SaaS(toolName, args, creds, meta, riskMetadata);
3465
+ const initResult = await initNode9SaaS(
3466
+ toolName,
3467
+ args,
3468
+ creds,
3469
+ meta,
3470
+ riskMetadata,
3471
+ config.settings.agentPolicy,
3472
+ forceReview
3473
+ );
3432
3474
  if (!initResult.pending) {
3433
3475
  if (initResult.shadowMode) {
3434
3476
  return { approved: true, checkedBy: "cloud" };
@@ -3443,9 +3485,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3443
3485
  };
3444
3486
  }
3445
3487
  }
3446
- if (!localSmartRuleMatched && !options?.localSmartRuleMatched) {
3447
- cloudRequestId = initResult.requestId || null;
3448
- }
3488
+ if (initResult.pending) cloudRequestId = initResult.requestId || null;
3449
3489
  if (!taintWarning) explainableLabel = "Organization Policy (SaaS)";
3450
3490
  } catch {
3451
3491
  }
@@ -3502,7 +3542,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3502
3542
  }
3503
3543
  }
3504
3544
  }
3505
- if (cloudEnforced && cloudRequestId && !localSmartRuleMatched && !options?.localSmartRuleMatched) {
3545
+ if (cloudEnforced && cloudRequestId) {
3506
3546
  racePromises.push(
3507
3547
  (async () => {
3508
3548
  try {
@@ -3534,7 +3574,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3534
3574
  signal,
3535
3575
  policyMatchedField,
3536
3576
  policyMatchedWord,
3537
- daemonAllowCount
3577
+ daemonAllowCount,
3578
+ riskMetadata?.ruleDescription
3538
3579
  );
3539
3580
  if (decision === "always_allow") {
3540
3581
  writeTrustSession(toolName, 36e5);
@@ -6036,14 +6077,181 @@ var init_patch = __esm({
6036
6077
  }
6037
6078
  });
6038
6079
 
6039
- // src/daemon/server.ts
6040
- import http from "http";
6080
+ // src/costSync.ts
6041
6081
  import fs16 from "fs";
6042
6082
  import path19 from "path";
6083
+ import os14 from "os";
6084
+ function normalizeModel(raw) {
6085
+ return raw.replace(/-\d{8}$/, "");
6086
+ }
6087
+ function pricingFor(model) {
6088
+ const norm = normalizeModel(model);
6089
+ if (PRICING[norm]) return PRICING[norm];
6090
+ let best = null;
6091
+ for (const key of Object.keys(PRICING)) {
6092
+ if (norm.startsWith(key) && (best === null || key.length > best.length)) best = key;
6093
+ }
6094
+ return best ? PRICING[best] : null;
6095
+ }
6096
+ function parseJSONLFile(filePath) {
6097
+ let content;
6098
+ try {
6099
+ content = fs16.readFileSync(filePath, "utf8");
6100
+ } catch {
6101
+ return /* @__PURE__ */ new Map();
6102
+ }
6103
+ const daily = /* @__PURE__ */ new Map();
6104
+ for (const line of content.split("\n")) {
6105
+ if (!line.trim()) continue;
6106
+ let row;
6107
+ try {
6108
+ row = JSON.parse(line);
6109
+ } catch {
6110
+ continue;
6111
+ }
6112
+ if (row["type"] !== "assistant") continue;
6113
+ const msg = row["message"];
6114
+ if (!msg?.["usage"] || typeof msg["model"] !== "string") continue;
6115
+ const usage = msg["usage"];
6116
+ const model = msg["model"];
6117
+ const timestamp = row["timestamp"];
6118
+ if (typeof timestamp !== "string" || timestamp.length < 10) continue;
6119
+ const date = timestamp.slice(0, 10);
6120
+ const p = pricingFor(model);
6121
+ if (!p) continue;
6122
+ const inp = Number(usage["input_tokens"] ?? 0);
6123
+ const out = Number(usage["output_tokens"] ?? 0);
6124
+ const cw = Number(usage["cache_creation_input_tokens"] ?? 0);
6125
+ const cr = Number(usage["cache_read_input_tokens"] ?? 0);
6126
+ const cost = inp * p[0] + out * p[1] + cw * p[2] + cr * p[3];
6127
+ const norm = normalizeModel(model);
6128
+ const key = `${date}::${norm}`;
6129
+ const prev = daily.get(key);
6130
+ if (prev) {
6131
+ prev.costUSD += cost;
6132
+ prev.inputTokens += inp;
6133
+ prev.outputTokens += out;
6134
+ prev.cacheWriteTokens += cw;
6135
+ prev.cacheReadTokens += cr;
6136
+ } else {
6137
+ daily.set(key, {
6138
+ date,
6139
+ model: norm,
6140
+ costUSD: cost,
6141
+ inputTokens: inp,
6142
+ outputTokens: out,
6143
+ cacheWriteTokens: cw,
6144
+ cacheReadTokens: cr
6145
+ });
6146
+ }
6147
+ }
6148
+ return daily;
6149
+ }
6150
+ function collectEntries() {
6151
+ const projectsDir = path19.join(os14.homedir(), ".claude", "projects");
6152
+ if (!fs16.existsSync(projectsDir)) return [];
6153
+ const combined = /* @__PURE__ */ new Map();
6154
+ let dirs;
6155
+ try {
6156
+ dirs = fs16.readdirSync(projectsDir);
6157
+ } catch {
6158
+ return [];
6159
+ }
6160
+ for (const dir of dirs) {
6161
+ const dirPath = path19.join(projectsDir, dir);
6162
+ try {
6163
+ if (!fs16.statSync(dirPath).isDirectory()) continue;
6164
+ } catch {
6165
+ continue;
6166
+ }
6167
+ let files;
6168
+ try {
6169
+ files = fs16.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
6170
+ } catch {
6171
+ continue;
6172
+ }
6173
+ for (const file of files) {
6174
+ const entries = parseJSONLFile(path19.join(dirPath, file));
6175
+ for (const [key, e] of entries) {
6176
+ const prev = combined.get(key);
6177
+ if (prev) {
6178
+ prev.costUSD += e.costUSD;
6179
+ prev.inputTokens += e.inputTokens;
6180
+ prev.outputTokens += e.outputTokens;
6181
+ prev.cacheWriteTokens += e.cacheWriteTokens;
6182
+ prev.cacheReadTokens += e.cacheReadTokens;
6183
+ } else {
6184
+ combined.set(key, { ...e });
6185
+ }
6186
+ }
6187
+ }
6188
+ }
6189
+ return [...combined.values()];
6190
+ }
6191
+ async function syncCost() {
6192
+ const creds = getCredentials();
6193
+ if (!creds?.apiKey || !creds?.apiUrl) return;
6194
+ const entries = collectEntries();
6195
+ if (entries.length === 0) return;
6196
+ let username = "unknown";
6197
+ try {
6198
+ username = os14.userInfo().username;
6199
+ } catch {
6200
+ }
6201
+ const machineId = `${os14.hostname()}:${username}`;
6202
+ try {
6203
+ const res = await fetch(`${creds.apiUrl}/cost-sync`, {
6204
+ method: "POST",
6205
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${creds.apiKey}` },
6206
+ body: JSON.stringify({ machineId, entries }),
6207
+ signal: AbortSignal.timeout(15e3)
6208
+ });
6209
+ if (!res.ok) {
6210
+ fs16.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] HTTP ${res.status}
6211
+ `);
6212
+ }
6213
+ } catch (err2) {
6214
+ fs16.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] ${err2.message}
6215
+ `);
6216
+ }
6217
+ }
6218
+ function startCostSync() {
6219
+ syncCost().catch(() => {
6220
+ });
6221
+ const timer = setInterval(() => {
6222
+ syncCost().catch(() => {
6223
+ });
6224
+ }, SYNC_INTERVAL_MS);
6225
+ timer.unref();
6226
+ }
6227
+ var SYNC_INTERVAL_MS, PRICING;
6228
+ var init_costSync = __esm({
6229
+ "src/costSync.ts"() {
6230
+ "use strict";
6231
+ init_config();
6232
+ init_audit();
6233
+ SYNC_INTERVAL_MS = 10 * 60 * 1e3;
6234
+ PRICING = {
6235
+ "claude-opus-4": [5e-6, 25e-6, 625e-8, 5e-7],
6236
+ "claude-sonnet-4": [3e-6, 15e-6, 375e-8, 3e-7],
6237
+ "claude-haiku-4": [8e-7, 4e-6, 1e-6, 8e-8],
6238
+ "claude-3-7-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
6239
+ "claude-3-5-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
6240
+ "claude-3-5-haiku": [8e-7, 4e-6, 1e-6, 8e-8],
6241
+ "claude-3-haiku": [25e-8, 125e-8, 3e-7, 3e-8]
6242
+ };
6243
+ }
6244
+ });
6245
+
6246
+ // src/daemon/server.ts
6247
+ import http from "http";
6248
+ import fs17 from "fs";
6249
+ import path20 from "path";
6043
6250
  import { randomUUID as randomUUID4 } from "crypto";
6044
6251
  import { spawnSync as spawnSync2 } from "child_process";
6045
6252
  import chalk2 from "chalk";
6046
6253
  function startDaemon() {
6254
+ startCostSync();
6047
6255
  loadInsightCounts();
6048
6256
  const csrfToken = randomUUID4();
6049
6257
  const internalToken = randomUUID4();
@@ -6059,7 +6267,7 @@ function startDaemon() {
6059
6267
  idleTimer = setTimeout(() => {
6060
6268
  if (autoStarted) {
6061
6269
  try {
6062
- fs16.unlinkSync(DAEMON_PID_FILE);
6270
+ fs17.unlinkSync(DAEMON_PID_FILE);
6063
6271
  } catch {
6064
6272
  }
6065
6273
  }
@@ -6222,7 +6430,7 @@ data: ${JSON.stringify(item.data)}
6222
6430
  status: "pending"
6223
6431
  });
6224
6432
  }
6225
- const projectCwd = typeof cwd === "string" && path19.isAbsolute(cwd) ? cwd : void 0;
6433
+ const projectCwd = typeof cwd === "string" && path20.isAbsolute(cwd) ? cwd : void 0;
6226
6434
  const projectConfig = getConfig(projectCwd);
6227
6435
  const browserEnabled = projectConfig.settings.approvers?.browser !== false;
6228
6436
  const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
@@ -6612,8 +6820,8 @@ data: ${JSON.stringify(item.data)}
6612
6820
  const body = await readBody(req);
6613
6821
  const data = body ? JSON.parse(body) : {};
6614
6822
  const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
6615
- const node9Dir = path19.dirname(GLOBAL_CONFIG_PATH);
6616
- if (!path19.resolve(configPath).startsWith(node9Dir + path19.sep)) {
6823
+ const node9Dir = path20.dirname(GLOBAL_CONFIG_PATH);
6824
+ if (!path20.resolve(configPath).startsWith(node9Dir + path20.sep)) {
6617
6825
  res.writeHead(400, { "Content-Type": "application/json" });
6618
6826
  return res.end(
6619
6827
  JSON.stringify({ error: "configPath must be within the node9 config directory" })
@@ -6724,14 +6932,14 @@ data: ${JSON.stringify(item.data)}
6724
6932
  server.on("error", (e) => {
6725
6933
  if (e.code === "EADDRINUSE") {
6726
6934
  try {
6727
- if (fs16.existsSync(DAEMON_PID_FILE)) {
6728
- const { pid } = JSON.parse(fs16.readFileSync(DAEMON_PID_FILE, "utf-8"));
6935
+ if (fs17.existsSync(DAEMON_PID_FILE)) {
6936
+ const { pid } = JSON.parse(fs17.readFileSync(DAEMON_PID_FILE, "utf-8"));
6729
6937
  process.kill(pid, 0);
6730
6938
  return process.exit(0);
6731
6939
  }
6732
6940
  } catch {
6733
6941
  try {
6734
- fs16.unlinkSync(DAEMON_PID_FILE);
6942
+ fs17.unlinkSync(DAEMON_PID_FILE);
6735
6943
  } catch {
6736
6944
  }
6737
6945
  server.listen(DAEMON_PORT, DAEMON_HOST);
@@ -6799,32 +7007,33 @@ var init_server = __esm({
6799
7007
  init_state2();
6800
7008
  init_patch();
6801
7009
  init_config_schema();
7010
+ init_costSync();
6802
7011
  }
6803
7012
  });
6804
7013
 
6805
7014
  // src/daemon/index.ts
6806
- import fs17 from "fs";
7015
+ import fs18 from "fs";
6807
7016
  import chalk3 from "chalk";
6808
7017
  import { spawnSync as spawnSync3 } from "child_process";
6809
7018
  function stopDaemon() {
6810
- if (!fs17.existsSync(DAEMON_PID_FILE)) return console.log(chalk3.yellow("Not running."));
7019
+ if (!fs18.existsSync(DAEMON_PID_FILE)) return console.log(chalk3.yellow("Not running."));
6811
7020
  try {
6812
- const { pid } = JSON.parse(fs17.readFileSync(DAEMON_PID_FILE, "utf-8"));
7021
+ const { pid } = JSON.parse(fs18.readFileSync(DAEMON_PID_FILE, "utf-8"));
6813
7022
  process.kill(pid, "SIGTERM");
6814
7023
  console.log(chalk3.green("\u2705 Stopped."));
6815
7024
  } catch {
6816
7025
  console.log(chalk3.gray("Cleaned up stale PID file."));
6817
7026
  } finally {
6818
7027
  try {
6819
- fs17.unlinkSync(DAEMON_PID_FILE);
7028
+ fs18.unlinkSync(DAEMON_PID_FILE);
6820
7029
  } catch {
6821
7030
  }
6822
7031
  }
6823
7032
  }
6824
7033
  function daemonStatus() {
6825
- if (fs17.existsSync(DAEMON_PID_FILE)) {
7034
+ if (fs18.existsSync(DAEMON_PID_FILE)) {
6826
7035
  try {
6827
- const { pid } = JSON.parse(fs17.readFileSync(DAEMON_PID_FILE, "utf-8"));
7036
+ const { pid } = JSON.parse(fs18.readFileSync(DAEMON_PID_FILE, "utf-8"));
6828
7037
  process.kill(pid, 0);
6829
7038
  console.log(chalk3.green("Node9 daemon: running"));
6830
7039
  return;
@@ -6859,9 +7068,9 @@ __export(tail_exports, {
6859
7068
  });
6860
7069
  import http2 from "http";
6861
7070
  import chalk19 from "chalk";
6862
- import fs28 from "fs";
6863
- import os24 from "os";
6864
- import path31 from "path";
7071
+ import fs29 from "fs";
7072
+ import os25 from "os";
7073
+ import path32 from "path";
6865
7074
  import readline5 from "readline";
6866
7075
  import { spawn as spawn10, execSync as execSync3 } from "child_process";
6867
7076
  function getIcon(tool) {
@@ -6884,7 +7093,7 @@ function formatBase(activity) {
6884
7093
  const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
6885
7094
  const icon = getIcon(activity.tool);
6886
7095
  const toolName = activity.tool.slice(0, 16).padEnd(16);
6887
- const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os24.homedir(), "~");
7096
+ const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os25.homedir(), "~");
6888
7097
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
6889
7098
  return `${chalk19.gray(time)} ${icon} ${chalk19.white.bold(toolName)} ${chalk19.dim(argsPreview)}`;
6890
7099
  }
@@ -6923,9 +7132,9 @@ function renderPending(activity) {
6923
7132
  }
6924
7133
  async function ensureDaemon() {
6925
7134
  let pidPort = null;
6926
- if (fs28.existsSync(PID_FILE)) {
7135
+ if (fs29.existsSync(PID_FILE)) {
6927
7136
  try {
6928
- const { port } = JSON.parse(fs28.readFileSync(PID_FILE, "utf-8"));
7137
+ const { port } = JSON.parse(fs29.readFileSync(PID_FILE, "utf-8"));
6929
7138
  pidPort = port;
6930
7139
  } catch {
6931
7140
  console.error(chalk19.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
@@ -7002,6 +7211,9 @@ function buildCardLines(req, localCount = 0) {
7002
7211
  `${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}`,
7003
7212
  `${CYAN}\u2551${RESET2} Reason: ${tierLabel} \u2014 ${blockedBy}${RESET2}`
7004
7213
  ];
7214
+ if (req.riskMetadata?.ruleDescription) {
7215
+ lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u2139 ${req.riskMetadata.ruleDescription}${RESET2}`);
7216
+ }
7005
7217
  if (req.riskMetadata?.ruleName && blockedBy.includes("Taint")) {
7006
7218
  lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u26A0 ${req.riskMetadata.ruleName}${RESET2}`);
7007
7219
  }
@@ -7045,9 +7257,9 @@ function buildRecoveryCardLines(req) {
7045
7257
  ];
7046
7258
  }
7047
7259
  function readApproversFromDisk() {
7048
- const configPath = path31.join(os24.homedir(), ".node9", "config.json");
7260
+ const configPath = path32.join(os25.homedir(), ".node9", "config.json");
7049
7261
  try {
7050
- const raw = JSON.parse(fs28.readFileSync(configPath, "utf-8"));
7262
+ const raw = JSON.parse(fs29.readFileSync(configPath, "utf-8"));
7051
7263
  const settings = raw.settings ?? {};
7052
7264
  return settings.approvers ?? {};
7053
7265
  } catch {
@@ -7063,15 +7275,15 @@ function approverStatusLine() {
7063
7275
  return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
7064
7276
  }
7065
7277
  function toggleApprover(channel) {
7066
- const configPath = path31.join(os24.homedir(), ".node9", "config.json");
7278
+ const configPath = path32.join(os25.homedir(), ".node9", "config.json");
7067
7279
  try {
7068
- const raw = JSON.parse(fs28.readFileSync(configPath, "utf-8"));
7280
+ const raw = JSON.parse(fs29.readFileSync(configPath, "utf-8"));
7069
7281
  const settings = raw.settings ?? {};
7070
7282
  const approvers = settings.approvers ?? {};
7071
7283
  approvers[channel] = approvers[channel] === false;
7072
7284
  settings.approvers = approvers;
7073
7285
  raw.settings = settings;
7074
- fs28.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
7286
+ fs29.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
7075
7287
  } catch (err2) {
7076
7288
  process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
7077
7289
  `);
@@ -7241,8 +7453,8 @@ async function startTail(options = {}) {
7241
7453
  }
7242
7454
  postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
7243
7455
  try {
7244
- fs28.appendFileSync(
7245
- path31.join(os24.homedir(), ".node9", "hook-debug.log"),
7456
+ fs29.appendFileSync(
7457
+ path32.join(os25.homedir(), ".node9", "hook-debug.log"),
7246
7458
  `[tail] POST /decision failed: ${String(err2)}
7247
7459
  `
7248
7460
  );
@@ -7502,7 +7714,7 @@ var init_tail = __esm({
7502
7714
  init_daemon2();
7503
7715
  init_daemon();
7504
7716
  init_core();
7505
- PID_FILE = path31.join(os24.homedir(), ".node9", "daemon.pid");
7717
+ PID_FILE = path32.join(os25.homedir(), ".node9", "daemon.pid");
7506
7718
  ICONS = {
7507
7719
  bash: "\u{1F4BB}",
7508
7720
  shell: "\u{1F4BB}",
@@ -7543,9 +7755,9 @@ __export(hud_exports, {
7543
7755
  main: () => main,
7544
7756
  renderEnvironmentLine: () => renderEnvironmentLine
7545
7757
  });
7546
- import fs29 from "fs";
7547
- import path32 from "path";
7548
- import os25 from "os";
7758
+ import fs30 from "fs";
7759
+ import path33 from "path";
7760
+ import os26 from "os";
7549
7761
  import http3 from "http";
7550
7762
  async function readStdin() {
7551
7763
  const chunks = [];
@@ -7621,9 +7833,9 @@ function formatTimeLeft(resetsAt) {
7621
7833
  return ` (${m}m left)`;
7622
7834
  }
7623
7835
  function safeReadJson(filePath) {
7624
- if (!fs29.existsSync(filePath)) return null;
7836
+ if (!fs30.existsSync(filePath)) return null;
7625
7837
  try {
7626
- return JSON.parse(fs29.readFileSync(filePath, "utf-8"));
7838
+ return JSON.parse(fs30.readFileSync(filePath, "utf-8"));
7627
7839
  } catch {
7628
7840
  return null;
7629
7841
  }
@@ -7644,12 +7856,12 @@ function countHooksInFile(filePath) {
7644
7856
  return Object.keys(cfg.hooks).length;
7645
7857
  }
7646
7858
  function countRulesInDir(rulesDir) {
7647
- if (!fs29.existsSync(rulesDir)) return 0;
7859
+ if (!fs30.existsSync(rulesDir)) return 0;
7648
7860
  let count = 0;
7649
7861
  try {
7650
- for (const entry of fs29.readdirSync(rulesDir, { withFileTypes: true })) {
7862
+ for (const entry of fs30.readdirSync(rulesDir, { withFileTypes: true })) {
7651
7863
  if (entry.isDirectory()) {
7652
- count += countRulesInDir(path32.join(rulesDir, entry.name));
7864
+ count += countRulesInDir(path33.join(rulesDir, entry.name));
7653
7865
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
7654
7866
  count++;
7655
7867
  }
@@ -7660,46 +7872,46 @@ function countRulesInDir(rulesDir) {
7660
7872
  }
7661
7873
  function isSamePath(a, b) {
7662
7874
  try {
7663
- return path32.resolve(a) === path32.resolve(b);
7875
+ return path33.resolve(a) === path33.resolve(b);
7664
7876
  } catch {
7665
7877
  return false;
7666
7878
  }
7667
7879
  }
7668
7880
  function countConfigs(cwd) {
7669
- const homeDir2 = os25.homedir();
7670
- const claudeDir = path32.join(homeDir2, ".claude");
7881
+ const homeDir2 = os26.homedir();
7882
+ const claudeDir = path33.join(homeDir2, ".claude");
7671
7883
  let claudeMdCount = 0;
7672
7884
  let rulesCount = 0;
7673
7885
  let hooksCount = 0;
7674
7886
  const userMcpServers = /* @__PURE__ */ new Set();
7675
7887
  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");
7888
+ if (fs30.existsSync(path33.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
7889
+ rulesCount += countRulesInDir(path33.join(claudeDir, "rules"));
7890
+ const userSettings = path33.join(claudeDir, "settings.json");
7679
7891
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
7680
7892
  hooksCount += countHooksInFile(userSettings);
7681
- const userClaudeJson = path32.join(homeDir2, ".claude.json");
7893
+ const userClaudeJson = path33.join(homeDir2, ".claude.json");
7682
7894
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
7683
7895
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
7684
7896
  userMcpServers.delete(name);
7685
7897
  }
7686
7898
  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");
7899
+ if (fs30.existsSync(path33.join(cwd, "CLAUDE.md"))) claudeMdCount++;
7900
+ if (fs30.existsSync(path33.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
7901
+ const projectClaudeDir = path33.join(cwd, ".claude");
7690
7902
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
7691
7903
  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");
7904
+ if (fs30.existsSync(path33.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
7905
+ rulesCount += countRulesInDir(path33.join(projectClaudeDir, "rules"));
7906
+ const projSettings = path33.join(projectClaudeDir, "settings.json");
7695
7907
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
7696
7908
  hooksCount += countHooksInFile(projSettings);
7697
7909
  }
7698
- if (fs29.existsSync(path32.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
7699
- const localSettings = path32.join(projectClaudeDir, "settings.local.json");
7910
+ if (fs30.existsSync(path33.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
7911
+ const localSettings = path33.join(projectClaudeDir, "settings.local.json");
7700
7912
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
7701
7913
  hooksCount += countHooksInFile(localSettings);
7702
- const mcpJsonServers = getMcpServerNames(path32.join(cwd, ".mcp.json"));
7914
+ const mcpJsonServers = getMcpServerNames(path33.join(cwd, ".mcp.json"));
7703
7915
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
7704
7916
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
7705
7917
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -7732,12 +7944,12 @@ function readActiveShieldsHud() {
7732
7944
  return shieldsCache.value;
7733
7945
  }
7734
7946
  try {
7735
- const shieldsPath = path32.join(os25.homedir(), ".node9", "shields.json");
7736
- if (!fs29.existsSync(shieldsPath)) {
7947
+ const shieldsPath = path33.join(os26.homedir(), ".node9", "shields.json");
7948
+ if (!fs30.existsSync(shieldsPath)) {
7737
7949
  shieldsCache = { value: [], ts: now };
7738
7950
  return [];
7739
7951
  }
7740
- const parsed = JSON.parse(fs29.readFileSync(shieldsPath, "utf-8"));
7952
+ const parsed = JSON.parse(fs30.readFileSync(shieldsPath, "utf-8"));
7741
7953
  if (!Array.isArray(parsed.active)) {
7742
7954
  shieldsCache = { value: [], ts: now };
7743
7955
  return [];
@@ -7839,17 +8051,17 @@ function renderContextLine(stdin) {
7839
8051
  async function main() {
7840
8052
  try {
7841
8053
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
7842
- if (fs29.existsSync(path32.join(os25.homedir(), ".node9", "hud-debug"))) {
8054
+ if (fs30.existsSync(path33.join(os26.homedir(), ".node9", "hud-debug"))) {
7843
8055
  try {
7844
- const logPath = path32.join(os25.homedir(), ".node9", "hud-debug.log");
8056
+ const logPath = path33.join(os26.homedir(), ".node9", "hud-debug.log");
7845
8057
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
7846
8058
  let size = 0;
7847
8059
  try {
7848
- size = fs29.statSync(logPath).size;
8060
+ size = fs30.statSync(logPath).size;
7849
8061
  } catch {
7850
8062
  }
7851
8063
  if (size < MAX_LOG_SIZE) {
7852
- fs29.appendFileSync(
8064
+ fs30.appendFileSync(
7853
8065
  logPath,
7854
8066
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
7855
8067
  );
@@ -7870,11 +8082,11 @@ async function main() {
7870
8082
  try {
7871
8083
  const cwd = stdin.cwd ?? process.cwd();
7872
8084
  for (const configPath of [
7873
- path32.join(cwd, "node9.config.json"),
7874
- path32.join(os25.homedir(), ".node9", "config.json")
8085
+ path33.join(cwd, "node9.config.json"),
8086
+ path33.join(os26.homedir(), ".node9", "config.json")
7875
8087
  ]) {
7876
- if (!fs29.existsSync(configPath)) continue;
7877
- const cfg = JSON.parse(fs29.readFileSync(configPath, "utf-8"));
8088
+ if (!fs30.existsSync(configPath)) continue;
8089
+ const cfg = JSON.parse(fs30.readFileSync(configPath, "utf-8"));
7878
8090
  const hud = cfg.settings?.hud;
7879
8091
  if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
7880
8092
  }
@@ -8504,9 +8716,9 @@ function teardownHud() {
8504
8716
  // src/cli.ts
8505
8717
  init_daemon2();
8506
8718
  import chalk20 from "chalk";
8507
- import fs30 from "fs";
8508
- import path33 from "path";
8509
- import os26 from "os";
8719
+ import fs31 from "fs";
8720
+ import path34 from "path";
8721
+ import os27 from "os";
8510
8722
  import { confirm as confirm2 } from "@inquirer/prompts";
8511
8723
 
8512
8724
  // src/utils/duration.ts
@@ -8735,19 +8947,19 @@ init_daemon();
8735
8947
  init_config();
8736
8948
  init_policy();
8737
8949
  import chalk5 from "chalk";
8738
- import fs19 from "fs";
8950
+ import fs20 from "fs";
8739
8951
  import { spawn as spawn6 } from "child_process";
8740
- import path21 from "path";
8741
- import os15 from "os";
8952
+ import path22 from "path";
8953
+ import os16 from "os";
8742
8954
 
8743
8955
  // src/undo.ts
8744
8956
  import { spawnSync as spawnSync4, spawn as spawn5 } from "child_process";
8745
8957
  import crypto3 from "crypto";
8746
- import fs18 from "fs";
8958
+ import fs19 from "fs";
8747
8959
  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");
8960
+ import path21 from "path";
8961
+ import os15 from "os";
8962
+ var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path21.join(os15.tmpdir(), "node9-activity.sock");
8751
8963
  function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
8752
8964
  try {
8753
8965
  const payload = JSON.stringify({
@@ -8767,22 +8979,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
8767
8979
  } catch {
8768
8980
  }
8769
8981
  }
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");
8982
+ var SNAPSHOT_STACK_PATH = path21.join(os15.homedir(), ".node9", "snapshots.json");
8983
+ var UNDO_LATEST_PATH = path21.join(os15.homedir(), ".node9", "undo_latest.txt");
8772
8984
  var MAX_SNAPSHOTS = 10;
8773
8985
  var GIT_TIMEOUT = 15e3;
8774
8986
  function readStack() {
8775
8987
  try {
8776
- if (fs18.existsSync(SNAPSHOT_STACK_PATH))
8777
- return JSON.parse(fs18.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
8988
+ if (fs19.existsSync(SNAPSHOT_STACK_PATH))
8989
+ return JSON.parse(fs19.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
8778
8990
  } catch {
8779
8991
  }
8780
8992
  return [];
8781
8993
  }
8782
8994
  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));
8995
+ const dir = path21.dirname(SNAPSHOT_STACK_PATH);
8996
+ if (!fs19.existsSync(dir)) fs19.mkdirSync(dir, { recursive: true });
8997
+ fs19.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
8786
8998
  }
8787
8999
  function extractFilePath(args) {
8788
9000
  if (!args || typeof args !== "object") return null;
@@ -8802,12 +9014,12 @@ function buildArgsSummary(tool, args) {
8802
9014
  return "";
8803
9015
  }
8804
9016
  function findProjectRoot(filePath) {
8805
- let dir = path20.dirname(filePath);
9017
+ let dir = path21.dirname(filePath);
8806
9018
  while (true) {
8807
- if (fs18.existsSync(path20.join(dir, ".git")) || fs18.existsSync(path20.join(dir, "package.json"))) {
9019
+ if (fs19.existsSync(path21.join(dir, ".git")) || fs19.existsSync(path21.join(dir, "package.json"))) {
8808
9020
  return dir;
8809
9021
  }
8810
- const parent = path20.dirname(dir);
9022
+ const parent = path21.dirname(dir);
8811
9023
  if (parent === dir) return process.cwd();
8812
9024
  dir = parent;
8813
9025
  }
@@ -8815,7 +9027,7 @@ function findProjectRoot(filePath) {
8815
9027
  function normalizeCwdForHash(cwd) {
8816
9028
  let normalized;
8817
9029
  try {
8818
- normalized = fs18.realpathSync(cwd);
9030
+ normalized = fs19.realpathSync(cwd);
8819
9031
  } catch {
8820
9032
  normalized = cwd;
8821
9033
  }
@@ -8825,16 +9037,16 @@ function normalizeCwdForHash(cwd) {
8825
9037
  }
8826
9038
  function getShadowRepoDir(cwd) {
8827
9039
  const hash = crypto3.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
8828
- return path20.join(os14.homedir(), ".node9", "snapshots", hash);
9040
+ return path21.join(os15.homedir(), ".node9", "snapshots", hash);
8829
9041
  }
8830
9042
  function cleanOrphanedIndexFiles(shadowDir) {
8831
9043
  try {
8832
9044
  const cutoff = Date.now() - 6e4;
8833
- for (const f of fs18.readdirSync(shadowDir)) {
9045
+ for (const f of fs19.readdirSync(shadowDir)) {
8834
9046
  if (f.startsWith("index_")) {
8835
- const fp = path20.join(shadowDir, f);
9047
+ const fp = path21.join(shadowDir, f);
8836
9048
  try {
8837
- if (fs18.statSync(fp).mtimeMs < cutoff) fs18.unlinkSync(fp);
9049
+ if (fs19.statSync(fp).mtimeMs < cutoff) fs19.unlinkSync(fp);
8838
9050
  } catch {
8839
9051
  }
8840
9052
  }
@@ -8846,7 +9058,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
8846
9058
  const hardcoded = [".git", ".node9"];
8847
9059
  const lines = [...hardcoded, ...ignorePaths].join("\n");
8848
9060
  try {
8849
- fs18.writeFileSync(path20.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
9061
+ fs19.writeFileSync(path21.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
8850
9062
  } catch {
8851
9063
  }
8852
9064
  }
@@ -8859,25 +9071,25 @@ function ensureShadowRepo(shadowDir, cwd) {
8859
9071
  timeout: 3e3
8860
9072
  });
8861
9073
  if (check.status === 0) {
8862
- const ptPath = path20.join(shadowDir, "project-path.txt");
9074
+ const ptPath = path21.join(shadowDir, "project-path.txt");
8863
9075
  try {
8864
- const stored = fs18.readFileSync(ptPath, "utf8").trim();
9076
+ const stored = fs19.readFileSync(ptPath, "utf8").trim();
8865
9077
  if (stored === normalizedCwd) return true;
8866
9078
  if (process.env.NODE9_DEBUG === "1")
8867
9079
  console.error(
8868
9080
  `[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
8869
9081
  );
8870
- fs18.rmSync(shadowDir, { recursive: true, force: true });
9082
+ fs19.rmSync(shadowDir, { recursive: true, force: true });
8871
9083
  } catch {
8872
9084
  try {
8873
- fs18.writeFileSync(ptPath, normalizedCwd, "utf8");
9085
+ fs19.writeFileSync(ptPath, normalizedCwd, "utf8");
8874
9086
  } catch {
8875
9087
  }
8876
9088
  return true;
8877
9089
  }
8878
9090
  }
8879
9091
  try {
8880
- fs18.mkdirSync(shadowDir, { recursive: true });
9092
+ fs19.mkdirSync(shadowDir, { recursive: true });
8881
9093
  } catch {
8882
9094
  }
8883
9095
  const init = spawnSync4("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
@@ -8886,7 +9098,7 @@ function ensureShadowRepo(shadowDir, cwd) {
8886
9098
  if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
8887
9099
  return false;
8888
9100
  }
8889
- const configFile = path20.join(shadowDir, "config");
9101
+ const configFile = path21.join(shadowDir, "config");
8890
9102
  spawnSync4("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
8891
9103
  timeout: 3e3
8892
9104
  });
@@ -8894,7 +9106,7 @@ function ensureShadowRepo(shadowDir, cwd) {
8894
9106
  timeout: 3e3
8895
9107
  });
8896
9108
  try {
8897
- fs18.writeFileSync(path20.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
9109
+ fs19.writeFileSync(path21.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
8898
9110
  } catch {
8899
9111
  }
8900
9112
  return true;
@@ -8914,12 +9126,12 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8914
9126
  let indexFile = null;
8915
9127
  try {
8916
9128
  const rawFilePath = extractFilePath(args);
8917
- const absFilePath = rawFilePath && path20.isAbsolute(rawFilePath) ? rawFilePath : null;
9129
+ const absFilePath = rawFilePath && path21.isAbsolute(rawFilePath) ? rawFilePath : null;
8918
9130
  const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
8919
9131
  const shadowDir = getShadowRepoDir(cwd);
8920
9132
  if (!ensureShadowRepo(shadowDir, cwd)) return null;
8921
9133
  writeShadowExcludes(shadowDir, ignorePaths);
8922
- indexFile = path20.join(shadowDir, `index_${process.pid}_${Date.now()}`);
9134
+ indexFile = path21.join(shadowDir, `index_${process.pid}_${Date.now()}`);
8923
9135
  const shadowEnv = {
8924
9136
  ...process.env,
8925
9137
  GIT_DIR: shadowDir,
@@ -8991,7 +9203,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8991
9203
  writeStack(stack);
8992
9204
  const entry = stack[stack.length - 1];
8993
9205
  notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
8994
- fs18.writeFileSync(UNDO_LATEST_PATH, commitHash);
9206
+ fs19.writeFileSync(UNDO_LATEST_PATH, commitHash);
8995
9207
  if (shouldGc) {
8996
9208
  spawn5("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
8997
9209
  }
@@ -9002,7 +9214,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
9002
9214
  } finally {
9003
9215
  if (indexFile) {
9004
9216
  try {
9005
- fs18.unlinkSync(indexFile);
9217
+ fs19.unlinkSync(indexFile);
9006
9218
  } catch {
9007
9219
  }
9008
9220
  }
@@ -9078,9 +9290,9 @@ function applyUndo(hash, cwd) {
9078
9290
  timeout: GIT_TIMEOUT
9079
9291
  }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
9080
9292
  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);
9293
+ const fullPath = path21.join(dir, file);
9294
+ if (!snapshotFiles.has(file) && fs19.existsSync(fullPath)) {
9295
+ fs19.unlinkSync(fullPath);
9084
9296
  }
9085
9297
  }
9086
9298
  return true;
@@ -9104,9 +9316,9 @@ function registerCheckCommand(program2) {
9104
9316
  } catch (err2) {
9105
9317
  const tempConfig = getConfig();
9106
9318
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
9107
- const logPath = path21.join(os15.homedir(), ".node9", "hook-debug.log");
9319
+ const logPath = path22.join(os16.homedir(), ".node9", "hook-debug.log");
9108
9320
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
9109
- fs19.appendFileSync(
9321
+ fs20.appendFileSync(
9110
9322
  logPath,
9111
9323
  `[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
9112
9324
  RAW: ${raw}
@@ -9119,13 +9331,13 @@ RAW: ${raw}
9119
9331
  if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
9120
9332
  try {
9121
9333
  const scriptPath = process.argv[1];
9122
- if (typeof scriptPath !== "string" || !path21.isAbsolute(scriptPath))
9334
+ if (typeof scriptPath !== "string" || !path22.isAbsolute(scriptPath))
9123
9335
  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"));
9126
- if (resolvedScript !== expectedCli)
9336
+ const resolvedScript = fs20.realpathSync(scriptPath);
9337
+ const packageDist = fs20.realpathSync(path22.resolve(__dirname, "../.."));
9338
+ if (!resolvedScript.startsWith(packageDist + path22.sep) && resolvedScript !== packageDist)
9127
9339
  throw new Error(
9128
- "node9: daemon spawn aborted \u2014 argv[1] does not resolve to the node9 CLI"
9340
+ `node9: daemon spawn aborted \u2014 argv[1] (${resolvedScript}) is outside package dist (${packageDist})`
9129
9341
  );
9130
9342
  const safeEnv = { ...process.env };
9131
9343
  for (const key of [
@@ -9144,14 +9356,24 @@ RAW: ${raw}
9144
9356
  env: { ...safeEnv, NODE9_AUTO_STARTED: "1", NODE9_BROWSER_OPENED: "1" }
9145
9357
  });
9146
9358
  d.unref();
9147
- } catch {
9359
+ } catch (spawnErr) {
9360
+ const logPath = path22.join(os16.homedir(), ".node9", "hook-debug.log");
9361
+ const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
9362
+ try {
9363
+ fs20.appendFileSync(
9364
+ logPath,
9365
+ `[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-failed: ${msg}
9366
+ `
9367
+ );
9368
+ } catch {
9369
+ }
9148
9370
  }
9149
9371
  }
9150
9372
  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}
9373
+ const logPath = path22.join(os16.homedir(), ".node9", "hook-debug.log");
9374
+ if (!fs20.existsSync(path22.dirname(logPath)))
9375
+ fs20.mkdirSync(path22.dirname(logPath), { recursive: true });
9376
+ fs20.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
9155
9377
  `);
9156
9378
  }
9157
9379
  const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
@@ -9164,8 +9386,8 @@ RAW: ${raw}
9164
9386
  const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
9165
9387
  let ttyFd = null;
9166
9388
  try {
9167
- ttyFd = fs19.openSync("/dev/tty", "w");
9168
- const writeTty = (line) => fs19.writeSync(ttyFd, line + "\n");
9389
+ ttyFd = fs20.openSync("/dev/tty", "w");
9390
+ const writeTty = (line) => fs20.writeSync(ttyFd, line + "\n");
9169
9391
  if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
9170
9392
  writeTty(chalk5.bgRed.white.bold(`
9171
9393
  \u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
@@ -9184,7 +9406,7 @@ RAW: ${raw}
9184
9406
  } finally {
9185
9407
  if (ttyFd !== null)
9186
9408
  try {
9187
- fs19.closeSync(ttyFd);
9409
+ fs20.closeSync(ttyFd);
9188
9410
  } catch {
9189
9411
  }
9190
9412
  }
@@ -9216,7 +9438,7 @@ RAW: ${raw}
9216
9438
  if (shouldSnapshot(toolName, toolInput, config)) {
9217
9439
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
9218
9440
  }
9219
- const safeCwdForAuth = typeof payload.cwd === "string" && path21.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9441
+ const safeCwdForAuth = typeof payload.cwd === "string" && path22.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9220
9442
  const result = await authorizeHeadless(toolName, toolInput, meta, {
9221
9443
  cwd: safeCwdForAuth
9222
9444
  });
@@ -9228,12 +9450,12 @@ RAW: ${raw}
9228
9450
  }
9229
9451
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
9230
9452
  try {
9231
- const tty = fs19.openSync("/dev/tty", "w");
9232
- fs19.writeSync(
9453
+ const tty = fs20.openSync("/dev/tty", "w");
9454
+ fs20.writeSync(
9233
9455
  tty,
9234
9456
  chalk5.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
9235
9457
  );
9236
- fs19.closeSync(tty);
9458
+ fs20.closeSync(tty);
9237
9459
  } catch {
9238
9460
  }
9239
9461
  const daemonReady = await autoStartDaemonAndWait();
@@ -9260,9 +9482,9 @@ RAW: ${raw}
9260
9482
  });
9261
9483
  } catch (err2) {
9262
9484
  if (process.env.NODE9_DEBUG === "1") {
9263
- const logPath = path21.join(os15.homedir(), ".node9", "hook-debug.log");
9485
+ const logPath = path22.join(os16.homedir(), ".node9", "hook-debug.log");
9264
9486
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
9265
- fs19.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
9487
+ fs20.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
9266
9488
  `);
9267
9489
  }
9268
9490
  process.exit(0);
@@ -9299,9 +9521,9 @@ RAW: ${raw}
9299
9521
  init_audit();
9300
9522
  init_config();
9301
9523
  init_policy();
9302
- import fs20 from "fs";
9303
- import path22 from "path";
9304
- import os16 from "os";
9524
+ import fs21 from "fs";
9525
+ import path23 from "path";
9526
+ import os17 from "os";
9305
9527
  init_daemon();
9306
9528
 
9307
9529
  // src/utils/cp-mv-parser.ts
@@ -9374,10 +9596,10 @@ function registerLogCommand(program2) {
9374
9596
  decision: "allowed",
9375
9597
  source: "post-hook"
9376
9598
  };
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");
9599
+ const logPath = path23.join(os17.homedir(), ".node9", "audit.log");
9600
+ if (!fs21.existsSync(path23.dirname(logPath)))
9601
+ fs21.mkdirSync(path23.dirname(logPath), { recursive: true });
9602
+ fs21.appendFileSync(logPath, JSON.stringify(entry) + "\n");
9381
9603
  if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
9382
9604
  const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
9383
9605
  if (command) {
@@ -9410,7 +9632,7 @@ function registerLogCommand(program2) {
9410
9632
  }
9411
9633
  }
9412
9634
  }
9413
- const safeCwd = typeof payload.cwd === "string" && path22.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9635
+ const safeCwd = typeof payload.cwd === "string" && path23.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9414
9636
  const config = getConfig(safeCwd);
9415
9637
  if (shouldSnapshot(tool, {}, config)) {
9416
9638
  await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
@@ -9419,9 +9641,9 @@ function registerLogCommand(program2) {
9419
9641
  const msg = err2 instanceof Error ? err2.message : String(err2);
9420
9642
  process.stderr.write(`[Node9] audit log error: ${msg}
9421
9643
  `);
9422
- const debugPath = path22.join(os16.homedir(), ".node9", "hook-debug.log");
9644
+ const debugPath = path23.join(os17.homedir(), ".node9", "hook-debug.log");
9423
9645
  try {
9424
- fs20.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
9646
+ fs21.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
9425
9647
  `);
9426
9648
  } catch {
9427
9649
  }
@@ -9822,13 +10044,13 @@ function registerConfigShowCommand(program2) {
9822
10044
  // src/cli/commands/doctor.ts
9823
10045
  init_daemon();
9824
10046
  import chalk7 from "chalk";
9825
- import fs21 from "fs";
9826
- import path23 from "path";
9827
- import os17 from "os";
10047
+ import fs22 from "fs";
10048
+ import path24 from "path";
10049
+ import os18 from "os";
9828
10050
  import { execSync as execSync2 } from "child_process";
9829
10051
  function registerDoctorCommand(program2, version2) {
9830
10052
  program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
9831
- const homeDir2 = os17.homedir();
10053
+ const homeDir2 = os18.homedir();
9832
10054
  let failures = 0;
9833
10055
  function pass(msg) {
9834
10056
  console.log(chalk7.green(" \u2705 ") + msg);
@@ -9877,10 +10099,10 @@ function registerDoctorCommand(program2, version2) {
9877
10099
  );
9878
10100
  }
9879
10101
  section("Configuration");
9880
- const globalConfigPath = path23.join(homeDir2, ".node9", "config.json");
9881
- if (fs21.existsSync(globalConfigPath)) {
10102
+ const globalConfigPath = path24.join(homeDir2, ".node9", "config.json");
10103
+ if (fs22.existsSync(globalConfigPath)) {
9882
10104
  try {
9883
- JSON.parse(fs21.readFileSync(globalConfigPath, "utf-8"));
10105
+ JSON.parse(fs22.readFileSync(globalConfigPath, "utf-8"));
9884
10106
  pass("~/.node9/config.json found and valid");
9885
10107
  } catch {
9886
10108
  fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
@@ -9888,10 +10110,10 @@ function registerDoctorCommand(program2, version2) {
9888
10110
  } else {
9889
10111
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
9890
10112
  }
9891
- const projectConfigPath = path23.join(process.cwd(), "node9.config.json");
9892
- if (fs21.existsSync(projectConfigPath)) {
10113
+ const projectConfigPath = path24.join(process.cwd(), "node9.config.json");
10114
+ if (fs22.existsSync(projectConfigPath)) {
9893
10115
  try {
9894
- JSON.parse(fs21.readFileSync(projectConfigPath, "utf-8"));
10116
+ JSON.parse(fs22.readFileSync(projectConfigPath, "utf-8"));
9895
10117
  pass("node9.config.json found and valid (project)");
9896
10118
  } catch {
9897
10119
  fail(
@@ -9900,8 +10122,8 @@ function registerDoctorCommand(program2, version2) {
9900
10122
  );
9901
10123
  }
9902
10124
  }
9903
- const credsPath = path23.join(homeDir2, ".node9", "credentials.json");
9904
- if (fs21.existsSync(credsPath)) {
10125
+ const credsPath = path24.join(homeDir2, ".node9", "credentials.json");
10126
+ if (fs22.existsSync(credsPath)) {
9905
10127
  pass("Cloud credentials found (~/.node9/credentials.json)");
9906
10128
  } else {
9907
10129
  warn(
@@ -9910,10 +10132,10 @@ function registerDoctorCommand(program2, version2) {
9910
10132
  );
9911
10133
  }
9912
10134
  section("Agent Hooks");
9913
- const claudeSettingsPath = path23.join(homeDir2, ".claude", "settings.json");
9914
- if (fs21.existsSync(claudeSettingsPath)) {
10135
+ const claudeSettingsPath = path24.join(homeDir2, ".claude", "settings.json");
10136
+ if (fs22.existsSync(claudeSettingsPath)) {
9915
10137
  try {
9916
- const cs = JSON.parse(fs21.readFileSync(claudeSettingsPath, "utf-8"));
10138
+ const cs = JSON.parse(fs22.readFileSync(claudeSettingsPath, "utf-8"));
9917
10139
  const hasHook = cs.hooks?.PreToolUse?.some(
9918
10140
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
9919
10141
  );
@@ -9929,10 +10151,10 @@ function registerDoctorCommand(program2, version2) {
9929
10151
  } else {
9930
10152
  warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
9931
10153
  }
9932
- const geminiSettingsPath = path23.join(homeDir2, ".gemini", "settings.json");
9933
- if (fs21.existsSync(geminiSettingsPath)) {
10154
+ const geminiSettingsPath = path24.join(homeDir2, ".gemini", "settings.json");
10155
+ if (fs22.existsSync(geminiSettingsPath)) {
9934
10156
  try {
9935
- const gs = JSON.parse(fs21.readFileSync(geminiSettingsPath, "utf-8"));
10157
+ const gs = JSON.parse(fs22.readFileSync(geminiSettingsPath, "utf-8"));
9936
10158
  const hasHook = gs.hooks?.BeforeTool?.some(
9937
10159
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
9938
10160
  );
@@ -9948,10 +10170,10 @@ function registerDoctorCommand(program2, version2) {
9948
10170
  } else {
9949
10171
  warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
9950
10172
  }
9951
- const cursorHooksPath = path23.join(homeDir2, ".cursor", "hooks.json");
9952
- if (fs21.existsSync(cursorHooksPath)) {
10173
+ const cursorHooksPath = path24.join(homeDir2, ".cursor", "hooks.json");
10174
+ if (fs22.existsSync(cursorHooksPath)) {
9953
10175
  try {
9954
- const cur = JSON.parse(fs21.readFileSync(cursorHooksPath, "utf-8"));
10176
+ const cur = JSON.parse(fs22.readFileSync(cursorHooksPath, "utf-8"));
9955
10177
  const hasHook = cur.hooks?.preToolUse?.some(
9956
10178
  (h) => h.command?.includes("node9") || h.command?.includes("cli.js")
9957
10179
  );
@@ -9989,9 +10211,9 @@ function registerDoctorCommand(program2, version2) {
9989
10211
 
9990
10212
  // src/cli/commands/audit.ts
9991
10213
  import chalk8 from "chalk";
9992
- import fs22 from "fs";
9993
- import path24 from "path";
9994
- import os18 from "os";
10214
+ import fs23 from "fs";
10215
+ import path25 from "path";
10216
+ import os19 from "os";
9995
10217
  function formatRelativeTime(timestamp) {
9996
10218
  const diff = Date.now() - new Date(timestamp).getTime();
9997
10219
  const sec = Math.floor(diff / 1e3);
@@ -10004,14 +10226,14 @@ function formatRelativeTime(timestamp) {
10004
10226
  }
10005
10227
  function registerAuditCommand(program2) {
10006
10228
  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)) {
10229
+ const logPath = path25.join(os19.homedir(), ".node9", "audit.log");
10230
+ if (!fs23.existsSync(logPath)) {
10009
10231
  console.log(
10010
10232
  chalk8.yellow("No audit logs found. Run node9 with an agent to generate entries.")
10011
10233
  );
10012
10234
  return;
10013
10235
  }
10014
- const raw = fs22.readFileSync(logPath, "utf-8");
10236
+ const raw = fs23.readFileSync(logPath, "utf-8");
10015
10237
  const lines = raw.split("\n").filter((l) => l.trim() !== "");
10016
10238
  let entries = lines.flatMap((line) => {
10017
10239
  try {
@@ -10065,9 +10287,9 @@ function registerAuditCommand(program2) {
10065
10287
 
10066
10288
  // src/cli/commands/report.ts
10067
10289
  import chalk9 from "chalk";
10068
- import fs23 from "fs";
10069
- import path25 from "path";
10070
- import os19 from "os";
10290
+ import fs24 from "fs";
10291
+ import path26 from "path";
10292
+ import os20 from "os";
10071
10293
  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
10294
  function buildTestTimestamps(allEntries) {
10073
10295
  const testTs = /* @__PURE__ */ new Set();
@@ -10114,8 +10336,8 @@ function getDateRange(period) {
10114
10336
  }
10115
10337
  }
10116
10338
  function parseAuditLog(logPath) {
10117
- if (!fs23.existsSync(logPath)) return [];
10118
- const raw = fs23.readFileSync(logPath, "utf-8");
10339
+ if (!fs24.existsSync(logPath)) return [];
10340
+ const raw = fs24.readFileSync(logPath, "utf-8");
10119
10341
  return raw.split("\n").flatMap((line) => {
10120
10342
  if (!line.trim()) return [];
10121
10343
  try {
@@ -10177,29 +10399,39 @@ function claudeModelPrice(model) {
10177
10399
  return null;
10178
10400
  }
10179
10401
  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() };
10402
+ const empty = {
10403
+ total: 0,
10404
+ byDay: /* @__PURE__ */ new Map(),
10405
+ byModel: /* @__PURE__ */ new Map(),
10406
+ inputTokens: 0,
10407
+ cacheReadTokens: 0
10408
+ };
10409
+ const projectsDir = path26.join(os20.homedir(), ".claude", "projects");
10410
+ if (!fs24.existsSync(projectsDir)) return empty;
10182
10411
  let dirs;
10183
10412
  try {
10184
- dirs = fs23.readdirSync(projectsDir);
10413
+ dirs = fs24.readdirSync(projectsDir);
10185
10414
  } catch {
10186
- return { total: 0, byDay: /* @__PURE__ */ new Map() };
10415
+ return empty;
10187
10416
  }
10188
10417
  let total = 0;
10418
+ let inputTokens = 0;
10419
+ let cacheReadTokens = 0;
10189
10420
  const byDay = /* @__PURE__ */ new Map();
10421
+ const byModel = /* @__PURE__ */ new Map();
10190
10422
  for (const proj of dirs) {
10191
- const projPath = path25.join(projectsDir, proj);
10423
+ const projPath = path26.join(projectsDir, proj);
10192
10424
  let files;
10193
10425
  try {
10194
- const stat = fs23.statSync(projPath);
10426
+ const stat = fs24.statSync(projPath);
10195
10427
  if (!stat.isDirectory()) continue;
10196
- files = fs23.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
10428
+ files = fs24.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
10197
10429
  } catch {
10198
10430
  continue;
10199
10431
  }
10200
10432
  for (const file of files) {
10201
10433
  try {
10202
- const raw = fs23.readFileSync(path25.join(projPath, file), "utf-8");
10434
+ const raw = fs24.readFileSync(path26.join(projPath, file), "utf-8");
10203
10435
  for (const line of raw.split("\n")) {
10204
10436
  if (!line.trim()) continue;
10205
10437
  let entry;
@@ -10217,24 +10449,32 @@ function loadClaudeCost(start, end) {
10217
10449
  if (!usage || !model) continue;
10218
10450
  const p = claudeModelPrice(model);
10219
10451
  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;
10452
+ const inp = usage.input_tokens ?? 0;
10453
+ const out = usage.output_tokens ?? 0;
10454
+ const cw = usage.cache_creation_input_tokens ?? 0;
10455
+ const cr = usage.cache_read_input_tokens ?? 0;
10456
+ const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
10221
10457
  total += cost;
10458
+ inputTokens += inp;
10459
+ cacheReadTokens += cr;
10222
10460
  const dateKey = entry.timestamp.slice(0, 10);
10223
10461
  byDay.set(dateKey, (byDay.get(dateKey) ?? 0) + cost);
10462
+ const normModel = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
10463
+ byModel.set(normModel, (byModel.get(normModel) ?? 0) + cost);
10224
10464
  }
10225
10465
  } catch {
10226
10466
  continue;
10227
10467
  }
10228
10468
  }
10229
10469
  }
10230
- return { total, byDay };
10470
+ return { total, byDay, byModel, inputTokens, cacheReadTokens };
10231
10471
  }
10232
10472
  function registerReportCommand(program2) {
10233
10473
  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
10474
  const period = ["today", "7d", "30d", "month"].includes(
10235
10475
  options.period
10236
10476
  ) ? options.period : "7d";
10237
- const logPath = path25.join(os19.homedir(), ".node9", "audit.log");
10477
+ const logPath = path26.join(os20.homedir(), ".node9", "audit.log");
10238
10478
  const allEntries = parseAuditLog(logPath);
10239
10479
  if (allEntries.length === 0) {
10240
10480
  console.log(
@@ -10243,7 +10483,13 @@ function registerReportCommand(program2) {
10243
10483
  return;
10244
10484
  }
10245
10485
  const { start, end } = getDateRange(period);
10246
- const { total: costUSD, byDay: costByDay } = loadClaudeCost(start, end);
10486
+ const {
10487
+ total: costUSD,
10488
+ byDay: costByDay,
10489
+ byModel: costByModel,
10490
+ inputTokens: costInputTokens,
10491
+ cacheReadTokens: costCacheRead
10492
+ } = loadClaudeCost(start, end);
10247
10493
  const periodMs = end.getTime() - start.getTime();
10248
10494
  const priorEnd = new Date(start.getTime() - 1);
10249
10495
  const priorStart = new Date(start.getTime() - periodMs);
@@ -10345,7 +10591,6 @@ function registerReportCommand(program2) {
10345
10591
  const blockLabel = blocked > 0 ? chalk9.red(`\u{1F6D1} ${num(blocked)} blocked`) : chalk9.dim("\u{1F6D1} 0 blocked");
10346
10592
  const dlpLabel = dlpHits > 0 ? chalk9.yellow(`\u{1F6A8} ${dlpHits} DLP hits`) : chalk9.dim("\u{1F6A8} 0 DLP hits");
10347
10593
  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
10594
  const currentRate = total > 0 ? blocked / total : 0;
10350
10595
  const trendLabel = (() => {
10351
10596
  if (priorBlockRate === null) return chalk9.dim(`${pct(blocked, total)} block rate`);
@@ -10358,7 +10603,7 @@ function registerReportCommand(program2) {
10358
10603
  const ratioLabel = reads > 0 ? chalk9.dim(`edit/read ${(edits / reads).toFixed(1)}`) : chalk9.dim("edit/read \u2013");
10359
10604
  const testLabel = testPasses + testFails > 0 ? chalk9.dim("tests ") + chalk9.green(`${testPasses}\u2713`) + (testFails > 0 ? " " + chalk9.red(`${testFails}\u2717`) : "") : chalk9.dim("tests \u2013");
10360
10605
  console.log(
10361
- " " + chalk9.green(`\u2705 ${num(allowed)} allowed`) + " " + blockLabel + " " + dlpLabel + " " + loopLabel + " " + trendLabel + " " + costLabel
10606
+ " " + chalk9.green(`\u2705 ${num(allowed)} allowed`) + " " + blockLabel + " " + dlpLabel + " " + loopLabel + " " + trendLabel
10362
10607
  );
10363
10608
  console.log(" " + ratioLabel + " " + testLabel);
10364
10609
  console.log("");
@@ -10443,6 +10688,30 @@ function registerReportCommand(program2) {
10443
10688
  );
10444
10689
  }
10445
10690
  }
10691
+ if (costUSD > 0) {
10692
+ const periodDays = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 864e5));
10693
+ const avgPerDay = costUSD / periodDays;
10694
+ const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
10695
+ const costHeaderRight = [
10696
+ chalk9.yellow(fmtCost(costUSD)),
10697
+ chalk9.dim(`avg ${fmtCost(avgPerDay)}/day`),
10698
+ cacheHitPct > 0 ? chalk9.dim(`${cacheHitPct}% cache hit`) : null
10699
+ ].filter(Boolean).join(chalk9.dim(" \xB7 "));
10700
+ console.log("");
10701
+ console.log(" " + chalk9.bold("Cost") + " " + costHeaderRight);
10702
+ console.log(" " + chalk9.dim("\u2500".repeat(Math.min(50, W - 4))));
10703
+ const modelList = [...costByModel.entries()].sort((a, b) => b[1] - a[1]);
10704
+ const maxModelCost = Math.max(...modelList.map(([, v]) => v), 1e-9);
10705
+ const MODEL_LABEL = 22;
10706
+ const MODEL_BAR = Math.max(6, Math.min(20, W - MODEL_LABEL - 12));
10707
+ for (const [model, cost] of modelList) {
10708
+ const label = model.length > MODEL_LABEL - 1 ? model.slice(0, MODEL_LABEL - 2) + "\u2026" : model;
10709
+ const b = colorBar(cost, maxModelCost, MODEL_BAR);
10710
+ console.log(
10711
+ " " + chalk9.white(label.padEnd(MODEL_LABEL)) + b + " " + chalk9.yellow(fmtCost(cost))
10712
+ );
10713
+ }
10714
+ }
10446
10715
  console.log("");
10447
10716
  console.log(
10448
10717
  " " + chalk9.dim("node9 audit --deny") + chalk9.dim(" \xB7 ") + chalk9.dim("node9 report --period today|7d|30d|month --no-tests")
@@ -10519,12 +10788,12 @@ function registerDaemonCommand(program2) {
10519
10788
  init_core();
10520
10789
  init_daemon();
10521
10790
  import chalk11 from "chalk";
10522
- import fs24 from "fs";
10523
- import path26 from "path";
10524
- import os20 from "os";
10791
+ import fs25 from "fs";
10792
+ import path27 from "path";
10793
+ import os21 from "os";
10525
10794
  function readJson2(filePath) {
10526
10795
  try {
10527
- if (fs24.existsSync(filePath)) return JSON.parse(fs24.readFileSync(filePath, "utf-8"));
10796
+ if (fs25.existsSync(filePath)) return JSON.parse(fs25.readFileSync(filePath, "utf-8"));
10528
10797
  } catch {
10529
10798
  }
10530
10799
  return null;
@@ -10589,28 +10858,28 @@ function registerStatusCommand(program2) {
10589
10858
  console.log("");
10590
10859
  const modeLabel = settings.mode === "audit" ? chalk11.blue("audit") : settings.mode === "strict" ? chalk11.red("strict") : chalk11.white("standard");
10591
10860
  console.log(` Mode: ${modeLabel}`);
10592
- const projectConfig = path26.join(process.cwd(), "node9.config.json");
10593
- const globalConfig = path26.join(os20.homedir(), ".node9", "config.json");
10861
+ const projectConfig = path27.join(process.cwd(), "node9.config.json");
10862
+ const globalConfig = path27.join(os21.homedir(), ".node9", "config.json");
10594
10863
  console.log(
10595
- ` Local: ${fs24.existsSync(projectConfig) ? chalk11.green("Active (node9.config.json)") : chalk11.gray("Not present")}`
10864
+ ` Local: ${fs25.existsSync(projectConfig) ? chalk11.green("Active (node9.config.json)") : chalk11.gray("Not present")}`
10596
10865
  );
10597
10866
  console.log(
10598
- ` Global: ${fs24.existsSync(globalConfig) ? chalk11.green("Active (~/.node9/config.json)") : chalk11.gray("Not present")}`
10867
+ ` Global: ${fs25.existsSync(globalConfig) ? chalk11.green("Active (~/.node9/config.json)") : chalk11.gray("Not present")}`
10599
10868
  );
10600
10869
  if (mergedConfig.policy.sandboxPaths.length > 0) {
10601
10870
  console.log(
10602
10871
  ` Sandbox: ${chalk11.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
10603
10872
  );
10604
10873
  }
10605
- const homeDir2 = os20.homedir();
10874
+ const homeDir2 = os21.homedir();
10606
10875
  const claudeSettings = readJson2(
10607
- path26.join(homeDir2, ".claude", "settings.json")
10876
+ path27.join(homeDir2, ".claude", "settings.json")
10608
10877
  );
10609
- const claudeConfig = readJson2(path26.join(homeDir2, ".claude.json"));
10878
+ const claudeConfig = readJson2(path27.join(homeDir2, ".claude.json"));
10610
10879
  const geminiSettings = readJson2(
10611
- path26.join(homeDir2, ".gemini", "settings.json")
10880
+ path27.join(homeDir2, ".gemini", "settings.json")
10612
10881
  );
10613
- const cursorConfig = readJson2(path26.join(homeDir2, ".cursor", "mcp.json"));
10882
+ const cursorConfig = readJson2(path27.join(homeDir2, ".cursor", "mcp.json"));
10614
10883
  const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
10615
10884
  if (agentFound) {
10616
10885
  console.log("");
@@ -10670,9 +10939,9 @@ function registerStatusCommand(program2) {
10670
10939
  // src/cli/commands/init.ts
10671
10940
  init_core();
10672
10941
  import chalk12 from "chalk";
10673
- import fs25 from "fs";
10674
- import path27 from "path";
10675
- import os21 from "os";
10942
+ import fs26 from "fs";
10943
+ import path28 from "path";
10944
+ import os22 from "os";
10676
10945
  import https2 from "https";
10677
10946
  init_shields();
10678
10947
  var DEFAULT_SHIELDS = ["bash-safe", "filesystem", "postgres"];
@@ -10731,15 +11000,15 @@ function registerInitCommand(program2) {
10731
11000
  }
10732
11001
  console.log("");
10733
11002
  }
10734
- const configPath = path27.join(os21.homedir(), ".node9", "config.json");
10735
- if (fs25.existsSync(configPath) && !options.force) {
11003
+ const configPath = path28.join(os22.homedir(), ".node9", "config.json");
11004
+ if (fs26.existsSync(configPath) && !options.force) {
10736
11005
  try {
10737
- const existing = JSON.parse(fs25.readFileSync(configPath, "utf-8"));
11006
+ const existing = JSON.parse(fs26.readFileSync(configPath, "utf-8"));
10738
11007
  const settings = existing.settings ?? {};
10739
11008
  if (settings.mode !== chosenMode) {
10740
11009
  settings.mode = chosenMode;
10741
11010
  existing.settings = settings;
10742
- fs25.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
11011
+ fs26.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
10743
11012
  console.log(chalk12.green(`\u2705 Mode updated: ${chosenMode}`));
10744
11013
  } else {
10745
11014
  console.log(chalk12.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
@@ -10752,9 +11021,9 @@ function registerInitCommand(program2) {
10752
11021
  ...DEFAULT_CONFIG,
10753
11022
  settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
10754
11023
  };
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");
11024
+ const dir = path28.dirname(configPath);
11025
+ if (!fs26.existsSync(dir)) fs26.mkdirSync(dir, { recursive: true });
11026
+ fs26.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
10758
11027
  console.log(chalk12.green(`\u2705 Config created: ${configPath}`));
10759
11028
  console.log(chalk12.gray(` Mode: ${chosenMode}`));
10760
11029
  }
@@ -10808,7 +11077,7 @@ function registerInitCommand(program2) {
10808
11077
  }
10809
11078
 
10810
11079
  // src/cli/commands/undo.ts
10811
- import path28 from "path";
11080
+ import path29 from "path";
10812
11081
  import chalk14 from "chalk";
10813
11082
 
10814
11083
  // src/tui/undo-navigator.ts
@@ -10967,7 +11236,7 @@ function findMatchingCwd(startDir, history) {
10967
11236
  let dir = startDir;
10968
11237
  while (true) {
10969
11238
  if (cwds.has(dir)) return dir;
10970
- const parent = path28.dirname(dir);
11239
+ const parent = path29.dirname(dir);
10971
11240
  if (parent === dir) return null;
10972
11241
  dir = parent;
10973
11242
  }
@@ -11163,12 +11432,12 @@ import { execa as execa2 } from "execa";
11163
11432
  init_provenance();
11164
11433
 
11165
11434
  // src/mcp-pin.ts
11166
- import fs26 from "fs";
11167
- import path29 from "path";
11168
- import os22 from "os";
11435
+ import fs27 from "fs";
11436
+ import path30 from "path";
11437
+ import os23 from "os";
11169
11438
  import crypto4 from "crypto";
11170
11439
  function getPinsFilePath() {
11171
- return path29.join(os22.homedir(), ".node9", "mcp-pins.json");
11440
+ return path30.join(os23.homedir(), ".node9", "mcp-pins.json");
11172
11441
  }
11173
11442
  function hashToolDefinitions(tools) {
11174
11443
  const sorted = [...tools].sort((a, b) => {
@@ -11185,7 +11454,7 @@ function getServerKey(upstreamCommand) {
11185
11454
  function readMcpPinsSafe() {
11186
11455
  const filePath = getPinsFilePath();
11187
11456
  try {
11188
- const raw = fs26.readFileSync(filePath, "utf-8");
11457
+ const raw = fs27.readFileSync(filePath, "utf-8");
11189
11458
  if (!raw.trim()) {
11190
11459
  return { ok: false, reason: "corrupt", detail: "empty file" };
11191
11460
  }
@@ -11209,10 +11478,10 @@ function readMcpPins() {
11209
11478
  }
11210
11479
  function writeMcpPins(data) {
11211
11480
  const filePath = getPinsFilePath();
11212
- fs26.mkdirSync(path29.dirname(filePath), { recursive: true });
11481
+ fs27.mkdirSync(path30.dirname(filePath), { recursive: true });
11213
11482
  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);
11483
+ fs27.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
11484
+ fs27.renameSync(tmp, filePath);
11216
11485
  }
11217
11486
  function checkPin(serverKey, currentHash) {
11218
11487
  const result = readMcpPinsSafe();
@@ -11584,9 +11853,9 @@ function registerMcpGatewayCommand(program2) {
11584
11853
 
11585
11854
  // src/mcp-server/index.ts
11586
11855
  import readline4 from "readline";
11587
- import fs27 from "fs";
11588
- import os23 from "os";
11589
- import path30 from "path";
11856
+ import fs28 from "fs";
11857
+ import os24 from "os";
11858
+ import path31 from "path";
11590
11859
  init_core();
11591
11860
  init_daemon();
11592
11861
  init_shields();
@@ -11761,13 +12030,13 @@ function handleStatus() {
11761
12030
  lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
11762
12031
  lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
11763
12032
  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");
12033
+ const projectConfig = path31.join(process.cwd(), "node9.config.json");
12034
+ const globalConfig = path31.join(os24.homedir(), ".node9", "config.json");
11766
12035
  lines.push(
11767
- `Project config (node9.config.json): ${fs27.existsSync(projectConfig) ? "present" : "not found"}`
12036
+ `Project config (node9.config.json): ${fs28.existsSync(projectConfig) ? "present" : "not found"}`
11768
12037
  );
11769
12038
  lines.push(
11770
- `Global config (~/.node9/config.json): ${fs27.existsSync(globalConfig) ? "present" : "not found"}`
12039
+ `Global config (~/.node9/config.json): ${fs28.existsSync(globalConfig) ? "present" : "not found"}`
11771
12040
  );
11772
12041
  return lines.join("\n");
11773
12042
  }
@@ -11841,21 +12110,21 @@ function handleShieldDisable(args) {
11841
12110
  writeActiveShields(active.filter((s) => s !== name));
11842
12111
  return `Shield "${name}" disabled.`;
11843
12112
  }
11844
- var GLOBAL_CONFIG_PATH2 = path30.join(os23.homedir(), ".node9", "config.json");
12113
+ var GLOBAL_CONFIG_PATH2 = path31.join(os24.homedir(), ".node9", "config.json");
11845
12114
  var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
11846
12115
  function readGlobalConfigRaw() {
11847
12116
  try {
11848
- if (fs27.existsSync(GLOBAL_CONFIG_PATH2)) {
11849
- return JSON.parse(fs27.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
12117
+ if (fs28.existsSync(GLOBAL_CONFIG_PATH2)) {
12118
+ return JSON.parse(fs28.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
11850
12119
  }
11851
12120
  } catch {
11852
12121
  }
11853
12122
  return {};
11854
12123
  }
11855
12124
  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");
12125
+ const dir = path31.dirname(GLOBAL_CONFIG_PATH2);
12126
+ if (!fs28.existsSync(dir)) fs28.mkdirSync(dir, { recursive: true });
12127
+ fs28.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
11859
12128
  }
11860
12129
  function handleApproverList() {
11861
12130
  const config = getConfig();
@@ -11898,9 +12167,9 @@ function handleApproverSet(args) {
11898
12167
  }
11899
12168
  function handleAuditGet(args) {
11900
12169
  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);
12170
+ const auditPath = path31.join(os24.homedir(), ".node9", "audit.log");
12171
+ if (!fs28.existsSync(auditPath)) return "No audit log found.";
12172
+ const lines = fs28.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
11904
12173
  const recent = lines.slice(-limit);
11905
12174
  const entries = recent.map((line) => {
11906
12175
  try {
@@ -12220,20 +12489,20 @@ function registerMcpPinCommand(program2) {
12220
12489
 
12221
12490
  // src/cli.ts
12222
12491
  var { version } = JSON.parse(
12223
- fs30.readFileSync(path33.join(__dirname, "../package.json"), "utf-8")
12492
+ fs31.readFileSync(path34.join(__dirname, "../package.json"), "utf-8")
12224
12493
  );
12225
12494
  var program = new Command();
12226
12495
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
12227
12496
  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
12497
  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 });
12498
+ const credPath = path34.join(os27.homedir(), ".node9", "credentials.json");
12499
+ if (!fs31.existsSync(path34.dirname(credPath)))
12500
+ fs31.mkdirSync(path34.dirname(credPath), { recursive: true });
12232
12501
  const profileName = options.profile || "default";
12233
12502
  let existingCreds = {};
12234
12503
  try {
12235
- if (fs30.existsSync(credPath)) {
12236
- const raw = JSON.parse(fs30.readFileSync(credPath, "utf-8"));
12504
+ if (fs31.existsSync(credPath)) {
12505
+ const raw = JSON.parse(fs31.readFileSync(credPath, "utf-8"));
12237
12506
  if (raw.apiKey) {
12238
12507
  existingCreds = {
12239
12508
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
@@ -12245,13 +12514,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
12245
12514
  } catch {
12246
12515
  }
12247
12516
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
12248
- fs30.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
12517
+ fs31.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
12249
12518
  if (profileName === "default") {
12250
- const configPath = path33.join(os26.homedir(), ".node9", "config.json");
12519
+ const configPath = path34.join(os27.homedir(), ".node9", "config.json");
12251
12520
  let config = {};
12252
12521
  try {
12253
- if (fs30.existsSync(configPath))
12254
- config = JSON.parse(fs30.readFileSync(configPath, "utf-8"));
12522
+ if (fs31.existsSync(configPath))
12523
+ config = JSON.parse(fs31.readFileSync(configPath, "utf-8"));
12255
12524
  } catch {
12256
12525
  }
12257
12526
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -12266,9 +12535,9 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
12266
12535
  approvers.cloud = false;
12267
12536
  }
12268
12537
  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 });
12538
+ if (!fs31.existsSync(path34.dirname(configPath)))
12539
+ fs31.mkdirSync(path34.dirname(configPath), { recursive: true });
12540
+ fs31.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
12272
12541
  }
12273
12542
  if (options.profile && profileName !== "default") {
12274
12543
  console.log(chalk20.green(`\u2705 Profile "${profileName}" saved`));
@@ -12362,15 +12631,15 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
12362
12631
  }
12363
12632
  }
12364
12633
  if (options.purge) {
12365
- const node9Dir = path33.join(os26.homedir(), ".node9");
12366
- if (fs30.existsSync(node9Dir)) {
12634
+ const node9Dir = path34.join(os27.homedir(), ".node9");
12635
+ if (fs31.existsSync(node9Dir)) {
12367
12636
  const confirmed = await confirm2({
12368
12637
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
12369
12638
  default: false
12370
12639
  });
12371
12640
  if (confirmed) {
12372
- fs30.rmSync(node9Dir, { recursive: true });
12373
- if (fs30.existsSync(node9Dir)) {
12641
+ fs31.rmSync(node9Dir, { recursive: true });
12642
+ if (fs31.existsSync(node9Dir)) {
12374
12643
  console.error(
12375
12644
  chalk20.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
12376
12645
  );
@@ -12511,14 +12780,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
12511
12780
  Run "node9 addto claude" to register it as the statusLine.`
12512
12781
  ).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
12782
  if (subcommand === "debug") {
12514
- const flagFile = path33.join(os26.homedir(), ".node9", "hud-debug");
12783
+ const flagFile = path34.join(os27.homedir(), ".node9", "hud-debug");
12515
12784
  if (state === "on") {
12516
- fs30.mkdirSync(path33.dirname(flagFile), { recursive: true });
12517
- fs30.writeFileSync(flagFile, "");
12785
+ fs31.mkdirSync(path34.dirname(flagFile), { recursive: true });
12786
+ fs31.writeFileSync(flagFile, "");
12518
12787
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
12519
12788
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
12520
12789
  } else if (state === "off") {
12521
- if (fs30.existsSync(flagFile)) fs30.unlinkSync(flagFile);
12790
+ if (fs31.existsSync(flagFile)) fs31.unlinkSync(flagFile);
12522
12791
  console.log("HUD debug logging disabled.");
12523
12792
  } else {
12524
12793
  console.error("Usage: node9 hud debug on|off");
@@ -12625,9 +12894,9 @@ if (process.argv[2] !== "daemon") {
12625
12894
  const isCheckHook = process.argv[2] === "check";
12626
12895
  if (isCheckHook) {
12627
12896
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
12628
- const logPath = path33.join(os26.homedir(), ".node9", "hook-debug.log");
12897
+ const logPath = path34.join(os27.homedir(), ".node9", "hook-debug.log");
12629
12898
  const msg = reason instanceof Error ? reason.message : String(reason);
12630
- fs30.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
12899
+ fs31.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
12631
12900
  `);
12632
12901
  }
12633
12902
  process.exit(0);