@node9/proxy 1.10.1 → 1.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -168,8 +168,8 @@ function sanitizeConfig(raw) {
168
168
  }
169
169
  }
170
170
  const lines = result.error.issues.map((issue) => {
171
- const path35 = issue.path.length > 0 ? issue.path.join(".") : "root";
172
- return ` \u2022 ${path35}: ${issue.message}`;
171
+ const path39 = issue.path.length > 0 ? issue.path.join(".") : "root";
172
+ return ` \u2022 ${path39}: ${issue.message}`;
173
173
  });
174
174
  return {
175
175
  sanitized,
@@ -254,7 +254,8 @@ var init_config_schema = __esm({
254
254
  enableTrustSessions: import_zod.z.boolean().optional(),
255
255
  allowGlobalPause: import_zod.z.boolean().optional(),
256
256
  auditHashArgs: import_zod.z.boolean().optional(),
257
- agentPolicy: import_zod.z.enum(["require_approval", "block_on_rules"]).optional()
257
+ agentPolicy: import_zod.z.enum(["require_approval", "block_on_rules"]).optional(),
258
+ cloudSyncIntervalHours: import_zod.z.number().positive().optional()
258
259
  }).optional(),
259
260
  policy: import_zod.z.object({
260
261
  sandboxPaths: import_zod.z.array(import_zod.z.string()).optional(),
@@ -514,11 +515,11 @@ function getGlobalSettings() {
514
515
  };
515
516
  }
516
517
  function getCredentials() {
517
- const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
518
+ const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
518
519
  if (process.env.NODE9_API_KEY) {
519
520
  return {
520
521
  apiKey: process.env.NODE9_API_KEY,
521
- apiUrl: process.env.NODE9_API_URL || DEFAULT_API_URL
522
+ apiUrl: process.env.NODE9_API_URL || DEFAULT_API_URL2
522
523
  };
523
524
  }
524
525
  try {
@@ -530,13 +531,13 @@ function getCredentials() {
530
531
  if (profile?.apiKey) {
531
532
  return {
532
533
  apiKey: profile.apiKey,
533
- apiUrl: profile.apiUrl || DEFAULT_API_URL
534
+ apiUrl: profile.apiUrl || DEFAULT_API_URL2
534
535
  };
535
536
  }
536
537
  if (creds.apiKey) {
537
538
  return {
538
539
  apiKey: creds.apiKey,
539
- apiUrl: creds.apiUrl || DEFAULT_API_URL
540
+ apiUrl: creds.apiUrl || DEFAULT_API_URL2
540
541
  };
541
542
  }
542
543
  }
@@ -587,6 +588,8 @@ function getConfig(cwd) {
587
588
  if (s.approvalTimeoutSeconds !== void 0 && s.approvalTimeoutMs === void 0)
588
589
  mergedSettings.approvalTimeoutMs = s.approvalTimeoutSeconds * 1e3;
589
590
  if (s.environment !== void 0) mergedSettings.environment = s.environment;
591
+ if (s.cloudSyncIntervalHours !== void 0)
592
+ mergedSettings.cloudSyncIntervalHours = s.cloudSyncIntervalHours;
590
593
  if (s.hud !== void 0) mergedSettings.hud = { ...mergedSettings.hud, ...s.hud };
591
594
  if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths);
592
595
  if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools);
@@ -596,7 +599,12 @@ function getConfig(cwd) {
596
599
  if (p.smartRules) {
597
600
  const defaultBlocks = mergedPolicy.smartRules.filter((r) => r.verdict === "block");
598
601
  const defaultNonBlocks = mergedPolicy.smartRules.filter((r) => r.verdict !== "block");
599
- mergedPolicy.smartRules = [...defaultBlocks, ...p.smartRules, ...defaultNonBlocks];
602
+ const userRuleNames = new Set(p.smartRules.filter((r) => r.name).map((r) => r.name));
603
+ const filteredBlocks = defaultBlocks.filter((r) => !r.name || !userRuleNames.has(r.name));
604
+ const filteredNonBlocks = defaultNonBlocks.filter(
605
+ (r) => !r.name || !userRuleNames.has(r.name)
606
+ );
607
+ mergedPolicy.smartRules = [...filteredBlocks, ...p.smartRules, ...filteredNonBlocks];
600
608
  }
601
609
  if (p.snapshot) {
602
610
  const s2 = p.snapshot;
@@ -630,6 +638,16 @@ function getConfig(cwd) {
630
638
  };
631
639
  applyLayer(globalConfig);
632
640
  applyLayer(projectConfig);
641
+ {
642
+ const cacheFile = import_path3.default.join(import_os3.default.homedir(), ".node9", "rules-cache.json");
643
+ try {
644
+ const raw = JSON.parse(import_fs3.default.readFileSync(cacheFile, "utf-8"));
645
+ if (Array.isArray(raw.rules) && raw.rules.length > 0) {
646
+ applyLayer({ policy: { smartRules: raw.rules } });
647
+ }
648
+ } catch {
649
+ }
650
+ }
633
651
  const shieldOverrides = readShieldOverrides();
634
652
  for (const shieldName of readActiveShields()) {
635
653
  const shield = getShield(shieldName);
@@ -748,7 +766,8 @@ var init_config = __esm({
748
766
  // 120-second auto-deny timeout
749
767
  flightRecorder: true,
750
768
  auditHashArgs: true,
751
- approvers: { native: true, browser: true, cloud: false, terminal: true }
769
+ approvers: { native: true, browser: true, cloud: false, terminal: true },
770
+ cloudSyncIntervalHours: 5
752
771
  },
753
772
  policy: {
754
773
  sandboxPaths: ["/tmp/**", "**/sandbox/**", "**/test-results/**"],
@@ -1727,9 +1746,9 @@ function matchesPattern(text, patterns) {
1727
1746
  const withoutDotSlash = text.replace(/^\.\//, "");
1728
1747
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1729
1748
  }
1730
- function getNestedValue(obj, path35) {
1749
+ function getNestedValue(obj, path39) {
1731
1750
  if (!obj || typeof obj !== "object") return null;
1732
- return path35.split(".").reduce((prev, curr) => prev?.[curr], obj);
1751
+ return path39.split(".").reduce((prev, curr) => prev?.[curr], obj);
1733
1752
  }
1734
1753
  function shouldSnapshot(toolName, args, config) {
1735
1754
  if (!config.settings.enableUndo) return false;
@@ -2204,8 +2223,8 @@ async function explainPolicy(toolName, args) {
2204
2223
  const flattenedArgs = JSON.stringify(args).toLowerCase();
2205
2224
  const extraTokens = flattenedArgs.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 1);
2206
2225
  allTokens.push(...extraTokens);
2207
- const preview = extraTokens.slice(0, 8).join(", ") + (extraTokens.length > 8 ? "\u2026" : "");
2208
- detail += ` + deep scan of args: [${preview}]`;
2226
+ const preview2 = extraTokens.slice(0, 8).join(", ") + (extraTokens.length > 8 ? "\u2026" : "");
2227
+ detail += ` + deep scan of args: [${preview2}]`;
2209
2228
  }
2210
2229
  steps.push({ name: "Input parsing", outcome: "checked", detail });
2211
2230
  }
@@ -2457,19 +2476,44 @@ function getInternalToken() {
2457
2476
  function isDaemonRunning() {
2458
2477
  const pidFile = import_path10.default.join(import_os8.default.homedir(), ".node9", "daemon.pid");
2459
2478
  if (import_fs9.default.existsSync(pidFile)) {
2479
+ let pid;
2480
+ let port;
2460
2481
  try {
2461
- const { pid, port } = JSON.parse(import_fs9.default.readFileSync(pidFile, "utf-8"));
2462
- if (port !== DAEMON_PORT) return false;
2463
- process.kill(pid, 0);
2464
- return true;
2482
+ const data = JSON.parse(import_fs9.default.readFileSync(pidFile, "utf-8"));
2483
+ pid = data.pid;
2484
+ port = data.port;
2465
2485
  } catch {
2466
2486
  return false;
2467
2487
  }
2488
+ if (port !== DAEMON_PORT) {
2489
+ return false;
2490
+ }
2491
+ const MAX_PID2 = 4194304;
2492
+ if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID2) {
2493
+ return false;
2494
+ }
2495
+ try {
2496
+ process.kill(pid, 0);
2497
+ } catch (err2) {
2498
+ if (err2 instanceof Error && "code" in err2 && err2.code === "ESRCH") {
2499
+ try {
2500
+ import_fs9.default.unlinkSync(pidFile);
2501
+ } catch {
2502
+ }
2503
+ }
2504
+ return false;
2505
+ }
2506
+ const r = (0, import_child_process.spawnSync)("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
2507
+ encoding: "utf8",
2508
+ timeout: 300
2509
+ });
2510
+ if (r.status === 0 && (r.stdout ?? "").includes(`:${DAEMON_PORT}`)) return true;
2511
+ return false;
2468
2512
  }
2469
2513
  try {
2470
2514
  const r = (0, import_child_process.spawnSync)("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
2471
2515
  encoding: "utf8",
2472
- timeout: 500
2516
+ timeout: 300
2473
2517
  });
2474
2518
  return r.status === 0 && (r.stdout ?? "").includes(`:${DAEMON_PORT}`);
2475
2519
  } catch {
@@ -6241,9 +6285,146 @@ var init_costSync = __esm({
6241
6285
  }
6242
6286
  });
6243
6287
 
6288
+ // src/daemon/sync.ts
6289
+ function readCredentials() {
6290
+ if (process.env.NODE9_API_KEY) {
6291
+ return {
6292
+ apiKey: process.env.NODE9_API_KEY,
6293
+ apiUrl: process.env.NODE9_API_URL ?? DEFAULT_API_URL
6294
+ };
6295
+ }
6296
+ try {
6297
+ const credPath = import_path20.default.join(import_os15.default.homedir(), ".node9", "credentials.json");
6298
+ const creds = JSON.parse(import_fs17.default.readFileSync(credPath, "utf-8"));
6299
+ const profileName = process.env.NODE9_PROFILE ?? "default";
6300
+ const profile = creds[profileName];
6301
+ if (typeof profile?.apiKey === "string" && profile.apiKey.length > 0) {
6302
+ return {
6303
+ apiKey: profile.apiKey,
6304
+ apiUrl: typeof profile.apiUrl === "string" ? profile.apiUrl.replace(/\/intercept$/, "/policy") : DEFAULT_API_URL
6305
+ };
6306
+ }
6307
+ if (typeof creds.apiKey === "string" && creds.apiKey.length > 0) {
6308
+ return { apiKey: creds.apiKey, apiUrl: DEFAULT_API_URL };
6309
+ }
6310
+ } catch {
6311
+ }
6312
+ return null;
6313
+ }
6314
+ function fetchCloudRules(apiKey, apiUrl) {
6315
+ const parsed = new URL(apiUrl);
6316
+ return new Promise((resolve, reject) => {
6317
+ const req = import_https.default.request(
6318
+ {
6319
+ hostname: parsed.hostname,
6320
+ port: parsed.port ? parseInt(parsed.port, 10) : void 0,
6321
+ path: parsed.pathname + parsed.search,
6322
+ method: "GET",
6323
+ headers: {
6324
+ Authorization: `Bearer ${apiKey}`,
6325
+ "Content-Type": "application/json"
6326
+ },
6327
+ timeout: 1e4
6328
+ },
6329
+ (res) => {
6330
+ const chunks = [];
6331
+ res.on("data", (chunk) => chunks.push(chunk));
6332
+ res.on("end", () => {
6333
+ if (res.statusCode !== 200) {
6334
+ reject(new Error(`API returned ${res.statusCode ?? "unknown"}`));
6335
+ return;
6336
+ }
6337
+ try {
6338
+ const body = JSON.parse(Buffer.concat(chunks).toString("utf-8"));
6339
+ const rules = Array.isArray(body) ? body : Array.isArray(body.rules) ? body.rules : [];
6340
+ resolve(rules);
6341
+ } catch (e) {
6342
+ reject(e);
6343
+ }
6344
+ });
6345
+ }
6346
+ );
6347
+ req.on("error", reject);
6348
+ req.on("timeout", () => {
6349
+ req.destroy(new Error("Cloud policy fetch timed out"));
6350
+ });
6351
+ req.end();
6352
+ });
6353
+ }
6354
+ async function syncOnce() {
6355
+ const creds = readCredentials();
6356
+ if (!creds) return;
6357
+ try {
6358
+ const rules = await fetchCloudRules(creds.apiKey, creds.apiUrl);
6359
+ const cache = { fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), rules };
6360
+ const dir = import_path20.default.dirname(rulesCacheFile());
6361
+ if (!import_fs17.default.existsSync(dir)) import_fs17.default.mkdirSync(dir, { recursive: true });
6362
+ import_fs17.default.writeFileSync(rulesCacheFile(), JSON.stringify(cache, null, 2) + "\n", "utf-8");
6363
+ } catch {
6364
+ }
6365
+ }
6366
+ async function runCloudSync() {
6367
+ const creds = readCredentials();
6368
+ if (!creds) {
6369
+ return { ok: false, reason: "No API key configured. Add credentials with: node9 login" };
6370
+ }
6371
+ try {
6372
+ const rules = await fetchCloudRules(creds.apiKey, creds.apiUrl);
6373
+ const cache = { fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), rules };
6374
+ const dir = import_path20.default.dirname(rulesCacheFile());
6375
+ if (!import_fs17.default.existsSync(dir)) import_fs17.default.mkdirSync(dir, { recursive: true });
6376
+ import_fs17.default.writeFileSync(rulesCacheFile(), JSON.stringify(cache, null, 2) + "\n", "utf-8");
6377
+ return { ok: true, rules: rules.length, fetchedAt: cache.fetchedAt };
6378
+ } catch (err2) {
6379
+ return { ok: false, reason: err2 instanceof Error ? err2.message : String(err2) };
6380
+ }
6381
+ }
6382
+ function getCloudSyncStatus() {
6383
+ try {
6384
+ const raw = JSON.parse(import_fs17.default.readFileSync(rulesCacheFile(), "utf-8"));
6385
+ if (!Array.isArray(raw.rules) || typeof raw.fetchedAt !== "string") return { cached: false };
6386
+ return { cached: true, rules: raw.rules.length, fetchedAt: raw.fetchedAt };
6387
+ } catch {
6388
+ return { cached: false };
6389
+ }
6390
+ }
6391
+ function getCloudRules() {
6392
+ try {
6393
+ const raw = JSON.parse(import_fs17.default.readFileSync(rulesCacheFile(), "utf-8"));
6394
+ return Array.isArray(raw.rules) ? raw.rules : null;
6395
+ } catch {
6396
+ return null;
6397
+ }
6398
+ }
6399
+ function startCloudSync() {
6400
+ const rawHours = getConfig().settings.cloudSyncIntervalHours ?? DEFAULT_INTERVAL_HOURS;
6401
+ const intervalHours = Math.max(rawHours, MIN_INTERVAL_HOURS);
6402
+ const intervalMs = intervalHours * 60 * 60 * 1e3;
6403
+ const initial = setTimeout(() => void syncOnce(), 3e4);
6404
+ initial.unref();
6405
+ const recurring = setInterval(() => void syncOnce(), intervalMs);
6406
+ recurring.unref();
6407
+ }
6408
+ var import_fs17, import_https, import_os15, import_path20, rulesCacheFile, DEFAULT_API_URL, DEFAULT_INTERVAL_HOURS, MIN_INTERVAL_HOURS;
6409
+ var init_sync = __esm({
6410
+ "src/daemon/sync.ts"() {
6411
+ "use strict";
6412
+ import_fs17 = __toESM(require("fs"));
6413
+ import_https = __toESM(require("https"));
6414
+ import_os15 = __toESM(require("os"));
6415
+ import_path20 = __toESM(require("path"));
6416
+ init_config();
6417
+ rulesCacheFile = () => import_path20.default.join(import_os15.default.homedir(), ".node9", "rules-cache.json");
6418
+ DEFAULT_API_URL = "https://api.node9.ai/api/v1/policy";
6419
+ DEFAULT_INTERVAL_HOURS = 5;
6420
+ MIN_INTERVAL_HOURS = 1;
6421
+ }
6422
+ });
6423
+
6244
6424
  // src/daemon/server.ts
6245
6425
  function startDaemon() {
6246
6426
  startCostSync();
6427
+ startCloudSync();
6247
6428
  loadInsightCounts();
6248
6429
  const csrfToken = (0, import_crypto7.randomUUID)();
6249
6430
  const internalToken = (0, import_crypto7.randomUUID)();
@@ -6259,7 +6440,7 @@ function startDaemon() {
6259
6440
  idleTimer = setTimeout(() => {
6260
6441
  if (autoStarted) {
6261
6442
  try {
6262
- import_fs17.default.unlinkSync(DAEMON_PID_FILE);
6443
+ import_fs18.default.unlinkSync(DAEMON_PID_FILE);
6263
6444
  } catch {
6264
6445
  }
6265
6446
  }
@@ -6422,7 +6603,7 @@ data: ${JSON.stringify(item.data)}
6422
6603
  status: "pending"
6423
6604
  });
6424
6605
  }
6425
- const projectCwd = typeof cwd === "string" && import_path20.default.isAbsolute(cwd) ? cwd : void 0;
6606
+ const projectCwd = typeof cwd === "string" && import_path21.default.isAbsolute(cwd) ? cwd : void 0;
6426
6607
  const projectConfig = getConfig(projectCwd);
6427
6608
  const browserEnabled = projectConfig.settings.approvers?.browser !== false;
6428
6609
  const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
@@ -6812,8 +6993,8 @@ data: ${JSON.stringify(item.data)}
6812
6993
  const body = await readBody(req);
6813
6994
  const data = body ? JSON.parse(body) : {};
6814
6995
  const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
6815
- const node9Dir = import_path20.default.dirname(GLOBAL_CONFIG_PATH);
6816
- if (!import_path20.default.resolve(configPath).startsWith(node9Dir + import_path20.default.sep)) {
6996
+ const node9Dir = import_path21.default.dirname(GLOBAL_CONFIG_PATH);
6997
+ if (!import_path21.default.resolve(configPath).startsWith(node9Dir + import_path21.default.sep)) {
6817
6998
  res.writeHead(400, { "Content-Type": "application/json" });
6818
6999
  return res.end(
6819
7000
  JSON.stringify({ error: "configPath must be within the node9 config directory" })
@@ -6924,14 +7105,14 @@ data: ${JSON.stringify(item.data)}
6924
7105
  server.on("error", (e) => {
6925
7106
  if (e.code === "EADDRINUSE") {
6926
7107
  try {
6927
- if (import_fs17.default.existsSync(DAEMON_PID_FILE)) {
6928
- const { pid } = JSON.parse(import_fs17.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
7108
+ if (import_fs18.default.existsSync(DAEMON_PID_FILE)) {
7109
+ const { pid } = JSON.parse(import_fs18.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
6929
7110
  process.kill(pid, 0);
6930
7111
  return process.exit(0);
6931
7112
  }
6932
7113
  } catch {
6933
7114
  try {
6934
- import_fs17.default.unlinkSync(DAEMON_PID_FILE);
7115
+ import_fs18.default.unlinkSync(DAEMON_PID_FILE);
6935
7116
  } catch {
6936
7117
  }
6937
7118
  server.listen(DAEMON_PORT, DAEMON_HOST);
@@ -6990,13 +7171,13 @@ data: ${JSON.stringify(item.data)}
6990
7171
  }
6991
7172
  startActivitySocket();
6992
7173
  }
6993
- var import_http, import_fs17, import_path20, import_crypto7, import_child_process4, import_chalk2;
7174
+ var import_http, import_fs18, import_path21, import_crypto7, import_child_process4, import_chalk2;
6994
7175
  var init_server = __esm({
6995
7176
  "src/daemon/server.ts"() {
6996
7177
  "use strict";
6997
7178
  import_http = __toESM(require("http"));
6998
- import_fs17 = __toESM(require("fs"));
6999
- import_path20 = __toESM(require("path"));
7179
+ import_fs18 = __toESM(require("fs"));
7180
+ import_path21 = __toESM(require("path"));
7000
7181
  import_crypto7 = require("crypto");
7001
7182
  import_child_process4 = require("child_process");
7002
7183
  import_chalk2 = __toESM(require("chalk"));
@@ -7007,57 +7188,327 @@ var init_server = __esm({
7007
7188
  init_patch();
7008
7189
  init_config_schema();
7009
7190
  init_costSync();
7191
+ init_sync();
7192
+ }
7193
+ });
7194
+
7195
+ // src/daemon/service.ts
7196
+ function resolveNode9Binary() {
7197
+ try {
7198
+ const script = process.argv[1];
7199
+ if (typeof script === "string" && import_path22.default.isAbsolute(script) && import_fs19.default.existsSync(script)) {
7200
+ return import_fs19.default.realpathSync(script);
7201
+ }
7202
+ } catch {
7203
+ }
7204
+ try {
7205
+ const cmd = process.platform === "win32" ? "where" : "which";
7206
+ const r = (0, import_child_process5.spawnSync)(cmd, ["node9"], { encoding: "utf8", timeout: 3e3 });
7207
+ if (r.status === 0 && r.stdout.trim()) {
7208
+ return r.stdout.trim().split("\n")[0].trim();
7209
+ }
7210
+ } catch {
7211
+ }
7212
+ return null;
7213
+ }
7214
+ function xmlEscape(s) {
7215
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
7216
+ }
7217
+ function launchdPlist(binaryPath) {
7218
+ const logDir = import_path22.default.join(import_os16.default.homedir(), ".node9");
7219
+ const nodePath = xmlEscape(process.execPath);
7220
+ const scriptPath = xmlEscape(binaryPath);
7221
+ const outLog = xmlEscape(import_path22.default.join(logDir, "daemon.log"));
7222
+ const errLog = xmlEscape(import_path22.default.join(logDir, "daemon-error.log"));
7223
+ return `<?xml version="1.0" encoding="UTF-8"?>
7224
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
7225
+ <plist version="1.0">
7226
+ <dict>
7227
+ <key>Label</key>
7228
+ <string>${LAUNCHD_LABEL}</string>
7229
+ <key>ProgramArguments</key>
7230
+ <array>
7231
+ <string>${nodePath}</string>
7232
+ <string>${scriptPath}</string>
7233
+ <string>daemon</string>
7234
+ </array>
7235
+ <key>RunAtLoad</key>
7236
+ <true/>
7237
+ <key>KeepAlive</key>
7238
+ <true/>
7239
+ <key>ThrottleInterval</key>
7240
+ <integer>10</integer>
7241
+ <key>StandardOutPath</key>
7242
+ <string>${outLog}</string>
7243
+ <key>StandardErrorPath</key>
7244
+ <string>${errLog}</string>
7245
+ <key>EnvironmentVariables</key>
7246
+ <dict>
7247
+ <key>NODE9_AUTO_STARTED</key>
7248
+ <string>1</string>
7249
+ <key>NODE9_BROWSER_OPENED</key>
7250
+ <string>1</string>
7251
+ </dict>
7252
+ </dict>
7253
+ </plist>
7254
+ `;
7255
+ }
7256
+ function installLaunchd(binaryPath) {
7257
+ const dir = import_path22.default.dirname(LAUNCHD_PLIST);
7258
+ if (!import_fs19.default.existsSync(dir)) import_fs19.default.mkdirSync(dir, { recursive: true });
7259
+ import_fs19.default.writeFileSync(LAUNCHD_PLIST, launchdPlist(binaryPath), "utf-8");
7260
+ (0, import_child_process5.spawnSync)("launchctl", ["unload", LAUNCHD_PLIST], { encoding: "utf8" });
7261
+ const r = (0, import_child_process5.spawnSync)("launchctl", ["load", "-w", LAUNCHD_PLIST], {
7262
+ encoding: "utf8",
7263
+ timeout: 5e3
7264
+ });
7265
+ if (r.status !== 0) {
7266
+ throw new Error(`launchctl load failed: ${r.stderr || r.stdout || "unknown error"}`);
7267
+ }
7268
+ }
7269
+ function uninstallLaunchd() {
7270
+ if (import_fs19.default.existsSync(LAUNCHD_PLIST)) {
7271
+ (0, import_child_process5.spawnSync)("launchctl", ["unload", "-w", LAUNCHD_PLIST], { encoding: "utf8", timeout: 5e3 });
7272
+ import_fs19.default.unlinkSync(LAUNCHD_PLIST);
7273
+ }
7274
+ }
7275
+ function isLaunchdInstalled() {
7276
+ return import_fs19.default.existsSync(LAUNCHD_PLIST);
7277
+ }
7278
+ function systemdUnit(binaryPath) {
7279
+ return `[Unit]
7280
+ Description=node9 approval daemon
7281
+ After=network.target
7282
+
7283
+ [Service]
7284
+ Type=simple
7285
+ ExecStart=${process.execPath} ${binaryPath} daemon
7286
+ Restart=on-failure
7287
+ RestartSec=10s
7288
+ Environment=NODE9_AUTO_STARTED=1
7289
+ Environment=NODE9_BROWSER_OPENED=1
7290
+
7291
+ [Install]
7292
+ WantedBy=default.target
7293
+ `;
7294
+ }
7295
+ function installSystemd(binaryPath) {
7296
+ if (!import_fs19.default.existsSync(SYSTEMD_UNIT_DIR)) {
7297
+ import_fs19.default.mkdirSync(SYSTEMD_UNIT_DIR, { recursive: true });
7298
+ }
7299
+ import_fs19.default.writeFileSync(SYSTEMD_UNIT, systemdUnit(binaryPath), "utf-8");
7300
+ try {
7301
+ (0, import_child_process5.execFileSync)("loginctl", ["enable-linger", import_os16.default.userInfo().username], { timeout: 3e3 });
7302
+ } catch {
7303
+ }
7304
+ const reload = (0, import_child_process5.spawnSync)("systemctl", ["--user", "daemon-reload"], {
7305
+ encoding: "utf8",
7306
+ timeout: 5e3
7307
+ });
7308
+ if (reload.status !== 0) {
7309
+ throw new Error(`systemctl daemon-reload failed: ${reload.stderr}`);
7310
+ }
7311
+ (0, import_child_process5.spawnSync)("systemctl", ["--user", "stop", "node9-daemon"], { encoding: "utf8", timeout: 3e3 });
7312
+ const enable = (0, import_child_process5.spawnSync)("systemctl", ["--user", "enable", "--now", "node9-daemon"], {
7313
+ encoding: "utf8",
7314
+ timeout: 5e3
7315
+ });
7316
+ if (enable.status !== 0) {
7317
+ throw new Error(`systemctl enable failed: ${enable.stderr}`);
7318
+ }
7319
+ }
7320
+ function uninstallSystemd() {
7321
+ if (import_fs19.default.existsSync(SYSTEMD_UNIT)) {
7322
+ (0, import_child_process5.spawnSync)("systemctl", ["--user", "disable", "--now", "node9-daemon"], {
7323
+ encoding: "utf8",
7324
+ timeout: 5e3
7325
+ });
7326
+ (0, import_child_process5.spawnSync)("systemctl", ["--user", "daemon-reload"], { encoding: "utf8", timeout: 5e3 });
7327
+ import_fs19.default.unlinkSync(SYSTEMD_UNIT);
7328
+ }
7329
+ }
7330
+ function isSystemdInstalled() {
7331
+ return import_fs19.default.existsSync(SYSTEMD_UNIT);
7332
+ }
7333
+ function stopRunningDaemon() {
7334
+ const pidFile = import_path22.default.join(import_os16.default.homedir(), ".node9", "daemon.pid");
7335
+ if (!import_fs19.default.existsSync(pidFile)) return;
7336
+ try {
7337
+ const data = JSON.parse(import_fs19.default.readFileSync(pidFile, "utf-8"));
7338
+ const pid = data.pid;
7339
+ const MAX_PID2 = 4194304;
7340
+ if (typeof pid === "number" && Number.isInteger(pid) && pid > 0 && pid <= MAX_PID2) {
7341
+ try {
7342
+ process.kill(pid, "SIGTERM");
7343
+ const deadline = Date.now() + 3e3;
7344
+ const pollStop = (0, import_child_process5.spawnSync)(
7345
+ "sh",
7346
+ ["-c", `while kill -0 ${pid} 2>/dev/null; do sleep 0.1; done`],
7347
+ {
7348
+ timeout: 3100
7349
+ }
7350
+ );
7351
+ void pollStop;
7352
+ void deadline;
7353
+ } catch {
7354
+ }
7355
+ }
7356
+ try {
7357
+ import_fs19.default.unlinkSync(pidFile);
7358
+ } catch {
7359
+ }
7360
+ } catch {
7361
+ }
7362
+ }
7363
+ function installDaemonService() {
7364
+ const binary = resolveNode9Binary();
7365
+ if (!binary) {
7366
+ return { ok: false, reason: "Could not locate the node9 binary. Is it in your PATH?" };
7367
+ }
7368
+ stopRunningDaemon();
7369
+ try {
7370
+ if (process.platform === "darwin") {
7371
+ const alreadyInstalled = isLaunchdInstalled();
7372
+ installLaunchd(binary);
7373
+ return { ok: true, platform: "launchd", alreadyInstalled };
7374
+ }
7375
+ if (process.platform === "linux") {
7376
+ const check = (0, import_child_process5.spawnSync)("systemctl", ["--user", "--version"], {
7377
+ encoding: "utf8",
7378
+ timeout: 2e3
7379
+ });
7380
+ if (check.status !== 0) {
7381
+ return {
7382
+ ok: false,
7383
+ reason: "systemd not available. Start the daemon manually with: node9 daemon start"
7384
+ };
7385
+ }
7386
+ const alreadyInstalled = isSystemdInstalled();
7387
+ installSystemd(binary);
7388
+ return { ok: true, platform: "systemd", alreadyInstalled };
7389
+ }
7390
+ return {
7391
+ ok: false,
7392
+ reason: `Automatic service install is not supported on ${process.platform}. Start the daemon manually with: node9 daemon start`
7393
+ };
7394
+ } catch (err2) {
7395
+ return {
7396
+ ok: false,
7397
+ reason: err2 instanceof Error ? err2.message : String(err2)
7398
+ };
7399
+ }
7400
+ }
7401
+ function uninstallDaemonService() {
7402
+ try {
7403
+ if (process.platform === "darwin") {
7404
+ uninstallLaunchd();
7405
+ return { ok: true, platform: "launchd", alreadyInstalled: false };
7406
+ }
7407
+ if (process.platform === "linux") {
7408
+ uninstallSystemd();
7409
+ return { ok: true, platform: "systemd", alreadyInstalled: false };
7410
+ }
7411
+ return {
7412
+ ok: false,
7413
+ reason: `Service management not supported on ${process.platform}.`
7414
+ };
7415
+ } catch (err2) {
7416
+ return {
7417
+ ok: false,
7418
+ reason: err2 instanceof Error ? err2.message : String(err2)
7419
+ };
7420
+ }
7421
+ }
7422
+ function isDaemonServiceInstalled() {
7423
+ if (process.platform === "darwin") return isLaunchdInstalled();
7424
+ if (process.platform === "linux") return isSystemdInstalled();
7425
+ return false;
7426
+ }
7427
+ var import_fs19, import_path22, import_os16, import_child_process5, LAUNCHD_LABEL, LAUNCHD_PLIST, SYSTEMD_UNIT_DIR, SYSTEMD_UNIT;
7428
+ var init_service = __esm({
7429
+ "src/daemon/service.ts"() {
7430
+ "use strict";
7431
+ import_fs19 = __toESM(require("fs"));
7432
+ import_path22 = __toESM(require("path"));
7433
+ import_os16 = __toESM(require("os"));
7434
+ import_child_process5 = require("child_process");
7435
+ LAUNCHD_LABEL = "ai.node9.daemon";
7436
+ LAUNCHD_PLIST = import_path22.default.join(import_os16.default.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
7437
+ SYSTEMD_UNIT_DIR = import_path22.default.join(import_os16.default.homedir(), ".config", "systemd", "user");
7438
+ SYSTEMD_UNIT = import_path22.default.join(SYSTEMD_UNIT_DIR, "node9-daemon.service");
7010
7439
  }
7011
7440
  });
7012
7441
 
7013
7442
  // src/daemon/index.ts
7014
7443
  function stopDaemon() {
7015
- if (!import_fs18.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
7444
+ if (!import_fs20.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
7016
7445
  try {
7017
- const { pid } = JSON.parse(import_fs18.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
7446
+ const data = JSON.parse(import_fs20.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
7447
+ const pid = data.pid;
7448
+ if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
7449
+ console.log(import_chalk3.default.gray("Cleaned up invalid PID file."));
7450
+ return;
7451
+ }
7018
7452
  process.kill(pid, "SIGTERM");
7019
7453
  console.log(import_chalk3.default.green("\u2705 Stopped."));
7020
7454
  } catch {
7021
7455
  console.log(import_chalk3.default.gray("Cleaned up stale PID file."));
7022
7456
  } finally {
7023
7457
  try {
7024
- import_fs18.default.unlinkSync(DAEMON_PID_FILE);
7458
+ import_fs20.default.unlinkSync(DAEMON_PID_FILE);
7025
7459
  } catch {
7026
7460
  }
7027
7461
  }
7028
7462
  }
7029
7463
  function daemonStatus() {
7030
- if (import_fs18.default.existsSync(DAEMON_PID_FILE)) {
7464
+ const serviceInstalled = isDaemonServiceInstalled();
7465
+ const serviceLabel = serviceInstalled ? import_chalk3.default.green("installed (starts on login)") : import_chalk3.default.yellow("not installed \u2014 run: node9 daemon install");
7466
+ let processStatus;
7467
+ if (import_fs20.default.existsSync(DAEMON_PID_FILE)) {
7031
7468
  try {
7032
- const { pid } = JSON.parse(import_fs18.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
7033
- process.kill(pid, 0);
7034
- console.log(import_chalk3.default.green("Node9 daemon: running"));
7035
- return;
7469
+ const data = JSON.parse(import_fs20.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
7470
+ const pid = data.pid;
7471
+ const port = data.port;
7472
+ if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
7473
+ processStatus = import_chalk3.default.yellow("not running (invalid PID file)");
7474
+ } else {
7475
+ process.kill(pid, 0);
7476
+ processStatus = import_chalk3.default.green(
7477
+ `running (PID ${pid}, port ${typeof port === "number" ? port : DAEMON_PORT})`
7478
+ );
7479
+ }
7036
7480
  } catch {
7037
- console.log(import_chalk3.default.yellow("Node9 daemon: not running (stale PID)"));
7038
- return;
7481
+ processStatus = import_chalk3.default.yellow("not running (stale PID file)");
7039
7482
  }
7040
- }
7041
- const r = (0, import_child_process5.spawnSync)("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
7042
- encoding: "utf8",
7043
- timeout: 500
7044
- });
7045
- if (r.status === 0 && (r.stdout ?? "").includes(`:${DAEMON_PORT}`)) {
7046
- console.log(import_chalk3.default.yellow("Node9 daemon: running (no PID file \u2014 orphaned)"));
7047
7483
  } else {
7048
- console.log(import_chalk3.default.yellow("Node9 daemon: not running"));
7484
+ const r = (0, import_child_process6.spawnSync)("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
7485
+ encoding: "utf8",
7486
+ timeout: 500
7487
+ });
7488
+ if (r.status === 0 && (r.stdout ?? "").includes(`:${DAEMON_PORT}`)) {
7489
+ processStatus = import_chalk3.default.yellow(`running (orphaned \u2014 no PID file)`);
7490
+ } else {
7491
+ processStatus = import_chalk3.default.yellow("not running");
7492
+ }
7049
7493
  }
7494
+ console.log(`
7495
+ Process : ${processStatus}`);
7496
+ console.log(` Service : ${serviceLabel}
7497
+ `);
7050
7498
  }
7051
- var import_fs18, import_chalk3, import_child_process5;
7499
+ var import_fs20, import_chalk3, import_child_process6, MAX_PID;
7052
7500
  var init_daemon2 = __esm({
7053
7501
  "src/daemon/index.ts"() {
7054
7502
  "use strict";
7055
- import_fs18 = __toESM(require("fs"));
7503
+ import_fs20 = __toESM(require("fs"));
7056
7504
  import_chalk3 = __toESM(require("chalk"));
7057
- import_child_process5 = require("child_process");
7505
+ import_child_process6 = require("child_process");
7058
7506
  init_server();
7059
7507
  init_state2();
7508
+ init_service();
7060
7509
  init_state2();
7510
+ init_service();
7511
+ MAX_PID = 4194304;
7061
7512
  }
7062
7513
  });
7063
7514
 
@@ -7086,22 +7537,22 @@ function formatBase(activity) {
7086
7537
  const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
7087
7538
  const icon = getIcon(activity.tool);
7088
7539
  const toolName = activity.tool.slice(0, 16).padEnd(16);
7089
- const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os25.default.homedir(), "~");
7540
+ const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os29.default.homedir(), "~");
7090
7541
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
7091
- return `${import_chalk19.default.gray(time)} ${icon} ${import_chalk19.default.white.bold(toolName)} ${import_chalk19.default.dim(argsPreview)}`;
7542
+ return `${import_chalk23.default.gray(time)} ${icon} ${import_chalk23.default.white.bold(toolName)} ${import_chalk23.default.dim(argsPreview)}`;
7092
7543
  }
7093
7544
  function renderResult(activity, result) {
7094
7545
  const base = formatBase(activity);
7095
7546
  let status;
7096
7547
  if (result.status === "allow") {
7097
- status = import_chalk19.default.green("\u2713 ALLOW");
7548
+ status = import_chalk23.default.green("\u2713 ALLOW");
7098
7549
  } else if (result.status === "dlp") {
7099
- status = import_chalk19.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
7550
+ status = import_chalk23.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
7100
7551
  } else {
7101
- status = import_chalk19.default.red("\u2717 BLOCK");
7552
+ status = import_chalk23.default.red("\u2717 BLOCK");
7102
7553
  }
7103
7554
  const cost = result.costEstimate ?? activity.costEstimate;
7104
- const costSuffix = cost == null ? "" : import_chalk19.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
7555
+ const costSuffix = cost == null ? "" : import_chalk23.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
7105
7556
  if (process.stdout.isTTY) {
7106
7557
  if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
7107
7558
  import_readline5.default.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
@@ -7118,19 +7569,19 @@ function renderResult(activity, result) {
7118
7569
  }
7119
7570
  function renderPending(activity) {
7120
7571
  if (!process.stdout.isTTY) return;
7121
- const line = `${formatBase(activity)} ${import_chalk19.default.yellow("\u25CF \u2026")}`;
7572
+ const line = `${formatBase(activity)} ${import_chalk23.default.yellow("\u25CF \u2026")}`;
7122
7573
  pendingShownForId = activity.id;
7123
7574
  pendingWrappedLines = wrappedLineCount(line);
7124
7575
  process.stdout.write(`${line}\r`);
7125
7576
  }
7126
7577
  async function ensureDaemon() {
7127
7578
  let pidPort = null;
7128
- if (import_fs29.default.existsSync(PID_FILE)) {
7579
+ if (import_fs33.default.existsSync(PID_FILE)) {
7129
7580
  try {
7130
- const { port } = JSON.parse(import_fs29.default.readFileSync(PID_FILE, "utf-8"));
7581
+ const { port } = JSON.parse(import_fs33.default.readFileSync(PID_FILE, "utf-8"));
7131
7582
  pidPort = port;
7132
7583
  } catch {
7133
- console.error(import_chalk19.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
7584
+ console.error(import_chalk23.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
7134
7585
  }
7135
7586
  }
7136
7587
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -7141,8 +7592,8 @@ async function ensureDaemon() {
7141
7592
  if (res.ok) return checkPort;
7142
7593
  } catch {
7143
7594
  }
7144
- console.log(import_chalk19.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
7145
- const child = (0, import_child_process14.spawn)(process.execPath, [process.argv[1], "daemon"], {
7595
+ console.log(import_chalk23.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
7596
+ const child = (0, import_child_process15.spawn)(process.execPath, [process.argv[1], "daemon"], {
7146
7597
  detached: true,
7147
7598
  stdio: "ignore",
7148
7599
  env: { ...process.env, NODE9_AUTO_STARTED: "1" }
@@ -7158,7 +7609,7 @@ async function ensureDaemon() {
7158
7609
  } catch {
7159
7610
  }
7160
7611
  }
7161
- console.error(import_chalk19.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
7612
+ console.error(import_chalk23.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
7162
7613
  process.exit(1);
7163
7614
  }
7164
7615
  function postDecisionHttp(id, decision, csrfToken, port, opts) {
@@ -7190,27 +7641,56 @@ function postDecisionHttp(id, decision, csrfToken, port, opts) {
7190
7641
  req.end(body);
7191
7642
  });
7192
7643
  }
7644
+ function extractArgsSummary(toolName, args) {
7645
+ const a = args;
7646
+ if (!a) return "";
7647
+ const cmd = a["command"] ?? a["cmd"];
7648
+ if (typeof cmd === "string") return cmd.replace(/\s+/g, " ").trim();
7649
+ const fp = a["file_path"] ?? a["path"] ?? a["filepath"];
7650
+ if (typeof fp === "string") return fp;
7651
+ const q = a["query"] ?? a["sql"];
7652
+ if (typeof q === "string") return q.replace(/\s+/g, " ").trim();
7653
+ const s = JSON.stringify(a).replace(/\s+/g, " ");
7654
+ return s.length > 80 ? s.slice(0, 80) + "\u2026" : s;
7655
+ }
7656
+ function cleanReason(raw) {
7657
+ return raw.replace(/\s*[—–-]+\s*(blocked by|requires human approval)[^)]*(\([^)]*\))?\.?\s*$/i, "").trim();
7658
+ }
7659
+ function cleanBlockedBy(raw) {
7660
+ const shieldMatch = raw.match(/shield:([^:]+):/i);
7661
+ if (shieldMatch) return shieldMatch[1];
7662
+ const smartMatch = raw.match(/^Smart Rule:\s*(.+)$/i);
7663
+ if (smartMatch) return smartMatch[1];
7664
+ return raw;
7665
+ }
7193
7666
  function buildCardLines(req, localCount = 0) {
7194
7667
  if (req.recoveryCommand) {
7195
7668
  return buildRecoveryCardLines(req);
7196
7669
  }
7197
- const argsStr = JSON.stringify(req.args ?? {}).replace(/\s+/g, " ");
7198
- const argsPreview = argsStr.length > 60 ? argsStr.slice(0, 60) + "\u2026" : argsStr;
7199
- const tierLabel = req.riskMetadata?.tier != null ? req.riskMetadata.tier <= 2 ? `${YELLOW}\u26A0 Tier ${req.riskMetadata.tier}` : `${RED}\u{1F6D1} Tier ${req.riskMetadata.tier}` : `${YELLOW}\u26A0 Review`;
7200
- const blockedBy = req.riskMetadata?.blockedByLabel ?? "Policy rule";
7670
+ const argsSummary = extractArgsSummary(req.toolName, req.args);
7671
+ const argsPreview = argsSummary.length > 72 ? argsSummary.slice(0, 72) + "\u2026" : argsSummary;
7672
+ const rawBlockedBy = req.riskMetadata?.blockedByLabel ?? "Policy rule";
7673
+ const blockedBy = cleanBlockedBy(rawBlockedBy);
7674
+ const isBlock = req.riskMetadata?.tier != null && req.riskMetadata.tier <= 2;
7675
+ const severityIcon = isBlock ? `${RED}\u{1F6D1}` : `${YELLOW}\u26A0 `;
7676
+ const rawDesc = req.riskMetadata?.ruleDescription ?? "";
7677
+ const description = rawDesc ? cleanReason(rawDesc) : "";
7201
7678
  const lines = [
7202
7679
  ``,
7203
7680
  `${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
7204
7681
  `${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}`,
7205
- `${CYAN}\u2551${RESET2} Reason: ${tierLabel} \u2014 ${blockedBy}${RESET2}`
7682
+ `${CYAN}\u2551${RESET2} Policy: ${severityIcon} ${blockedBy}${RESET2}`
7206
7683
  ];
7207
- if (req.riskMetadata?.ruleDescription) {
7208
- lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u2139 ${req.riskMetadata.ruleDescription}${RESET2}`);
7684
+ if (description) {
7685
+ lines.push(`${CYAN}\u2551${RESET2} Why: ${YELLOW}${description}${RESET2}`);
7209
7686
  }
7210
- if (req.riskMetadata?.ruleName && blockedBy.includes("Taint")) {
7687
+ if (req.riskMetadata?.ruleName && rawBlockedBy.includes("Taint")) {
7211
7688
  lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u26A0 ${req.riskMetadata.ruleName}${RESET2}`);
7212
7689
  }
7213
- lines.push(`${CYAN}\u2551${RESET2} Args: ${GRAY}${argsPreview}${RESET2}`);
7690
+ if (argsPreview) {
7691
+ const argLabel = req.toolName.toLowerCase().includes("bash") ? "Command" : "Args ";
7692
+ lines.push(`${CYAN}\u2551${RESET2} ${argLabel}: ${GRAY}${argsPreview}${RESET2}`);
7693
+ }
7214
7694
  if (localCount >= 2) {
7215
7695
  lines.push(
7216
7696
  `${CYAN}\u2551${RESET2} ${YELLOW}\u{1F4A1}${RESET2} Approved ${localCount}\xD7 before \u2014 ${BOLD2}[a]${RESET2}${YELLOW} creates a permanent rule${RESET2}`
@@ -7250,9 +7730,9 @@ function buildRecoveryCardLines(req) {
7250
7730
  ];
7251
7731
  }
7252
7732
  function readApproversFromDisk() {
7253
- const configPath = import_path32.default.join(import_os25.default.homedir(), ".node9", "config.json");
7733
+ const configPath = import_path36.default.join(import_os29.default.homedir(), ".node9", "config.json");
7254
7734
  try {
7255
- const raw = JSON.parse(import_fs29.default.readFileSync(configPath, "utf-8"));
7735
+ const raw = JSON.parse(import_fs33.default.readFileSync(configPath, "utf-8"));
7256
7736
  const settings = raw.settings ?? {};
7257
7737
  return settings.approvers ?? {};
7258
7738
  } catch {
@@ -7263,20 +7743,20 @@ function approverStatusLine() {
7263
7743
  const a = readApproversFromDisk();
7264
7744
  const fmt = (label, key) => {
7265
7745
  const on = a[key] !== false;
7266
- return `[${key[0]}]${label.slice(1)} ${on ? import_chalk19.default.green("\u2713") : import_chalk19.default.dim("\u2717")}`;
7746
+ return `[${key[0]}]${label.slice(1)} ${on ? import_chalk23.default.green("\u2713") : import_chalk23.default.dim("\u2717")}`;
7267
7747
  };
7268
7748
  return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
7269
7749
  }
7270
7750
  function toggleApprover(channel) {
7271
- const configPath = import_path32.default.join(import_os25.default.homedir(), ".node9", "config.json");
7751
+ const configPath = import_path36.default.join(import_os29.default.homedir(), ".node9", "config.json");
7272
7752
  try {
7273
- const raw = JSON.parse(import_fs29.default.readFileSync(configPath, "utf-8"));
7753
+ const raw = JSON.parse(import_fs33.default.readFileSync(configPath, "utf-8"));
7274
7754
  const settings = raw.settings ?? {};
7275
7755
  const approvers = settings.approvers ?? {};
7276
7756
  approvers[channel] = approvers[channel] === false;
7277
7757
  settings.approvers = approvers;
7278
7758
  raw.settings = settings;
7279
- import_fs29.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
7759
+ import_fs33.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
7280
7760
  } catch (err2) {
7281
7761
  process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
7282
7762
  `);
@@ -7308,7 +7788,7 @@ async function startTail(options = {}) {
7308
7788
  req2.end();
7309
7789
  });
7310
7790
  if (result.ok) {
7311
- console.log(import_chalk19.default.green("\u2713 Flight Recorder buffer cleared."));
7791
+ console.log(import_chalk23.default.green("\u2713 Flight Recorder buffer cleared."));
7312
7792
  } else if (result.code === "ECONNREFUSED") {
7313
7793
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
7314
7794
  } else if (result.code === "ETIMEDOUT") {
@@ -7352,7 +7832,7 @@ async function startTail(options = {}) {
7352
7832
  const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
7353
7833
  if (channel) {
7354
7834
  toggleApprover(channel);
7355
- console.log(import_chalk19.default.dim(` Approvers: ${approverStatusLine()}`));
7835
+ console.log(import_chalk23.default.dim(` Approvers: ${approverStatusLine()}`));
7356
7836
  }
7357
7837
  };
7358
7838
  process.stdin.on("keypress", idleKeypressHandler);
@@ -7418,7 +7898,7 @@ async function startTail(options = {}) {
7418
7898
  localAllowCounts.get(req2.toolName) ?? 0
7419
7899
  )
7420
7900
  );
7421
- const decisionStamp = action === "always-allow" ? import_chalk19.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk19.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk19.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk19.default.yellow("\u21A9 REDIRECT AI") : import_chalk19.default.red("\u2717 DENIED");
7901
+ const decisionStamp = action === "always-allow" ? import_chalk23.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk23.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk23.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk23.default.yellow("\u21A9 REDIRECT AI") : import_chalk23.default.red("\u2717 DENIED");
7422
7902
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
7423
7903
  for (const line of stampedLines) process.stdout.write(line + "\n");
7424
7904
  process.stdout.write(SHOW_CURSOR);
@@ -7446,8 +7926,8 @@ async function startTail(options = {}) {
7446
7926
  }
7447
7927
  postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
7448
7928
  try {
7449
- import_fs29.default.appendFileSync(
7450
- import_path32.default.join(import_os25.default.homedir(), ".node9", "hook-debug.log"),
7929
+ import_fs33.default.appendFileSync(
7930
+ import_path36.default.join(import_os29.default.homedir(), ".node9", "hook-debug.log"),
7451
7931
  `[tail] POST /decision failed: ${String(err2)}
7452
7932
  `
7453
7933
  );
@@ -7469,7 +7949,7 @@ async function startTail(options = {}) {
7469
7949
  );
7470
7950
  const stampedLines = buildCardLines(req2, priorCount);
7471
7951
  if (externalDecision) {
7472
- const source = externalDecision === "allow" ? import_chalk19.default.green("\u2713 ALLOWED") : import_chalk19.default.red("\u2717 DENIED");
7952
+ const source = externalDecision === "allow" ? import_chalk23.default.green("\u2713 ALLOWED") : import_chalk23.default.red("\u2717 DENIED");
7473
7953
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
7474
7954
  }
7475
7955
  for (const line of stampedLines) process.stdout.write(line + "\n");
@@ -7515,10 +7995,10 @@ async function startTail(options = {}) {
7515
7995
  try {
7516
7996
  const browserEnabled = getConfig().settings.approvers?.browser !== false;
7517
7997
  if (browserEnabled) {
7518
- if (process.platform === "darwin") (0, import_child_process14.execSync)(`open "${dashboardUrl}"`, { stdio: "ignore" });
7998
+ if (process.platform === "darwin") (0, import_child_process15.execSync)(`open "${dashboardUrl}"`, { stdio: "ignore" });
7519
7999
  else if (process.platform === "win32")
7520
- (0, import_child_process14.execSync)(`cmd /c start "" "${dashboardUrl}"`, { stdio: "ignore" });
7521
- else (0, import_child_process14.execSync)(`xdg-open "${dashboardUrl}"`, { stdio: "ignore" });
8000
+ (0, import_child_process15.execSync)(`cmd /c start "" "${dashboardUrl}"`, { stdio: "ignore" });
8001
+ else (0, import_child_process15.execSync)(`xdg-open "${dashboardUrl}"`, { stdio: "ignore" });
7522
8002
  const intToken = getInternalToken();
7523
8003
  fetch(`http://127.0.0.1:${port}/browser-opened`, {
7524
8004
  method: "POST",
@@ -7528,16 +8008,16 @@ async function startTail(options = {}) {
7528
8008
  }
7529
8009
  } catch {
7530
8010
  }
7531
- console.log(import_chalk19.default.cyan.bold(`
7532
- \u{1F6F0}\uFE0F Node9 tail `) + import_chalk19.default.dim(`\u2192 ${dashboardUrl}`));
8011
+ console.log(import_chalk23.default.cyan.bold(`
8012
+ \u{1F6F0}\uFE0F Node9 tail `) + import_chalk23.default.dim(`\u2192 ${dashboardUrl}`));
7533
8013
  if (canApprove) {
7534
- console.log(import_chalk19.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
7535
- console.log(import_chalk19.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
8014
+ console.log(import_chalk23.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
8015
+ console.log(import_chalk23.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
7536
8016
  }
7537
8017
  if (options.history) {
7538
- console.log(import_chalk19.default.dim("Showing history + live events.\n"));
8018
+ console.log(import_chalk23.default.dim("Showing history + live events.\n"));
7539
8019
  } else {
7540
- console.log(import_chalk19.default.dim("Showing live events only. Use --history to include past.\n"));
8020
+ console.log(import_chalk23.default.dim("Showing live events only. Use --history to include past.\n"));
7541
8021
  }
7542
8022
  process.on("SIGINT", () => {
7543
8023
  exitIdleMode();
@@ -7547,13 +8027,13 @@ async function startTail(options = {}) {
7547
8027
  import_readline5.default.clearLine(process.stdout, 0);
7548
8028
  import_readline5.default.cursorTo(process.stdout, 0);
7549
8029
  }
7550
- console.log(import_chalk19.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
8030
+ console.log(import_chalk23.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
7551
8031
  process.exit(0);
7552
8032
  });
7553
8033
  const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
7554
8034
  const req = import_http2.default.get(sseUrl, (res) => {
7555
8035
  if (res.statusCode !== 200) {
7556
- console.error(import_chalk19.default.red(`Failed to connect: HTTP ${res.statusCode}`));
8036
+ console.error(import_chalk23.default.red(`Failed to connect: HTTP ${res.statusCode}`));
7557
8037
  process.exit(1);
7558
8038
  }
7559
8039
  if (canApprove) enterIdleMode();
@@ -7584,7 +8064,7 @@ async function startTail(options = {}) {
7584
8064
  import_readline5.default.clearLine(process.stdout, 0);
7585
8065
  import_readline5.default.cursorTo(process.stdout, 0);
7586
8066
  }
7587
- console.log(import_chalk19.default.red("\n\u274C Daemon disconnected."));
8067
+ console.log(import_chalk23.default.red("\n\u274C Daemon disconnected."));
7588
8068
  process.exit(1);
7589
8069
  });
7590
8070
  });
@@ -7676,9 +8156,9 @@ async function startTail(options = {}) {
7676
8156
  const hash = data.hash ?? "";
7677
8157
  const summary = data.argsSummary ?? data.tool;
7678
8158
  const fileCount = data.fileCount ?? 0;
7679
- const files = fileCount > 0 ? import_chalk19.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
8159
+ const files = fileCount > 0 ? import_chalk23.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
7680
8160
  process.stdout.write(
7681
- `${import_chalk19.default.dim(time)} ${import_chalk19.default.cyan("\u{1F4F8} snapshot")} ${import_chalk19.default.dim(hash)} ${summary}${files}
8161
+ `${import_chalk23.default.dim(time)} ${import_chalk23.default.cyan("\u{1F4F8} snapshot")} ${import_chalk23.default.dim(hash)} ${summary}${files}
7682
8162
  `
7683
8163
  );
7684
8164
  return;
@@ -7695,26 +8175,26 @@ async function startTail(options = {}) {
7695
8175
  }
7696
8176
  req.on("error", (err2) => {
7697
8177
  const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
7698
- console.error(import_chalk19.default.red(`
8178
+ console.error(import_chalk23.default.red(`
7699
8179
  \u274C ${msg}`));
7700
8180
  process.exit(1);
7701
8181
  });
7702
8182
  }
7703
- var import_http2, import_chalk19, import_fs29, import_os25, import_path32, import_readline5, import_child_process14, PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
8183
+ var import_http2, import_chalk23, import_fs33, import_os29, import_path36, import_readline5, import_child_process15, PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
7704
8184
  var init_tail = __esm({
7705
8185
  "src/tui/tail.ts"() {
7706
8186
  "use strict";
7707
8187
  import_http2 = __toESM(require("http"));
7708
- import_chalk19 = __toESM(require("chalk"));
7709
- import_fs29 = __toESM(require("fs"));
7710
- import_os25 = __toESM(require("os"));
7711
- import_path32 = __toESM(require("path"));
8188
+ import_chalk23 = __toESM(require("chalk"));
8189
+ import_fs33 = __toESM(require("fs"));
8190
+ import_os29 = __toESM(require("os"));
8191
+ import_path36 = __toESM(require("path"));
7712
8192
  import_readline5 = __toESM(require("readline"));
7713
- import_child_process14 = require("child_process");
8193
+ import_child_process15 = require("child_process");
7714
8194
  init_daemon2();
7715
8195
  init_daemon();
7716
8196
  init_core();
7717
- PID_FILE = import_path32.default.join(import_os25.default.homedir(), ".node9", "daemon.pid");
8197
+ PID_FILE = import_path36.default.join(import_os29.default.homedir(), ".node9", "daemon.pid");
7718
8198
  ICONS = {
7719
8199
  bash: "\u{1F4BB}",
7720
8200
  shell: "\u{1F4BB}",
@@ -7829,9 +8309,9 @@ function formatTimeLeft(resetsAt) {
7829
8309
  return ` (${m}m left)`;
7830
8310
  }
7831
8311
  function safeReadJson(filePath) {
7832
- if (!import_fs30.default.existsSync(filePath)) return null;
8312
+ if (!import_fs34.default.existsSync(filePath)) return null;
7833
8313
  try {
7834
- return JSON.parse(import_fs30.default.readFileSync(filePath, "utf-8"));
8314
+ return JSON.parse(import_fs34.default.readFileSync(filePath, "utf-8"));
7835
8315
  } catch {
7836
8316
  return null;
7837
8317
  }
@@ -7852,12 +8332,12 @@ function countHooksInFile(filePath) {
7852
8332
  return Object.keys(cfg.hooks).length;
7853
8333
  }
7854
8334
  function countRulesInDir(rulesDir) {
7855
- if (!import_fs30.default.existsSync(rulesDir)) return 0;
8335
+ if (!import_fs34.default.existsSync(rulesDir)) return 0;
7856
8336
  let count = 0;
7857
8337
  try {
7858
- for (const entry of import_fs30.default.readdirSync(rulesDir, { withFileTypes: true })) {
8338
+ for (const entry of import_fs34.default.readdirSync(rulesDir, { withFileTypes: true })) {
7859
8339
  if (entry.isDirectory()) {
7860
- count += countRulesInDir(import_path33.default.join(rulesDir, entry.name));
8340
+ count += countRulesInDir(import_path37.default.join(rulesDir, entry.name));
7861
8341
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
7862
8342
  count++;
7863
8343
  }
@@ -7868,46 +8348,46 @@ function countRulesInDir(rulesDir) {
7868
8348
  }
7869
8349
  function isSamePath(a, b) {
7870
8350
  try {
7871
- return import_path33.default.resolve(a) === import_path33.default.resolve(b);
8351
+ return import_path37.default.resolve(a) === import_path37.default.resolve(b);
7872
8352
  } catch {
7873
8353
  return false;
7874
8354
  }
7875
8355
  }
7876
8356
  function countConfigs(cwd) {
7877
- const homeDir2 = import_os26.default.homedir();
7878
- const claudeDir = import_path33.default.join(homeDir2, ".claude");
8357
+ const homeDir2 = import_os30.default.homedir();
8358
+ const claudeDir = import_path37.default.join(homeDir2, ".claude");
7879
8359
  let claudeMdCount = 0;
7880
8360
  let rulesCount = 0;
7881
8361
  let hooksCount = 0;
7882
8362
  const userMcpServers = /* @__PURE__ */ new Set();
7883
8363
  const projectMcpServers = /* @__PURE__ */ new Set();
7884
- if (import_fs30.default.existsSync(import_path33.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
7885
- rulesCount += countRulesInDir(import_path33.default.join(claudeDir, "rules"));
7886
- const userSettings = import_path33.default.join(claudeDir, "settings.json");
8364
+ if (import_fs34.default.existsSync(import_path37.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
8365
+ rulesCount += countRulesInDir(import_path37.default.join(claudeDir, "rules"));
8366
+ const userSettings = import_path37.default.join(claudeDir, "settings.json");
7887
8367
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
7888
8368
  hooksCount += countHooksInFile(userSettings);
7889
- const userClaudeJson = import_path33.default.join(homeDir2, ".claude.json");
8369
+ const userClaudeJson = import_path37.default.join(homeDir2, ".claude.json");
7890
8370
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
7891
8371
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
7892
8372
  userMcpServers.delete(name);
7893
8373
  }
7894
8374
  if (cwd) {
7895
- if (import_fs30.default.existsSync(import_path33.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
7896
- if (import_fs30.default.existsSync(import_path33.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
7897
- const projectClaudeDir = import_path33.default.join(cwd, ".claude");
8375
+ if (import_fs34.default.existsSync(import_path37.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
8376
+ if (import_fs34.default.existsSync(import_path37.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
8377
+ const projectClaudeDir = import_path37.default.join(cwd, ".claude");
7898
8378
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
7899
8379
  if (!overlapsUserScope) {
7900
- if (import_fs30.default.existsSync(import_path33.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
7901
- rulesCount += countRulesInDir(import_path33.default.join(projectClaudeDir, "rules"));
7902
- const projSettings = import_path33.default.join(projectClaudeDir, "settings.json");
8380
+ if (import_fs34.default.existsSync(import_path37.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
8381
+ rulesCount += countRulesInDir(import_path37.default.join(projectClaudeDir, "rules"));
8382
+ const projSettings = import_path37.default.join(projectClaudeDir, "settings.json");
7903
8383
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
7904
8384
  hooksCount += countHooksInFile(projSettings);
7905
8385
  }
7906
- if (import_fs30.default.existsSync(import_path33.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
7907
- const localSettings = import_path33.default.join(projectClaudeDir, "settings.local.json");
8386
+ if (import_fs34.default.existsSync(import_path37.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
8387
+ const localSettings = import_path37.default.join(projectClaudeDir, "settings.local.json");
7908
8388
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
7909
8389
  hooksCount += countHooksInFile(localSettings);
7910
- const mcpJsonServers = getMcpServerNames(import_path33.default.join(cwd, ".mcp.json"));
8390
+ const mcpJsonServers = getMcpServerNames(import_path37.default.join(cwd, ".mcp.json"));
7911
8391
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
7912
8392
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
7913
8393
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -7940,12 +8420,12 @@ function readActiveShieldsHud() {
7940
8420
  return shieldsCache.value;
7941
8421
  }
7942
8422
  try {
7943
- const shieldsPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "shields.json");
7944
- if (!import_fs30.default.existsSync(shieldsPath)) {
8423
+ const shieldsPath = import_path37.default.join(import_os30.default.homedir(), ".node9", "shields.json");
8424
+ if (!import_fs34.default.existsSync(shieldsPath)) {
7945
8425
  shieldsCache = { value: [], ts: now };
7946
8426
  return [];
7947
8427
  }
7948
- const parsed = JSON.parse(import_fs30.default.readFileSync(shieldsPath, "utf-8"));
8428
+ const parsed = JSON.parse(import_fs34.default.readFileSync(shieldsPath, "utf-8"));
7949
8429
  if (!Array.isArray(parsed.active)) {
7950
8430
  shieldsCache = { value: [], ts: now };
7951
8431
  return [];
@@ -8047,17 +8527,17 @@ function renderContextLine(stdin) {
8047
8527
  async function main() {
8048
8528
  try {
8049
8529
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
8050
- if (import_fs30.default.existsSync(import_path33.default.join(import_os26.default.homedir(), ".node9", "hud-debug"))) {
8530
+ if (import_fs34.default.existsSync(import_path37.default.join(import_os30.default.homedir(), ".node9", "hud-debug"))) {
8051
8531
  try {
8052
- const logPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "hud-debug.log");
8532
+ const logPath = import_path37.default.join(import_os30.default.homedir(), ".node9", "hud-debug.log");
8053
8533
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
8054
8534
  let size = 0;
8055
8535
  try {
8056
- size = import_fs30.default.statSync(logPath).size;
8536
+ size = import_fs34.default.statSync(logPath).size;
8057
8537
  } catch {
8058
8538
  }
8059
8539
  if (size < MAX_LOG_SIZE) {
8060
- import_fs30.default.appendFileSync(
8540
+ import_fs34.default.appendFileSync(
8061
8541
  logPath,
8062
8542
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
8063
8543
  );
@@ -8078,11 +8558,11 @@ async function main() {
8078
8558
  try {
8079
8559
  const cwd = stdin.cwd ?? process.cwd();
8080
8560
  for (const configPath of [
8081
- import_path33.default.join(cwd, "node9.config.json"),
8082
- import_path33.default.join(import_os26.default.homedir(), ".node9", "config.json")
8561
+ import_path37.default.join(cwd, "node9.config.json"),
8562
+ import_path37.default.join(import_os30.default.homedir(), ".node9", "config.json")
8083
8563
  ]) {
8084
- if (!import_fs30.default.existsSync(configPath)) continue;
8085
- const cfg = JSON.parse(import_fs30.default.readFileSync(configPath, "utf-8"));
8564
+ if (!import_fs34.default.existsSync(configPath)) continue;
8565
+ const cfg = JSON.parse(import_fs34.default.readFileSync(configPath, "utf-8"));
8086
8566
  const hud = cfg.settings?.hud;
8087
8567
  if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
8088
8568
  }
@@ -8100,13 +8580,13 @@ async function main() {
8100
8580
  renderOffline();
8101
8581
  }
8102
8582
  }
8103
- var import_fs30, import_path33, import_os26, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
8583
+ var import_fs34, import_path37, import_os30, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
8104
8584
  var init_hud = __esm({
8105
8585
  "src/cli/hud.ts"() {
8106
8586
  "use strict";
8107
- import_fs30 = __toESM(require("fs"));
8108
- import_path33 = __toESM(require("path"));
8109
- import_os26 = __toESM(require("os"));
8587
+ import_fs34 = __toESM(require("fs"));
8588
+ import_path37 = __toESM(require("path"));
8589
+ import_os30 = __toESM(require("os"));
8110
8590
  import_http3 = __toESM(require("http"));
8111
8591
  init_daemon();
8112
8592
  RESET3 = "\x1B[0m";
@@ -8517,7 +8997,9 @@ function detectAgents(homeDir2 = import_os11.default.homedir()) {
8517
8997
  claude: exists(import_path15.default.join(homeDir2, ".claude")) || exists(import_path15.default.join(homeDir2, ".claude.json")),
8518
8998
  gemini: exists(import_path15.default.join(homeDir2, ".gemini")),
8519
8999
  cursor: exists(import_path15.default.join(homeDir2, ".cursor")),
8520
- codex: exists(import_path15.default.join(homeDir2, ".codex"))
9000
+ codex: exists(import_path15.default.join(homeDir2, ".codex")),
9001
+ windsurf: exists(import_path15.default.join(homeDir2, ".codeium", "windsurf")),
9002
+ vscode: exists(import_path15.default.join(homeDir2, ".vscode"))
8521
9003
  };
8522
9004
  }
8523
9005
  async function setupCursor() {
@@ -8666,6 +9148,38 @@ async function setupCodex() {
8666
9148
  printDaemonTip();
8667
9149
  }
8668
9150
  }
9151
+ function teardownCodex() {
9152
+ const homeDir2 = import_os11.default.homedir();
9153
+ const configPath = import_path15.default.join(homeDir2, ".codex", "config.toml");
9154
+ const config = readToml(configPath);
9155
+ if (!config?.mcp_servers) {
9156
+ console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.codex/config.toml not found \u2014 nothing to remove"));
9157
+ return;
9158
+ }
9159
+ let changed = false;
9160
+ if (removeNode9McpServer(config.mcp_servers)) {
9161
+ changed = true;
9162
+ console.log(import_chalk.default.green(" \u2705 Removed node9 MCP server entry from ~/.codex/config.toml"));
9163
+ }
9164
+ for (const [name, server] of Object.entries(config.mcp_servers)) {
9165
+ const args = server.args;
9166
+ if (server.command === "node9" && Array.isArray(args) && args[0] === "mcp" && args[1] === "--upstream" && typeof args[2] === "string") {
9167
+ const [originalCmd, ...originalArgs] = args[2].split(" ");
9168
+ config.mcp_servers[name] = {
9169
+ ...server,
9170
+ command: originalCmd,
9171
+ args: originalArgs.length ? originalArgs : void 0
9172
+ };
9173
+ changed = true;
9174
+ }
9175
+ }
9176
+ if (changed) {
9177
+ writeToml(configPath, config);
9178
+ console.log(import_chalk.default.green(" \u2705 Unwrapped MCP servers in ~/.codex/config.toml"));
9179
+ } else {
9180
+ console.log(import_chalk.default.blue(" \u2139\uFE0F No Node9-wrapped MCP servers found in ~/.codex/config.toml"));
9181
+ }
9182
+ }
8669
9183
  function setupHud() {
8670
9184
  const homeDir2 = import_os11.default.homedir();
8671
9185
  const hooksPath = import_path15.default.join(homeDir2, ".claude", "settings.json");
@@ -8712,38 +9226,310 @@ function teardownHud() {
8712
9226
  console.log(import_chalk.default.green(" \u2705 node9 HUD removed from ~/.claude/settings.json"));
8713
9227
  console.log(import_chalk.default.gray(" Restart Claude Code for changes to take effect."));
8714
9228
  }
8715
-
8716
- // src/cli.ts
8717
- init_daemon2();
8718
- var import_chalk20 = __toESM(require("chalk"));
8719
- var import_fs31 = __toESM(require("fs"));
8720
- var import_path34 = __toESM(require("path"));
8721
- var import_os27 = __toESM(require("os"));
8722
- var import_prompts2 = require("@inquirer/prompts");
8723
-
8724
- // src/utils/duration.ts
8725
- function parseDuration(str) {
8726
- const m = str.trim().match(/^(\d+(?:\.\d+)?)\s*(s|m|h|d)?$/i);
8727
- if (!m) return null;
8728
- const n = parseFloat(m[1]);
8729
- switch ((m[2] ?? "m").toLowerCase()) {
8730
- case "s":
8731
- return Math.round(n * 1e3);
8732
- case "m":
8733
- return Math.round(n * 6e4);
8734
- case "h":
8735
- return Math.round(n * 36e5);
8736
- case "d":
8737
- return Math.round(n * 864e5);
8738
- default:
8739
- return null;
9229
+ async function setupWindsurf() {
9230
+ const homeDir2 = import_os11.default.homedir();
9231
+ const mcpPath = import_path15.default.join(homeDir2, ".codeium", "windsurf", "mcp_config.json");
9232
+ const mcpConfig = readJson(mcpPath) ?? {};
9233
+ const servers = mcpConfig.mcpServers ?? {};
9234
+ let anythingChanged = false;
9235
+ if (!hasNode9McpServer(servers)) {
9236
+ servers["node9"] = NODE9_MCP_SERVER_ENTRY;
9237
+ mcpConfig.mcpServers = servers;
9238
+ writeJson(mcpPath, mcpConfig);
9239
+ console.log(import_chalk.default.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
9240
+ anythingChanged = true;
8740
9241
  }
8741
- }
8742
-
8743
- // src/proxy/index.ts
8744
- var import_readline = __toESM(require("readline"));
9242
+ const serversToWrap = [];
9243
+ for (const [name, server] of Object.entries(servers)) {
9244
+ if (!server.command || server.command === "node9") continue;
9245
+ serversToWrap.push({ name, upstream: [server.command, ...server.args ?? []].join(" ") });
9246
+ }
9247
+ if (serversToWrap.length > 0) {
9248
+ console.log(import_chalk.default.bold("The following existing entries will be modified:\n"));
9249
+ console.log(import_chalk.default.white(` ${mcpPath}`));
9250
+ for (const { name, upstream } of serversToWrap) {
9251
+ console.log(import_chalk.default.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
9252
+ }
9253
+ console.log("");
9254
+ const proceed = await (0, import_prompts.confirm)({ message: "Wrap these MCP servers?", default: true });
9255
+ if (proceed) {
9256
+ for (const { name, upstream } of serversToWrap) {
9257
+ servers[name] = {
9258
+ ...servers[name],
9259
+ command: "node9",
9260
+ args: ["mcp", "--upstream", upstream]
9261
+ };
9262
+ }
9263
+ mcpConfig.mcpServers = servers;
9264
+ writeJson(mcpPath, mcpConfig);
9265
+ console.log(import_chalk.default.green(`
9266
+ \u2705 ${serversToWrap.length} MCP server(s) wrapped`));
9267
+ anythingChanged = true;
9268
+ } else {
9269
+ console.log(import_chalk.default.yellow(" Skipped MCP server wrapping."));
9270
+ }
9271
+ console.log("");
9272
+ }
9273
+ console.log(
9274
+ import_chalk.default.yellow(
9275
+ " \u26A0\uFE0F Note: Windsurf does not yet support native pre-execution hooks.\n MCP proxy wrapping is the only supported protection mode for Windsurf."
9276
+ )
9277
+ );
9278
+ console.log("");
9279
+ if (!anythingChanged && serversToWrap.length === 0) {
9280
+ console.log(import_chalk.default.blue("\u2139\uFE0F Node9 is already fully configured for Windsurf."));
9281
+ printDaemonTip();
9282
+ return;
9283
+ }
9284
+ if (anythingChanged) {
9285
+ console.log(import_chalk.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Windsurf via MCP proxy!"));
9286
+ console.log(import_chalk.default.gray(" Restart Windsurf for changes to take effect."));
9287
+ printDaemonTip();
9288
+ }
9289
+ }
9290
+ function teardownWindsurf() {
9291
+ const homeDir2 = import_os11.default.homedir();
9292
+ const mcpPath = import_path15.default.join(homeDir2, ".codeium", "windsurf", "mcp_config.json");
9293
+ const mcpConfig = readJson(mcpPath);
9294
+ if (!mcpConfig?.mcpServers) {
9295
+ console.log(
9296
+ import_chalk.default.blue(" \u2139\uFE0F ~/.codeium/windsurf/mcp_config.json not found \u2014 nothing to remove")
9297
+ );
9298
+ return;
9299
+ }
9300
+ let changed = false;
9301
+ if (removeNode9McpServer(mcpConfig.mcpServers)) {
9302
+ changed = true;
9303
+ console.log(
9304
+ import_chalk.default.green(" \u2705 Removed node9 MCP server entry from ~/.codeium/windsurf/mcp_config.json")
9305
+ );
9306
+ }
9307
+ for (const [name, server] of Object.entries(mcpConfig.mcpServers)) {
9308
+ const args = server.args;
9309
+ if (server.command === "node9" && Array.isArray(args) && args[0] === "mcp" && args[1] === "--upstream" && typeof args[2] === "string") {
9310
+ const [originalCmd, ...originalArgs] = args[2].split(" ");
9311
+ mcpConfig.mcpServers[name] = {
9312
+ ...server,
9313
+ command: originalCmd,
9314
+ args: originalArgs.length ? originalArgs : void 0
9315
+ };
9316
+ changed = true;
9317
+ }
9318
+ }
9319
+ if (changed) {
9320
+ writeJson(mcpPath, mcpConfig);
9321
+ console.log(import_chalk.default.green(" \u2705 Unwrapped MCP servers in ~/.codeium/windsurf/mcp_config.json"));
9322
+ } else {
9323
+ console.log(
9324
+ import_chalk.default.blue(" \u2139\uFE0F No Node9-wrapped MCP servers found in ~/.codeium/windsurf/mcp_config.json")
9325
+ );
9326
+ }
9327
+ }
9328
+ function hasNode9McpServerVSCode(servers) {
9329
+ const entry = servers["node9"];
9330
+ return !!entry && entry.command === "node9" && Array.isArray(entry.args) && entry.args[0] === "mcp-server";
9331
+ }
9332
+ async function setupVSCode() {
9333
+ const homeDir2 = import_os11.default.homedir();
9334
+ const mcpPath = import_path15.default.join(homeDir2, ".vscode", "mcp.json");
9335
+ const mcpConfig = readJson(mcpPath) ?? {};
9336
+ const servers = mcpConfig.servers ?? {};
9337
+ let anythingChanged = false;
9338
+ if (!hasNode9McpServerVSCode(servers)) {
9339
+ servers["node9"] = { type: "stdio", command: "node9", args: ["mcp-server"] };
9340
+ mcpConfig.servers = servers;
9341
+ writeJson(mcpPath, mcpConfig);
9342
+ console.log(import_chalk.default.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
9343
+ anythingChanged = true;
9344
+ }
9345
+ const serversToWrap = [];
9346
+ for (const [name, server] of Object.entries(servers)) {
9347
+ if (!server.command || server.command === "node9") continue;
9348
+ serversToWrap.push({ name, upstream: [server.command, ...server.args ?? []].join(" ") });
9349
+ }
9350
+ if (serversToWrap.length > 0) {
9351
+ console.log(import_chalk.default.bold("The following existing entries will be modified:\n"));
9352
+ console.log(import_chalk.default.white(` ${mcpPath}`));
9353
+ for (const { name, upstream } of serversToWrap) {
9354
+ console.log(import_chalk.default.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
9355
+ }
9356
+ console.log("");
9357
+ const proceed = await (0, import_prompts.confirm)({ message: "Wrap these MCP servers?", default: true });
9358
+ if (proceed) {
9359
+ for (const { name, upstream } of serversToWrap) {
9360
+ servers[name] = {
9361
+ ...servers[name],
9362
+ type: "stdio",
9363
+ command: "node9",
9364
+ args: ["mcp", "--upstream", upstream]
9365
+ };
9366
+ }
9367
+ mcpConfig.servers = servers;
9368
+ writeJson(mcpPath, mcpConfig);
9369
+ console.log(import_chalk.default.green(`
9370
+ \u2705 ${serversToWrap.length} MCP server(s) wrapped`));
9371
+ anythingChanged = true;
9372
+ } else {
9373
+ console.log(import_chalk.default.yellow(" Skipped MCP server wrapping."));
9374
+ }
9375
+ console.log("");
9376
+ }
9377
+ console.log(
9378
+ import_chalk.default.yellow(
9379
+ " \u26A0\uFE0F Note: VSCode MCP support requires the GitHub Copilot extension (v1.99+).\n Pre-execution hooks are not supported \u2014 MCP proxy wrapping only."
9380
+ )
9381
+ );
9382
+ console.log("");
9383
+ if (!anythingChanged && serversToWrap.length === 0) {
9384
+ console.log(import_chalk.default.blue("\u2139\uFE0F Node9 is already fully configured for VSCode."));
9385
+ printDaemonTip();
9386
+ return;
9387
+ }
9388
+ if (anythingChanged) {
9389
+ console.log(import_chalk.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting VSCode via MCP proxy!"));
9390
+ console.log(import_chalk.default.gray(" Restart VSCode for changes to take effect."));
9391
+ printDaemonTip();
9392
+ }
9393
+ }
9394
+ function teardownVSCode() {
9395
+ const homeDir2 = import_os11.default.homedir();
9396
+ const mcpPath = import_path15.default.join(homeDir2, ".vscode", "mcp.json");
9397
+ const mcpConfig = readJson(mcpPath);
9398
+ if (!mcpConfig?.servers) {
9399
+ console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.vscode/mcp.json not found \u2014 nothing to remove"));
9400
+ return;
9401
+ }
9402
+ let changed = false;
9403
+ if (hasNode9McpServerVSCode(mcpConfig.servers)) {
9404
+ delete mcpConfig.servers["node9"];
9405
+ changed = true;
9406
+ console.log(import_chalk.default.green(" \u2705 Removed node9 MCP server entry from ~/.vscode/mcp.json"));
9407
+ }
9408
+ for (const [name, server] of Object.entries(mcpConfig.servers)) {
9409
+ const args = server.args;
9410
+ if (server.command === "node9" && Array.isArray(args) && args[0] === "mcp" && args[1] === "--upstream" && typeof args[2] === "string") {
9411
+ const [originalCmd, ...originalArgs] = args[2].split(" ");
9412
+ mcpConfig.servers[name] = {
9413
+ ...server,
9414
+ type: "stdio",
9415
+ command: originalCmd,
9416
+ args: originalArgs.length ? originalArgs : void 0
9417
+ };
9418
+ changed = true;
9419
+ }
9420
+ }
9421
+ if (changed) {
9422
+ writeJson(mcpPath, mcpConfig);
9423
+ console.log(import_chalk.default.green(" \u2705 Unwrapped MCP servers in ~/.vscode/mcp.json"));
9424
+ } else {
9425
+ console.log(import_chalk.default.blue(" \u2139\uFE0F No Node9-wrapped MCP servers found in ~/.vscode/mcp.json"));
9426
+ }
9427
+ }
9428
+ function getAgentsStatus(homeDir2 = import_os11.default.homedir()) {
9429
+ const detected = detectAgents(homeDir2);
9430
+ const claudeWired = (() => {
9431
+ const settings = readJson(import_path15.default.join(homeDir2, ".claude", "settings.json"));
9432
+ return !!settings?.hooks?.PreToolUse?.some((m) => m.hooks.some((h) => isNode9Hook(h.command)));
9433
+ })();
9434
+ const geminiWired = (() => {
9435
+ const settings = readJson(import_path15.default.join(homeDir2, ".gemini", "settings.json"));
9436
+ return !!settings?.hooks?.BeforeTool?.some((m) => m.hooks.some((h) => isNode9Hook(h.command)));
9437
+ })();
9438
+ const cursorWired = (() => {
9439
+ const cfg = readJson(import_path15.default.join(homeDir2, ".cursor", "mcp.json"));
9440
+ return !!(cfg?.mcpServers && hasNode9McpServer(cfg.mcpServers));
9441
+ })();
9442
+ const codexWired = (() => {
9443
+ const cfg = readToml(import_path15.default.join(homeDir2, ".codex", "config.toml"));
9444
+ return !!(cfg?.mcp_servers && hasNode9McpServer(cfg.mcp_servers));
9445
+ })();
9446
+ const windsurfWired = (() => {
9447
+ const cfg = readJson(
9448
+ import_path15.default.join(homeDir2, ".codeium", "windsurf", "mcp_config.json")
9449
+ );
9450
+ return !!(cfg?.mcpServers && hasNode9McpServer(cfg.mcpServers));
9451
+ })();
9452
+ const vscodeWired = (() => {
9453
+ const cfg = readJson(import_path15.default.join(homeDir2, ".vscode", "mcp.json"));
9454
+ return !!(cfg?.servers && hasNode9McpServerVSCode(cfg.servers));
9455
+ })();
9456
+ return [
9457
+ {
9458
+ name: "claude",
9459
+ label: "Claude Code",
9460
+ installed: detected.claude,
9461
+ wired: claudeWired,
9462
+ mode: detected.claude ? "hooks" : null
9463
+ },
9464
+ {
9465
+ name: "gemini",
9466
+ label: "Gemini CLI",
9467
+ installed: detected.gemini,
9468
+ wired: geminiWired,
9469
+ mode: detected.gemini ? "hooks" : null
9470
+ },
9471
+ {
9472
+ name: "cursor",
9473
+ label: "Cursor",
9474
+ installed: detected.cursor,
9475
+ wired: cursorWired,
9476
+ mode: detected.cursor ? "mcp" : null
9477
+ },
9478
+ {
9479
+ name: "windsurf",
9480
+ label: "Windsurf",
9481
+ installed: detected.windsurf,
9482
+ wired: windsurfWired,
9483
+ mode: detected.windsurf ? "mcp" : null
9484
+ },
9485
+ {
9486
+ name: "vscode",
9487
+ label: "VSCode",
9488
+ installed: detected.vscode,
9489
+ wired: vscodeWired,
9490
+ mode: detected.vscode ? "mcp" : null
9491
+ },
9492
+ {
9493
+ name: "codex",
9494
+ label: "Codex",
9495
+ installed: detected.codex,
9496
+ wired: codexWired,
9497
+ mode: detected.codex ? "mcp" : null
9498
+ }
9499
+ ];
9500
+ }
9501
+
9502
+ // src/cli.ts
9503
+ init_daemon2();
9504
+ var import_chalk24 = __toESM(require("chalk"));
9505
+ var import_fs35 = __toESM(require("fs"));
9506
+ var import_path38 = __toESM(require("path"));
9507
+ var import_os31 = __toESM(require("os"));
9508
+ var import_prompts2 = require("@inquirer/prompts");
9509
+
9510
+ // src/utils/duration.ts
9511
+ function parseDuration(str) {
9512
+ const m = str.trim().match(/^(\d+(?:\.\d+)?)\s*(s|m|h|d)?$/i);
9513
+ if (!m) return null;
9514
+ const n = parseFloat(m[1]);
9515
+ switch ((m[2] ?? "m").toLowerCase()) {
9516
+ case "s":
9517
+ return Math.round(n * 1e3);
9518
+ case "m":
9519
+ return Math.round(n * 6e4);
9520
+ case "h":
9521
+ return Math.round(n * 36e5);
9522
+ case "d":
9523
+ return Math.round(n * 864e5);
9524
+ default:
9525
+ return null;
9526
+ }
9527
+ }
9528
+
9529
+ // src/proxy/index.ts
9530
+ var import_readline = __toESM(require("readline"));
8745
9531
  var import_chalk4 = __toESM(require("chalk"));
8746
- var import_child_process6 = require("child_process");
9532
+ var import_child_process7 = require("child_process");
8747
9533
  var import_execa = require("execa");
8748
9534
  var import_execa2 = require("execa");
8749
9535
  init_orchestrator();
@@ -8831,11 +9617,11 @@ async function runProxy(targetCommand) {
8831
9617
  }
8832
9618
  console.error(import_chalk4.default.green(`\u{1F680} Node9 Proxy Active: Monitoring [${targetCommand}]`));
8833
9619
  const spawnEnv = { ...process.env, FORCE_COLOR: "1" };
8834
- const child = useShell ? (0, import_child_process6.spawn)("/bin/bash", ["-c", targetCommand], {
9620
+ const child = useShell ? (0, import_child_process7.spawn)("/bin/bash", ["-c", targetCommand], {
8835
9621
  stdio: ["pipe", "pipe", "inherit"],
8836
9622
  shell: false,
8837
9623
  env: spawnEnv
8838
- }) : (0, import_child_process6.spawn)(executable, args, { stdio: ["pipe", "pipe", "inherit"], shell: false, env: spawnEnv });
9624
+ }) : (0, import_child_process7.spawn)(executable, args, { stdio: ["pipe", "pipe", "inherit"], shell: false, env: spawnEnv });
8839
9625
  const agentIn = import_readline.default.createInterface({ input: process.stdin, terminal: false });
8840
9626
  agentIn.on("line", async (line) => {
8841
9627
  let message;
@@ -8899,22 +9685,22 @@ async function runProxy(targetCommand) {
8899
9685
  }
8900
9686
 
8901
9687
  // src/cli/daemon-starter.ts
8902
- var import_child_process7 = require("child_process");
9688
+ var import_child_process8 = require("child_process");
8903
9689
  init_daemon();
8904
9690
  function openBrowserLocal() {
8905
9691
  const url = `http://${DAEMON_HOST}:${DAEMON_PORT}/`;
8906
9692
  try {
8907
9693
  const opts = { stdio: "ignore" };
8908
- if (process.platform === "darwin") (0, import_child_process7.execSync)(`open "${url}"`, opts);
8909
- else if (process.platform === "win32") (0, import_child_process7.execSync)(`cmd /c start "" "${url}"`, opts);
8910
- else (0, import_child_process7.execSync)(`xdg-open "${url}"`, opts);
9694
+ if (process.platform === "darwin") (0, import_child_process8.execSync)(`open "${url}"`, opts);
9695
+ else if (process.platform === "win32") (0, import_child_process8.execSync)(`cmd /c start "" "${url}"`, opts);
9696
+ else (0, import_child_process8.execSync)(`xdg-open "${url}"`, opts);
8911
9697
  } catch {
8912
9698
  }
8913
9699
  }
8914
9700
  async function autoStartDaemonAndWait() {
8915
9701
  if (process.env.NODE9_TESTING === "1") return false;
8916
9702
  try {
8917
- const child = (0, import_child_process7.spawn)(process.execPath, [process.argv[1], "daemon"], {
9703
+ const child = (0, import_child_process8.spawn)(process.execPath, [process.argv[1], "daemon"], {
8918
9704
  detached: true,
8919
9705
  stdio: "ignore",
8920
9706
  // NODE9_BROWSER_OPENED=1 tells the daemon we will open the browser ourselves
@@ -8943,23 +9729,23 @@ async function autoStartDaemonAndWait() {
8943
9729
 
8944
9730
  // src/cli/commands/check.ts
8945
9731
  var import_chalk5 = __toESM(require("chalk"));
8946
- var import_fs20 = __toESM(require("fs"));
8947
- var import_child_process9 = require("child_process");
8948
- var import_path22 = __toESM(require("path"));
8949
- var import_os16 = __toESM(require("os"));
9732
+ var import_fs22 = __toESM(require("fs"));
9733
+ var import_child_process10 = require("child_process");
9734
+ var import_path24 = __toESM(require("path"));
9735
+ var import_os18 = __toESM(require("os"));
8950
9736
  init_orchestrator();
8951
9737
  init_daemon();
8952
9738
  init_config();
8953
9739
  init_policy();
8954
9740
 
8955
9741
  // src/undo.ts
8956
- var import_child_process8 = require("child_process");
9742
+ var import_child_process9 = require("child_process");
8957
9743
  var import_crypto8 = __toESM(require("crypto"));
8958
- var import_fs19 = __toESM(require("fs"));
9744
+ var import_fs21 = __toESM(require("fs"));
8959
9745
  var import_net3 = __toESM(require("net"));
8960
- var import_path21 = __toESM(require("path"));
8961
- var import_os15 = __toESM(require("os"));
8962
- var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path21.default.join(import_os15.default.tmpdir(), "node9-activity.sock");
9746
+ var import_path23 = __toESM(require("path"));
9747
+ var import_os17 = __toESM(require("os"));
9748
+ var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path23.default.join(import_os17.default.tmpdir(), "node9-activity.sock");
8963
9749
  function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
8964
9750
  try {
8965
9751
  const payload = JSON.stringify({
@@ -8979,22 +9765,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
8979
9765
  } catch {
8980
9766
  }
8981
9767
  }
8982
- var SNAPSHOT_STACK_PATH = import_path21.default.join(import_os15.default.homedir(), ".node9", "snapshots.json");
8983
- var UNDO_LATEST_PATH = import_path21.default.join(import_os15.default.homedir(), ".node9", "undo_latest.txt");
9768
+ var SNAPSHOT_STACK_PATH = import_path23.default.join(import_os17.default.homedir(), ".node9", "snapshots.json");
9769
+ var UNDO_LATEST_PATH = import_path23.default.join(import_os17.default.homedir(), ".node9", "undo_latest.txt");
8984
9770
  var MAX_SNAPSHOTS = 10;
8985
9771
  var GIT_TIMEOUT = 15e3;
8986
9772
  function readStack() {
8987
9773
  try {
8988
- if (import_fs19.default.existsSync(SNAPSHOT_STACK_PATH))
8989
- return JSON.parse(import_fs19.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
9774
+ if (import_fs21.default.existsSync(SNAPSHOT_STACK_PATH))
9775
+ return JSON.parse(import_fs21.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
8990
9776
  } catch {
8991
9777
  }
8992
9778
  return [];
8993
9779
  }
8994
9780
  function writeStack(stack) {
8995
- const dir = import_path21.default.dirname(SNAPSHOT_STACK_PATH);
8996
- if (!import_fs19.default.existsSync(dir)) import_fs19.default.mkdirSync(dir, { recursive: true });
8997
- import_fs19.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
9781
+ const dir = import_path23.default.dirname(SNAPSHOT_STACK_PATH);
9782
+ if (!import_fs21.default.existsSync(dir)) import_fs21.default.mkdirSync(dir, { recursive: true });
9783
+ import_fs21.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
8998
9784
  }
8999
9785
  function extractFilePath(args) {
9000
9786
  if (!args || typeof args !== "object") return null;
@@ -9014,12 +9800,12 @@ function buildArgsSummary(tool, args) {
9014
9800
  return "";
9015
9801
  }
9016
9802
  function findProjectRoot(filePath) {
9017
- let dir = import_path21.default.dirname(filePath);
9803
+ let dir = import_path23.default.dirname(filePath);
9018
9804
  while (true) {
9019
- if (import_fs19.default.existsSync(import_path21.default.join(dir, ".git")) || import_fs19.default.existsSync(import_path21.default.join(dir, "package.json"))) {
9805
+ if (import_fs21.default.existsSync(import_path23.default.join(dir, ".git")) || import_fs21.default.existsSync(import_path23.default.join(dir, "package.json"))) {
9020
9806
  return dir;
9021
9807
  }
9022
- const parent = import_path21.default.dirname(dir);
9808
+ const parent = import_path23.default.dirname(dir);
9023
9809
  if (parent === dir) return process.cwd();
9024
9810
  dir = parent;
9025
9811
  }
@@ -9027,7 +9813,7 @@ function findProjectRoot(filePath) {
9027
9813
  function normalizeCwdForHash(cwd) {
9028
9814
  let normalized;
9029
9815
  try {
9030
- normalized = import_fs19.default.realpathSync(cwd);
9816
+ normalized = import_fs21.default.realpathSync(cwd);
9031
9817
  } catch {
9032
9818
  normalized = cwd;
9033
9819
  }
@@ -9037,16 +9823,16 @@ function normalizeCwdForHash(cwd) {
9037
9823
  }
9038
9824
  function getShadowRepoDir(cwd) {
9039
9825
  const hash = import_crypto8.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
9040
- return import_path21.default.join(import_os15.default.homedir(), ".node9", "snapshots", hash);
9826
+ return import_path23.default.join(import_os17.default.homedir(), ".node9", "snapshots", hash);
9041
9827
  }
9042
9828
  function cleanOrphanedIndexFiles(shadowDir) {
9043
9829
  try {
9044
9830
  const cutoff = Date.now() - 6e4;
9045
- for (const f of import_fs19.default.readdirSync(shadowDir)) {
9831
+ for (const f of import_fs21.default.readdirSync(shadowDir)) {
9046
9832
  if (f.startsWith("index_")) {
9047
- const fp = import_path21.default.join(shadowDir, f);
9833
+ const fp = import_path23.default.join(shadowDir, f);
9048
9834
  try {
9049
- if (import_fs19.default.statSync(fp).mtimeMs < cutoff) import_fs19.default.unlinkSync(fp);
9835
+ if (import_fs21.default.statSync(fp).mtimeMs < cutoff) import_fs21.default.unlinkSync(fp);
9050
9836
  } catch {
9051
9837
  }
9052
9838
  }
@@ -9058,7 +9844,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
9058
9844
  const hardcoded = [".git", ".node9"];
9059
9845
  const lines = [...hardcoded, ...ignorePaths].join("\n");
9060
9846
  try {
9061
- import_fs19.default.writeFileSync(import_path21.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
9847
+ import_fs21.default.writeFileSync(import_path23.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
9062
9848
  } catch {
9063
9849
  }
9064
9850
  }
@@ -9066,54 +9852,54 @@ function ensureShadowRepo(shadowDir, cwd) {
9066
9852
  cleanOrphanedIndexFiles(shadowDir);
9067
9853
  const normalizedCwd = normalizeCwdForHash(cwd);
9068
9854
  const shadowEnvBase = { ...process.env, GIT_DIR: shadowDir, GIT_WORK_TREE: cwd };
9069
- const check = (0, import_child_process8.spawnSync)("git", ["rev-parse", "--git-dir"], {
9855
+ const check = (0, import_child_process9.spawnSync)("git", ["rev-parse", "--git-dir"], {
9070
9856
  env: shadowEnvBase,
9071
9857
  timeout: 3e3
9072
9858
  });
9073
9859
  if (check.status === 0) {
9074
- const ptPath = import_path21.default.join(shadowDir, "project-path.txt");
9860
+ const ptPath = import_path23.default.join(shadowDir, "project-path.txt");
9075
9861
  try {
9076
- const stored = import_fs19.default.readFileSync(ptPath, "utf8").trim();
9862
+ const stored = import_fs21.default.readFileSync(ptPath, "utf8").trim();
9077
9863
  if (stored === normalizedCwd) return true;
9078
9864
  if (process.env.NODE9_DEBUG === "1")
9079
9865
  console.error(
9080
9866
  `[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
9081
9867
  );
9082
- import_fs19.default.rmSync(shadowDir, { recursive: true, force: true });
9868
+ import_fs21.default.rmSync(shadowDir, { recursive: true, force: true });
9083
9869
  } catch {
9084
9870
  try {
9085
- import_fs19.default.writeFileSync(ptPath, normalizedCwd, "utf8");
9871
+ import_fs21.default.writeFileSync(ptPath, normalizedCwd, "utf8");
9086
9872
  } catch {
9087
9873
  }
9088
9874
  return true;
9089
9875
  }
9090
9876
  }
9091
9877
  try {
9092
- import_fs19.default.mkdirSync(shadowDir, { recursive: true });
9878
+ import_fs21.default.mkdirSync(shadowDir, { recursive: true });
9093
9879
  } catch {
9094
9880
  }
9095
- const init = (0, import_child_process8.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
9881
+ const init = (0, import_child_process9.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
9096
9882
  if (init.status !== 0 || init.error) {
9097
9883
  const reason = init.error ? init.error.message : init.stderr?.toString();
9098
9884
  if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
9099
9885
  return false;
9100
9886
  }
9101
- const configFile = import_path21.default.join(shadowDir, "config");
9102
- (0, import_child_process8.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
9887
+ const configFile = import_path23.default.join(shadowDir, "config");
9888
+ (0, import_child_process9.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
9103
9889
  timeout: 3e3
9104
9890
  });
9105
- (0, import_child_process8.spawnSync)("git", ["config", "--file", configFile, "core.fsmonitor", "true"], {
9891
+ (0, import_child_process9.spawnSync)("git", ["config", "--file", configFile, "core.fsmonitor", "true"], {
9106
9892
  timeout: 3e3
9107
9893
  });
9108
9894
  try {
9109
- import_fs19.default.writeFileSync(import_path21.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
9895
+ import_fs21.default.writeFileSync(import_path23.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
9110
9896
  } catch {
9111
9897
  }
9112
9898
  return true;
9113
9899
  }
9114
9900
  function buildGitEnv(cwd) {
9115
9901
  const shadowDir = getShadowRepoDir(cwd);
9116
- const check = (0, import_child_process8.spawnSync)("git", ["rev-parse", "--git-dir"], {
9902
+ const check = (0, import_child_process9.spawnSync)("git", ["rev-parse", "--git-dir"], {
9117
9903
  env: { ...process.env, GIT_DIR: shadowDir, GIT_WORK_TREE: cwd },
9118
9904
  timeout: 2e3
9119
9905
  });
@@ -9126,23 +9912,23 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
9126
9912
  let indexFile = null;
9127
9913
  try {
9128
9914
  const rawFilePath = extractFilePath(args);
9129
- const absFilePath = rawFilePath && import_path21.default.isAbsolute(rawFilePath) ? rawFilePath : null;
9915
+ const absFilePath = rawFilePath && import_path23.default.isAbsolute(rawFilePath) ? rawFilePath : null;
9130
9916
  const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
9131
9917
  const shadowDir = getShadowRepoDir(cwd);
9132
9918
  if (!ensureShadowRepo(shadowDir, cwd)) return null;
9133
9919
  writeShadowExcludes(shadowDir, ignorePaths);
9134
- indexFile = import_path21.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
9920
+ indexFile = import_path23.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
9135
9921
  const shadowEnv = {
9136
9922
  ...process.env,
9137
9923
  GIT_DIR: shadowDir,
9138
9924
  GIT_WORK_TREE: cwd,
9139
9925
  GIT_INDEX_FILE: indexFile
9140
9926
  };
9141
- (0, import_child_process8.spawnSync)("git", ["add", "-A"], { env: shadowEnv, timeout: GIT_TIMEOUT });
9142
- const treeRes = (0, import_child_process8.spawnSync)("git", ["write-tree"], { env: shadowEnv, timeout: GIT_TIMEOUT });
9927
+ (0, import_child_process9.spawnSync)("git", ["add", "-A"], { env: shadowEnv, timeout: GIT_TIMEOUT });
9928
+ const treeRes = (0, import_child_process9.spawnSync)("git", ["write-tree"], { env: shadowEnv, timeout: GIT_TIMEOUT });
9143
9929
  const treeHash = treeRes.stdout?.toString().trim();
9144
9930
  if (!treeHash || treeRes.status !== 0) return null;
9145
- const commitRes = (0, import_child_process8.spawnSync)(
9931
+ const commitRes = (0, import_child_process9.spawnSync)(
9146
9932
  "git",
9147
9933
  ["commit-tree", treeHash, "-m", `Node9 AI Snapshot: ${(/* @__PURE__ */ new Date()).toISOString()}`],
9148
9934
  { env: shadowEnv, timeout: GIT_TIMEOUT }
@@ -9154,7 +9940,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
9154
9940
  let capturedFiles = [];
9155
9941
  let capturedDiff = null;
9156
9942
  if (prevEntry) {
9157
- const filesRes = (0, import_child_process8.spawnSync)("git", ["diff", "--name-only", prevEntry.hash, commitHash], {
9943
+ const filesRes = (0, import_child_process9.spawnSync)("git", ["diff", "--name-only", prevEntry.hash, commitHash], {
9158
9944
  env: shadowEnv,
9159
9945
  timeout: GIT_TIMEOUT
9160
9946
  });
@@ -9164,7 +9950,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
9164
9950
  if (capturedFiles.length === 0) {
9165
9951
  return prevEntry.hash;
9166
9952
  }
9167
- const diffRes = (0, import_child_process8.spawnSync)("git", ["diff", prevEntry.hash, commitHash], {
9953
+ const diffRes = (0, import_child_process9.spawnSync)("git", ["diff", prevEntry.hash, commitHash], {
9168
9954
  env: shadowEnv,
9169
9955
  timeout: GIT_TIMEOUT
9170
9956
  });
@@ -9172,7 +9958,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
9172
9958
  capturedDiff = diffRes.stdout?.toString() || null;
9173
9959
  }
9174
9960
  } else {
9175
- const filesRes = (0, import_child_process8.spawnSync)("git", ["ls-tree", "-r", "--name-only", commitHash], {
9961
+ const filesRes = (0, import_child_process9.spawnSync)("git", ["ls-tree", "-r", "--name-only", commitHash], {
9176
9962
  env: shadowEnv,
9177
9963
  timeout: GIT_TIMEOUT
9178
9964
  });
@@ -9203,9 +9989,9 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
9203
9989
  writeStack(stack);
9204
9990
  const entry = stack[stack.length - 1];
9205
9991
  notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
9206
- import_fs19.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
9992
+ import_fs21.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
9207
9993
  if (shouldGc) {
9208
- (0, import_child_process8.spawn)("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
9994
+ (0, import_child_process9.spawn)("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
9209
9995
  }
9210
9996
  return commitHash;
9211
9997
  } catch (err2) {
@@ -9214,7 +10000,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
9214
10000
  } finally {
9215
10001
  if (indexFile) {
9216
10002
  try {
9217
- import_fs19.default.unlinkSync(indexFile);
10003
+ import_fs21.default.unlinkSync(indexFile);
9218
10004
  } catch {
9219
10005
  }
9220
10006
  }
@@ -9226,14 +10012,14 @@ function getSnapshotHistory() {
9226
10012
  function computeUndoDiff(hash, cwd) {
9227
10013
  try {
9228
10014
  const env = buildGitEnv(cwd);
9229
- const statRes = (0, import_child_process8.spawnSync)("git", ["diff", hash, "--stat", "--", "."], {
10015
+ const statRes = (0, import_child_process9.spawnSync)("git", ["diff", hash, "--stat", "--", "."], {
9230
10016
  cwd,
9231
10017
  env,
9232
10018
  timeout: GIT_TIMEOUT
9233
10019
  });
9234
10020
  const stat = statRes.stdout?.toString().trim();
9235
10021
  if (!stat || statRes.status !== 0) return null;
9236
- const diffRes = (0, import_child_process8.spawnSync)("git", ["diff", hash, "--", "."], {
10022
+ const diffRes = (0, import_child_process9.spawnSync)("git", ["diff", hash, "--", "."], {
9237
10023
  cwd,
9238
10024
  env,
9239
10025
  timeout: GIT_TIMEOUT
@@ -9252,7 +10038,7 @@ function applyUndo(hash, cwd) {
9252
10038
  try {
9253
10039
  const dir = cwd ?? process.cwd();
9254
10040
  const env = buildGitEnv(dir);
9255
- const restore = (0, import_child_process8.spawnSync)("git", ["restore", "--source", hash, "--staged", "--worktree", "."], {
10041
+ const restore = (0, import_child_process9.spawnSync)("git", ["restore", "--source", hash, "--staged", "--worktree", "."], {
9256
10042
  cwd: dir,
9257
10043
  env,
9258
10044
  timeout: GIT_TIMEOUT
@@ -9264,7 +10050,7 @@ function applyUndo(hash, cwd) {
9264
10050
  }
9265
10051
  return false;
9266
10052
  }
9267
- const lsTree = (0, import_child_process8.spawnSync)("git", ["ls-tree", "-r", "--name-only", hash], {
10053
+ const lsTree = (0, import_child_process9.spawnSync)("git", ["ls-tree", "-r", "--name-only", hash], {
9268
10054
  cwd: dir,
9269
10055
  env,
9270
10056
  timeout: GIT_TIMEOUT
@@ -9283,16 +10069,16 @@ function applyUndo(hash, cwd) {
9283
10069
  `);
9284
10070
  return false;
9285
10071
  }
9286
- const tracked = (0, import_child_process8.spawnSync)("git", ["ls-files"], { cwd: dir, env, timeout: GIT_TIMEOUT }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
9287
- const untracked = (0, import_child_process8.spawnSync)("git", ["ls-files", "--others", "--exclude-standard"], {
10072
+ const tracked = (0, import_child_process9.spawnSync)("git", ["ls-files"], { cwd: dir, env, timeout: GIT_TIMEOUT }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
10073
+ const untracked = (0, import_child_process9.spawnSync)("git", ["ls-files", "--others", "--exclude-standard"], {
9288
10074
  cwd: dir,
9289
10075
  env,
9290
10076
  timeout: GIT_TIMEOUT
9291
10077
  }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
9292
10078
  for (const file of [...tracked, ...untracked]) {
9293
- const fullPath = import_path21.default.join(dir, file);
9294
- if (!snapshotFiles.has(file) && import_fs19.default.existsSync(fullPath)) {
9295
- import_fs19.default.unlinkSync(fullPath);
10079
+ const fullPath = import_path23.default.join(dir, file);
10080
+ if (!snapshotFiles.has(file) && import_fs21.default.existsSync(fullPath)) {
10081
+ import_fs21.default.unlinkSync(fullPath);
9296
10082
  }
9297
10083
  }
9298
10084
  return true;
@@ -9316,9 +10102,9 @@ function registerCheckCommand(program2) {
9316
10102
  } catch (err2) {
9317
10103
  const tempConfig = getConfig();
9318
10104
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
9319
- const logPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "hook-debug.log");
10105
+ const logPath = import_path24.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log");
9320
10106
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
9321
- import_fs20.default.appendFileSync(
10107
+ import_fs22.default.appendFileSync(
9322
10108
  logPath,
9323
10109
  `[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
9324
10110
  RAW: ${raw}
@@ -9331,13 +10117,13 @@ RAW: ${raw}
9331
10117
  if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
9332
10118
  try {
9333
10119
  const scriptPath = process.argv[1];
9334
- if (typeof scriptPath !== "string" || !import_path22.default.isAbsolute(scriptPath))
10120
+ if (typeof scriptPath !== "string" || !import_path24.default.isAbsolute(scriptPath))
9335
10121
  throw new Error("node9: argv[1] is not an absolute path");
9336
- const resolvedScript = import_fs20.default.realpathSync(scriptPath);
9337
- const expectedCli = import_fs20.default.realpathSync(import_path22.default.resolve(__dirname, "../../cli.js"));
9338
- if (resolvedScript !== expectedCli)
10122
+ const resolvedScript = import_fs22.default.realpathSync(scriptPath);
10123
+ const packageDist = import_fs22.default.realpathSync(import_path24.default.resolve(__dirname, "../.."));
10124
+ if (!resolvedScript.startsWith(packageDist + import_path24.default.sep) && resolvedScript !== packageDist)
9339
10125
  throw new Error(
9340
- "node9: daemon spawn aborted \u2014 argv[1] does not resolve to the node9 CLI"
10126
+ `node9: daemon spawn aborted \u2014 argv[1] (${resolvedScript}) is outside package dist (${packageDist})`
9341
10127
  );
9342
10128
  const safeEnv = { ...process.env };
9343
10129
  for (const key of [
@@ -9350,20 +10136,30 @@ RAW: ${raw}
9350
10136
  ]) {
9351
10137
  delete safeEnv[key];
9352
10138
  }
9353
- const d = (0, import_child_process9.spawn)(process.execPath, [scriptPath, "daemon"], {
10139
+ const d = (0, import_child_process10.spawn)(process.execPath, [scriptPath, "daemon"], {
9354
10140
  detached: true,
9355
10141
  stdio: "ignore",
9356
10142
  env: { ...safeEnv, NODE9_AUTO_STARTED: "1", NODE9_BROWSER_OPENED: "1" }
9357
10143
  });
9358
10144
  d.unref();
9359
- } catch {
10145
+ } catch (spawnErr) {
10146
+ const logPath = import_path24.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log");
10147
+ const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
10148
+ try {
10149
+ import_fs22.default.appendFileSync(
10150
+ logPath,
10151
+ `[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-failed: ${msg}
10152
+ `
10153
+ );
10154
+ } catch {
10155
+ }
9360
10156
  }
9361
10157
  }
9362
10158
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
9363
- const logPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "hook-debug.log");
9364
- if (!import_fs20.default.existsSync(import_path22.default.dirname(logPath)))
9365
- import_fs20.default.mkdirSync(import_path22.default.dirname(logPath), { recursive: true });
9366
- import_fs20.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
10159
+ const logPath = import_path24.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log");
10160
+ if (!import_fs22.default.existsSync(import_path24.default.dirname(logPath)))
10161
+ import_fs22.default.mkdirSync(import_path24.default.dirname(logPath), { recursive: true });
10162
+ import_fs22.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
9367
10163
  `);
9368
10164
  }
9369
10165
  const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
@@ -9376,8 +10172,8 @@ RAW: ${raw}
9376
10172
  const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
9377
10173
  let ttyFd = null;
9378
10174
  try {
9379
- ttyFd = import_fs20.default.openSync("/dev/tty", "w");
9380
- const writeTty = (line) => import_fs20.default.writeSync(ttyFd, line + "\n");
10175
+ ttyFd = import_fs22.default.openSync("/dev/tty", "w");
10176
+ const writeTty = (line) => import_fs22.default.writeSync(ttyFd, line + "\n");
9381
10177
  if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
9382
10178
  writeTty(import_chalk5.default.bgRed.white.bold(`
9383
10179
  \u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
@@ -9396,7 +10192,7 @@ RAW: ${raw}
9396
10192
  } finally {
9397
10193
  if (ttyFd !== null)
9398
10194
  try {
9399
- import_fs20.default.closeSync(ttyFd);
10195
+ import_fs22.default.closeSync(ttyFd);
9400
10196
  } catch {
9401
10197
  }
9402
10198
  }
@@ -9428,7 +10224,7 @@ RAW: ${raw}
9428
10224
  if (shouldSnapshot(toolName, toolInput, config)) {
9429
10225
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
9430
10226
  }
9431
- const safeCwdForAuth = typeof payload.cwd === "string" && import_path22.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
10227
+ const safeCwdForAuth = typeof payload.cwd === "string" && import_path24.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9432
10228
  const result = await authorizeHeadless(toolName, toolInput, meta, {
9433
10229
  cwd: safeCwdForAuth
9434
10230
  });
@@ -9440,12 +10236,12 @@ RAW: ${raw}
9440
10236
  }
9441
10237
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
9442
10238
  try {
9443
- const tty = import_fs20.default.openSync("/dev/tty", "w");
9444
- import_fs20.default.writeSync(
10239
+ const tty = import_fs22.default.openSync("/dev/tty", "w");
10240
+ import_fs22.default.writeSync(
9445
10241
  tty,
9446
10242
  import_chalk5.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
9447
10243
  );
9448
- import_fs20.default.closeSync(tty);
10244
+ import_fs22.default.closeSync(tty);
9449
10245
  } catch {
9450
10246
  }
9451
10247
  const daemonReady = await autoStartDaemonAndWait();
@@ -9472,9 +10268,9 @@ RAW: ${raw}
9472
10268
  });
9473
10269
  } catch (err2) {
9474
10270
  if (process.env.NODE9_DEBUG === "1") {
9475
- const logPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "hook-debug.log");
10271
+ const logPath = import_path24.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log");
9476
10272
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
9477
- import_fs20.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
10273
+ import_fs22.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
9478
10274
  `);
9479
10275
  }
9480
10276
  process.exit(0);
@@ -9508,9 +10304,9 @@ RAW: ${raw}
9508
10304
  }
9509
10305
 
9510
10306
  // src/cli/commands/log.ts
9511
- var import_fs21 = __toESM(require("fs"));
9512
- var import_path23 = __toESM(require("path"));
9513
- var import_os17 = __toESM(require("os"));
10307
+ var import_fs23 = __toESM(require("fs"));
10308
+ var import_path25 = __toESM(require("path"));
10309
+ var import_os19 = __toESM(require("os"));
9514
10310
  init_audit();
9515
10311
  init_config();
9516
10312
  init_policy();
@@ -9586,10 +10382,10 @@ function registerLogCommand(program2) {
9586
10382
  decision: "allowed",
9587
10383
  source: "post-hook"
9588
10384
  };
9589
- const logPath = import_path23.default.join(import_os17.default.homedir(), ".node9", "audit.log");
9590
- if (!import_fs21.default.existsSync(import_path23.default.dirname(logPath)))
9591
- import_fs21.default.mkdirSync(import_path23.default.dirname(logPath), { recursive: true });
9592
- import_fs21.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
10385
+ const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "audit.log");
10386
+ if (!import_fs23.default.existsSync(import_path25.default.dirname(logPath)))
10387
+ import_fs23.default.mkdirSync(import_path25.default.dirname(logPath), { recursive: true });
10388
+ import_fs23.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
9593
10389
  if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
9594
10390
  const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
9595
10391
  if (command) {
@@ -9622,7 +10418,7 @@ function registerLogCommand(program2) {
9622
10418
  }
9623
10419
  }
9624
10420
  }
9625
- const safeCwd = typeof payload.cwd === "string" && import_path23.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
10421
+ const safeCwd = typeof payload.cwd === "string" && import_path25.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9626
10422
  const config = getConfig(safeCwd);
9627
10423
  if (shouldSnapshot(tool, {}, config)) {
9628
10424
  await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
@@ -9631,9 +10427,9 @@ function registerLogCommand(program2) {
9631
10427
  const msg = err2 instanceof Error ? err2.message : String(err2);
9632
10428
  process.stderr.write(`[Node9] audit log error: ${msg}
9633
10429
  `);
9634
- const debugPath = import_path23.default.join(import_os17.default.homedir(), ".node9", "hook-debug.log");
10430
+ const debugPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "hook-debug.log");
9635
10431
  try {
9636
- import_fs21.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
10432
+ import_fs23.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
9637
10433
  `);
9638
10434
  } catch {
9639
10435
  }
@@ -9663,10 +10459,10 @@ init_audit();
9663
10459
  init_config();
9664
10460
 
9665
10461
  // src/utils/https-fetch.ts
9666
- var import_https = __toESM(require("https"));
10462
+ var import_https2 = __toESM(require("https"));
9667
10463
  function httpsFetch(url) {
9668
10464
  return new Promise((resolve, reject) => {
9669
- import_https.default.get(url, (res) => {
10465
+ import_https2.default.get(url, (res) => {
9670
10466
  if (res.statusCode !== 200) {
9671
10467
  reject(new Error(`HTTP ${String(res.statusCode)} for ${url}`));
9672
10468
  res.resume();
@@ -10033,14 +10829,14 @@ function registerConfigShowCommand(program2) {
10033
10829
 
10034
10830
  // src/cli/commands/doctor.ts
10035
10831
  var import_chalk7 = __toESM(require("chalk"));
10036
- var import_fs22 = __toESM(require("fs"));
10037
- var import_path24 = __toESM(require("path"));
10038
- var import_os18 = __toESM(require("os"));
10039
- var import_child_process10 = require("child_process");
10832
+ var import_fs24 = __toESM(require("fs"));
10833
+ var import_path26 = __toESM(require("path"));
10834
+ var import_os20 = __toESM(require("os"));
10835
+ var import_child_process11 = require("child_process");
10040
10836
  init_daemon();
10041
10837
  function registerDoctorCommand(program2, version2) {
10042
10838
  program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
10043
- const homeDir2 = import_os18.default.homedir();
10839
+ const homeDir2 = import_os20.default.homedir();
10044
10840
  let failures = 0;
10045
10841
  function pass(msg) {
10046
10842
  console.log(import_chalk7.default.green(" \u2705 ") + msg);
@@ -10062,7 +10858,7 @@ function registerDoctorCommand(program2, version2) {
10062
10858
  `));
10063
10859
  section("Binary");
10064
10860
  try {
10065
- const which = (0, import_child_process10.execSync)("which node9", { encoding: "utf-8", timeout: 3e3 }).trim();
10861
+ const which = (0, import_child_process11.execSync)("which node9", { encoding: "utf-8", timeout: 3e3 }).trim();
10066
10862
  pass(`node9 found at ${which}`);
10067
10863
  } catch {
10068
10864
  warn(
@@ -10080,7 +10876,7 @@ function registerDoctorCommand(program2, version2) {
10080
10876
  );
10081
10877
  }
10082
10878
  try {
10083
- const gitVersion = (0, import_child_process10.execSync)("git --version", { encoding: "utf-8", timeout: 3e3 }).trim();
10879
+ const gitVersion = (0, import_child_process11.execSync)("git --version", { encoding: "utf-8", timeout: 3e3 }).trim();
10084
10880
  pass(gitVersion);
10085
10881
  } catch {
10086
10882
  warn(
@@ -10089,10 +10885,10 @@ function registerDoctorCommand(program2, version2) {
10089
10885
  );
10090
10886
  }
10091
10887
  section("Configuration");
10092
- const globalConfigPath = import_path24.default.join(homeDir2, ".node9", "config.json");
10093
- if (import_fs22.default.existsSync(globalConfigPath)) {
10888
+ const globalConfigPath = import_path26.default.join(homeDir2, ".node9", "config.json");
10889
+ if (import_fs24.default.existsSync(globalConfigPath)) {
10094
10890
  try {
10095
- JSON.parse(import_fs22.default.readFileSync(globalConfigPath, "utf-8"));
10891
+ JSON.parse(import_fs24.default.readFileSync(globalConfigPath, "utf-8"));
10096
10892
  pass("~/.node9/config.json found and valid");
10097
10893
  } catch {
10098
10894
  fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
@@ -10100,10 +10896,10 @@ function registerDoctorCommand(program2, version2) {
10100
10896
  } else {
10101
10897
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
10102
10898
  }
10103
- const projectConfigPath = import_path24.default.join(process.cwd(), "node9.config.json");
10104
- if (import_fs22.default.existsSync(projectConfigPath)) {
10899
+ const projectConfigPath = import_path26.default.join(process.cwd(), "node9.config.json");
10900
+ if (import_fs24.default.existsSync(projectConfigPath)) {
10105
10901
  try {
10106
- JSON.parse(import_fs22.default.readFileSync(projectConfigPath, "utf-8"));
10902
+ JSON.parse(import_fs24.default.readFileSync(projectConfigPath, "utf-8"));
10107
10903
  pass("node9.config.json found and valid (project)");
10108
10904
  } catch {
10109
10905
  fail(
@@ -10112,8 +10908,8 @@ function registerDoctorCommand(program2, version2) {
10112
10908
  );
10113
10909
  }
10114
10910
  }
10115
- const credsPath = import_path24.default.join(homeDir2, ".node9", "credentials.json");
10116
- if (import_fs22.default.existsSync(credsPath)) {
10911
+ const credsPath = import_path26.default.join(homeDir2, ".node9", "credentials.json");
10912
+ if (import_fs24.default.existsSync(credsPath)) {
10117
10913
  pass("Cloud credentials found (~/.node9/credentials.json)");
10118
10914
  } else {
10119
10915
  warn(
@@ -10122,10 +10918,10 @@ function registerDoctorCommand(program2, version2) {
10122
10918
  );
10123
10919
  }
10124
10920
  section("Agent Hooks");
10125
- const claudeSettingsPath = import_path24.default.join(homeDir2, ".claude", "settings.json");
10126
- if (import_fs22.default.existsSync(claudeSettingsPath)) {
10921
+ const claudeSettingsPath = import_path26.default.join(homeDir2, ".claude", "settings.json");
10922
+ if (import_fs24.default.existsSync(claudeSettingsPath)) {
10127
10923
  try {
10128
- const cs = JSON.parse(import_fs22.default.readFileSync(claudeSettingsPath, "utf-8"));
10924
+ const cs = JSON.parse(import_fs24.default.readFileSync(claudeSettingsPath, "utf-8"));
10129
10925
  const hasHook = cs.hooks?.PreToolUse?.some(
10130
10926
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
10131
10927
  );
@@ -10141,10 +10937,10 @@ function registerDoctorCommand(program2, version2) {
10141
10937
  } else {
10142
10938
  warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
10143
10939
  }
10144
- const geminiSettingsPath = import_path24.default.join(homeDir2, ".gemini", "settings.json");
10145
- if (import_fs22.default.existsSync(geminiSettingsPath)) {
10940
+ const geminiSettingsPath = import_path26.default.join(homeDir2, ".gemini", "settings.json");
10941
+ if (import_fs24.default.existsSync(geminiSettingsPath)) {
10146
10942
  try {
10147
- const gs = JSON.parse(import_fs22.default.readFileSync(geminiSettingsPath, "utf-8"));
10943
+ const gs = JSON.parse(import_fs24.default.readFileSync(geminiSettingsPath, "utf-8"));
10148
10944
  const hasHook = gs.hooks?.BeforeTool?.some(
10149
10945
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
10150
10946
  );
@@ -10160,10 +10956,10 @@ function registerDoctorCommand(program2, version2) {
10160
10956
  } else {
10161
10957
  warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
10162
10958
  }
10163
- const cursorHooksPath = import_path24.default.join(homeDir2, ".cursor", "hooks.json");
10164
- if (import_fs22.default.existsSync(cursorHooksPath)) {
10959
+ const cursorHooksPath = import_path26.default.join(homeDir2, ".cursor", "hooks.json");
10960
+ if (import_fs24.default.existsSync(cursorHooksPath)) {
10165
10961
  try {
10166
- const cur = JSON.parse(import_fs22.default.readFileSync(cursorHooksPath, "utf-8"));
10962
+ const cur = JSON.parse(import_fs24.default.readFileSync(cursorHooksPath, "utf-8"));
10167
10963
  const hasHook = cur.hooks?.preToolUse?.some(
10168
10964
  (h) => h.command?.includes("node9") || h.command?.includes("cli.js")
10169
10965
  );
@@ -10201,9 +10997,9 @@ function registerDoctorCommand(program2, version2) {
10201
10997
 
10202
10998
  // src/cli/commands/audit.ts
10203
10999
  var import_chalk8 = __toESM(require("chalk"));
10204
- var import_fs23 = __toESM(require("fs"));
10205
- var import_path25 = __toESM(require("path"));
10206
- var import_os19 = __toESM(require("os"));
11000
+ var import_fs25 = __toESM(require("fs"));
11001
+ var import_path27 = __toESM(require("path"));
11002
+ var import_os21 = __toESM(require("os"));
10207
11003
  function formatRelativeTime(timestamp) {
10208
11004
  const diff = Date.now() - new Date(timestamp).getTime();
10209
11005
  const sec = Math.floor(diff / 1e3);
@@ -10216,14 +11012,14 @@ function formatRelativeTime(timestamp) {
10216
11012
  }
10217
11013
  function registerAuditCommand(program2) {
10218
11014
  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) => {
10219
- const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "audit.log");
10220
- if (!import_fs23.default.existsSync(logPath)) {
11015
+ const logPath = import_path27.default.join(import_os21.default.homedir(), ".node9", "audit.log");
11016
+ if (!import_fs25.default.existsSync(logPath)) {
10221
11017
  console.log(
10222
11018
  import_chalk8.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
10223
11019
  );
10224
11020
  return;
10225
11021
  }
10226
- const raw = import_fs23.default.readFileSync(logPath, "utf-8");
11022
+ const raw = import_fs25.default.readFileSync(logPath, "utf-8");
10227
11023
  const lines = raw.split("\n").filter((l) => l.trim() !== "");
10228
11024
  let entries = lines.flatMap((line) => {
10229
11025
  try {
@@ -10277,9 +11073,9 @@ function registerAuditCommand(program2) {
10277
11073
 
10278
11074
  // src/cli/commands/report.ts
10279
11075
  var import_chalk9 = __toESM(require("chalk"));
10280
- var import_fs24 = __toESM(require("fs"));
10281
- var import_path26 = __toESM(require("path"));
10282
- var import_os20 = __toESM(require("os"));
11076
+ var import_fs26 = __toESM(require("fs"));
11077
+ var import_path28 = __toESM(require("path"));
11078
+ var import_os22 = __toESM(require("os"));
10283
11079
  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;
10284
11080
  function buildTestTimestamps(allEntries) {
10285
11081
  const testTs = /* @__PURE__ */ new Set();
@@ -10326,8 +11122,8 @@ function getDateRange(period) {
10326
11122
  }
10327
11123
  }
10328
11124
  function parseAuditLog(logPath) {
10329
- if (!import_fs24.default.existsSync(logPath)) return [];
10330
- const raw = import_fs24.default.readFileSync(logPath, "utf-8");
11125
+ if (!import_fs26.default.existsSync(logPath)) return [];
11126
+ const raw = import_fs26.default.readFileSync(logPath, "utf-8");
10331
11127
  return raw.split("\n").flatMap((line) => {
10332
11128
  if (!line.trim()) return [];
10333
11129
  try {
@@ -10353,9 +11149,9 @@ function colorBar(value, max, width) {
10353
11149
  const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
10354
11150
  return import_chalk9.default.cyan(s.slice(0, filled)) + import_chalk9.default.dim(s.slice(filled));
10355
11151
  }
10356
- function pct(num2, total) {
11152
+ function pct(num3, total) {
10357
11153
  if (total === 0) return "\u2013";
10358
- return Math.round(num2 / total * 100) + "%";
11154
+ return Math.round(num3 / total * 100) + "%";
10359
11155
  }
10360
11156
  function fmtDate(d) {
10361
11157
  const date = typeof d === "string" ? /* @__PURE__ */ new Date(d + "T12:00:00") : d;
@@ -10396,11 +11192,11 @@ function loadClaudeCost(start, end) {
10396
11192
  inputTokens: 0,
10397
11193
  cacheReadTokens: 0
10398
11194
  };
10399
- const projectsDir = import_path26.default.join(import_os20.default.homedir(), ".claude", "projects");
10400
- if (!import_fs24.default.existsSync(projectsDir)) return empty;
11195
+ const projectsDir = import_path28.default.join(import_os22.default.homedir(), ".claude", "projects");
11196
+ if (!import_fs26.default.existsSync(projectsDir)) return empty;
10401
11197
  let dirs;
10402
11198
  try {
10403
- dirs = import_fs24.default.readdirSync(projectsDir);
11199
+ dirs = import_fs26.default.readdirSync(projectsDir);
10404
11200
  } catch {
10405
11201
  return empty;
10406
11202
  }
@@ -10410,18 +11206,18 @@ function loadClaudeCost(start, end) {
10410
11206
  const byDay = /* @__PURE__ */ new Map();
10411
11207
  const byModel = /* @__PURE__ */ new Map();
10412
11208
  for (const proj of dirs) {
10413
- const projPath = import_path26.default.join(projectsDir, proj);
11209
+ const projPath = import_path28.default.join(projectsDir, proj);
10414
11210
  let files;
10415
11211
  try {
10416
- const stat = import_fs24.default.statSync(projPath);
11212
+ const stat = import_fs26.default.statSync(projPath);
10417
11213
  if (!stat.isDirectory()) continue;
10418
- files = import_fs24.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
11214
+ files = import_fs26.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
10419
11215
  } catch {
10420
11216
  continue;
10421
11217
  }
10422
11218
  for (const file of files) {
10423
11219
  try {
10424
- const raw = import_fs24.default.readFileSync(import_path26.default.join(projPath, file), "utf-8");
11220
+ const raw = import_fs26.default.readFileSync(import_path28.default.join(projPath, file), "utf-8");
10425
11221
  for (const line of raw.split("\n")) {
10426
11222
  if (!line.trim()) continue;
10427
11223
  let entry;
@@ -10464,7 +11260,7 @@ function registerReportCommand(program2) {
10464
11260
  const period = ["today", "7d", "30d", "month"].includes(
10465
11261
  options.period
10466
11262
  ) ? options.period : "7d";
10467
- const logPath = import_path26.default.join(import_os20.default.homedir(), ".node9", "audit.log");
11263
+ const logPath = import_path28.default.join(import_os22.default.homedir(), ".node9", "audit.log");
10468
11264
  const allEntries = parseAuditLog(logPath);
10469
11265
  if (allEntries.length === 0) {
10470
11266
  console.log(
@@ -10712,22 +11508,63 @@ function registerReportCommand(program2) {
10712
11508
 
10713
11509
  // src/cli/commands/daemon-cmd.ts
10714
11510
  var import_chalk10 = __toESM(require("chalk"));
10715
- var import_child_process11 = require("child_process");
11511
+ var import_child_process12 = require("child_process");
10716
11512
  init_daemon2();
10717
11513
  init_daemon();
11514
+ var VALID_ACTIONS = "start | stop | restart | status | install | uninstall";
10718
11515
  function registerDaemonCommand(program2) {
10719
- program2.command("daemon").description("Run the local approval server").argument("[action]", "start | stop | status (default: start)").option("-b, --background", "Start the daemon in the background (detached)").option("-o, --openui", "Start in background and open browser").option(
11516
+ program2.command("daemon").description("Manage the local approval daemon").argument("[action]", `${VALID_ACTIONS} (default: start)`).option("-b, --background", "Start the daemon in the background (detached)").option("-o, --openui", "Start in background and open browser").option(
10720
11517
  "-w, --watch",
10721
11518
  "Start daemon + open browser, stay alive permanently (Flight Recorder mode)"
10722
11519
  ).action(
10723
11520
  async (action, options) => {
10724
11521
  const cmd = (action ?? "start").toLowerCase();
11522
+ if (cmd === "install") {
11523
+ const result = installDaemonService();
11524
+ if (!result.ok) {
11525
+ console.error(import_chalk10.default.red(`\u2717 ${result.reason}`));
11526
+ process.exit(1);
11527
+ }
11528
+ if (result.alreadyInstalled) {
11529
+ console.log(import_chalk10.default.green(`\u2713 Daemon service reinstalled (${result.platform})`));
11530
+ } else {
11531
+ console.log(import_chalk10.default.green(`\u2713 Daemon installed as login service (${result.platform})`));
11532
+ console.log(import_chalk10.default.gray(" The daemon will now start automatically on login."));
11533
+ }
11534
+ process.exit(0);
11535
+ }
11536
+ if (cmd === "uninstall") {
11537
+ const result = uninstallDaemonService();
11538
+ if (!result.ok) {
11539
+ console.error(import_chalk10.default.red(`\u2717 ${result.reason}`));
11540
+ process.exit(1);
11541
+ }
11542
+ console.log(import_chalk10.default.green(`\u2713 Daemon service removed (${result.platform})`));
11543
+ console.log(import_chalk10.default.gray(" The daemon will no longer start automatically on login."));
11544
+ console.log(import_chalk10.default.gray(" To stop the running daemon: node9 daemon stop"));
11545
+ process.exit(0);
11546
+ }
10725
11547
  if (cmd === "stop") return stopDaemon();
11548
+ if (cmd === "restart") {
11549
+ stopDaemon();
11550
+ await new Promise((r) => setTimeout(r, 500));
11551
+ const child = (0, import_child_process12.spawn)(process.execPath, [process.argv[1], "daemon"], {
11552
+ detached: true,
11553
+ stdio: "ignore",
11554
+ env: { ...process.env, NODE9_AUTO_STARTED: "1", NODE9_BROWSER_OPENED: "1" }
11555
+ });
11556
+ child.unref();
11557
+ if (child.pid) {
11558
+ console.log(import_chalk10.default.green(`\u2713 Daemon restarted (PID ${child.pid})`));
11559
+ } else {
11560
+ console.error(import_chalk10.default.red("\u2717 Failed to restart daemon \u2014 spawn returned no PID"));
11561
+ process.exit(1);
11562
+ }
11563
+ process.exit(0);
11564
+ }
10726
11565
  if (cmd === "status") return daemonStatus();
10727
11566
  if (cmd !== "start" && action !== void 0) {
10728
- console.error(
10729
- import_chalk10.default.red(`Unknown daemon action: "${action}". Use: start | stop | status`)
10730
- );
11567
+ console.error(import_chalk10.default.red(`Unknown daemon action: "${action}". Use: ${VALID_ACTIONS}`));
10731
11568
  process.exit(1);
10732
11569
  }
10733
11570
  if (options.watch) {
@@ -10745,7 +11582,7 @@ function registerDaemonCommand(program2) {
10745
11582
  console.log(import_chalk10.default.green(`\u{1F310} Opened browser: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
10746
11583
  process.exit(0);
10747
11584
  }
10748
- const child = (0, import_child_process11.spawn)(process.execPath, [process.argv[1], "daemon"], {
11585
+ const child = (0, import_child_process12.spawn)(process.execPath, [process.argv[1], "daemon"], {
10749
11586
  detached: true,
10750
11587
  stdio: "ignore"
10751
11588
  });
@@ -10760,7 +11597,7 @@ function registerDaemonCommand(program2) {
10760
11597
  process.exit(0);
10761
11598
  }
10762
11599
  if (options.background) {
10763
- const child = (0, import_child_process11.spawn)(process.execPath, [process.argv[1], "daemon"], {
11600
+ const child = (0, import_child_process12.spawn)(process.execPath, [process.argv[1], "daemon"], {
10764
11601
  detached: true,
10765
11602
  stdio: "ignore"
10766
11603
  });
@@ -10776,14 +11613,14 @@ function registerDaemonCommand(program2) {
10776
11613
 
10777
11614
  // src/cli/commands/status.ts
10778
11615
  var import_chalk11 = __toESM(require("chalk"));
10779
- var import_fs25 = __toESM(require("fs"));
10780
- var import_path27 = __toESM(require("path"));
10781
- var import_os21 = __toESM(require("os"));
11616
+ var import_fs27 = __toESM(require("fs"));
11617
+ var import_path29 = __toESM(require("path"));
11618
+ var import_os23 = __toESM(require("os"));
10782
11619
  init_core();
10783
11620
  init_daemon();
10784
11621
  function readJson2(filePath) {
10785
11622
  try {
10786
- if (import_fs25.default.existsSync(filePath)) return JSON.parse(import_fs25.default.readFileSync(filePath, "utf-8"));
11623
+ if (import_fs27.default.existsSync(filePath)) return JSON.parse(import_fs27.default.readFileSync(filePath, "utf-8"));
10787
11624
  } catch {
10788
11625
  }
10789
11626
  return null;
@@ -10848,28 +11685,28 @@ function registerStatusCommand(program2) {
10848
11685
  console.log("");
10849
11686
  const modeLabel = settings.mode === "audit" ? import_chalk11.default.blue("audit") : settings.mode === "strict" ? import_chalk11.default.red("strict") : import_chalk11.default.white("standard");
10850
11687
  console.log(` Mode: ${modeLabel}`);
10851
- const projectConfig = import_path27.default.join(process.cwd(), "node9.config.json");
10852
- const globalConfig = import_path27.default.join(import_os21.default.homedir(), ".node9", "config.json");
11688
+ const projectConfig = import_path29.default.join(process.cwd(), "node9.config.json");
11689
+ const globalConfig = import_path29.default.join(import_os23.default.homedir(), ".node9", "config.json");
10853
11690
  console.log(
10854
- ` Local: ${import_fs25.default.existsSync(projectConfig) ? import_chalk11.default.green("Active (node9.config.json)") : import_chalk11.default.gray("Not present")}`
11691
+ ` Local: ${import_fs27.default.existsSync(projectConfig) ? import_chalk11.default.green("Active (node9.config.json)") : import_chalk11.default.gray("Not present")}`
10855
11692
  );
10856
11693
  console.log(
10857
- ` Global: ${import_fs25.default.existsSync(globalConfig) ? import_chalk11.default.green("Active (~/.node9/config.json)") : import_chalk11.default.gray("Not present")}`
11694
+ ` Global: ${import_fs27.default.existsSync(globalConfig) ? import_chalk11.default.green("Active (~/.node9/config.json)") : import_chalk11.default.gray("Not present")}`
10858
11695
  );
10859
11696
  if (mergedConfig.policy.sandboxPaths.length > 0) {
10860
11697
  console.log(
10861
11698
  ` Sandbox: ${import_chalk11.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
10862
11699
  );
10863
11700
  }
10864
- const homeDir2 = import_os21.default.homedir();
11701
+ const homeDir2 = import_os23.default.homedir();
10865
11702
  const claudeSettings = readJson2(
10866
- import_path27.default.join(homeDir2, ".claude", "settings.json")
11703
+ import_path29.default.join(homeDir2, ".claude", "settings.json")
10867
11704
  );
10868
- const claudeConfig = readJson2(import_path27.default.join(homeDir2, ".claude.json"));
11705
+ const claudeConfig = readJson2(import_path29.default.join(homeDir2, ".claude.json"));
10869
11706
  const geminiSettings = readJson2(
10870
- import_path27.default.join(homeDir2, ".gemini", "settings.json")
11707
+ import_path29.default.join(homeDir2, ".gemini", "settings.json")
10871
11708
  );
10872
- const cursorConfig = readJson2(import_path27.default.join(homeDir2, ".cursor", "mcp.json"));
11709
+ const cursorConfig = readJson2(import_path29.default.join(homeDir2, ".cursor", "mcp.json"));
10873
11710
  const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
10874
11711
  if (agentFound) {
10875
11712
  console.log("");
@@ -10928,12 +11765,13 @@ function registerStatusCommand(program2) {
10928
11765
 
10929
11766
  // src/cli/commands/init.ts
10930
11767
  var import_chalk12 = __toESM(require("chalk"));
10931
- var import_fs26 = __toESM(require("fs"));
10932
- var import_path28 = __toESM(require("path"));
10933
- var import_os22 = __toESM(require("os"));
10934
- var import_https2 = __toESM(require("https"));
11768
+ var import_fs28 = __toESM(require("fs"));
11769
+ var import_path30 = __toESM(require("path"));
11770
+ var import_os24 = __toESM(require("os"));
11771
+ var import_https3 = __toESM(require("https"));
10935
11772
  init_core();
10936
11773
  init_shields();
11774
+ init_service();
10937
11775
  var DEFAULT_SHIELDS = ["bash-safe", "filesystem", "postgres"];
10938
11776
  function fireTelemetryPing(agents) {
10939
11777
  try {
@@ -10943,7 +11781,7 @@ function fireTelemetryPing(agents) {
10943
11781
  os: process.platform,
10944
11782
  node9_version: process.env.npm_package_version ?? "unknown"
10945
11783
  });
10946
- const req = import_https2.default.request(
11784
+ const req = import_https3.default.request(
10947
11785
  {
10948
11786
  hostname: "api.node9.ai",
10949
11787
  path: "/api/v1/telemetry",
@@ -10990,15 +11828,15 @@ function registerInitCommand(program2) {
10990
11828
  }
10991
11829
  console.log("");
10992
11830
  }
10993
- const configPath = import_path28.default.join(import_os22.default.homedir(), ".node9", "config.json");
10994
- if (import_fs26.default.existsSync(configPath) && !options.force) {
11831
+ const configPath = import_path30.default.join(import_os24.default.homedir(), ".node9", "config.json");
11832
+ if (import_fs28.default.existsSync(configPath) && !options.force) {
10995
11833
  try {
10996
- const existing = JSON.parse(import_fs26.default.readFileSync(configPath, "utf-8"));
11834
+ const existing = JSON.parse(import_fs28.default.readFileSync(configPath, "utf-8"));
10997
11835
  const settings = existing.settings ?? {};
10998
11836
  if (settings.mode !== chosenMode) {
10999
11837
  settings.mode = chosenMode;
11000
11838
  existing.settings = settings;
11001
- import_fs26.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
11839
+ import_fs28.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
11002
11840
  console.log(import_chalk12.default.green(`\u2705 Mode updated: ${chosenMode}`));
11003
11841
  } else {
11004
11842
  console.log(import_chalk12.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
@@ -11011,9 +11849,9 @@ function registerInitCommand(program2) {
11011
11849
  ...DEFAULT_CONFIG,
11012
11850
  settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
11013
11851
  };
11014
- const dir = import_path28.default.dirname(configPath);
11015
- if (!import_fs26.default.existsSync(dir)) import_fs26.default.mkdirSync(dir, { recursive: true });
11016
- import_fs26.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
11852
+ const dir = import_path30.default.dirname(configPath);
11853
+ if (!import_fs28.default.existsSync(dir)) import_fs28.default.mkdirSync(dir, { recursive: true });
11854
+ import_fs28.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
11017
11855
  console.log(import_chalk12.default.green(`\u2705 Config created: ${configPath}`));
11018
11856
  console.log(import_chalk12.default.gray(` Mode: ${chosenMode}`));
11019
11857
  }
@@ -11025,9 +11863,13 @@ function registerInitCommand(program2) {
11025
11863
  );
11026
11864
  if (found.length === 0) {
11027
11865
  console.log(
11028
- import_chalk12.default.gray("No AI agents detected. Install Claude Code, Gemini CLI, Cursor, or Codex")
11866
+ import_chalk12.default.gray(
11867
+ "No AI agents detected. Install Claude Code, Gemini CLI, Cursor, Windsurf, VSCode, or Codex"
11868
+ )
11869
+ );
11870
+ console.log(
11871
+ import_chalk12.default.gray("then run: node9 agents add <claude|gemini|cursor|windsurf|vscode|codex>")
11029
11872
  );
11030
- console.log(import_chalk12.default.gray("then run: node9 addto <claude|gemini|cursor|codex>"));
11031
11873
  return;
11032
11874
  }
11033
11875
  console.log(import_chalk12.default.bold("Detected agents:"));
@@ -11041,6 +11883,32 @@ function registerInitCommand(program2) {
11041
11883
  else if (agent === "gemini") await setupGemini();
11042
11884
  else if (agent === "cursor") await setupCursor();
11043
11885
  else if (agent === "codex") await setupCodex();
11886
+ else if (agent === "windsurf") await setupWindsurf();
11887
+ else if (agent === "vscode") await setupVSCode();
11888
+ console.log("");
11889
+ }
11890
+ if ((process.platform === "darwin" || process.platform === "linux") && process.stdout.isTTY) {
11891
+ const alreadyInstalled = isDaemonServiceInstalled();
11892
+ if (!alreadyInstalled) {
11893
+ const { confirm: confirm3 } = await import("@inquirer/prompts");
11894
+ const installService = await confirm3({
11895
+ message: "Install daemon as a login service? (starts automatically on login)",
11896
+ default: true
11897
+ });
11898
+ if (installService) {
11899
+ const result = installDaemonService();
11900
+ if (result.ok) {
11901
+ console.log(
11902
+ import_chalk12.default.green(` \u2713 Daemon installed as login service (${result.platform})`)
11903
+ );
11904
+ } else {
11905
+ console.log(import_chalk12.default.yellow(` \u26A0\uFE0F Could not install service: ${result.reason}`));
11906
+ console.log(import_chalk12.default.gray(" You can try again later with: node9 daemon install"));
11907
+ }
11908
+ }
11909
+ } else {
11910
+ console.log(import_chalk12.default.green(" \u2713 Daemon login service already installed"));
11911
+ }
11044
11912
  console.log("");
11045
11913
  }
11046
11914
  {
@@ -11067,7 +11935,7 @@ function registerInitCommand(program2) {
11067
11935
  }
11068
11936
 
11069
11937
  // src/cli/commands/undo.ts
11070
- var import_path29 = __toESM(require("path"));
11938
+ var import_path31 = __toESM(require("path"));
11071
11939
  var import_chalk14 = __toESM(require("chalk"));
11072
11940
 
11073
11941
  // src/tui/undo-navigator.ts
@@ -11226,7 +12094,7 @@ function findMatchingCwd(startDir, history) {
11226
12094
  let dir = startDir;
11227
12095
  while (true) {
11228
12096
  if (cwds.has(dir)) return dir;
11229
- const parent = import_path29.default.dirname(dir);
12097
+ const parent = import_path31.default.dirname(dir);
11230
12098
  if (parent === dir) return null;
11231
12099
  dir = parent;
11232
12100
  }
@@ -11354,7 +12222,7 @@ function registerUndoCommand(program2) {
11354
12222
 
11355
12223
  // src/cli/commands/watch.ts
11356
12224
  var import_chalk15 = __toESM(require("chalk"));
11357
- var import_child_process12 = require("child_process");
12225
+ var import_child_process13 = require("child_process");
11358
12226
  init_daemon();
11359
12227
  function registerWatchCommand(program2) {
11360
12228
  program2.command("watch").description("Run a command under Node9 watch mode (daemon stays alive for the session)").argument("<command>", "Command to run").argument("[args...]", "Arguments for the command").action(async (cmd, args) => {
@@ -11371,7 +12239,7 @@ function registerWatchCommand(program2) {
11371
12239
  }
11372
12240
  } catch {
11373
12241
  console.error(import_chalk15.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
11374
- const child = (0, import_child_process12.spawn)(process.execPath, [process.argv[1], "daemon"], {
12242
+ const child = (0, import_child_process13.spawn)(process.execPath, [process.argv[1], "daemon"], {
11375
12243
  detached: true,
11376
12244
  stdio: "ignore",
11377
12245
  env: { ...process.env, NODE9_AUTO_STARTED: "1", NODE9_WATCH_MODE: "1" }
@@ -11401,7 +12269,7 @@ function registerWatchCommand(program2) {
11401
12269
  "\n Tip: run `node9 tail` in another terminal to review and approve AI actions.\n"
11402
12270
  )
11403
12271
  );
11404
- const result = (0, import_child_process12.spawnSync)(cmd, args, {
12272
+ const result = (0, import_child_process13.spawnSync)(cmd, args, {
11405
12273
  stdio: "inherit",
11406
12274
  env: { ...process.env, NODE9_WATCH_MODE: "1" }
11407
12275
  });
@@ -11416,18 +12284,18 @@ function registerWatchCommand(program2) {
11416
12284
  // src/mcp-gateway/index.ts
11417
12285
  var import_readline3 = __toESM(require("readline"));
11418
12286
  var import_chalk16 = __toESM(require("chalk"));
11419
- var import_child_process13 = require("child_process");
12287
+ var import_child_process14 = require("child_process");
11420
12288
  var import_execa3 = require("execa");
11421
12289
  init_orchestrator();
11422
12290
  init_provenance();
11423
12291
 
11424
12292
  // src/mcp-pin.ts
11425
- var import_fs27 = __toESM(require("fs"));
11426
- var import_path30 = __toESM(require("path"));
11427
- var import_os23 = __toESM(require("os"));
12293
+ var import_fs29 = __toESM(require("fs"));
12294
+ var import_path32 = __toESM(require("path"));
12295
+ var import_os25 = __toESM(require("os"));
11428
12296
  var import_crypto9 = __toESM(require("crypto"));
11429
12297
  function getPinsFilePath() {
11430
- return import_path30.default.join(import_os23.default.homedir(), ".node9", "mcp-pins.json");
12298
+ return import_path32.default.join(import_os25.default.homedir(), ".node9", "mcp-pins.json");
11431
12299
  }
11432
12300
  function hashToolDefinitions(tools) {
11433
12301
  const sorted = [...tools].sort((a, b) => {
@@ -11444,7 +12312,7 @@ function getServerKey(upstreamCommand) {
11444
12312
  function readMcpPinsSafe() {
11445
12313
  const filePath = getPinsFilePath();
11446
12314
  try {
11447
- const raw = import_fs27.default.readFileSync(filePath, "utf-8");
12315
+ const raw = import_fs29.default.readFileSync(filePath, "utf-8");
11448
12316
  if (!raw.trim()) {
11449
12317
  return { ok: false, reason: "corrupt", detail: "empty file" };
11450
12318
  }
@@ -11468,10 +12336,10 @@ function readMcpPins() {
11468
12336
  }
11469
12337
  function writeMcpPins(data) {
11470
12338
  const filePath = getPinsFilePath();
11471
- import_fs27.default.mkdirSync(import_path30.default.dirname(filePath), { recursive: true });
12339
+ import_fs29.default.mkdirSync(import_path32.default.dirname(filePath), { recursive: true });
11472
12340
  const tmp = `${filePath}.${import_crypto9.default.randomBytes(6).toString("hex")}.tmp`;
11473
- import_fs27.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
11474
- import_fs27.default.renameSync(tmp, filePath);
12341
+ import_fs29.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
12342
+ import_fs29.default.renameSync(tmp, filePath);
11475
12343
  }
11476
12344
  function checkPin(serverKey, currentHash) {
11477
12345
  const result = readMcpPinsSafe();
@@ -11588,7 +12456,7 @@ async function runMcpGateway(upstreamCommand) {
11588
12456
  const safeEnv = Object.fromEntries(
11589
12457
  Object.entries(process.env).filter(([k]) => !UPSTREAM_INJECTOR_VARS.has(k))
11590
12458
  );
11591
- const child = (0, import_child_process13.spawn)(executable, cmdArgs, {
12459
+ const child = (0, import_child_process14.spawn)(executable, cmdArgs, {
11592
12460
  stdio: ["pipe", "pipe", "inherit"],
11593
12461
  // control stdin/stdout; inherit stderr
11594
12462
  shell: false,
@@ -11843,9 +12711,9 @@ function registerMcpGatewayCommand(program2) {
11843
12711
 
11844
12712
  // src/mcp-server/index.ts
11845
12713
  var import_readline4 = __toESM(require("readline"));
11846
- var import_fs28 = __toESM(require("fs"));
11847
- var import_os24 = __toESM(require("os"));
11848
- var import_path31 = __toESM(require("path"));
12714
+ var import_fs30 = __toESM(require("fs"));
12715
+ var import_os26 = __toESM(require("os"));
12716
+ var import_path33 = __toESM(require("path"));
11849
12717
  init_core();
11850
12718
  init_daemon();
11851
12719
  init_shields();
@@ -12020,13 +12888,13 @@ function handleStatus() {
12020
12888
  lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
12021
12889
  lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
12022
12890
  lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
12023
- const projectConfig = import_path31.default.join(process.cwd(), "node9.config.json");
12024
- const globalConfig = import_path31.default.join(import_os24.default.homedir(), ".node9", "config.json");
12891
+ const projectConfig = import_path33.default.join(process.cwd(), "node9.config.json");
12892
+ const globalConfig = import_path33.default.join(import_os26.default.homedir(), ".node9", "config.json");
12025
12893
  lines.push(
12026
- `Project config (node9.config.json): ${import_fs28.default.existsSync(projectConfig) ? "present" : "not found"}`
12894
+ `Project config (node9.config.json): ${import_fs30.default.existsSync(projectConfig) ? "present" : "not found"}`
12027
12895
  );
12028
12896
  lines.push(
12029
- `Global config (~/.node9/config.json): ${import_fs28.default.existsSync(globalConfig) ? "present" : "not found"}`
12897
+ `Global config (~/.node9/config.json): ${import_fs30.default.existsSync(globalConfig) ? "present" : "not found"}`
12030
12898
  );
12031
12899
  return lines.join("\n");
12032
12900
  }
@@ -12100,21 +12968,21 @@ function handleShieldDisable(args) {
12100
12968
  writeActiveShields(active.filter((s) => s !== name));
12101
12969
  return `Shield "${name}" disabled.`;
12102
12970
  }
12103
- var GLOBAL_CONFIG_PATH2 = import_path31.default.join(import_os24.default.homedir(), ".node9", "config.json");
12971
+ var GLOBAL_CONFIG_PATH2 = import_path33.default.join(import_os26.default.homedir(), ".node9", "config.json");
12104
12972
  var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
12105
12973
  function readGlobalConfigRaw() {
12106
12974
  try {
12107
- if (import_fs28.default.existsSync(GLOBAL_CONFIG_PATH2)) {
12108
- return JSON.parse(import_fs28.default.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
12975
+ if (import_fs30.default.existsSync(GLOBAL_CONFIG_PATH2)) {
12976
+ return JSON.parse(import_fs30.default.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
12109
12977
  }
12110
12978
  } catch {
12111
12979
  }
12112
12980
  return {};
12113
12981
  }
12114
12982
  function writeGlobalConfigRaw(data) {
12115
- const dir = import_path31.default.dirname(GLOBAL_CONFIG_PATH2);
12116
- if (!import_fs28.default.existsSync(dir)) import_fs28.default.mkdirSync(dir, { recursive: true });
12117
- import_fs28.default.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
12983
+ const dir = import_path33.default.dirname(GLOBAL_CONFIG_PATH2);
12984
+ if (!import_fs30.default.existsSync(dir)) import_fs30.default.mkdirSync(dir, { recursive: true });
12985
+ import_fs30.default.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
12118
12986
  }
12119
12987
  function handleApproverList() {
12120
12988
  const config = getConfig();
@@ -12157,9 +13025,9 @@ function handleApproverSet(args) {
12157
13025
  }
12158
13026
  function handleAuditGet(args) {
12159
13027
  const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
12160
- const auditPath = import_path31.default.join(import_os24.default.homedir(), ".node9", "audit.log");
12161
- if (!import_fs28.default.existsSync(auditPath)) return "No audit log found.";
12162
- const lines = import_fs28.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
13028
+ const auditPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "audit.log");
13029
+ if (!import_fs30.default.existsSync(auditPath)) return "No audit log found.";
13030
+ const lines = import_fs30.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
12163
13031
  const recent = lines.slice(-limit);
12164
13032
  const entries = recent.map((line) => {
12165
13033
  try {
@@ -12477,25 +13345,950 @@ function registerMcpPinCommand(program2) {
12477
13345
  });
12478
13346
  }
12479
13347
 
13348
+ // src/cli/commands/sync.ts
13349
+ var import_chalk19 = __toESM(require("chalk"));
13350
+ init_sync();
13351
+ function registerSyncCommand(program2) {
13352
+ const policy = program2.command("policy").description("Manage cloud policy rules");
13353
+ policy.command("sync").description("Sync cloud policy rules to local cache (~/.node9/rules-cache.json)").action(async () => {
13354
+ process.stdout.write(import_chalk19.default.cyan("Syncing cloud policy rules\u2026"));
13355
+ const result = await runCloudSync();
13356
+ process.stdout.write("\n");
13357
+ if (!result.ok) {
13358
+ console.error(import_chalk19.default.red(`\u2717 ${result.reason}`));
13359
+ process.exit(1);
13360
+ }
13361
+ console.log(
13362
+ import_chalk19.default.green(`\u2713 Synced ${result.rules} rule${result.rules === 1 ? "" : "s"} from cloud`)
13363
+ );
13364
+ console.log(import_chalk19.default.gray(` Cached at: ${result.fetchedAt}`));
13365
+ console.log(import_chalk19.default.gray(` File: ~/.node9/rules-cache.json`));
13366
+ });
13367
+ policy.command("show").description("List all cloud policy rules in the local cache").action(() => {
13368
+ const status = getCloudSyncStatus();
13369
+ if (!status.cached) {
13370
+ console.log(import_chalk19.default.yellow("\n No cloud rules cached \u2014 run: node9 policy sync\n"));
13371
+ return;
13372
+ }
13373
+ const rules = getCloudRules() ?? [];
13374
+ const age = Math.round((Date.now() - new Date(status.fetchedAt).getTime()) / 6e4);
13375
+ console.log(
13376
+ import_chalk19.default.bold(`
13377
+ Cloud policy rules`) + import_chalk19.default.gray(
13378
+ ` (${rules.length} rule${rules.length === 1 ? "" : "s"}, synced ${age}m ago)
13379
+ `
13380
+ )
13381
+ );
13382
+ if (rules.length === 0) {
13383
+ console.log(import_chalk19.default.gray(" No rules defined in cloud policy.\n"));
13384
+ return;
13385
+ }
13386
+ for (const rule of rules) {
13387
+ const r = rule;
13388
+ const verdictColor = r.verdict === "block" ? import_chalk19.default.red : r.verdict === "allow" ? import_chalk19.default.green : import_chalk19.default.yellow;
13389
+ console.log(
13390
+ ` ${verdictColor(
13391
+ String(r.verdict ?? "unknown").toUpperCase().padEnd(6)
13392
+ )} ${import_chalk19.default.white(String(r.name ?? "(unnamed)"))}`
13393
+ );
13394
+ if (r.reason) console.log(import_chalk19.default.gray(` ${String(r.reason)}`));
13395
+ }
13396
+ console.log("");
13397
+ });
13398
+ policy.command("status").description("Show current cloud policy cache status").action(() => {
13399
+ const s = getCloudSyncStatus();
13400
+ if (!s.cached) {
13401
+ console.log(import_chalk19.default.yellow("\n No cache yet \u2014 run: node9 policy sync\n"));
13402
+ } else {
13403
+ const age = Math.round((Date.now() - new Date(s.fetchedAt).getTime()) / 6e4);
13404
+ console.log(`
13405
+ Rules : ${import_chalk19.default.green(String(s.rules))} cloud rules loaded`);
13406
+ console.log(
13407
+ ` Synced : ${import_chalk19.default.gray(`${age} minute${age === 1 ? "" : "s"} ago`)} (${s.fetchedAt})
13408
+ `
13409
+ );
13410
+ }
13411
+ });
13412
+ }
13413
+
13414
+ // src/cli/commands/agents.ts
13415
+ var import_chalk20 = __toESM(require("chalk"));
13416
+ var SETUP_FN = {
13417
+ claude: setupClaude,
13418
+ gemini: setupGemini,
13419
+ cursor: setupCursor,
13420
+ codex: setupCodex,
13421
+ windsurf: setupWindsurf,
13422
+ vscode: setupVSCode
13423
+ };
13424
+ var TEARDOWN_FN = {
13425
+ claude: teardownClaude,
13426
+ gemini: teardownGemini,
13427
+ cursor: teardownCursor,
13428
+ codex: teardownCodex,
13429
+ windsurf: teardownWindsurf,
13430
+ vscode: teardownVSCode
13431
+ };
13432
+ var AGENT_NAMES = Object.keys(SETUP_FN);
13433
+ function registerAgentsCommand(program2) {
13434
+ const agents = program2.command("agents").description("List and manage AI agent integrations");
13435
+ agents.command("list").description("Show all supported agents and their Node9 status").action(() => {
13436
+ const statuses = getAgentsStatus();
13437
+ const anyInstalled = statuses.some((s) => s.installed);
13438
+ console.log("");
13439
+ console.log(` ${"Agent".padEnd(14)}${"Installed".padEnd(11)}${"Wired".padEnd(8)}Mode`);
13440
+ console.log(" " + "\u2500".repeat(44));
13441
+ for (const s of statuses) {
13442
+ const installed = s.installed ? import_chalk20.default.green("\u2713") : import_chalk20.default.gray("\u2717");
13443
+ const wired = !s.installed ? import_chalk20.default.gray("\u2014") : s.wired ? import_chalk20.default.green("\u2713") : import_chalk20.default.yellow("\u2717");
13444
+ const mode = s.mode ? import_chalk20.default.gray(s.mode) : import_chalk20.default.gray("\u2014");
13445
+ const hint = s.installed && !s.wired ? import_chalk20.default.gray(` \u2190 node9 agents add ${s.name}`) : "";
13446
+ console.log(` ${s.label.padEnd(14)}${installed} ${wired} ${mode}${hint}`);
13447
+ }
13448
+ console.log("");
13449
+ if (!anyInstalled) {
13450
+ console.log(
13451
+ import_chalk20.default.gray(" No AI agents detected. Install Claude Code, Gemini CLI, Cursor,\n") + import_chalk20.default.gray(" Windsurf, VSCode, or Codex then run: node9 agents list\n")
13452
+ );
13453
+ return;
13454
+ }
13455
+ const unwired = statuses.filter((s) => s.installed && !s.wired);
13456
+ if (unwired.length > 0) {
13457
+ console.log(
13458
+ import_chalk20.default.yellow(` ${unwired.length} agent(s) not yet wired. Run: `) + import_chalk20.default.white(`node9 agents add ${unwired[0].name}`) + "\n"
13459
+ );
13460
+ }
13461
+ });
13462
+ agents.command("add").description("Wire Node9 into an agent").argument("<agent>", `Agent to wire: ${AGENT_NAMES.join(" | ")}`).action(async (agent) => {
13463
+ const name = agent.toLowerCase();
13464
+ const fn = SETUP_FN[name];
13465
+ if (!fn) {
13466
+ console.error(import_chalk20.default.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
13467
+ process.exit(1);
13468
+ }
13469
+ await fn();
13470
+ });
13471
+ agents.command("remove").description("Remove Node9 from an agent").argument("<agent>", `Agent to unwire: ${AGENT_NAMES.join(" | ")}`).action((agent) => {
13472
+ const name = agent.toLowerCase();
13473
+ const fn = TEARDOWN_FN[name];
13474
+ if (!fn) {
13475
+ console.error(import_chalk20.default.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
13476
+ process.exit(1);
13477
+ }
13478
+ console.log(import_chalk20.default.cyan(`
13479
+ \u{1F6E1}\uFE0F Node9: removing from ${name}...
13480
+ `));
13481
+ fn();
13482
+ console.log(import_chalk20.default.gray("\n Restart the agent for changes to take effect."));
13483
+ });
13484
+ }
13485
+
13486
+ // src/cli/commands/scan.ts
13487
+ var import_chalk21 = __toESM(require("chalk"));
13488
+ var import_fs31 = __toESM(require("fs"));
13489
+ var import_path34 = __toESM(require("path"));
13490
+ var import_os27 = __toESM(require("os"));
13491
+ init_shields();
13492
+ init_config();
13493
+ init_policy();
13494
+ init_dlp();
13495
+ var CLAUDE_PRICING2 = {
13496
+ "claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
13497
+ "claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
13498
+ "claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
13499
+ "claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
13500
+ "claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
13501
+ "claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
13502
+ "claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
13503
+ "claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
13504
+ "claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
13505
+ "claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
13506
+ };
13507
+ function claudeModelPrice2(model) {
13508
+ const base = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
13509
+ for (const [key, p] of Object.entries(CLAUDE_PRICING2)) {
13510
+ if (base === key || base.startsWith(key)) return p;
13511
+ }
13512
+ return null;
13513
+ }
13514
+ function num2(n) {
13515
+ return n.toLocaleString();
13516
+ }
13517
+ function fmtCost2(usd) {
13518
+ if (usd < 1e-3) return "< $0.001";
13519
+ if (usd < 1) return "$" + usd.toFixed(4);
13520
+ return "$" + usd.toFixed(2);
13521
+ }
13522
+ function fmtTs(ts) {
13523
+ try {
13524
+ return new Date(ts).toLocaleDateString("en-US", {
13525
+ month: "short",
13526
+ day: "numeric",
13527
+ year: "numeric"
13528
+ });
13529
+ } catch {
13530
+ return ts.slice(0, 10);
13531
+ }
13532
+ }
13533
+ function preview(input, max) {
13534
+ const cmd = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
13535
+ const s = String(cmd).replace(/\s+/g, " ").trim();
13536
+ return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
13537
+ }
13538
+ function buildRuleSources() {
13539
+ const sources = [];
13540
+ for (const [shieldName, shield] of Object.entries(SHIELDS)) {
13541
+ for (const rule of shield.smartRules) {
13542
+ sources.push({ shieldName, shieldLabel: shieldName, rule });
13543
+ }
13544
+ }
13545
+ try {
13546
+ const config = getConfig();
13547
+ for (const rule of config.policy.smartRules) {
13548
+ if (!rule.name) continue;
13549
+ if (rule.name.startsWith("shield:")) continue;
13550
+ const isCloud = rule.name.startsWith("cloud:");
13551
+ sources.push({
13552
+ shieldName: isCloud ? "cloud" : "custom",
13553
+ shieldLabel: isCloud ? "Cloud Policy" : "Your Rules",
13554
+ rule
13555
+ });
13556
+ }
13557
+ } catch {
13558
+ }
13559
+ return sources;
13560
+ }
13561
+ function scanClaudeHistory(startDate) {
13562
+ const projectsDir = import_path34.default.join(import_os27.default.homedir(), ".claude", "projects");
13563
+ const result = {
13564
+ filesScanned: 0,
13565
+ sessions: 0,
13566
+ totalToolCalls: 0,
13567
+ bashCalls: 0,
13568
+ findings: [],
13569
+ dlpFindings: [],
13570
+ totalCostUSD: 0,
13571
+ firstDate: null,
13572
+ lastDate: null
13573
+ };
13574
+ if (!import_fs31.default.existsSync(projectsDir)) return result;
13575
+ let projDirs;
13576
+ try {
13577
+ projDirs = import_fs31.default.readdirSync(projectsDir);
13578
+ } catch {
13579
+ return result;
13580
+ }
13581
+ const ruleSources = buildRuleSources();
13582
+ for (const proj of projDirs) {
13583
+ const projPath = import_path34.default.join(projectsDir, proj);
13584
+ try {
13585
+ if (!import_fs31.default.statSync(projPath).isDirectory()) continue;
13586
+ } catch {
13587
+ continue;
13588
+ }
13589
+ const projLabel = decodeURIComponent(proj).replace(import_os27.default.homedir(), "~").slice(0, 40);
13590
+ let files;
13591
+ try {
13592
+ files = import_fs31.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
13593
+ } catch {
13594
+ continue;
13595
+ }
13596
+ for (const file of files) {
13597
+ result.filesScanned++;
13598
+ result.sessions++;
13599
+ let raw;
13600
+ try {
13601
+ raw = import_fs31.default.readFileSync(import_path34.default.join(projPath, file), "utf-8");
13602
+ } catch {
13603
+ continue;
13604
+ }
13605
+ for (const line of raw.split("\n")) {
13606
+ if (!line.trim()) continue;
13607
+ let entry;
13608
+ try {
13609
+ entry = JSON.parse(line);
13610
+ } catch {
13611
+ continue;
13612
+ }
13613
+ if (entry.type !== "assistant") continue;
13614
+ if (startDate && entry.timestamp) {
13615
+ if (new Date(entry.timestamp) < startDate) continue;
13616
+ }
13617
+ if (entry.timestamp) {
13618
+ if (!result.firstDate || entry.timestamp < result.firstDate)
13619
+ result.firstDate = entry.timestamp;
13620
+ if (!result.lastDate || entry.timestamp > result.lastDate)
13621
+ result.lastDate = entry.timestamp;
13622
+ }
13623
+ const usage = entry.message?.usage;
13624
+ const model = entry.message?.model;
13625
+ if (usage && model) {
13626
+ const p = claudeModelPrice2(model);
13627
+ if (p) {
13628
+ result.totalCostUSD += (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;
13629
+ }
13630
+ }
13631
+ const content = entry.message?.content;
13632
+ if (!Array.isArray(content)) continue;
13633
+ for (const block of content) {
13634
+ if (block.type !== "tool_use") continue;
13635
+ result.totalToolCalls++;
13636
+ const toolName = block.name ?? "";
13637
+ const toolNameLower = toolName.toLowerCase();
13638
+ const input = block.input ?? {};
13639
+ if (toolNameLower === "bash" || toolNameLower === "execute_bash") {
13640
+ result.bashCalls++;
13641
+ }
13642
+ const dlpMatch = scanArgs(input);
13643
+ if (dlpMatch) {
13644
+ const isDupe = result.dlpFindings.some(
13645
+ (f) => f.patternName === dlpMatch.patternName && f.redactedSample === dlpMatch.redactedSample && f.project === projLabel
13646
+ );
13647
+ if (!isDupe) {
13648
+ result.dlpFindings.push({
13649
+ patternName: dlpMatch.patternName,
13650
+ redactedSample: dlpMatch.redactedSample,
13651
+ toolName,
13652
+ timestamp: entry.timestamp ?? "",
13653
+ project: projLabel
13654
+ });
13655
+ }
13656
+ }
13657
+ for (const source of ruleSources) {
13658
+ const { rule } = source;
13659
+ if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
13660
+ if (!evaluateSmartConditions(input, rule)) continue;
13661
+ const inputPreview = preview(input, 120);
13662
+ const isDupe = result.findings.some(
13663
+ (f) => f.source.rule.name === rule.name && preview(f.input, 120) === inputPreview && f.project === projLabel
13664
+ );
13665
+ if (!isDupe) {
13666
+ result.findings.push({
13667
+ source,
13668
+ toolName,
13669
+ input,
13670
+ timestamp: entry.timestamp ?? "",
13671
+ project: projLabel
13672
+ });
13673
+ }
13674
+ break;
13675
+ }
13676
+ }
13677
+ }
13678
+ }
13679
+ }
13680
+ return result;
13681
+ }
13682
+ function registerScanCommand(program2) {
13683
+ program2.command("scan").description("Forecast: scan agent history and show what node9 would catch if installed").option("--all", "Scan all history (default: last 90 days)").option("--days <n>", "Scan last N days of history", "90").option("--top <n>", "Max findings to show per shield", "5").action((options) => {
13684
+ const topN = Math.max(1, parseInt(options.top, 10) || 5);
13685
+ const startDate = options.all ? null : (() => {
13686
+ const d = /* @__PURE__ */ new Date();
13687
+ d.setDate(d.getDate() - (parseInt(options.days, 10) || 90));
13688
+ d.setHours(0, 0, 0, 0);
13689
+ return d;
13690
+ })();
13691
+ console.log("");
13692
+ console.log(import_chalk21.default.cyan.bold("\u{1F50D} node9 scan") + import_chalk21.default.dim(" \u2014 what would node9 catch?"));
13693
+ console.log("");
13694
+ const projectsDir = import_path34.default.join(import_os27.default.homedir(), ".claude", "projects");
13695
+ if (!import_fs31.default.existsSync(projectsDir)) {
13696
+ console.log(import_chalk21.default.yellow(" No Claude history found at ~/.claude/projects/"));
13697
+ console.log(import_chalk21.default.gray(" Install Claude Code, run a few sessions, then try again.\n"));
13698
+ return;
13699
+ }
13700
+ process.stdout.write(import_chalk21.default.dim(" Scanning\u2026"));
13701
+ const scan = scanClaudeHistory(startDate);
13702
+ process.stdout.write("\r" + " ".repeat(20) + "\r");
13703
+ if (scan.filesScanned === 0) {
13704
+ console.log(import_chalk21.default.yellow(" No JSONL session files found.\n"));
13705
+ return;
13706
+ }
13707
+ const rangeLabel = options.all ? import_chalk21.default.dim("all time") : import_chalk21.default.dim(`last ${options.days ?? 90} days`);
13708
+ const dateRange = scan.firstDate && scan.lastDate ? import_chalk21.default.dim(` ${fmtTs(scan.firstDate)} \u2013 ${fmtTs(scan.lastDate)}`) : "";
13709
+ console.log(
13710
+ " " + import_chalk21.default.white(num2(scan.sessions)) + import_chalk21.default.dim(" sessions ") + import_chalk21.default.white(num2(scan.totalToolCalls)) + import_chalk21.default.dim(" tool calls ") + import_chalk21.default.white(num2(scan.bashCalls)) + import_chalk21.default.dim(" bash commands ") + rangeLabel + dateRange
13711
+ );
13712
+ console.log("");
13713
+ const byShield = /* @__PURE__ */ new Map();
13714
+ for (const f of scan.findings) {
13715
+ const key = f.source.shieldName;
13716
+ const entry = byShield.get(key) ?? { label: f.source.shieldLabel, findings: [] };
13717
+ entry.findings.push(f);
13718
+ byShield.set(key, entry);
13719
+ }
13720
+ const totalFindings = scan.findings.length;
13721
+ if (totalFindings === 0 && scan.dlpFindings.length === 0) {
13722
+ console.log(import_chalk21.default.green(" \u2705 No findings across all shields and rules."));
13723
+ console.log(import_chalk21.default.dim(" node9 is still worth running \u2014 it monitors in real time.\n"));
13724
+ } else {
13725
+ if (totalFindings > 0) {
13726
+ console.log(
13727
+ " " + import_chalk21.default.bold("If node9 had been installed:") + " " + import_chalk21.default.yellow.bold(
13728
+ `${num2(totalFindings)} command${totalFindings !== 1 ? "s" : ""} flagged for review`
13729
+ )
13730
+ );
13731
+ console.log("");
13732
+ const sorted = [...byShield.entries()].sort(
13733
+ (a, b) => b[1].findings.length - a[1].findings.length
13734
+ );
13735
+ for (const [shieldName, { label, findings }] of sorted) {
13736
+ const count = findings.length;
13737
+ const isUserRule = shieldName === "custom" || shieldName === "cloud";
13738
+ const shieldBadge = isUserRule ? import_chalk21.default.magenta(label) : import_chalk21.default.cyan(label);
13739
+ console.log(" " + import_chalk21.default.dim("\u2500".repeat(70)));
13740
+ console.log(
13741
+ " " + shieldBadge + import_chalk21.default.dim(" \xB7 ") + import_chalk21.default.yellow(`${num2(count)} finding${count !== 1 ? "s" : ""}`) + (isUserRule ? "" : import_chalk21.default.dim(` \u2192 node9 shield enable ${shieldName}`))
13742
+ );
13743
+ const byRule = /* @__PURE__ */ new Map();
13744
+ for (const f of findings) {
13745
+ const ruleKey = f.source.rule.name ?? "unnamed";
13746
+ const arr = byRule.get(ruleKey) ?? [];
13747
+ arr.push(f);
13748
+ byRule.set(ruleKey, arr);
13749
+ }
13750
+ for (const [, ruleFindings] of byRule) {
13751
+ const rule = ruleFindings[0].source.rule;
13752
+ const ruleCount = ruleFindings.length;
13753
+ const countBadge = ruleCount > 1 ? import_chalk21.default.white(` \xD7${ruleCount}`) : "";
13754
+ const shortName = (rule.name ?? "unnamed").replace(/^shield:[^:]+:/, "");
13755
+ console.log(
13756
+ " " + import_chalk21.default.white(shortName) + countBadge + (rule.reason ? import_chalk21.default.dim(` \u2014 ${rule.reason}`) : "")
13757
+ );
13758
+ const shown = ruleFindings.slice(0, topN);
13759
+ for (const f of shown) {
13760
+ const ts = f.timestamp ? import_chalk21.default.dim(fmtTs(f.timestamp) + " ") : "";
13761
+ const proj = import_chalk21.default.dim(f.project.slice(0, 22).padEnd(22) + " ");
13762
+ const cmd = import_chalk21.default.gray(preview(f.input, 55));
13763
+ console.log(` ${ts}${proj}${cmd}`);
13764
+ }
13765
+ if (ruleFindings.length > topN) {
13766
+ console.log(
13767
+ import_chalk21.default.dim(
13768
+ ` \u2026 and ${ruleFindings.length - topN} more (--top ${ruleFindings.length})`
13769
+ )
13770
+ );
13771
+ }
13772
+ }
13773
+ console.log("");
13774
+ }
13775
+ }
13776
+ if (scan.dlpFindings.length > 0) {
13777
+ console.log(" " + import_chalk21.default.dim("\u2500".repeat(70)));
13778
+ console.log(
13779
+ " " + import_chalk21.default.red.bold("Secrets / DLP") + import_chalk21.default.dim(" \xB7 ") + import_chalk21.default.red(
13780
+ `${num2(scan.dlpFindings.length)} potential secret leak${scan.dlpFindings.length !== 1 ? "s" : ""}`
13781
+ )
13782
+ );
13783
+ const shownDlp = scan.dlpFindings.slice(0, topN);
13784
+ for (const f of shownDlp) {
13785
+ const ts = f.timestamp ? import_chalk21.default.dim(fmtTs(f.timestamp) + " ") : "";
13786
+ const proj = import_chalk21.default.dim(f.project.slice(0, 22).padEnd(22) + " ");
13787
+ console.log(
13788
+ ` ${ts}${proj}` + import_chalk21.default.yellow(f.patternName) + import_chalk21.default.dim(" ") + import_chalk21.default.gray(f.redactedSample)
13789
+ );
13790
+ }
13791
+ if (scan.dlpFindings.length > topN) {
13792
+ console.log(
13793
+ import_chalk21.default.dim(
13794
+ ` \u2026 and ${scan.dlpFindings.length - topN} more (--top ${scan.dlpFindings.length})`
13795
+ )
13796
+ );
13797
+ }
13798
+ console.log("");
13799
+ }
13800
+ }
13801
+ if (scan.totalCostUSD > 0) {
13802
+ console.log(
13803
+ " " + import_chalk21.default.bold("Claude spend:") + " " + import_chalk21.default.yellow(fmtCost2(scan.totalCostUSD)) + import_chalk21.default.dim(" (for per-period breakdown: node9 report)")
13804
+ );
13805
+ console.log("");
13806
+ }
13807
+ const auditLog = import_path34.default.join(import_os27.default.homedir(), ".node9", "audit.log");
13808
+ if (import_fs31.default.existsSync(auditLog)) {
13809
+ console.log(import_chalk21.default.green(" \u2705 node9 is active \u2014 future sessions are protected."));
13810
+ console.log(
13811
+ import_chalk21.default.dim(" Run ") + import_chalk21.default.cyan("node9 report") + import_chalk21.default.dim(" to see live stats.")
13812
+ );
13813
+ } else {
13814
+ console.log(import_chalk21.default.yellow.bold(" \u26A1 node9 was not running during these sessions."));
13815
+ console.log(
13816
+ " " + import_chalk21.default.white("Run ") + import_chalk21.default.cyan("node9 init") + import_chalk21.default.white(" to start protecting your AI agents.")
13817
+ );
13818
+ }
13819
+ console.log("");
13820
+ });
13821
+ }
13822
+
13823
+ // src/cli/commands/sessions.ts
13824
+ var import_chalk22 = __toESM(require("chalk"));
13825
+ var import_fs32 = __toESM(require("fs"));
13826
+ var import_path35 = __toESM(require("path"));
13827
+ var import_os28 = __toESM(require("os"));
13828
+ var CLAUDE_PRICING3 = {
13829
+ "claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
13830
+ "claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
13831
+ "claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
13832
+ "claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
13833
+ "claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
13834
+ "claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
13835
+ "claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
13836
+ "claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
13837
+ "claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
13838
+ "claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
13839
+ };
13840
+ function modelPrice(model) {
13841
+ const base = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
13842
+ for (const [key, p] of Object.entries(CLAUDE_PRICING3)) {
13843
+ if (base === key || base.startsWith(key)) return p;
13844
+ }
13845
+ return null;
13846
+ }
13847
+ function encodeProjectPath(projectPath) {
13848
+ return projectPath.replace(/\//g, "-");
13849
+ }
13850
+ function sessionJsonlPath(projectPath, sessionId) {
13851
+ const encoded = encodeProjectPath(projectPath);
13852
+ return import_path35.default.join(import_os28.default.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
13853
+ }
13854
+ function projectLabel(projectPath) {
13855
+ return projectPath.replace(import_os28.default.homedir(), "~");
13856
+ }
13857
+ function parseHistoryLines(lines) {
13858
+ const entries = [];
13859
+ for (const line of lines) {
13860
+ if (!line.trim()) continue;
13861
+ try {
13862
+ const obj = JSON.parse(line);
13863
+ if (typeof obj["display"] === "string" && (typeof obj["timestamp"] === "string" || typeof obj["timestamp"] === "number") && typeof obj["project"] === "string" && typeof obj["sessionId"] === "string") {
13864
+ const ts = typeof obj["timestamp"] === "number" ? new Date(obj["timestamp"]).toISOString() : obj["timestamp"];
13865
+ entries.push({
13866
+ display: obj["display"],
13867
+ timestamp: ts,
13868
+ project: obj["project"],
13869
+ sessionId: obj["sessionId"]
13870
+ });
13871
+ }
13872
+ } catch {
13873
+ }
13874
+ }
13875
+ return entries;
13876
+ }
13877
+ function parseSessionLines(lines) {
13878
+ const toolCalls = [];
13879
+ let costUSD = 0;
13880
+ let hasSnapshot = false;
13881
+ const modifiedFiles = [];
13882
+ const seenFiles = /* @__PURE__ */ new Set();
13883
+ for (const line of lines) {
13884
+ if (!line.trim()) continue;
13885
+ let entry;
13886
+ try {
13887
+ entry = JSON.parse(line);
13888
+ } catch {
13889
+ continue;
13890
+ }
13891
+ if (entry.type === "file-history-snapshot") {
13892
+ hasSnapshot = true;
13893
+ continue;
13894
+ }
13895
+ if (entry.type !== "assistant") continue;
13896
+ const usage = entry.message?.usage;
13897
+ const model = entry.message?.model;
13898
+ if (usage && model) {
13899
+ const p = modelPrice(model);
13900
+ if (p) {
13901
+ costUSD += (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;
13902
+ }
13903
+ }
13904
+ const content = entry.message?.content;
13905
+ if (!Array.isArray(content)) continue;
13906
+ for (const block of content) {
13907
+ if (block.type !== "tool_use") continue;
13908
+ const tool = block.name ?? "";
13909
+ const input = block.input ?? {};
13910
+ toolCalls.push({ tool, input, timestamp: entry.timestamp ?? "" });
13911
+ const toolLower = tool.toLowerCase();
13912
+ if (toolLower === "write" || toolLower === "edit" || toolLower === "notebookedit") {
13913
+ const fp = input.file_path ?? input.path;
13914
+ if (typeof fp === "string" && !seenFiles.has(fp)) {
13915
+ seenFiles.add(fp);
13916
+ modifiedFiles.push(fp);
13917
+ }
13918
+ }
13919
+ }
13920
+ }
13921
+ return { toolCalls, costUSD, hasSnapshot, modifiedFiles };
13922
+ }
13923
+ function loadAuditEntries(auditPath) {
13924
+ const aPath = auditPath ?? import_path35.default.join(import_os28.default.homedir(), ".node9", "audit.log");
13925
+ let raw;
13926
+ try {
13927
+ raw = import_fs32.default.readFileSync(aPath, "utf-8");
13928
+ } catch {
13929
+ return [];
13930
+ }
13931
+ const entries = [];
13932
+ for (const line of raw.split("\n")) {
13933
+ if (!line.trim()) continue;
13934
+ try {
13935
+ const e = JSON.parse(line);
13936
+ if (!e.ts || !e.tool || !e.decision) continue;
13937
+ if (e.decision === "allow" || e.decision === "allowed") continue;
13938
+ entries.push(e);
13939
+ } catch {
13940
+ }
13941
+ }
13942
+ return entries;
13943
+ }
13944
+ function auditEntriesInWindow(entries, windowStart, windowEnd) {
13945
+ const start = new Date(windowStart).getTime();
13946
+ const end = new Date(windowEnd).getTime();
13947
+ const result = [];
13948
+ for (const e of entries) {
13949
+ const t = new Date(e.ts).getTime();
13950
+ if (t < start || t > end) continue;
13951
+ result.push({
13952
+ tool: e.tool,
13953
+ args: e.args,
13954
+ argsHash: e.argsHash,
13955
+ timestamp: e.ts,
13956
+ decision: e.decision,
13957
+ checkedBy: e.checkedBy
13958
+ });
13959
+ }
13960
+ return result;
13961
+ }
13962
+ function buildSessions(days, historyPath) {
13963
+ const hPath = historyPath ?? import_path35.default.join(import_os28.default.homedir(), ".claude", "history.jsonl");
13964
+ let historyRaw;
13965
+ try {
13966
+ historyRaw = import_fs32.default.readFileSync(hPath, "utf-8");
13967
+ } catch {
13968
+ return [];
13969
+ }
13970
+ const cutoff = days !== null ? (() => {
13971
+ const d = /* @__PURE__ */ new Date();
13972
+ d.setDate(d.getDate() - days);
13973
+ d.setHours(0, 0, 0, 0);
13974
+ return d;
13975
+ })() : null;
13976
+ const entries = parseHistoryLines(historyRaw.split("\n"));
13977
+ const bySession = /* @__PURE__ */ new Map();
13978
+ for (const e of entries) {
13979
+ if (cutoff && new Date(e.timestamp) < cutoff) continue;
13980
+ const existing = bySession.get(e.sessionId);
13981
+ if (!existing || e.timestamp < existing.timestamp) {
13982
+ bySession.set(e.sessionId, e);
13983
+ }
13984
+ }
13985
+ const allAuditEntries = loadAuditEntries();
13986
+ const summaries = [];
13987
+ for (const entry of bySession.values()) {
13988
+ const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
13989
+ let sessionLines = [];
13990
+ try {
13991
+ sessionLines = import_fs32.default.readFileSync(jsonlFile, "utf-8").split("\n");
13992
+ } catch {
13993
+ }
13994
+ const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
13995
+ const windowStart = entry.timestamp;
13996
+ const lastToolTs = toolCalls.length > 0 ? toolCalls[toolCalls.length - 1].timestamp : "";
13997
+ const windowEnd = new Date(
13998
+ Math.max(new Date(windowStart).getTime(), lastToolTs ? new Date(lastToolTs).getTime() : 0) + 5 * 60 * 1e3
13999
+ // 5 min buffer
14000
+ ).toISOString();
14001
+ const blockedCalls = auditEntriesInWindow(allAuditEntries, windowStart, windowEnd);
14002
+ summaries.push({
14003
+ sessionId: entry.sessionId,
14004
+ project: entry.project,
14005
+ projectLabel: projectLabel(entry.project),
14006
+ firstPrompt: entry.display,
14007
+ startTime: entry.timestamp,
14008
+ toolCalls,
14009
+ blockedCalls,
14010
+ costUSD,
14011
+ hasSnapshot,
14012
+ modifiedFiles
14013
+ });
14014
+ }
14015
+ summaries.sort((a, b) => a.startTime > b.startTime ? -1 : 1);
14016
+ return summaries;
14017
+ }
14018
+ function fmtCost3(usd) {
14019
+ if (usd === 0) return "";
14020
+ if (usd < 1e-3) return "< $0.001";
14021
+ if (usd < 1) return "$" + usd.toFixed(3);
14022
+ return "$" + usd.toFixed(2);
14023
+ }
14024
+ function fmtDate2(iso) {
14025
+ try {
14026
+ return new Date(iso).toLocaleDateString("en-US", { month: "short", day: "numeric" });
14027
+ } catch {
14028
+ return iso.slice(0, 10);
14029
+ }
14030
+ }
14031
+ function fmtTime(iso) {
14032
+ try {
14033
+ return new Date(iso).toLocaleTimeString("en-US", {
14034
+ hour: "2-digit",
14035
+ minute: "2-digit",
14036
+ hour12: false
14037
+ });
14038
+ } catch {
14039
+ return iso.slice(11, 16);
14040
+ }
14041
+ }
14042
+ function fmtDateTime(iso) {
14043
+ try {
14044
+ return new Date(iso).toLocaleString("en-US", {
14045
+ month: "short",
14046
+ day: "numeric",
14047
+ year: "numeric",
14048
+ hour: "2-digit",
14049
+ minute: "2-digit",
14050
+ hour12: false
14051
+ });
14052
+ } catch {
14053
+ return iso;
14054
+ }
14055
+ }
14056
+ function truncate(s, max) {
14057
+ return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
14058
+ }
14059
+ function toolInputSummary(tool, input) {
14060
+ const fp = input.file_path ?? input.path;
14061
+ if (typeof fp === "string") return fp;
14062
+ const cmd = input.command;
14063
+ if (typeof cmd === "string") return truncate(cmd.replace(/\s+/g, " "), 60);
14064
+ const q = input.query ?? input.url;
14065
+ if (typeof q === "string") return truncate(String(q), 60);
14066
+ return "";
14067
+ }
14068
+ function toolColor(tool) {
14069
+ const t = tool.toLowerCase();
14070
+ if (t === "bash" || t === "execute_bash") return import_chalk22.default.red;
14071
+ if (t === "write") return import_chalk22.default.green;
14072
+ if (t === "edit" || t === "notebookedit") return import_chalk22.default.yellow;
14073
+ if (t === "read") return import_chalk22.default.cyan;
14074
+ return import_chalk22.default.gray;
14075
+ }
14076
+ function barStr2(value, max, width) {
14077
+ if (max === 0 || width <= 0) return "\u2591".repeat(width);
14078
+ const filled = Math.max(1, Math.round(value / max * width));
14079
+ return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
14080
+ }
14081
+ function colorBar2(value, max, width) {
14082
+ const s = barStr2(value, max, width);
14083
+ const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
14084
+ return import_chalk22.default.cyan(s.slice(0, filled)) + import_chalk22.default.dim(s.slice(filled));
14085
+ }
14086
+ function renderSummary(summaries) {
14087
+ const totalTools = summaries.reduce((n, s) => n + s.toolCalls.length, 0);
14088
+ const totalCost = summaries.reduce((n, s) => n + s.costUSD, 0);
14089
+ const totalFiles = summaries.reduce((n, s) => n + s.modifiedFiles.length, 0);
14090
+ const totalBlocked = summaries.reduce((n, s) => n + s.blockedCalls.length, 0);
14091
+ const snapshots = summaries.filter((s) => s.hasSnapshot).length;
14092
+ const avgCost = summaries.length > 0 ? totalCost / summaries.length : 0;
14093
+ const toolCounts = /* @__PURE__ */ new Map();
14094
+ for (const s of summaries) {
14095
+ for (const tc of s.toolCalls) {
14096
+ const key = tc.tool.toLowerCase();
14097
+ toolCounts.set(key, (toolCounts.get(key) ?? 0) + 1);
14098
+ }
14099
+ }
14100
+ const groups = { Bash: 0, Read: 0, Write: 0, Edit: 0, Other: 0 };
14101
+ for (const [tool, count] of toolCounts) {
14102
+ if (tool === "bash" || tool === "execute_bash") groups["Bash"] += count;
14103
+ else if (tool === "read") groups["Read"] += count;
14104
+ else if (tool === "write") groups["Write"] += count;
14105
+ else if (tool === "edit" || tool === "notebookedit") groups["Edit"] += count;
14106
+ else groups["Other"] += count;
14107
+ }
14108
+ const projCosts = /* @__PURE__ */ new Map();
14109
+ for (const s of summaries) {
14110
+ projCosts.set(s.projectLabel, (projCosts.get(s.projectLabel) ?? 0) + s.costUSD);
14111
+ }
14112
+ const topProjects = [...projCosts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
14113
+ const W = 20;
14114
+ console.log(import_chalk22.default.dim(" " + "\u2500".repeat(70)));
14115
+ console.log(
14116
+ " " + import_chalk22.default.bold.white(String(summaries.length).padEnd(4)) + import_chalk22.default.dim("sessions ") + import_chalk22.default.bold.yellow(fmtCost3(totalCost).padEnd(10)) + import_chalk22.default.dim("total ") + import_chalk22.default.bold.white(String(totalTools).padEnd(6)) + import_chalk22.default.dim("tool calls ") + import_chalk22.default.bold.white(String(totalFiles)) + import_chalk22.default.dim(" files modified") + (totalBlocked > 0 ? import_chalk22.default.dim(" ") + import_chalk22.default.red.bold(String(totalBlocked)) + import_chalk22.default.dim(" blocked by node9") : "")
14117
+ );
14118
+ console.log(
14119
+ " " + import_chalk22.default.dim("avg ") + import_chalk22.default.white(fmtCost3(avgCost).padEnd(10)) + import_chalk22.default.dim("/session ") + import_chalk22.default.green(String(snapshots)) + import_chalk22.default.dim(` of ${summaries.length} sessions had snapshots`)
14120
+ );
14121
+ console.log("");
14122
+ console.log(" " + import_chalk22.default.dim("Tool breakdown:"));
14123
+ const maxGroup = Math.max(...Object.values(groups));
14124
+ for (const [label, count] of Object.entries(groups)) {
14125
+ if (count === 0) continue;
14126
+ const pct2 = totalTools > 0 ? Math.round(count / totalTools * 100) : 0;
14127
+ console.log(
14128
+ " " + label.padEnd(6) + " " + colorBar2(count, maxGroup, W) + " " + import_chalk22.default.white(String(count).padStart(4)) + import_chalk22.default.dim(` (${String(pct2)}%)`)
14129
+ );
14130
+ }
14131
+ console.log("");
14132
+ if (topProjects.length > 1) {
14133
+ console.log(" " + import_chalk22.default.dim("Cost by project:"));
14134
+ const maxProjCost = topProjects[0][1];
14135
+ for (const [proj, cost] of topProjects) {
14136
+ console.log(
14137
+ " " + proj.slice(0, 28).padEnd(28) + " " + colorBar2(cost, maxProjCost, W) + " " + import_chalk22.default.yellow(fmtCost3(cost))
14138
+ );
14139
+ }
14140
+ console.log("");
14141
+ }
14142
+ console.log(import_chalk22.default.dim(" " + "\u2500".repeat(70)));
14143
+ console.log("");
14144
+ }
14145
+ function renderList(summaries, totalCost) {
14146
+ if (summaries.length === 0) {
14147
+ console.log(import_chalk22.default.yellow(" No sessions found in the requested range.\n"));
14148
+ return;
14149
+ }
14150
+ const totalLabel = totalCost > 0 ? import_chalk22.default.dim(" ~" + fmtCost3(totalCost) + " total") : "";
14151
+ console.log(
14152
+ " " + import_chalk22.default.white(String(summaries.length)) + import_chalk22.default.dim(` session${summaries.length !== 1 ? "s" : ""}`) + totalLabel
14153
+ );
14154
+ console.log("");
14155
+ let lastGroup = "";
14156
+ for (const s of summaries) {
14157
+ const group = fmtDate2(s.startTime) + " " + s.projectLabel;
14158
+ if (group !== lastGroup) {
14159
+ console.log(
14160
+ import_chalk22.default.dim(" \u2500\u2500\u2500 ") + import_chalk22.default.bold(fmtDate2(s.startTime)) + import_chalk22.default.dim(" " + s.projectLabel)
14161
+ );
14162
+ lastGroup = group;
14163
+ }
14164
+ const timeStr = import_chalk22.default.dim(fmtTime(s.startTime));
14165
+ const prompt = import_chalk22.default.white(truncate(s.firstPrompt.replace(/\n/g, " "), 50).padEnd(50));
14166
+ const tools = s.toolCalls.length > 0 ? import_chalk22.default.dim(String(s.toolCalls.length).padStart(3) + " tools") : import_chalk22.default.dim(" 0 tools");
14167
+ const cost = s.costUSD > 0 ? import_chalk22.default.dim(" " + fmtCost3(s.costUSD).padEnd(8)) : " ";
14168
+ const blocked = s.blockedCalls.length > 0 ? import_chalk22.default.red(" \u{1F6D1} " + String(s.blockedCalls.length)) : "";
14169
+ const snap = s.hasSnapshot ? import_chalk22.default.green(" \u{1F4F8}") : "";
14170
+ const sid = import_chalk22.default.dim(" " + s.sessionId.slice(0, 8));
14171
+ console.log(` ${timeStr} ${prompt} ${tools}${cost}${blocked}${snap}${sid}`);
14172
+ }
14173
+ console.log("");
14174
+ console.log(
14175
+ import_chalk22.default.dim(" Run") + " " + import_chalk22.default.cyan("node9 sessions --detail <session-id>") + import_chalk22.default.dim(" for full tool trace.")
14176
+ );
14177
+ console.log("");
14178
+ }
14179
+ function renderDetail(s) {
14180
+ console.log("");
14181
+ console.log(import_chalk22.default.bold(" Session ") + import_chalk22.default.dim(s.sessionId));
14182
+ console.log(
14183
+ import_chalk22.default.bold(" Prompt ") + import_chalk22.default.white(s.firstPrompt.replace(/\n/g, " ").slice(0, 120))
14184
+ );
14185
+ console.log(import_chalk22.default.bold(" Project ") + import_chalk22.default.white(s.projectLabel));
14186
+ console.log(import_chalk22.default.bold(" When ") + import_chalk22.default.white(fmtDateTime(s.startTime)));
14187
+ if (s.costUSD > 0)
14188
+ console.log(import_chalk22.default.bold(" Cost ") + import_chalk22.default.yellow("~" + fmtCost3(s.costUSD)));
14189
+ console.log(
14190
+ import_chalk22.default.bold(" Snapshot ") + (s.hasSnapshot ? import_chalk22.default.green("\u2713 taken") : import_chalk22.default.dim("none"))
14191
+ );
14192
+ console.log("");
14193
+ if (s.toolCalls.length === 0 && s.blockedCalls.length === 0) {
14194
+ console.log(import_chalk22.default.dim(" No tool calls recorded.\n"));
14195
+ return;
14196
+ }
14197
+ const timeline = [
14198
+ ...s.toolCalls.map((tc) => ({ kind: "tool", tc })),
14199
+ ...s.blockedCalls.map((bc) => ({ kind: "blocked", bc }))
14200
+ ].sort((a, b) => {
14201
+ const ta = a.kind === "tool" ? a.tc.timestamp : a.bc.timestamp;
14202
+ const tb = b.kind === "tool" ? b.tc.timestamp : b.bc.timestamp;
14203
+ return ta < tb ? -1 : ta > tb ? 1 : 0;
14204
+ });
14205
+ const headerParts = [`Tool calls (${s.toolCalls.length})`];
14206
+ if (s.blockedCalls.length > 0)
14207
+ headerParts.push(import_chalk22.default.red(`${s.blockedCalls.length} blocked by node9`));
14208
+ console.log(import_chalk22.default.bold(" " + headerParts.join(" \xB7 ")));
14209
+ console.log("");
14210
+ for (const entry of timeline) {
14211
+ if (entry.kind === "tool") {
14212
+ const tc = entry.tc;
14213
+ const colorFn = toolColor(tc.tool);
14214
+ const toolPad = colorFn(tc.tool.padEnd(16));
14215
+ const detail = import_chalk22.default.gray(truncate(toolInputSummary(tc.tool, tc.input), 70));
14216
+ const ts = tc.timestamp ? import_chalk22.default.dim(fmtTime(tc.timestamp) + " ") : " ";
14217
+ console.log(` ${ts}${toolPad} ${detail}`);
14218
+ } else {
14219
+ const bc = entry.bc;
14220
+ const ts = bc.timestamp ? import_chalk22.default.dim(fmtTime(bc.timestamp) + " ") : " ";
14221
+ const label = import_chalk22.default.red("\u{1F6D1} BLOCKED".padEnd(16));
14222
+ const toolName = import_chalk22.default.red(bc.tool.padEnd(10));
14223
+ const argsSummary = bc.args ? import_chalk22.default.gray(truncate(toolInputSummary(bc.tool, bc.args), 40)) : import_chalk22.default.dim("[args not logged]");
14224
+ const reason = bc.checkedBy ? import_chalk22.default.dim(" \u2190 " + bc.checkedBy) : "";
14225
+ console.log(` ${ts}${label} ${toolName} ${argsSummary}${reason}`);
14226
+ }
14227
+ }
14228
+ console.log("");
14229
+ if (s.modifiedFiles.length > 0) {
14230
+ console.log(import_chalk22.default.bold(` Files modified (${s.modifiedFiles.length}):`));
14231
+ for (const f of s.modifiedFiles) {
14232
+ console.log(" " + import_chalk22.default.yellow(f));
14233
+ }
14234
+ console.log("");
14235
+ }
14236
+ }
14237
+ function registerSessionsCommand(program2) {
14238
+ program2.command("sessions").description("Show what your AI agent did \u2014 sessions, tool calls, cost, and file changes").option("--all", "Show all sessions (default: last 7 days)").option("--days <n>", "Show last N days of sessions", "7").option("--detail <sessionId>", "Show full tool trace for a session").action((options) => {
14239
+ console.log("");
14240
+ console.log(import_chalk22.default.cyan.bold("\u{1F4CB} node9 sessions") + import_chalk22.default.dim(" \u2014 what your AI agent did"));
14241
+ console.log("");
14242
+ const historyPath = import_path35.default.join(import_os28.default.homedir(), ".claude", "history.jsonl");
14243
+ if (!import_fs32.default.existsSync(historyPath)) {
14244
+ console.log(import_chalk22.default.yellow(" No Claude session history found at ~/.claude/history.jsonl"));
14245
+ console.log(import_chalk22.default.gray(" Install Claude Code, run a few sessions, then try again.\n"));
14246
+ return;
14247
+ }
14248
+ const days = options.detail || options.all ? null : Math.max(1, parseInt(options.days, 10) || 7);
14249
+ const rangeLabel = options.detail ? "all time" : options.all ? "all time" : `last ${String(days)} days`;
14250
+ console.log(import_chalk22.default.dim(" " + rangeLabel));
14251
+ console.log("");
14252
+ process.stdout.write(import_chalk22.default.dim(" Loading\u2026"));
14253
+ const summaries = buildSessions(days);
14254
+ process.stdout.write("\r" + " ".repeat(20) + "\r");
14255
+ if (options.detail) {
14256
+ const target = summaries.find(
14257
+ (s) => s.sessionId === options.detail || s.sessionId.startsWith(options.detail)
14258
+ );
14259
+ if (!target) {
14260
+ console.log(import_chalk22.default.red(` Session not found: ${options.detail}`));
14261
+ console.log(import_chalk22.default.dim(" Run `node9 sessions` to list recent sessions.\n"));
14262
+ return;
14263
+ }
14264
+ renderDetail(target);
14265
+ return;
14266
+ }
14267
+ const totalCost = summaries.reduce((sum, s) => sum + s.costUSD, 0);
14268
+ if (summaries.length > 0) renderSummary(summaries);
14269
+ renderList(summaries, totalCost);
14270
+ });
14271
+ }
14272
+
12480
14273
  // src/cli.ts
12481
14274
  var { version } = JSON.parse(
12482
- import_fs31.default.readFileSync(import_path34.default.join(__dirname, "../package.json"), "utf-8")
14275
+ import_fs35.default.readFileSync(import_path38.default.join(__dirname, "../package.json"), "utf-8")
12483
14276
  );
12484
14277
  var program = new import_commander.Command();
12485
14278
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
12486
14279
  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) => {
12487
- const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
12488
- const credPath = import_path34.default.join(import_os27.default.homedir(), ".node9", "credentials.json");
12489
- if (!import_fs31.default.existsSync(import_path34.default.dirname(credPath)))
12490
- import_fs31.default.mkdirSync(import_path34.default.dirname(credPath), { recursive: true });
14280
+ const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
14281
+ const credPath = import_path38.default.join(import_os31.default.homedir(), ".node9", "credentials.json");
14282
+ if (!import_fs35.default.existsSync(import_path38.default.dirname(credPath)))
14283
+ import_fs35.default.mkdirSync(import_path38.default.dirname(credPath), { recursive: true });
12491
14284
  const profileName = options.profile || "default";
12492
14285
  let existingCreds = {};
12493
14286
  try {
12494
- if (import_fs31.default.existsSync(credPath)) {
12495
- const raw = JSON.parse(import_fs31.default.readFileSync(credPath, "utf-8"));
14287
+ if (import_fs35.default.existsSync(credPath)) {
14288
+ const raw = JSON.parse(import_fs35.default.readFileSync(credPath, "utf-8"));
12496
14289
  if (raw.apiKey) {
12497
14290
  existingCreds = {
12498
- default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
14291
+ default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
12499
14292
  };
12500
14293
  } else {
12501
14294
  existingCreds = raw;
@@ -12503,14 +14296,14 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
12503
14296
  }
12504
14297
  } catch {
12505
14298
  }
12506
- existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
12507
- import_fs31.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
14299
+ existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
14300
+ import_fs35.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
12508
14301
  if (profileName === "default") {
12509
- const configPath = import_path34.default.join(import_os27.default.homedir(), ".node9", "config.json");
14302
+ const configPath = import_path38.default.join(import_os31.default.homedir(), ".node9", "config.json");
12510
14303
  let config = {};
12511
14304
  try {
12512
- if (import_fs31.default.existsSync(configPath))
12513
- config = JSON.parse(import_fs31.default.readFileSync(configPath, "utf-8"));
14305
+ if (import_fs35.default.existsSync(configPath))
14306
+ config = JSON.parse(import_fs35.default.readFileSync(configPath, "utf-8"));
12514
14307
  } catch {
12515
14308
  }
12516
14309
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -12525,39 +14318,47 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
12525
14318
  approvers.cloud = false;
12526
14319
  }
12527
14320
  s.approvers = approvers;
12528
- if (!import_fs31.default.existsSync(import_path34.default.dirname(configPath)))
12529
- import_fs31.default.mkdirSync(import_path34.default.dirname(configPath), { recursive: true });
12530
- import_fs31.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
14321
+ if (!import_fs35.default.existsSync(import_path38.default.dirname(configPath)))
14322
+ import_fs35.default.mkdirSync(import_path38.default.dirname(configPath), { recursive: true });
14323
+ import_fs35.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
12531
14324
  }
12532
14325
  if (options.profile && profileName !== "default") {
12533
- console.log(import_chalk20.default.green(`\u2705 Profile "${profileName}" saved`));
12534
- console.log(import_chalk20.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
14326
+ console.log(import_chalk24.default.green(`\u2705 Profile "${profileName}" saved`));
14327
+ console.log(import_chalk24.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
12535
14328
  } else if (options.local) {
12536
- console.log(import_chalk20.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
12537
- console.log(import_chalk20.default.gray(` All decisions stay on this machine.`));
14329
+ console.log(import_chalk24.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
14330
+ console.log(import_chalk24.default.gray(` All decisions stay on this machine.`));
12538
14331
  } else {
12539
- console.log(import_chalk20.default.green(`\u2705 Logged in \u2014 agent mode`));
12540
- console.log(import_chalk20.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
14332
+ console.log(import_chalk24.default.green(`\u2705 Logged in \u2014 agent mode`));
14333
+ console.log(import_chalk24.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
12541
14334
  }
12542
14335
  });
12543
- program.command("addto").description("Integrate Node9 with an AI agent").addHelpText("after", "\n Supported targets: claude gemini cursor hud").argument("<target>", "The agent to protect: claude | gemini | cursor | hud").action(async (target) => {
14336
+ program.command("addto").description("Integrate Node9 with an AI agent").addHelpText("after", "\n Supported targets: claude gemini cursor windsurf vscode hud").argument("<target>", "The agent to protect: claude | gemini | cursor | windsurf | vscode | hud").action(async (target) => {
12544
14337
  if (target === "gemini") return await setupGemini();
12545
14338
  if (target === "claude") return await setupClaude();
12546
14339
  if (target === "cursor") return await setupCursor();
14340
+ if (target === "windsurf") return await setupWindsurf();
14341
+ if (target === "vscode") return await setupVSCode();
12547
14342
  if (target === "hud") return setupHud();
12548
- console.error(import_chalk20.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
14343
+ console.error(
14344
+ import_chalk24.default.red(
14345
+ `Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
14346
+ )
14347
+ );
12549
14348
  process.exit(1);
12550
14349
  });
12551
- program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText("after", "\n Supported targets: claude gemini cursor hud").argument("[target]", "The agent to protect: claude | gemini | cursor | hud").action(async (target) => {
14350
+ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText("after", "\n Supported targets: claude gemini cursor windsurf vscode hud").argument("[target]", "The agent to protect: claude | gemini | cursor | windsurf | vscode | hud").action(async (target) => {
12552
14351
  if (!target) {
12553
- console.log(import_chalk20.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
12554
- console.log(" Usage: " + import_chalk20.default.white("node9 setup <target>") + "\n");
14352
+ console.log(import_chalk24.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
14353
+ console.log(" Usage: " + import_chalk24.default.white("node9 setup <target>") + "\n");
12555
14354
  console.log(" Targets:");
12556
- console.log(" " + import_chalk20.default.green("claude") + " \u2014 Claude Code (hook mode)");
12557
- console.log(" " + import_chalk20.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
12558
- console.log(" " + import_chalk20.default.green("cursor") + " \u2014 Cursor (hook mode)");
14355
+ console.log(" " + import_chalk24.default.green("claude") + " \u2014 Claude Code (hook mode)");
14356
+ console.log(" " + import_chalk24.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
14357
+ console.log(" " + import_chalk24.default.green("cursor") + " \u2014 Cursor (MCP proxy)");
14358
+ console.log(" " + import_chalk24.default.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
14359
+ console.log(" " + import_chalk24.default.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
12559
14360
  process.stdout.write(
12560
- " " + import_chalk20.default.green("hud") + " \u2014 Claude Code security statusline\n"
14361
+ " " + import_chalk24.default.green("hud") + " \u2014 Claude Code security statusline\n"
12561
14362
  );
12562
14363
  console.log("");
12563
14364
  return;
@@ -12566,93 +14367,108 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
12566
14367
  if (t === "gemini") return await setupGemini();
12567
14368
  if (t === "claude") return await setupClaude();
12568
14369
  if (t === "cursor") return await setupCursor();
14370
+ if (t === "windsurf") return await setupWindsurf();
14371
+ if (t === "vscode") return await setupVSCode();
12569
14372
  if (t === "hud") return setupHud();
12570
- console.error(import_chalk20.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
14373
+ console.error(
14374
+ import_chalk24.default.red(
14375
+ `Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
14376
+ )
14377
+ );
12571
14378
  process.exit(1);
12572
14379
  });
12573
- program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText("after", "\n Supported targets: claude gemini cursor").argument("<target>", "The agent to remove from: claude | gemini | cursor").action((target) => {
14380
+ program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText("after", "\n Supported targets: claude gemini cursor windsurf vscode hud").argument(
14381
+ "<target>",
14382
+ "The agent to remove from: claude | gemini | cursor | windsurf | vscode | hud"
14383
+ ).action((target) => {
12574
14384
  let fn;
12575
14385
  if (target === "claude") fn = teardownClaude;
12576
14386
  else if (target === "gemini") fn = teardownGemini;
12577
14387
  else if (target === "cursor") fn = teardownCursor;
14388
+ else if (target === "windsurf") fn = teardownWindsurf;
14389
+ else if (target === "vscode") fn = teardownVSCode;
12578
14390
  else if (target === "hud") fn = teardownHud;
12579
14391
  else {
12580
14392
  console.error(
12581
- import_chalk20.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`)
14393
+ import_chalk24.default.red(
14394
+ `Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
14395
+ )
12582
14396
  );
12583
14397
  process.exit(1);
12584
14398
  }
12585
- console.log(import_chalk20.default.cyan(`
14399
+ console.log(import_chalk24.default.cyan(`
12586
14400
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
12587
14401
  `));
12588
14402
  try {
12589
14403
  fn();
12590
14404
  } catch (err2) {
12591
- console.error(import_chalk20.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
14405
+ console.error(import_chalk24.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
12592
14406
  process.exit(1);
12593
14407
  }
12594
- console.log(import_chalk20.default.gray("\n Restart the agent for changes to take effect."));
14408
+ console.log(import_chalk24.default.gray("\n Restart the agent for changes to take effect."));
12595
14409
  });
12596
14410
  program.command("uninstall").description("Remove all Node9 hooks and optionally delete config files").option("--purge", "Also delete ~/.node9/ directory (config, audit log, credentials)").action(async (options) => {
12597
- console.log(import_chalk20.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
12598
- console.log(import_chalk20.default.bold("Stopping daemon..."));
14411
+ console.log(import_chalk24.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
14412
+ console.log(import_chalk24.default.bold("Stopping daemon..."));
12599
14413
  try {
12600
14414
  stopDaemon();
12601
- console.log(import_chalk20.default.green(" \u2705 Daemon stopped"));
14415
+ console.log(import_chalk24.default.green(" \u2705 Daemon stopped"));
12602
14416
  } catch {
12603
- console.log(import_chalk20.default.blue(" \u2139\uFE0F Daemon was not running"));
14417
+ console.log(import_chalk24.default.blue(" \u2139\uFE0F Daemon was not running"));
12604
14418
  }
12605
- console.log(import_chalk20.default.bold("\nRemoving hooks..."));
14419
+ console.log(import_chalk24.default.bold("\nRemoving hooks..."));
12606
14420
  let teardownFailed = false;
12607
14421
  for (const [label, fn] of [
12608
14422
  ["Claude", teardownClaude],
12609
14423
  ["Gemini", teardownGemini],
12610
- ["Cursor", teardownCursor]
14424
+ ["Cursor", teardownCursor],
14425
+ ["Windsurf", teardownWindsurf],
14426
+ ["VSCode", teardownVSCode]
12611
14427
  ]) {
12612
14428
  try {
12613
14429
  fn();
12614
14430
  } catch (err2) {
12615
14431
  teardownFailed = true;
12616
14432
  console.error(
12617
- import_chalk20.default.red(
14433
+ import_chalk24.default.red(
12618
14434
  ` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
12619
14435
  )
12620
14436
  );
12621
14437
  }
12622
14438
  }
12623
14439
  if (options.purge) {
12624
- const node9Dir = import_path34.default.join(import_os27.default.homedir(), ".node9");
12625
- if (import_fs31.default.existsSync(node9Dir)) {
14440
+ const node9Dir = import_path38.default.join(import_os31.default.homedir(), ".node9");
14441
+ if (import_fs35.default.existsSync(node9Dir)) {
12626
14442
  const confirmed = await (0, import_prompts2.confirm)({
12627
14443
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
12628
14444
  default: false
12629
14445
  });
12630
14446
  if (confirmed) {
12631
- import_fs31.default.rmSync(node9Dir, { recursive: true });
12632
- if (import_fs31.default.existsSync(node9Dir)) {
14447
+ import_fs35.default.rmSync(node9Dir, { recursive: true });
14448
+ if (import_fs35.default.existsSync(node9Dir)) {
12633
14449
  console.error(
12634
- import_chalk20.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
14450
+ import_chalk24.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
12635
14451
  );
12636
14452
  } else {
12637
- console.log(import_chalk20.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
14453
+ console.log(import_chalk24.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
12638
14454
  }
12639
14455
  } else {
12640
- console.log(import_chalk20.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
14456
+ console.log(import_chalk24.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
12641
14457
  }
12642
14458
  } else {
12643
- console.log(import_chalk20.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
14459
+ console.log(import_chalk24.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
12644
14460
  }
12645
14461
  } else {
12646
14462
  console.log(
12647
- import_chalk20.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
14463
+ import_chalk24.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
12648
14464
  );
12649
14465
  }
12650
14466
  if (teardownFailed) {
12651
- console.error(import_chalk20.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
14467
+ console.error(import_chalk24.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
12652
14468
  process.exit(1);
12653
14469
  }
12654
- console.log(import_chalk20.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
12655
- console.log(import_chalk20.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
14470
+ console.log(import_chalk24.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
14471
+ console.log(import_chalk24.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
12656
14472
  });
12657
14473
  registerDoctorCommand(program, version);
12658
14474
  program.command("explain").description(
@@ -12665,7 +14481,7 @@ program.command("explain").description(
12665
14481
  try {
12666
14482
  args = JSON.parse(trimmed);
12667
14483
  } catch {
12668
- console.error(import_chalk20.default.red(`
14484
+ console.error(import_chalk24.default.red(`
12669
14485
  \u274C Invalid JSON: ${trimmed}
12670
14486
  `));
12671
14487
  process.exit(1);
@@ -12676,54 +14492,54 @@ program.command("explain").description(
12676
14492
  }
12677
14493
  const result = await explainPolicy(tool, args);
12678
14494
  console.log("");
12679
- console.log(import_chalk20.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
14495
+ console.log(import_chalk24.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
12680
14496
  console.log("");
12681
- console.log(` ${import_chalk20.default.bold("Tool:")} ${import_chalk20.default.white(result.tool)}`);
14497
+ console.log(` ${import_chalk24.default.bold("Tool:")} ${import_chalk24.default.white(result.tool)}`);
12682
14498
  if (argsRaw) {
12683
- const preview = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
12684
- console.log(` ${import_chalk20.default.bold("Input:")} ${import_chalk20.default.gray(preview)}`);
14499
+ const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
14500
+ console.log(` ${import_chalk24.default.bold("Input:")} ${import_chalk24.default.gray(preview2)}`);
12685
14501
  }
12686
14502
  console.log("");
12687
- console.log(import_chalk20.default.bold("Config Sources (Waterfall):"));
14503
+ console.log(import_chalk24.default.bold("Config Sources (Waterfall):"));
12688
14504
  for (const tier of result.waterfall) {
12689
- const num2 = import_chalk20.default.gray(` ${tier.tier}.`);
14505
+ const num3 = import_chalk24.default.gray(` ${tier.tier}.`);
12690
14506
  const label = tier.label.padEnd(16);
12691
14507
  let statusStr;
12692
14508
  if (tier.tier === 1) {
12693
- statusStr = import_chalk20.default.gray(tier.note ?? "");
14509
+ statusStr = import_chalk24.default.gray(tier.note ?? "");
12694
14510
  } else if (tier.status === "active") {
12695
- const loc = tier.path ? import_chalk20.default.gray(tier.path) : "";
12696
- const note = tier.note ? import_chalk20.default.gray(`(${tier.note})`) : "";
12697
- statusStr = import_chalk20.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
14511
+ const loc = tier.path ? import_chalk24.default.gray(tier.path) : "";
14512
+ const note = tier.note ? import_chalk24.default.gray(`(${tier.note})`) : "";
14513
+ statusStr = import_chalk24.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
12698
14514
  } else {
12699
- statusStr = import_chalk20.default.gray("\u25CB " + (tier.note ?? "not found"));
14515
+ statusStr = import_chalk24.default.gray("\u25CB " + (tier.note ?? "not found"));
12700
14516
  }
12701
- console.log(`${num2} ${import_chalk20.default.white(label)} ${statusStr}`);
14517
+ console.log(`${num3} ${import_chalk24.default.white(label)} ${statusStr}`);
12702
14518
  }
12703
14519
  console.log("");
12704
- console.log(import_chalk20.default.bold("Policy Evaluation:"));
14520
+ console.log(import_chalk24.default.bold("Policy Evaluation:"));
12705
14521
  for (const step of result.steps) {
12706
14522
  const isFinal = step.isFinal;
12707
14523
  let icon;
12708
- if (step.outcome === "allow") icon = import_chalk20.default.green(" \u2705");
12709
- else if (step.outcome === "review") icon = import_chalk20.default.red(" \u{1F534}");
12710
- else if (step.outcome === "skip") icon = import_chalk20.default.gray(" \u2500 ");
12711
- else icon = import_chalk20.default.gray(" \u25CB ");
14524
+ if (step.outcome === "allow") icon = import_chalk24.default.green(" \u2705");
14525
+ else if (step.outcome === "review") icon = import_chalk24.default.red(" \u{1F534}");
14526
+ else if (step.outcome === "skip") icon = import_chalk24.default.gray(" \u2500 ");
14527
+ else icon = import_chalk24.default.gray(" \u25CB ");
12712
14528
  const name = step.name.padEnd(18);
12713
- const nameStr = isFinal ? import_chalk20.default.white.bold(name) : import_chalk20.default.white(name);
12714
- const detail = isFinal ? import_chalk20.default.white(step.detail) : import_chalk20.default.gray(step.detail);
12715
- const arrow = isFinal ? import_chalk20.default.yellow(" \u2190 STOP") : "";
14529
+ const nameStr = isFinal ? import_chalk24.default.white.bold(name) : import_chalk24.default.white(name);
14530
+ const detail = isFinal ? import_chalk24.default.white(step.detail) : import_chalk24.default.gray(step.detail);
14531
+ const arrow = isFinal ? import_chalk24.default.yellow(" \u2190 STOP") : "";
12716
14532
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
12717
14533
  }
12718
14534
  console.log("");
12719
14535
  if (result.decision === "allow") {
12720
- console.log(import_chalk20.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk20.default.gray(" \u2014 no approval needed"));
14536
+ console.log(import_chalk24.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk24.default.gray(" \u2014 no approval needed"));
12721
14537
  } else {
12722
14538
  console.log(
12723
- import_chalk20.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk20.default.gray(" \u2014 human approval required")
14539
+ import_chalk24.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk24.default.gray(" \u2014 human approval required")
12724
14540
  );
12725
14541
  if (result.blockedByLabel) {
12726
- console.log(import_chalk20.default.gray(` Reason: ${result.blockedByLabel}`));
14542
+ console.log(import_chalk24.default.gray(` Reason: ${result.blockedByLabel}`));
12727
14543
  }
12728
14544
  }
12729
14545
  console.log("");
@@ -12738,7 +14554,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
12738
14554
  try {
12739
14555
  await startTail2(options);
12740
14556
  } catch (err2) {
12741
- console.error(import_chalk20.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
14557
+ console.error(import_chalk24.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
12742
14558
  process.exit(1);
12743
14559
  }
12744
14560
  });
@@ -12770,14 +14586,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
12770
14586
  Run "node9 addto claude" to register it as the statusLine.`
12771
14587
  ).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
12772
14588
  if (subcommand === "debug") {
12773
- const flagFile = import_path34.default.join(import_os27.default.homedir(), ".node9", "hud-debug");
14589
+ const flagFile = import_path38.default.join(import_os31.default.homedir(), ".node9", "hud-debug");
12774
14590
  if (state === "on") {
12775
- import_fs31.default.mkdirSync(import_path34.default.dirname(flagFile), { recursive: true });
12776
- import_fs31.default.writeFileSync(flagFile, "");
14591
+ import_fs35.default.mkdirSync(import_path38.default.dirname(flagFile), { recursive: true });
14592
+ import_fs35.default.writeFileSync(flagFile, "");
12777
14593
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
12778
14594
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
12779
14595
  } else if (state === "off") {
12780
- if (import_fs31.default.existsSync(flagFile)) import_fs31.default.unlinkSync(flagFile);
14596
+ if (import_fs35.default.existsSync(flagFile)) import_fs35.default.unlinkSync(flagFile);
12781
14597
  console.log("HUD debug logging disabled.");
12782
14598
  } else {
12783
14599
  console.error("Usage: node9 hud debug on|off");
@@ -12792,7 +14608,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
12792
14608
  const ms = parseDuration(options.duration);
12793
14609
  if (ms === null) {
12794
14610
  console.error(
12795
- import_chalk20.default.red(`
14611
+ import_chalk24.default.red(`
12796
14612
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
12797
14613
  `)
12798
14614
  );
@@ -12800,20 +14616,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
12800
14616
  }
12801
14617
  pauseNode9(ms, options.duration);
12802
14618
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
12803
- console.log(import_chalk20.default.yellow(`
14619
+ console.log(import_chalk24.default.yellow(`
12804
14620
  \u23F8 Node9 paused until ${expiresAt}`));
12805
- console.log(import_chalk20.default.gray(` All tool calls will be allowed without review.`));
12806
- console.log(import_chalk20.default.gray(` Run "node9 resume" to re-enable early.
14621
+ console.log(import_chalk24.default.gray(` All tool calls will be allowed without review.`));
14622
+ console.log(import_chalk24.default.gray(` Run "node9 resume" to re-enable early.
12807
14623
  `));
12808
14624
  });
12809
14625
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
12810
14626
  const { paused } = checkPause();
12811
14627
  if (!paused) {
12812
- console.log(import_chalk20.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
14628
+ console.log(import_chalk24.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
12813
14629
  return;
12814
14630
  }
12815
14631
  resumeNode9();
12816
- console.log(import_chalk20.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
14632
+ console.log(import_chalk24.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
12817
14633
  });
12818
14634
  var HOOK_BASED_AGENTS = {
12819
14635
  claude: "claude",
@@ -12826,15 +14642,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12826
14642
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
12827
14643
  const target = HOOK_BASED_AGENTS[firstArg2];
12828
14644
  console.error(
12829
- import_chalk20.default.yellow(`
14645
+ import_chalk24.default.yellow(`
12830
14646
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
12831
14647
  );
12832
- console.error(import_chalk20.default.white(`
14648
+ console.error(import_chalk24.default.white(`
12833
14649
  "${target}" uses its own hook system. Use:`));
12834
14650
  console.error(
12835
- import_chalk20.default.green(` node9 addto ${target} `) + import_chalk20.default.gray("# one-time setup")
14651
+ import_chalk24.default.green(` node9 addto ${target} `) + import_chalk24.default.gray("# one-time setup")
12836
14652
  );
12837
- console.error(import_chalk20.default.green(` ${target} `) + import_chalk20.default.gray("# run normally"));
14653
+ console.error(import_chalk24.default.green(` ${target} `) + import_chalk24.default.gray("# run normally"));
12838
14654
  process.exit(1);
12839
14655
  }
12840
14656
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -12851,7 +14667,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12851
14667
  }
12852
14668
  );
12853
14669
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
12854
- console.error(import_chalk20.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
14670
+ console.error(import_chalk24.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
12855
14671
  const daemonReady = await autoStartDaemonAndWait();
12856
14672
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
12857
14673
  }
@@ -12864,12 +14680,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12864
14680
  }
12865
14681
  if (!result.approved) {
12866
14682
  console.error(
12867
- import_chalk20.default.red(`
14683
+ import_chalk24.default.red(`
12868
14684
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
12869
14685
  );
12870
14686
  process.exit(1);
12871
14687
  }
12872
- console.error(import_chalk20.default.green("\n\u2705 Approved \u2014 running command...\n"));
14688
+ console.error(import_chalk24.default.green("\n\u2705 Approved \u2014 running command...\n"));
12873
14689
  await runProxy(fullCommand);
12874
14690
  } else {
12875
14691
  program.help();
@@ -12879,14 +14695,18 @@ registerUndoCommand(program);
12879
14695
  registerShieldCommand(program);
12880
14696
  registerConfigShowCommand(program);
12881
14697
  registerTrustCommand(program);
14698
+ registerSyncCommand(program);
14699
+ registerAgentsCommand(program);
14700
+ registerScanCommand(program);
14701
+ registerSessionsCommand(program);
12882
14702
  if (process.argv[2] !== "daemon") {
12883
14703
  process.on("unhandledRejection", (reason) => {
12884
14704
  const isCheckHook = process.argv[2] === "check";
12885
14705
  if (isCheckHook) {
12886
14706
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
12887
- const logPath = import_path34.default.join(import_os27.default.homedir(), ".node9", "hook-debug.log");
14707
+ const logPath = import_path38.default.join(import_os31.default.homedir(), ".node9", "hook-debug.log");
12888
14708
  const msg = reason instanceof Error ? reason.message : String(reason);
12889
- import_fs31.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
14709
+ import_fs35.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
12890
14710
  `);
12891
14711
  }
12892
14712
  process.exit(0);