@node9/proxy 1.10.3 → 1.11.0

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 path39 = issue.path.length > 0 ? issue.path.join(".") : "root";
172
- return ` \u2022 ${path39}: ${issue.message}`;
171
+ const path41 = issue.path.length > 0 ? issue.path.join(".") : "root";
172
+ return ` \u2022 ${path41}: ${issue.message}`;
173
173
  });
174
174
  return {
175
175
  sanitized,
@@ -276,6 +276,11 @@ var init_config_schema = __esm({
276
276
  enabled: import_zod.z.boolean().optional(),
277
277
  threshold: import_zod.z.number().min(2).optional(),
278
278
  windowSeconds: import_zod.z.number().min(10).optional()
279
+ }).optional(),
280
+ skillPinning: import_zod.z.object({
281
+ enabled: import_zod.z.boolean().optional(),
282
+ mode: import_zod.z.enum(["warn", "block"]).optional(),
283
+ roots: import_zod.z.array(import_zod.z.string()).optional()
279
284
  }).optional()
280
285
  }).optional(),
281
286
  environments: import_zod.z.record(import_zod.z.object({ requireApproval: import_zod.z.boolean().optional() })).optional()
@@ -571,7 +576,11 @@ function getConfig(cwd) {
571
576
  ignorePaths: [...DEFAULT_CONFIG.policy.snapshot.ignorePaths]
572
577
  },
573
578
  dlp: { ...DEFAULT_CONFIG.policy.dlp },
574
- loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection }
579
+ loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection },
580
+ skillPinning: {
581
+ ...DEFAULT_CONFIG.policy.skillPinning,
582
+ roots: [...DEFAULT_CONFIG.policy.skillPinning.roots]
583
+ }
575
584
  };
576
585
  const mergedEnvironments = { ...DEFAULT_CONFIG.environments };
577
586
  const applyLayer = (source) => {
@@ -624,6 +633,16 @@ function getConfig(cwd) {
624
633
  if (ld.windowSeconds !== void 0)
625
634
  mergedPolicy.loopDetection.windowSeconds = ld.windowSeconds;
626
635
  }
636
+ if (p.skillPinning && typeof p.skillPinning === "object") {
637
+ const sp = p.skillPinning;
638
+ if (sp.enabled !== void 0) mergedPolicy.skillPinning.enabled = sp.enabled;
639
+ if (sp.mode !== void 0) mergedPolicy.skillPinning.mode = sp.mode;
640
+ if (Array.isArray(sp.roots)) {
641
+ for (const r of sp.roots) {
642
+ if (typeof r === "string" && r.length > 0) mergedPolicy.skillPinning.roots.push(r);
643
+ }
644
+ }
645
+ }
627
646
  const envs = source.environments || {};
628
647
  for (const [envName, envConfig] of Object.entries(envs)) {
629
648
  if (envConfig && typeof envConfig === "object") {
@@ -675,6 +694,7 @@ function getConfig(cwd) {
675
694
  mergedPolicy.sandboxPaths = [...new Set(mergedPolicy.sandboxPaths)];
676
695
  mergedPolicy.dangerousWords = [...new Set(mergedPolicy.dangerousWords)];
677
696
  mergedPolicy.ignoredTools = [...new Set(mergedPolicy.ignoredTools)];
697
+ mergedPolicy.skillPinning.roots = [...new Set(mergedPolicy.skillPinning.roots)];
678
698
  mergedPolicy.snapshot.tools = [...new Set(mergedPolicy.snapshot.tools)];
679
699
  mergedPolicy.snapshot.onlyPaths = [...new Set(mergedPolicy.snapshot.onlyPaths)];
680
700
  mergedPolicy.snapshot.ignorePaths = [...new Set(mergedPolicy.snapshot.ignorePaths)];
@@ -942,7 +962,8 @@ var init_config = __esm({
942
962
  }
943
963
  ],
944
964
  dlp: { enabled: true, scanIgnoredTools: true },
945
- loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 }
965
+ loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 },
966
+ skillPinning: { enabled: false, mode: "warn", roots: [] }
946
967
  },
947
968
  environments: {}
948
969
  };
@@ -1746,9 +1767,9 @@ function matchesPattern(text, patterns) {
1746
1767
  const withoutDotSlash = text.replace(/^\.\//, "");
1747
1768
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1748
1769
  }
1749
- function getNestedValue(obj, path39) {
1770
+ function getNestedValue(obj, path41) {
1750
1771
  if (!obj || typeof obj !== "object") return null;
1751
- return path39.split(".").reduce((prev, curr) => prev?.[curr], obj);
1772
+ return path41.split(".").reduce((prev, curr) => prev?.[curr], obj);
1752
1773
  }
1753
1774
  function shouldSnapshot(toolName, args, config) {
1754
1775
  if (!config.settings.enableUndo) return false;
@@ -2343,6 +2364,15 @@ var init_policy = __esm({
2343
2364
  });
2344
2365
 
2345
2366
  // src/auth/state.ts
2367
+ function extractCommandPattern(toolName, args) {
2368
+ const lower = toolName.toLowerCase();
2369
+ if (lower !== "bash" && lower !== "execute_bash" && lower !== "shell") return void 0;
2370
+ const a = args;
2371
+ const cmd = typeof a?.["command"] === "string" ? a["command"].trim() : "";
2372
+ if (!cmd) return void 0;
2373
+ const words = cmd.split(/\s+/);
2374
+ return words.slice(0, 2).join(" ");
2375
+ }
2346
2376
  function checkPause() {
2347
2377
  try {
2348
2378
  if (!import_fs8.default.existsSync(PAUSED_FILE)) return { paused: false };
@@ -2376,7 +2406,7 @@ function resumeNode9() {
2376
2406
  } catch {
2377
2407
  }
2378
2408
  }
2379
- function getActiveTrustSession(toolName) {
2409
+ function getActiveTrustSession(toolName, args) {
2380
2410
  try {
2381
2411
  if (!import_fs8.default.existsSync(TRUST_FILE)) return false;
2382
2412
  const trust = JSON.parse(import_fs8.default.readFileSync(TRUST_FILE, "utf-8"));
@@ -2385,12 +2415,20 @@ function getActiveTrustSession(toolName) {
2385
2415
  if (active.length !== trust.entries.length) {
2386
2416
  import_fs8.default.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
2387
2417
  }
2388
- return active.some((e) => e.tool === toolName || matchesPattern(toolName, e.tool));
2418
+ return active.some((e) => {
2419
+ if (!(e.tool === toolName || matchesPattern(toolName, e.tool))) return false;
2420
+ if (e.commandPattern) {
2421
+ const actual = extractCommandPattern(toolName, args) ?? "";
2422
+ return actual === e.commandPattern || actual.startsWith(e.commandPattern + " ");
2423
+ }
2424
+ return true;
2425
+ });
2389
2426
  } catch {
2390
2427
  return false;
2391
2428
  }
2392
2429
  }
2393
- function writeTrustSession(toolName, durationMs) {
2430
+ function writeTrustSession(toolName, durationMs, args) {
2431
+ const commandPattern = extractCommandPattern(toolName, args);
2394
2432
  try {
2395
2433
  let trust = { entries: [] };
2396
2434
  try {
@@ -2400,8 +2438,14 @@ function writeTrustSession(toolName, durationMs) {
2400
2438
  } catch {
2401
2439
  }
2402
2440
  const now = Date.now();
2403
- trust.entries = trust.entries.filter((e) => e.tool !== toolName && e.expiry > now);
2404
- trust.entries.push({ tool: toolName, expiry: now + durationMs });
2441
+ trust.entries = trust.entries.filter(
2442
+ (e) => !(e.tool === toolName && e.commandPattern === commandPattern) && e.expiry > now
2443
+ );
2444
+ trust.entries.push({
2445
+ tool: toolName,
2446
+ ...commandPattern && { commandPattern },
2447
+ expiry: now + durationMs
2448
+ });
2405
2449
  atomicWriteSync(TRUST_FILE, JSON.stringify(trust, null, 2));
2406
2450
  } catch (err2) {
2407
2451
  if (process.env.NODE9_DEBUG === "1") {
@@ -3400,12 +3444,6 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3400
3444
  };
3401
3445
  }
3402
3446
  }
3403
- if (getActiveTrustSession(toolName)) {
3404
- if (approvers.cloud && creds?.apiKey)
3405
- await auditLocalAllow(toolName, args, "trust", creds, meta);
3406
- if (!isManual) appendLocalAudit(toolName, args, "allow", "trust", meta, hashAuditArgs);
3407
- return { approved: true, checkedBy: "trust" };
3408
- }
3409
3447
  const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
3410
3448
  if (policyResult.decision === "allow") {
3411
3449
  if (approvers.cloud && creds?.apiKey)
@@ -3487,6 +3525,12 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3487
3525
  if (!isManual) appendLocalAudit(toolName, args, "allow", "ignored", meta, hashAuditArgs);
3488
3526
  return { approved: true };
3489
3527
  }
3528
+ if (!taintWarning && getActiveTrustSession(toolName, args)) {
3529
+ if (approvers.cloud && creds?.apiKey)
3530
+ await auditLocalAllow(toolName, args, "trust", creds, meta);
3531
+ if (!isManual) appendLocalAudit(toolName, args, "allow", "trust", meta, hashAuditArgs);
3532
+ return { approved: true, checkedBy: "trust" };
3533
+ }
3490
3534
  if (taintWarning) {
3491
3535
  explainableLabel = "\u{1F534} Node9 Taint (Exfiltration Prevention)";
3492
3536
  riskMetadata = computeRiskMetadata(
@@ -3619,7 +3663,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3619
3663
  riskMetadata?.ruleDescription
3620
3664
  );
3621
3665
  if (decision === "always_allow") {
3622
- writeTrustSession(toolName, 36e5);
3666
+ writeTrustSession(toolName, 36e5, args);
3623
3667
  return { approved: true, checkedBy: "trust" };
3624
3668
  }
3625
3669
  const isApproved = decision === "allow";
@@ -5792,7 +5836,7 @@ function writeGlobalSetting(key, value) {
5792
5836
  config.settings[key] = value;
5793
5837
  atomicWriteSync2(GLOBAL_CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 384 });
5794
5838
  }
5795
- function writeTrustEntry(toolName, durationMs) {
5839
+ function writeTrustEntry(toolName, durationMs, commandPattern) {
5796
5840
  try {
5797
5841
  let trust = { entries: [] };
5798
5842
  try {
@@ -5800,8 +5844,14 @@ function writeTrustEntry(toolName, durationMs) {
5800
5844
  trust = JSON.parse(import_fs14.default.readFileSync(TRUST_FILE2, "utf-8"));
5801
5845
  } catch {
5802
5846
  }
5803
- trust.entries = trust.entries.filter((e) => e.tool !== toolName && e.expiry > Date.now());
5804
- trust.entries.push({ tool: toolName, expiry: Date.now() + durationMs });
5847
+ trust.entries = trust.entries.filter(
5848
+ (e) => !(e.tool === toolName && e.commandPattern === commandPattern) && e.expiry > Date.now()
5849
+ );
5850
+ trust.entries.push({
5851
+ tool: toolName,
5852
+ ...commandPattern && { commandPattern },
5853
+ expiry: Date.now() + durationMs
5854
+ });
5805
5855
  atomicWriteSync2(TRUST_FILE2, JSON.stringify(trust, null, 2));
5806
5856
  } catch {
5807
5857
  }
@@ -6730,7 +6780,8 @@ data: ${JSON.stringify(item.data)}
6730
6780
  );
6731
6781
  if (decision === "trust" && trustDuration) {
6732
6782
  const ms = TRUST_DURATIONS[trustDuration] ?? 60 * 6e4;
6733
- writeTrustEntry(entry.toolName, ms);
6783
+ const commandPattern = extractCommandPattern(entry.toolName, entry.args);
6784
+ writeTrustEntry(entry.toolName, ms, commandPattern);
6734
6785
  appendAuditLog({
6735
6786
  toolName: entry.toolName,
6736
6787
  args: entry.args,
@@ -7185,6 +7236,7 @@ var init_server = __esm({
7185
7236
  init_shields();
7186
7237
  init_ui2();
7187
7238
  init_state2();
7239
+ init_state();
7188
7240
  init_patch();
7189
7241
  init_config_schema();
7190
7242
  init_costSync();
@@ -7537,22 +7589,22 @@ function formatBase(activity) {
7537
7589
  const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
7538
7590
  const icon = getIcon(activity.tool);
7539
7591
  const toolName = activity.tool.slice(0, 16).padEnd(16);
7540
- const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os29.default.homedir(), "~");
7592
+ const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os31.default.homedir(), "~");
7541
7593
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
7542
- return `${import_chalk23.default.gray(time)} ${icon} ${import_chalk23.default.white.bold(toolName)} ${import_chalk23.default.dim(argsPreview)}`;
7594
+ return `${import_chalk24.default.gray(time)} ${icon} ${import_chalk24.default.white.bold(toolName)} ${import_chalk24.default.dim(argsPreview)}`;
7543
7595
  }
7544
7596
  function renderResult(activity, result) {
7545
7597
  const base = formatBase(activity);
7546
7598
  let status;
7547
7599
  if (result.status === "allow") {
7548
- status = import_chalk23.default.green("\u2713 ALLOW");
7600
+ status = import_chalk24.default.green("\u2713 ALLOW");
7549
7601
  } else if (result.status === "dlp") {
7550
- status = import_chalk23.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
7602
+ status = import_chalk24.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
7551
7603
  } else {
7552
- status = import_chalk23.default.red("\u2717 BLOCK");
7604
+ status = import_chalk24.default.red("\u2717 BLOCK");
7553
7605
  }
7554
7606
  const cost = result.costEstimate ?? activity.costEstimate;
7555
- const costSuffix = cost == null ? "" : import_chalk23.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
7607
+ const costSuffix = cost == null ? "" : import_chalk24.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
7556
7608
  if (process.stdout.isTTY) {
7557
7609
  if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
7558
7610
  import_readline5.default.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
@@ -7569,19 +7621,19 @@ function renderResult(activity, result) {
7569
7621
  }
7570
7622
  function renderPending(activity) {
7571
7623
  if (!process.stdout.isTTY) return;
7572
- const line = `${formatBase(activity)} ${import_chalk23.default.yellow("\u25CF \u2026")}`;
7624
+ const line = `${formatBase(activity)} ${import_chalk24.default.yellow("\u25CF \u2026")}`;
7573
7625
  pendingShownForId = activity.id;
7574
7626
  pendingWrappedLines = wrappedLineCount(line);
7575
7627
  process.stdout.write(`${line}\r`);
7576
7628
  }
7577
7629
  async function ensureDaemon() {
7578
7630
  let pidPort = null;
7579
- if (import_fs33.default.existsSync(PID_FILE)) {
7631
+ if (import_fs35.default.existsSync(PID_FILE)) {
7580
7632
  try {
7581
- const { port } = JSON.parse(import_fs33.default.readFileSync(PID_FILE, "utf-8"));
7633
+ const { port } = JSON.parse(import_fs35.default.readFileSync(PID_FILE, "utf-8"));
7582
7634
  pidPort = port;
7583
7635
  } catch {
7584
- console.error(import_chalk23.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
7636
+ console.error(import_chalk24.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
7585
7637
  }
7586
7638
  }
7587
7639
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -7592,7 +7644,7 @@ async function ensureDaemon() {
7592
7644
  if (res.ok) return checkPort;
7593
7645
  } catch {
7594
7646
  }
7595
- console.log(import_chalk23.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
7647
+ console.log(import_chalk24.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
7596
7648
  const child = (0, import_child_process15.spawn)(process.execPath, [process.argv[1], "daemon"], {
7597
7649
  detached: true,
7598
7650
  stdio: "ignore",
@@ -7609,7 +7661,7 @@ async function ensureDaemon() {
7609
7661
  } catch {
7610
7662
  }
7611
7663
  }
7612
- console.error(import_chalk23.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
7664
+ console.error(import_chalk24.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
7613
7665
  process.exit(1);
7614
7666
  }
7615
7667
  function postDecisionHttp(id, decision, csrfToken, port, opts) {
@@ -7730,9 +7782,9 @@ function buildRecoveryCardLines(req) {
7730
7782
  ];
7731
7783
  }
7732
7784
  function readApproversFromDisk() {
7733
- const configPath = import_path36.default.join(import_os29.default.homedir(), ".node9", "config.json");
7785
+ const configPath = import_path38.default.join(import_os31.default.homedir(), ".node9", "config.json");
7734
7786
  try {
7735
- const raw = JSON.parse(import_fs33.default.readFileSync(configPath, "utf-8"));
7787
+ const raw = JSON.parse(import_fs35.default.readFileSync(configPath, "utf-8"));
7736
7788
  const settings = raw.settings ?? {};
7737
7789
  return settings.approvers ?? {};
7738
7790
  } catch {
@@ -7743,20 +7795,20 @@ function approverStatusLine() {
7743
7795
  const a = readApproversFromDisk();
7744
7796
  const fmt = (label, key) => {
7745
7797
  const on = a[key] !== false;
7746
- return `[${key[0]}]${label.slice(1)} ${on ? import_chalk23.default.green("\u2713") : import_chalk23.default.dim("\u2717")}`;
7798
+ return `[${key[0]}]${label.slice(1)} ${on ? import_chalk24.default.green("\u2713") : import_chalk24.default.dim("\u2717")}`;
7747
7799
  };
7748
7800
  return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
7749
7801
  }
7750
7802
  function toggleApprover(channel) {
7751
- const configPath = import_path36.default.join(import_os29.default.homedir(), ".node9", "config.json");
7803
+ const configPath = import_path38.default.join(import_os31.default.homedir(), ".node9", "config.json");
7752
7804
  try {
7753
- const raw = JSON.parse(import_fs33.default.readFileSync(configPath, "utf-8"));
7805
+ const raw = JSON.parse(import_fs35.default.readFileSync(configPath, "utf-8"));
7754
7806
  const settings = raw.settings ?? {};
7755
7807
  const approvers = settings.approvers ?? {};
7756
7808
  approvers[channel] = approvers[channel] === false;
7757
7809
  settings.approvers = approvers;
7758
7810
  raw.settings = settings;
7759
- import_fs33.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
7811
+ import_fs35.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
7760
7812
  } catch (err2) {
7761
7813
  process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
7762
7814
  `);
@@ -7788,7 +7840,7 @@ async function startTail(options = {}) {
7788
7840
  req2.end();
7789
7841
  });
7790
7842
  if (result.ok) {
7791
- console.log(import_chalk23.default.green("\u2713 Flight Recorder buffer cleared."));
7843
+ console.log(import_chalk24.default.green("\u2713 Flight Recorder buffer cleared."));
7792
7844
  } else if (result.code === "ECONNREFUSED") {
7793
7845
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
7794
7846
  } else if (result.code === "ETIMEDOUT") {
@@ -7832,7 +7884,7 @@ async function startTail(options = {}) {
7832
7884
  const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
7833
7885
  if (channel) {
7834
7886
  toggleApprover(channel);
7835
- console.log(import_chalk23.default.dim(` Approvers: ${approverStatusLine()}`));
7887
+ console.log(import_chalk24.default.dim(` Approvers: ${approverStatusLine()}`));
7836
7888
  }
7837
7889
  };
7838
7890
  process.stdin.on("keypress", idleKeypressHandler);
@@ -7898,7 +7950,7 @@ async function startTail(options = {}) {
7898
7950
  localAllowCounts.get(req2.toolName) ?? 0
7899
7951
  )
7900
7952
  );
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");
7953
+ const decisionStamp = action === "always-allow" ? import_chalk24.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk24.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk24.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk24.default.yellow("\u21A9 REDIRECT AI") : import_chalk24.default.red("\u2717 DENIED");
7902
7954
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
7903
7955
  for (const line of stampedLines) process.stdout.write(line + "\n");
7904
7956
  process.stdout.write(SHOW_CURSOR);
@@ -7926,8 +7978,8 @@ async function startTail(options = {}) {
7926
7978
  }
7927
7979
  postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
7928
7980
  try {
7929
- import_fs33.default.appendFileSync(
7930
- import_path36.default.join(import_os29.default.homedir(), ".node9", "hook-debug.log"),
7981
+ import_fs35.default.appendFileSync(
7982
+ import_path38.default.join(import_os31.default.homedir(), ".node9", "hook-debug.log"),
7931
7983
  `[tail] POST /decision failed: ${String(err2)}
7932
7984
  `
7933
7985
  );
@@ -7949,7 +8001,7 @@ async function startTail(options = {}) {
7949
8001
  );
7950
8002
  const stampedLines = buildCardLines(req2, priorCount);
7951
8003
  if (externalDecision) {
7952
- const source = externalDecision === "allow" ? import_chalk23.default.green("\u2713 ALLOWED") : import_chalk23.default.red("\u2717 DENIED");
8004
+ const source = externalDecision === "allow" ? import_chalk24.default.green("\u2713 ALLOWED") : import_chalk24.default.red("\u2717 DENIED");
7953
8005
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
7954
8006
  }
7955
8007
  for (const line of stampedLines) process.stdout.write(line + "\n");
@@ -8008,16 +8060,16 @@ async function startTail(options = {}) {
8008
8060
  }
8009
8061
  } catch {
8010
8062
  }
8011
- console.log(import_chalk23.default.cyan.bold(`
8012
- \u{1F6F0}\uFE0F Node9 tail `) + import_chalk23.default.dim(`\u2192 ${dashboardUrl}`));
8063
+ console.log(import_chalk24.default.cyan.bold(`
8064
+ \u{1F6F0}\uFE0F Node9 tail `) + import_chalk24.default.dim(`\u2192 ${dashboardUrl}`));
8013
8065
  if (canApprove) {
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`));
8066
+ console.log(import_chalk24.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
8067
+ console.log(import_chalk24.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
8016
8068
  }
8017
8069
  if (options.history) {
8018
- console.log(import_chalk23.default.dim("Showing history + live events.\n"));
8070
+ console.log(import_chalk24.default.dim("Showing history + live events.\n"));
8019
8071
  } else {
8020
- console.log(import_chalk23.default.dim("Showing live events only. Use --history to include past.\n"));
8072
+ console.log(import_chalk24.default.dim("Showing live events only. Use --history to include past.\n"));
8021
8073
  }
8022
8074
  process.on("SIGINT", () => {
8023
8075
  exitIdleMode();
@@ -8027,13 +8079,13 @@ async function startTail(options = {}) {
8027
8079
  import_readline5.default.clearLine(process.stdout, 0);
8028
8080
  import_readline5.default.cursorTo(process.stdout, 0);
8029
8081
  }
8030
- console.log(import_chalk23.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
8082
+ console.log(import_chalk24.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
8031
8083
  process.exit(0);
8032
8084
  });
8033
8085
  const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
8034
8086
  const req = import_http2.default.get(sseUrl, (res) => {
8035
8087
  if (res.statusCode !== 200) {
8036
- console.error(import_chalk23.default.red(`Failed to connect: HTTP ${res.statusCode}`));
8088
+ console.error(import_chalk24.default.red(`Failed to connect: HTTP ${res.statusCode}`));
8037
8089
  process.exit(1);
8038
8090
  }
8039
8091
  if (canApprove) enterIdleMode();
@@ -8064,7 +8116,7 @@ async function startTail(options = {}) {
8064
8116
  import_readline5.default.clearLine(process.stdout, 0);
8065
8117
  import_readline5.default.cursorTo(process.stdout, 0);
8066
8118
  }
8067
- console.log(import_chalk23.default.red("\n\u274C Daemon disconnected."));
8119
+ console.log(import_chalk24.default.red("\n\u274C Daemon disconnected."));
8068
8120
  process.exit(1);
8069
8121
  });
8070
8122
  });
@@ -8156,9 +8208,9 @@ async function startTail(options = {}) {
8156
8208
  const hash = data.hash ?? "";
8157
8209
  const summary = data.argsSummary ?? data.tool;
8158
8210
  const fileCount = data.fileCount ?? 0;
8159
- const files = fileCount > 0 ? import_chalk23.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
8211
+ const files = fileCount > 0 ? import_chalk24.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
8160
8212
  process.stdout.write(
8161
- `${import_chalk23.default.dim(time)} ${import_chalk23.default.cyan("\u{1F4F8} snapshot")} ${import_chalk23.default.dim(hash)} ${summary}${files}
8213
+ `${import_chalk24.default.dim(time)} ${import_chalk24.default.cyan("\u{1F4F8} snapshot")} ${import_chalk24.default.dim(hash)} ${summary}${files}
8162
8214
  `
8163
8215
  );
8164
8216
  return;
@@ -8175,26 +8227,26 @@ async function startTail(options = {}) {
8175
8227
  }
8176
8228
  req.on("error", (err2) => {
8177
8229
  const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
8178
- console.error(import_chalk23.default.red(`
8230
+ console.error(import_chalk24.default.red(`
8179
8231
  \u274C ${msg}`));
8180
8232
  process.exit(1);
8181
8233
  });
8182
8234
  }
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;
8235
+ var import_http2, import_chalk24, import_fs35, import_os31, import_path38, import_readline5, import_child_process15, PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
8184
8236
  var init_tail = __esm({
8185
8237
  "src/tui/tail.ts"() {
8186
8238
  "use strict";
8187
8239
  import_http2 = __toESM(require("http"));
8188
- import_chalk23 = __toESM(require("chalk"));
8189
- import_fs33 = __toESM(require("fs"));
8190
- import_os29 = __toESM(require("os"));
8191
- import_path36 = __toESM(require("path"));
8240
+ import_chalk24 = __toESM(require("chalk"));
8241
+ import_fs35 = __toESM(require("fs"));
8242
+ import_os31 = __toESM(require("os"));
8243
+ import_path38 = __toESM(require("path"));
8192
8244
  import_readline5 = __toESM(require("readline"));
8193
8245
  import_child_process15 = require("child_process");
8194
8246
  init_daemon2();
8195
8247
  init_daemon();
8196
8248
  init_core();
8197
- PID_FILE = import_path36.default.join(import_os29.default.homedir(), ".node9", "daemon.pid");
8249
+ PID_FILE = import_path38.default.join(import_os31.default.homedir(), ".node9", "daemon.pid");
8198
8250
  ICONS = {
8199
8251
  bash: "\u{1F4BB}",
8200
8252
  shell: "\u{1F4BB}",
@@ -8309,9 +8361,9 @@ function formatTimeLeft(resetsAt) {
8309
8361
  return ` (${m}m left)`;
8310
8362
  }
8311
8363
  function safeReadJson(filePath) {
8312
- if (!import_fs34.default.existsSync(filePath)) return null;
8364
+ if (!import_fs36.default.existsSync(filePath)) return null;
8313
8365
  try {
8314
- return JSON.parse(import_fs34.default.readFileSync(filePath, "utf-8"));
8366
+ return JSON.parse(import_fs36.default.readFileSync(filePath, "utf-8"));
8315
8367
  } catch {
8316
8368
  return null;
8317
8369
  }
@@ -8332,12 +8384,12 @@ function countHooksInFile(filePath) {
8332
8384
  return Object.keys(cfg.hooks).length;
8333
8385
  }
8334
8386
  function countRulesInDir(rulesDir) {
8335
- if (!import_fs34.default.existsSync(rulesDir)) return 0;
8387
+ if (!import_fs36.default.existsSync(rulesDir)) return 0;
8336
8388
  let count = 0;
8337
8389
  try {
8338
- for (const entry of import_fs34.default.readdirSync(rulesDir, { withFileTypes: true })) {
8390
+ for (const entry of import_fs36.default.readdirSync(rulesDir, { withFileTypes: true })) {
8339
8391
  if (entry.isDirectory()) {
8340
- count += countRulesInDir(import_path37.default.join(rulesDir, entry.name));
8392
+ count += countRulesInDir(import_path39.default.join(rulesDir, entry.name));
8341
8393
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
8342
8394
  count++;
8343
8395
  }
@@ -8348,46 +8400,46 @@ function countRulesInDir(rulesDir) {
8348
8400
  }
8349
8401
  function isSamePath(a, b) {
8350
8402
  try {
8351
- return import_path37.default.resolve(a) === import_path37.default.resolve(b);
8403
+ return import_path39.default.resolve(a) === import_path39.default.resolve(b);
8352
8404
  } catch {
8353
8405
  return false;
8354
8406
  }
8355
8407
  }
8356
8408
  function countConfigs(cwd) {
8357
- const homeDir2 = import_os30.default.homedir();
8358
- const claudeDir = import_path37.default.join(homeDir2, ".claude");
8409
+ const homeDir2 = import_os32.default.homedir();
8410
+ const claudeDir = import_path39.default.join(homeDir2, ".claude");
8359
8411
  let claudeMdCount = 0;
8360
8412
  let rulesCount = 0;
8361
8413
  let hooksCount = 0;
8362
8414
  const userMcpServers = /* @__PURE__ */ new Set();
8363
8415
  const projectMcpServers = /* @__PURE__ */ new Set();
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");
8416
+ if (import_fs36.default.existsSync(import_path39.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
8417
+ rulesCount += countRulesInDir(import_path39.default.join(claudeDir, "rules"));
8418
+ const userSettings = import_path39.default.join(claudeDir, "settings.json");
8367
8419
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
8368
8420
  hooksCount += countHooksInFile(userSettings);
8369
- const userClaudeJson = import_path37.default.join(homeDir2, ".claude.json");
8421
+ const userClaudeJson = import_path39.default.join(homeDir2, ".claude.json");
8370
8422
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
8371
8423
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
8372
8424
  userMcpServers.delete(name);
8373
8425
  }
8374
8426
  if (cwd) {
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");
8427
+ if (import_fs36.default.existsSync(import_path39.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
8428
+ if (import_fs36.default.existsSync(import_path39.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
8429
+ const projectClaudeDir = import_path39.default.join(cwd, ".claude");
8378
8430
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
8379
8431
  if (!overlapsUserScope) {
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");
8432
+ if (import_fs36.default.existsSync(import_path39.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
8433
+ rulesCount += countRulesInDir(import_path39.default.join(projectClaudeDir, "rules"));
8434
+ const projSettings = import_path39.default.join(projectClaudeDir, "settings.json");
8383
8435
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
8384
8436
  hooksCount += countHooksInFile(projSettings);
8385
8437
  }
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");
8438
+ if (import_fs36.default.existsSync(import_path39.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
8439
+ const localSettings = import_path39.default.join(projectClaudeDir, "settings.local.json");
8388
8440
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
8389
8441
  hooksCount += countHooksInFile(localSettings);
8390
- const mcpJsonServers = getMcpServerNames(import_path37.default.join(cwd, ".mcp.json"));
8442
+ const mcpJsonServers = getMcpServerNames(import_path39.default.join(cwd, ".mcp.json"));
8391
8443
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
8392
8444
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
8393
8445
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -8420,12 +8472,12 @@ function readActiveShieldsHud() {
8420
8472
  return shieldsCache.value;
8421
8473
  }
8422
8474
  try {
8423
- const shieldsPath = import_path37.default.join(import_os30.default.homedir(), ".node9", "shields.json");
8424
- if (!import_fs34.default.existsSync(shieldsPath)) {
8475
+ const shieldsPath = import_path39.default.join(import_os32.default.homedir(), ".node9", "shields.json");
8476
+ if (!import_fs36.default.existsSync(shieldsPath)) {
8425
8477
  shieldsCache = { value: [], ts: now };
8426
8478
  return [];
8427
8479
  }
8428
- const parsed = JSON.parse(import_fs34.default.readFileSync(shieldsPath, "utf-8"));
8480
+ const parsed = JSON.parse(import_fs36.default.readFileSync(shieldsPath, "utf-8"));
8429
8481
  if (!Array.isArray(parsed.active)) {
8430
8482
  shieldsCache = { value: [], ts: now };
8431
8483
  return [];
@@ -8527,17 +8579,17 @@ function renderContextLine(stdin) {
8527
8579
  async function main() {
8528
8580
  try {
8529
8581
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
8530
- if (import_fs34.default.existsSync(import_path37.default.join(import_os30.default.homedir(), ".node9", "hud-debug"))) {
8582
+ if (import_fs36.default.existsSync(import_path39.default.join(import_os32.default.homedir(), ".node9", "hud-debug"))) {
8531
8583
  try {
8532
- const logPath = import_path37.default.join(import_os30.default.homedir(), ".node9", "hud-debug.log");
8584
+ const logPath = import_path39.default.join(import_os32.default.homedir(), ".node9", "hud-debug.log");
8533
8585
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
8534
8586
  let size = 0;
8535
8587
  try {
8536
- size = import_fs34.default.statSync(logPath).size;
8588
+ size = import_fs36.default.statSync(logPath).size;
8537
8589
  } catch {
8538
8590
  }
8539
8591
  if (size < MAX_LOG_SIZE) {
8540
- import_fs34.default.appendFileSync(
8592
+ import_fs36.default.appendFileSync(
8541
8593
  logPath,
8542
8594
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
8543
8595
  );
@@ -8558,11 +8610,11 @@ async function main() {
8558
8610
  try {
8559
8611
  const cwd = stdin.cwd ?? process.cwd();
8560
8612
  for (const configPath of [
8561
- import_path37.default.join(cwd, "node9.config.json"),
8562
- import_path37.default.join(import_os30.default.homedir(), ".node9", "config.json")
8613
+ import_path39.default.join(cwd, "node9.config.json"),
8614
+ import_path39.default.join(import_os32.default.homedir(), ".node9", "config.json")
8563
8615
  ]) {
8564
- if (!import_fs34.default.existsSync(configPath)) continue;
8565
- const cfg = JSON.parse(import_fs34.default.readFileSync(configPath, "utf-8"));
8616
+ if (!import_fs36.default.existsSync(configPath)) continue;
8617
+ const cfg = JSON.parse(import_fs36.default.readFileSync(configPath, "utf-8"));
8566
8618
  const hud = cfg.settings?.hud;
8567
8619
  if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
8568
8620
  }
@@ -8580,13 +8632,13 @@ async function main() {
8580
8632
  renderOffline();
8581
8633
  }
8582
8634
  }
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;
8635
+ var import_fs36, import_path39, import_os32, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
8584
8636
  var init_hud = __esm({
8585
8637
  "src/cli/hud.ts"() {
8586
8638
  "use strict";
8587
- import_fs34 = __toESM(require("fs"));
8588
- import_path37 = __toESM(require("path"));
8589
- import_os30 = __toESM(require("os"));
8639
+ import_fs36 = __toESM(require("fs"));
8640
+ import_path39 = __toESM(require("path"));
8641
+ import_os32 = __toESM(require("os"));
8590
8642
  import_http3 = __toESM(require("http"));
8591
8643
  init_daemon();
8592
8644
  RESET3 = "\x1B[0m";
@@ -9501,10 +9553,10 @@ function getAgentsStatus(homeDir2 = import_os11.default.homedir()) {
9501
9553
 
9502
9554
  // src/cli.ts
9503
9555
  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"));
9556
+ var import_chalk25 = __toESM(require("chalk"));
9557
+ var import_fs37 = __toESM(require("fs"));
9558
+ var import_path40 = __toESM(require("path"));
9559
+ var import_os33 = __toESM(require("os"));
9508
9560
  var import_prompts2 = require("@inquirer/prompts");
9509
9561
 
9510
9562
  // src/utils/duration.ts
@@ -9729,10 +9781,10 @@ async function autoStartDaemonAndWait() {
9729
9781
 
9730
9782
  // src/cli/commands/check.ts
9731
9783
  var import_chalk5 = __toESM(require("chalk"));
9732
- var import_fs22 = __toESM(require("fs"));
9784
+ var import_fs23 = __toESM(require("fs"));
9733
9785
  var import_child_process10 = require("child_process");
9734
- var import_path24 = __toESM(require("path"));
9735
- var import_os18 = __toESM(require("os"));
9786
+ var import_path25 = __toESM(require("path"));
9787
+ var import_os19 = __toESM(require("os"));
9736
9788
  init_orchestrator();
9737
9789
  init_daemon();
9738
9790
  init_config();
@@ -10087,6 +10139,187 @@ function applyUndo(hash, cwd) {
10087
10139
  }
10088
10140
  }
10089
10141
 
10142
+ // src/skill-pin.ts
10143
+ var import_fs22 = __toESM(require("fs"));
10144
+ var import_path24 = __toESM(require("path"));
10145
+ var import_os18 = __toESM(require("os"));
10146
+ var import_crypto9 = __toESM(require("crypto"));
10147
+ function getPinsFilePath() {
10148
+ return import_path24.default.join(import_os18.default.homedir(), ".node9", "skill-pins.json");
10149
+ }
10150
+ var MAX_FILES = 5e3;
10151
+ var MAX_TOTAL_BYTES = 50 * 1024 * 1024;
10152
+ function sha256Bytes(buf) {
10153
+ return import_crypto9.default.createHash("sha256").update(buf).digest("hex");
10154
+ }
10155
+ function walkDir(root) {
10156
+ const out = [];
10157
+ let totalBytes = 0;
10158
+ const visit = (dir, relDir) => {
10159
+ if (out.length >= MAX_FILES) return;
10160
+ let entries;
10161
+ try {
10162
+ entries = import_fs22.default.readdirSync(dir, { withFileTypes: true });
10163
+ } catch {
10164
+ return;
10165
+ }
10166
+ entries.sort((a, b) => a.name.localeCompare(b.name));
10167
+ for (const entry of entries) {
10168
+ if (out.length >= MAX_FILES) return;
10169
+ const full = import_path24.default.join(dir, entry.name);
10170
+ const rel = relDir ? import_path24.default.posix.join(relDir, entry.name) : entry.name;
10171
+ let lst;
10172
+ try {
10173
+ lst = import_fs22.default.lstatSync(full);
10174
+ } catch {
10175
+ continue;
10176
+ }
10177
+ if (lst.isSymbolicLink()) continue;
10178
+ if (lst.isDirectory()) {
10179
+ visit(full, rel);
10180
+ continue;
10181
+ }
10182
+ if (!lst.isFile()) continue;
10183
+ if (totalBytes + lst.size > MAX_TOTAL_BYTES) continue;
10184
+ try {
10185
+ const buf = import_fs22.default.readFileSync(full);
10186
+ totalBytes += buf.length;
10187
+ out.push({ rel, hash: sha256Bytes(buf) });
10188
+ } catch {
10189
+ }
10190
+ }
10191
+ };
10192
+ visit(root, "");
10193
+ out.sort((a, b) => a.rel.localeCompare(b.rel));
10194
+ return out.map((e) => `${e.rel}\0${e.hash}`);
10195
+ }
10196
+ function hashSkillRoot(absPath) {
10197
+ let lst;
10198
+ try {
10199
+ lst = import_fs22.default.lstatSync(absPath);
10200
+ } catch {
10201
+ return { exists: false, contentHash: "", fileCount: 0 };
10202
+ }
10203
+ if (lst.isSymbolicLink()) return { exists: false, contentHash: "", fileCount: 0 };
10204
+ if (lst.isFile()) {
10205
+ try {
10206
+ return { exists: true, contentHash: sha256Bytes(import_fs22.default.readFileSync(absPath)), fileCount: 1 };
10207
+ } catch {
10208
+ return { exists: false, contentHash: "", fileCount: 0 };
10209
+ }
10210
+ }
10211
+ if (lst.isDirectory()) {
10212
+ const entries = walkDir(absPath);
10213
+ const contentHash = import_crypto9.default.createHash("sha256").update(entries.join("\n")).digest("hex");
10214
+ return { exists: true, contentHash, fileCount: entries.length };
10215
+ }
10216
+ return { exists: false, contentHash: "", fileCount: 0 };
10217
+ }
10218
+ function getRootKey(absPath) {
10219
+ return import_crypto9.default.createHash("sha256").update(absPath).digest("hex").slice(0, 16);
10220
+ }
10221
+ function readSkillPinsSafe() {
10222
+ const filePath = getPinsFilePath();
10223
+ try {
10224
+ const raw = import_fs22.default.readFileSync(filePath, "utf-8");
10225
+ if (!raw.trim()) return { ok: false, reason: "corrupt", detail: "empty file" };
10226
+ const parsed = JSON.parse(raw);
10227
+ if (!parsed.roots || typeof parsed.roots !== "object" || Array.isArray(parsed.roots)) {
10228
+ return { ok: false, reason: "corrupt", detail: "invalid structure: missing roots object" };
10229
+ }
10230
+ return { ok: true, pins: { roots: parsed.roots } };
10231
+ } catch (err2) {
10232
+ if (err2.code === "ENOENT") return { ok: false, reason: "missing" };
10233
+ return { ok: false, reason: "corrupt", detail: String(err2) };
10234
+ }
10235
+ }
10236
+ function readSkillPins() {
10237
+ const result = readSkillPinsSafe();
10238
+ if (result.ok) return result.pins;
10239
+ if (result.reason === "missing") return { roots: {} };
10240
+ throw new Error(`[node9] skill pin file is corrupt: ${result.detail}`);
10241
+ }
10242
+ function writeSkillPins(data) {
10243
+ const filePath = getPinsFilePath();
10244
+ import_fs22.default.mkdirSync(import_path24.default.dirname(filePath), { recursive: true });
10245
+ const tmp = `${filePath}.${import_crypto9.default.randomBytes(6).toString("hex")}.tmp`;
10246
+ import_fs22.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
10247
+ import_fs22.default.renameSync(tmp, filePath);
10248
+ }
10249
+ function removePin(rootKey) {
10250
+ const pins = readSkillPins();
10251
+ delete pins.roots[rootKey];
10252
+ writeSkillPins(pins);
10253
+ }
10254
+ function clearAllPins() {
10255
+ writeSkillPins({ roots: {} });
10256
+ }
10257
+ function verifyAndPinRoots(roots) {
10258
+ const pinsRead = readSkillPinsSafe();
10259
+ if (!pinsRead.ok && pinsRead.reason === "corrupt") {
10260
+ return { kind: "corrupt", detail: pinsRead.detail };
10261
+ }
10262
+ const pins = pinsRead.ok ? pinsRead.pins : { roots: {} };
10263
+ let mutated = false;
10264
+ for (const rootPath of new Set(roots)) {
10265
+ const rootKey = getRootKey(rootPath);
10266
+ const current = hashSkillRoot(rootPath);
10267
+ const existing = pins.roots[rootKey];
10268
+ if (!existing) {
10269
+ pins.roots[rootKey] = {
10270
+ rootPath,
10271
+ exists: current.exists,
10272
+ contentHash: current.contentHash,
10273
+ fileCount: current.fileCount,
10274
+ pinnedAt: (/* @__PURE__ */ new Date()).toISOString()
10275
+ };
10276
+ mutated = true;
10277
+ continue;
10278
+ }
10279
+ if (existing.exists !== current.exists || existing.contentHash !== current.contentHash) {
10280
+ let summary;
10281
+ if (existing.exists && !current.exists) summary = `vanished: ${rootPath}`;
10282
+ else if (!existing.exists && current.exists) summary = `appeared: ${rootPath}`;
10283
+ else summary = `changed: ${rootPath}`;
10284
+ return { kind: "drift", changedRootKey: rootKey, changedRootPath: rootPath, summary };
10285
+ }
10286
+ }
10287
+ if (mutated) writeSkillPins(pins);
10288
+ return { kind: "verified" };
10289
+ }
10290
+ function defaultSkillRoots(_cwd) {
10291
+ const marketplaces = import_path24.default.join(import_os18.default.homedir(), ".claude", "plugins", "marketplaces");
10292
+ const roots = [];
10293
+ let registries;
10294
+ try {
10295
+ registries = import_fs22.default.readdirSync(marketplaces, { withFileTypes: true });
10296
+ } catch {
10297
+ return [];
10298
+ }
10299
+ for (const registry of registries) {
10300
+ if (!registry.isDirectory()) continue;
10301
+ const pluginsDir = import_path24.default.join(marketplaces, registry.name, "plugins");
10302
+ let plugins;
10303
+ try {
10304
+ plugins = import_fs22.default.readdirSync(pluginsDir, { withFileTypes: true });
10305
+ } catch {
10306
+ continue;
10307
+ }
10308
+ for (const plugin of plugins) {
10309
+ if (!plugin.isDirectory()) continue;
10310
+ roots.push(import_path24.default.join(pluginsDir, plugin.name));
10311
+ }
10312
+ }
10313
+ return roots;
10314
+ }
10315
+ function resolveUserSkillRoot(entry, cwd) {
10316
+ if (!entry) return null;
10317
+ if (entry.startsWith("~/") || entry === "~") return import_path24.default.join(import_os18.default.homedir(), entry.slice(1));
10318
+ if (import_path24.default.isAbsolute(entry)) return entry;
10319
+ if (!cwd || !import_path24.default.isAbsolute(cwd)) return null;
10320
+ return import_path24.default.join(cwd, entry);
10321
+ }
10322
+
10090
10323
  // src/cli/commands/check.ts
10091
10324
  function sanitize2(value) {
10092
10325
  return value.replace(/[\x00-\x1F\x7F]/g, "");
@@ -10102,9 +10335,9 @@ function registerCheckCommand(program2) {
10102
10335
  } catch (err2) {
10103
10336
  const tempConfig = getConfig();
10104
10337
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
10105
- const logPath = import_path24.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log");
10338
+ const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "hook-debug.log");
10106
10339
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
10107
- import_fs22.default.appendFileSync(
10340
+ import_fs23.default.appendFileSync(
10108
10341
  logPath,
10109
10342
  `[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
10110
10343
  RAW: ${raw}
@@ -10117,11 +10350,11 @@ RAW: ${raw}
10117
10350
  if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
10118
10351
  try {
10119
10352
  const scriptPath = process.argv[1];
10120
- if (typeof scriptPath !== "string" || !import_path24.default.isAbsolute(scriptPath))
10353
+ if (typeof scriptPath !== "string" || !import_path25.default.isAbsolute(scriptPath))
10121
10354
  throw new Error("node9: argv[1] is not an absolute path");
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)
10355
+ const resolvedScript = import_fs23.default.realpathSync(scriptPath);
10356
+ const packageDist = import_fs23.default.realpathSync(import_path25.default.resolve(__dirname, "../.."));
10357
+ if (!resolvedScript.startsWith(packageDist + import_path25.default.sep) && resolvedScript !== packageDist)
10125
10358
  throw new Error(
10126
10359
  `node9: daemon spawn aborted \u2014 argv[1] (${resolvedScript}) is outside package dist (${packageDist})`
10127
10360
  );
@@ -10143,10 +10376,10 @@ RAW: ${raw}
10143
10376
  });
10144
10377
  d.unref();
10145
10378
  } catch (spawnErr) {
10146
- const logPath = import_path24.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log");
10379
+ const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "hook-debug.log");
10147
10380
  const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
10148
10381
  try {
10149
- import_fs22.default.appendFileSync(
10382
+ import_fs23.default.appendFileSync(
10150
10383
  logPath,
10151
10384
  `[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-failed: ${msg}
10152
10385
  `
@@ -10156,10 +10389,10 @@ RAW: ${raw}
10156
10389
  }
10157
10390
  }
10158
10391
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
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}
10392
+ const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "hook-debug.log");
10393
+ if (!import_fs23.default.existsSync(import_path25.default.dirname(logPath)))
10394
+ import_fs23.default.mkdirSync(import_path25.default.dirname(logPath), { recursive: true });
10395
+ import_fs23.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
10163
10396
  `);
10164
10397
  }
10165
10398
  const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
@@ -10172,8 +10405,8 @@ RAW: ${raw}
10172
10405
  const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
10173
10406
  let ttyFd = null;
10174
10407
  try {
10175
- ttyFd = import_fs22.default.openSync("/dev/tty", "w");
10176
- const writeTty = (line) => import_fs22.default.writeSync(ttyFd, line + "\n");
10408
+ ttyFd = import_fs23.default.openSync("/dev/tty", "w");
10409
+ const writeTty = (line) => import_fs23.default.writeSync(ttyFd, line + "\n");
10177
10410
  if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
10178
10411
  writeTty(import_chalk5.default.bgRed.white.bold(`
10179
10412
  \u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
@@ -10192,7 +10425,7 @@ RAW: ${raw}
10192
10425
  } finally {
10193
10426
  if (ttyFd !== null)
10194
10427
  try {
10195
- import_fs22.default.closeSync(ttyFd);
10428
+ import_fs23.default.closeSync(ttyFd);
10196
10429
  } catch {
10197
10430
  }
10198
10431
  }
@@ -10221,10 +10454,131 @@ RAW: ${raw}
10221
10454
  return;
10222
10455
  }
10223
10456
  const meta = { agent, mcpServer };
10457
+ const skillPinCfg = config.policy.skillPinning;
10458
+ const rawSessionId = typeof payload.session_id === "string" ? payload.session_id : "";
10459
+ const safeSessionId = /^[A-Za-z0-9_\-]{1,128}$/.test(rawSessionId) ? rawSessionId : "";
10460
+ if (skillPinCfg.enabled && safeSessionId) {
10461
+ try {
10462
+ const sessionsDir = import_path25.default.join(import_os19.default.homedir(), ".node9", "skill-sessions");
10463
+ const flagPath = import_path25.default.join(sessionsDir, `${safeSessionId}.json`);
10464
+ let flag = null;
10465
+ try {
10466
+ flag = JSON.parse(import_fs23.default.readFileSync(flagPath, "utf-8"));
10467
+ } catch {
10468
+ }
10469
+ const writeFlag = (data2) => {
10470
+ try {
10471
+ import_fs23.default.mkdirSync(sessionsDir, { recursive: true });
10472
+ import_fs23.default.writeFileSync(
10473
+ flagPath,
10474
+ JSON.stringify({ ...data2, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
10475
+ { mode: 384 }
10476
+ );
10477
+ } catch {
10478
+ }
10479
+ };
10480
+ const sendSkillWarn = (detail, recoveryCmd) => {
10481
+ let ttyFd = null;
10482
+ try {
10483
+ ttyFd = import_fs23.default.openSync("/dev/tty", "w");
10484
+ const w = (line) => import_fs23.default.writeSync(ttyFd, line + "\n");
10485
+ w(import_chalk5.default.yellow(`
10486
+ \u26A0\uFE0F Node9: installed skill drift detected`));
10487
+ w(import_chalk5.default.gray(` ${detail}`));
10488
+ w(
10489
+ import_chalk5.default.gray(
10490
+ ` If you updated a plugin, acknowledge the change to clear this warning.`
10491
+ )
10492
+ );
10493
+ if (recoveryCmd) w(import_chalk5.default.green(` \u{1F4A1} Run: ${recoveryCmd}`));
10494
+ w("");
10495
+ } catch {
10496
+ } finally {
10497
+ if (ttyFd !== null)
10498
+ try {
10499
+ import_fs23.default.closeSync(ttyFd);
10500
+ } catch {
10501
+ }
10502
+ }
10503
+ };
10504
+ if (flag && flag.state === "quarantined" && skillPinCfg.mode === "block") {
10505
+ sendBlock(
10506
+ `Node9: session quarantined \u2014 installed skill changed. Open a separate terminal and run: node9 skill pin list (to see what changed) then: node9 skill pin update <rootKey> (to acknowledge). If you updated a plugin intentionally, this is expected.`,
10507
+ {
10508
+ blockedByLabel: "Skill Pin Quarantine",
10509
+ recoveryCommand: "node9 skill pin list"
10510
+ }
10511
+ );
10512
+ return;
10513
+ }
10514
+ if (!flag || flag.state !== "verified" && flag.state !== "warned") {
10515
+ const absoluteCwd = typeof payload.cwd === "string" && import_path25.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
10516
+ const extraRoots = skillPinCfg.roots;
10517
+ const resolvedExtra = extraRoots.map((r) => resolveUserSkillRoot(r, absoluteCwd)).filter((r) => typeof r === "string");
10518
+ const roots = [...defaultSkillRoots(absoluteCwd), ...resolvedExtra];
10519
+ const result2 = verifyAndPinRoots(roots);
10520
+ if (result2.kind === "corrupt") {
10521
+ if (skillPinCfg.mode === "block") {
10522
+ writeFlag({
10523
+ state: "quarantined",
10524
+ detail: `pin file corrupt: ${result2.detail}`
10525
+ });
10526
+ sendBlock("Node9: skill pin file is corrupt \u2014 fail-closed.", {
10527
+ blockedByLabel: "Skill Pin Quarantine",
10528
+ recoveryCommand: "node9 skill pin reset"
10529
+ });
10530
+ return;
10531
+ }
10532
+ writeFlag({ state: "warned", detail: `pin file corrupt: ${result2.detail}` });
10533
+ sendSkillWarn(
10534
+ `Skill pin file is corrupt: ${result2.detail}`,
10535
+ "node9 skill pin reset"
10536
+ );
10537
+ } else if (result2.kind === "drift") {
10538
+ if (skillPinCfg.mode === "block") {
10539
+ writeFlag({ state: "quarantined", detail: result2.summary });
10540
+ sendBlock(
10541
+ `Node9: installed skill changed \u2014 ${result2.summary}. If you updated a plugin, open a separate terminal and run: node9 skill pin update ${result2.changedRootKey}`,
10542
+ {
10543
+ blockedByLabel: "Skill Pin Quarantine",
10544
+ recoveryCommand: `node9 skill pin update ${result2.changedRootKey}`
10545
+ }
10546
+ );
10547
+ return;
10548
+ }
10549
+ writeFlag({ state: "warned", detail: result2.summary });
10550
+ sendSkillWarn(result2.summary, `node9 skill pin update ${result2.changedRootKey}`);
10551
+ } else {
10552
+ writeFlag({ state: "verified" });
10553
+ }
10554
+ try {
10555
+ const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
10556
+ for (const name of import_fs23.default.readdirSync(sessionsDir)) {
10557
+ const p = import_path25.default.join(sessionsDir, name);
10558
+ try {
10559
+ if (import_fs23.default.statSync(p).mtimeMs < cutoff) import_fs23.default.unlinkSync(p);
10560
+ } catch {
10561
+ }
10562
+ }
10563
+ } catch {
10564
+ }
10565
+ }
10566
+ } catch (err2) {
10567
+ if (process.env.NODE9_DEBUG === "1") {
10568
+ try {
10569
+ const dbg = import_path25.default.join(import_os19.default.homedir(), ".node9", "hook-debug.log");
10570
+ const msg = err2 instanceof Error ? err2.message : String(err2);
10571
+ import_fs23.default.appendFileSync(dbg, `[${(/* @__PURE__ */ new Date()).toISOString()}] SKILL_PIN_ERROR: ${msg}
10572
+ `);
10573
+ } catch {
10574
+ }
10575
+ }
10576
+ }
10577
+ }
10224
10578
  if (shouldSnapshot(toolName, toolInput, config)) {
10225
10579
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
10226
10580
  }
10227
- const safeCwdForAuth = typeof payload.cwd === "string" && import_path24.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
10581
+ const safeCwdForAuth = typeof payload.cwd === "string" && import_path25.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
10228
10582
  const result = await authorizeHeadless(toolName, toolInput, meta, {
10229
10583
  cwd: safeCwdForAuth
10230
10584
  });
@@ -10236,12 +10590,12 @@ RAW: ${raw}
10236
10590
  }
10237
10591
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
10238
10592
  try {
10239
- const tty = import_fs22.default.openSync("/dev/tty", "w");
10240
- import_fs22.default.writeSync(
10593
+ const tty = import_fs23.default.openSync("/dev/tty", "w");
10594
+ import_fs23.default.writeSync(
10241
10595
  tty,
10242
10596
  import_chalk5.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
10243
10597
  );
10244
- import_fs22.default.closeSync(tty);
10598
+ import_fs23.default.closeSync(tty);
10245
10599
  } catch {
10246
10600
  }
10247
10601
  const daemonReady = await autoStartDaemonAndWait();
@@ -10268,9 +10622,9 @@ RAW: ${raw}
10268
10622
  });
10269
10623
  } catch (err2) {
10270
10624
  if (process.env.NODE9_DEBUG === "1") {
10271
- const logPath = import_path24.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log");
10625
+ const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "hook-debug.log");
10272
10626
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
10273
- import_fs22.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
10627
+ import_fs23.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
10274
10628
  `);
10275
10629
  }
10276
10630
  process.exit(0);
@@ -10304,9 +10658,9 @@ RAW: ${raw}
10304
10658
  }
10305
10659
 
10306
10660
  // src/cli/commands/log.ts
10307
- var import_fs23 = __toESM(require("fs"));
10308
- var import_path25 = __toESM(require("path"));
10309
- var import_os19 = __toESM(require("os"));
10661
+ var import_fs24 = __toESM(require("fs"));
10662
+ var import_path26 = __toESM(require("path"));
10663
+ var import_os20 = __toESM(require("os"));
10310
10664
  init_audit();
10311
10665
  init_config();
10312
10666
  init_policy();
@@ -10382,10 +10736,10 @@ function registerLogCommand(program2) {
10382
10736
  decision: "allowed",
10383
10737
  source: "post-hook"
10384
10738
  };
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");
10739
+ const logPath = import_path26.default.join(import_os20.default.homedir(), ".node9", "audit.log");
10740
+ if (!import_fs24.default.existsSync(import_path26.default.dirname(logPath)))
10741
+ import_fs24.default.mkdirSync(import_path26.default.dirname(logPath), { recursive: true });
10742
+ import_fs24.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
10389
10743
  if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
10390
10744
  const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
10391
10745
  if (command) {
@@ -10418,7 +10772,7 @@ function registerLogCommand(program2) {
10418
10772
  }
10419
10773
  }
10420
10774
  }
10421
- const safeCwd = typeof payload.cwd === "string" && import_path25.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
10775
+ const safeCwd = typeof payload.cwd === "string" && import_path26.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
10422
10776
  const config = getConfig(safeCwd);
10423
10777
  if (shouldSnapshot(tool, {}, config)) {
10424
10778
  await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
@@ -10427,9 +10781,9 @@ function registerLogCommand(program2) {
10427
10781
  const msg = err2 instanceof Error ? err2.message : String(err2);
10428
10782
  process.stderr.write(`[Node9] audit log error: ${msg}
10429
10783
  `);
10430
- const debugPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "hook-debug.log");
10784
+ const debugPath = import_path26.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log");
10431
10785
  try {
10432
- import_fs23.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
10786
+ import_fs24.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
10433
10787
  `);
10434
10788
  } catch {
10435
10789
  }
@@ -10829,14 +11183,14 @@ function registerConfigShowCommand(program2) {
10829
11183
 
10830
11184
  // src/cli/commands/doctor.ts
10831
11185
  var import_chalk7 = __toESM(require("chalk"));
10832
- var import_fs24 = __toESM(require("fs"));
10833
- var import_path26 = __toESM(require("path"));
10834
- var import_os20 = __toESM(require("os"));
11186
+ var import_fs25 = __toESM(require("fs"));
11187
+ var import_path27 = __toESM(require("path"));
11188
+ var import_os21 = __toESM(require("os"));
10835
11189
  var import_child_process11 = require("child_process");
10836
11190
  init_daemon();
10837
11191
  function registerDoctorCommand(program2, version2) {
10838
11192
  program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
10839
- const homeDir2 = import_os20.default.homedir();
11193
+ const homeDir2 = import_os21.default.homedir();
10840
11194
  let failures = 0;
10841
11195
  function pass(msg) {
10842
11196
  console.log(import_chalk7.default.green(" \u2705 ") + msg);
@@ -10885,10 +11239,10 @@ function registerDoctorCommand(program2, version2) {
10885
11239
  );
10886
11240
  }
10887
11241
  section("Configuration");
10888
- const globalConfigPath = import_path26.default.join(homeDir2, ".node9", "config.json");
10889
- if (import_fs24.default.existsSync(globalConfigPath)) {
11242
+ const globalConfigPath = import_path27.default.join(homeDir2, ".node9", "config.json");
11243
+ if (import_fs25.default.existsSync(globalConfigPath)) {
10890
11244
  try {
10891
- JSON.parse(import_fs24.default.readFileSync(globalConfigPath, "utf-8"));
11245
+ JSON.parse(import_fs25.default.readFileSync(globalConfigPath, "utf-8"));
10892
11246
  pass("~/.node9/config.json found and valid");
10893
11247
  } catch {
10894
11248
  fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
@@ -10896,10 +11250,10 @@ function registerDoctorCommand(program2, version2) {
10896
11250
  } else {
10897
11251
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
10898
11252
  }
10899
- const projectConfigPath = import_path26.default.join(process.cwd(), "node9.config.json");
10900
- if (import_fs24.default.existsSync(projectConfigPath)) {
11253
+ const projectConfigPath = import_path27.default.join(process.cwd(), "node9.config.json");
11254
+ if (import_fs25.default.existsSync(projectConfigPath)) {
10901
11255
  try {
10902
- JSON.parse(import_fs24.default.readFileSync(projectConfigPath, "utf-8"));
11256
+ JSON.parse(import_fs25.default.readFileSync(projectConfigPath, "utf-8"));
10903
11257
  pass("node9.config.json found and valid (project)");
10904
11258
  } catch {
10905
11259
  fail(
@@ -10908,8 +11262,8 @@ function registerDoctorCommand(program2, version2) {
10908
11262
  );
10909
11263
  }
10910
11264
  }
10911
- const credsPath = import_path26.default.join(homeDir2, ".node9", "credentials.json");
10912
- if (import_fs24.default.existsSync(credsPath)) {
11265
+ const credsPath = import_path27.default.join(homeDir2, ".node9", "credentials.json");
11266
+ if (import_fs25.default.existsSync(credsPath)) {
10913
11267
  pass("Cloud credentials found (~/.node9/credentials.json)");
10914
11268
  } else {
10915
11269
  warn(
@@ -10918,10 +11272,10 @@ function registerDoctorCommand(program2, version2) {
10918
11272
  );
10919
11273
  }
10920
11274
  section("Agent Hooks");
10921
- const claudeSettingsPath = import_path26.default.join(homeDir2, ".claude", "settings.json");
10922
- if (import_fs24.default.existsSync(claudeSettingsPath)) {
11275
+ const claudeSettingsPath = import_path27.default.join(homeDir2, ".claude", "settings.json");
11276
+ if (import_fs25.default.existsSync(claudeSettingsPath)) {
10923
11277
  try {
10924
- const cs = JSON.parse(import_fs24.default.readFileSync(claudeSettingsPath, "utf-8"));
11278
+ const cs = JSON.parse(import_fs25.default.readFileSync(claudeSettingsPath, "utf-8"));
10925
11279
  const hasHook = cs.hooks?.PreToolUse?.some(
10926
11280
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
10927
11281
  );
@@ -10937,10 +11291,10 @@ function registerDoctorCommand(program2, version2) {
10937
11291
  } else {
10938
11292
  warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
10939
11293
  }
10940
- const geminiSettingsPath = import_path26.default.join(homeDir2, ".gemini", "settings.json");
10941
- if (import_fs24.default.existsSync(geminiSettingsPath)) {
11294
+ const geminiSettingsPath = import_path27.default.join(homeDir2, ".gemini", "settings.json");
11295
+ if (import_fs25.default.existsSync(geminiSettingsPath)) {
10942
11296
  try {
10943
- const gs = JSON.parse(import_fs24.default.readFileSync(geminiSettingsPath, "utf-8"));
11297
+ const gs = JSON.parse(import_fs25.default.readFileSync(geminiSettingsPath, "utf-8"));
10944
11298
  const hasHook = gs.hooks?.BeforeTool?.some(
10945
11299
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
10946
11300
  );
@@ -10956,10 +11310,10 @@ function registerDoctorCommand(program2, version2) {
10956
11310
  } else {
10957
11311
  warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
10958
11312
  }
10959
- const cursorHooksPath = import_path26.default.join(homeDir2, ".cursor", "hooks.json");
10960
- if (import_fs24.default.existsSync(cursorHooksPath)) {
11313
+ const cursorHooksPath = import_path27.default.join(homeDir2, ".cursor", "hooks.json");
11314
+ if (import_fs25.default.existsSync(cursorHooksPath)) {
10961
11315
  try {
10962
- const cur = JSON.parse(import_fs24.default.readFileSync(cursorHooksPath, "utf-8"));
11316
+ const cur = JSON.parse(import_fs25.default.readFileSync(cursorHooksPath, "utf-8"));
10963
11317
  const hasHook = cur.hooks?.preToolUse?.some(
10964
11318
  (h) => h.command?.includes("node9") || h.command?.includes("cli.js")
10965
11319
  );
@@ -10997,9 +11351,9 @@ function registerDoctorCommand(program2, version2) {
10997
11351
 
10998
11352
  // src/cli/commands/audit.ts
10999
11353
  var import_chalk8 = __toESM(require("chalk"));
11000
- var import_fs25 = __toESM(require("fs"));
11001
- var import_path27 = __toESM(require("path"));
11002
- var import_os21 = __toESM(require("os"));
11354
+ var import_fs26 = __toESM(require("fs"));
11355
+ var import_path28 = __toESM(require("path"));
11356
+ var import_os22 = __toESM(require("os"));
11003
11357
  function formatRelativeTime(timestamp) {
11004
11358
  const diff = Date.now() - new Date(timestamp).getTime();
11005
11359
  const sec = Math.floor(diff / 1e3);
@@ -11012,14 +11366,14 @@ function formatRelativeTime(timestamp) {
11012
11366
  }
11013
11367
  function registerAuditCommand(program2) {
11014
11368
  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) => {
11015
- const logPath = import_path27.default.join(import_os21.default.homedir(), ".node9", "audit.log");
11016
- if (!import_fs25.default.existsSync(logPath)) {
11369
+ const logPath = import_path28.default.join(import_os22.default.homedir(), ".node9", "audit.log");
11370
+ if (!import_fs26.default.existsSync(logPath)) {
11017
11371
  console.log(
11018
11372
  import_chalk8.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
11019
11373
  );
11020
11374
  return;
11021
11375
  }
11022
- const raw = import_fs25.default.readFileSync(logPath, "utf-8");
11376
+ const raw = import_fs26.default.readFileSync(logPath, "utf-8");
11023
11377
  const lines = raw.split("\n").filter((l) => l.trim() !== "");
11024
11378
  let entries = lines.flatMap((line) => {
11025
11379
  try {
@@ -11073,9 +11427,9 @@ function registerAuditCommand(program2) {
11073
11427
 
11074
11428
  // src/cli/commands/report.ts
11075
11429
  var import_chalk9 = __toESM(require("chalk"));
11076
- var import_fs26 = __toESM(require("fs"));
11077
- var import_path28 = __toESM(require("path"));
11078
- var import_os22 = __toESM(require("os"));
11430
+ var import_fs27 = __toESM(require("fs"));
11431
+ var import_path29 = __toESM(require("path"));
11432
+ var import_os23 = __toESM(require("os"));
11079
11433
  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;
11080
11434
  function buildTestTimestamps(allEntries) {
11081
11435
  const testTs = /* @__PURE__ */ new Set();
@@ -11122,8 +11476,8 @@ function getDateRange(period) {
11122
11476
  }
11123
11477
  }
11124
11478
  function parseAuditLog(logPath) {
11125
- if (!import_fs26.default.existsSync(logPath)) return [];
11126
- const raw = import_fs26.default.readFileSync(logPath, "utf-8");
11479
+ if (!import_fs27.default.existsSync(logPath)) return [];
11480
+ const raw = import_fs27.default.readFileSync(logPath, "utf-8");
11127
11481
  return raw.split("\n").flatMap((line) => {
11128
11482
  if (!line.trim()) return [];
11129
11483
  try {
@@ -11192,11 +11546,11 @@ function loadClaudeCost(start, end) {
11192
11546
  inputTokens: 0,
11193
11547
  cacheReadTokens: 0
11194
11548
  };
11195
- const projectsDir = import_path28.default.join(import_os22.default.homedir(), ".claude", "projects");
11196
- if (!import_fs26.default.existsSync(projectsDir)) return empty;
11549
+ const projectsDir = import_path29.default.join(import_os23.default.homedir(), ".claude", "projects");
11550
+ if (!import_fs27.default.existsSync(projectsDir)) return empty;
11197
11551
  let dirs;
11198
11552
  try {
11199
- dirs = import_fs26.default.readdirSync(projectsDir);
11553
+ dirs = import_fs27.default.readdirSync(projectsDir);
11200
11554
  } catch {
11201
11555
  return empty;
11202
11556
  }
@@ -11206,18 +11560,18 @@ function loadClaudeCost(start, end) {
11206
11560
  const byDay = /* @__PURE__ */ new Map();
11207
11561
  const byModel = /* @__PURE__ */ new Map();
11208
11562
  for (const proj of dirs) {
11209
- const projPath = import_path28.default.join(projectsDir, proj);
11563
+ const projPath = import_path29.default.join(projectsDir, proj);
11210
11564
  let files;
11211
11565
  try {
11212
- const stat = import_fs26.default.statSync(projPath);
11566
+ const stat = import_fs27.default.statSync(projPath);
11213
11567
  if (!stat.isDirectory()) continue;
11214
- files = import_fs26.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
11568
+ files = import_fs27.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
11215
11569
  } catch {
11216
11570
  continue;
11217
11571
  }
11218
11572
  for (const file of files) {
11219
11573
  try {
11220
- const raw = import_fs26.default.readFileSync(import_path28.default.join(projPath, file), "utf-8");
11574
+ const raw = import_fs27.default.readFileSync(import_path29.default.join(projPath, file), "utf-8");
11221
11575
  for (const line of raw.split("\n")) {
11222
11576
  if (!line.trim()) continue;
11223
11577
  let entry;
@@ -11260,7 +11614,7 @@ function registerReportCommand(program2) {
11260
11614
  const period = ["today", "7d", "30d", "month"].includes(
11261
11615
  options.period
11262
11616
  ) ? options.period : "7d";
11263
- const logPath = import_path28.default.join(import_os22.default.homedir(), ".node9", "audit.log");
11617
+ const logPath = import_path29.default.join(import_os23.default.homedir(), ".node9", "audit.log");
11264
11618
  const allEntries = parseAuditLog(logPath);
11265
11619
  if (allEntries.length === 0) {
11266
11620
  console.log(
@@ -11613,14 +11967,14 @@ function registerDaemonCommand(program2) {
11613
11967
 
11614
11968
  // src/cli/commands/status.ts
11615
11969
  var import_chalk11 = __toESM(require("chalk"));
11616
- var import_fs27 = __toESM(require("fs"));
11617
- var import_path29 = __toESM(require("path"));
11618
- var import_os23 = __toESM(require("os"));
11970
+ var import_fs28 = __toESM(require("fs"));
11971
+ var import_path30 = __toESM(require("path"));
11972
+ var import_os24 = __toESM(require("os"));
11619
11973
  init_core();
11620
11974
  init_daemon();
11621
11975
  function readJson2(filePath) {
11622
11976
  try {
11623
- if (import_fs27.default.existsSync(filePath)) return JSON.parse(import_fs27.default.readFileSync(filePath, "utf-8"));
11977
+ if (import_fs28.default.existsSync(filePath)) return JSON.parse(import_fs28.default.readFileSync(filePath, "utf-8"));
11624
11978
  } catch {
11625
11979
  }
11626
11980
  return null;
@@ -11685,28 +12039,28 @@ function registerStatusCommand(program2) {
11685
12039
  console.log("");
11686
12040
  const modeLabel = settings.mode === "audit" ? import_chalk11.default.blue("audit") : settings.mode === "strict" ? import_chalk11.default.red("strict") : import_chalk11.default.white("standard");
11687
12041
  console.log(` Mode: ${modeLabel}`);
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");
12042
+ const projectConfig = import_path30.default.join(process.cwd(), "node9.config.json");
12043
+ const globalConfig = import_path30.default.join(import_os24.default.homedir(), ".node9", "config.json");
11690
12044
  console.log(
11691
- ` Local: ${import_fs27.default.existsSync(projectConfig) ? import_chalk11.default.green("Active (node9.config.json)") : import_chalk11.default.gray("Not present")}`
12045
+ ` Local: ${import_fs28.default.existsSync(projectConfig) ? import_chalk11.default.green("Active (node9.config.json)") : import_chalk11.default.gray("Not present")}`
11692
12046
  );
11693
12047
  console.log(
11694
- ` Global: ${import_fs27.default.existsSync(globalConfig) ? import_chalk11.default.green("Active (~/.node9/config.json)") : import_chalk11.default.gray("Not present")}`
12048
+ ` Global: ${import_fs28.default.existsSync(globalConfig) ? import_chalk11.default.green("Active (~/.node9/config.json)") : import_chalk11.default.gray("Not present")}`
11695
12049
  );
11696
12050
  if (mergedConfig.policy.sandboxPaths.length > 0) {
11697
12051
  console.log(
11698
12052
  ` Sandbox: ${import_chalk11.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
11699
12053
  );
11700
12054
  }
11701
- const homeDir2 = import_os23.default.homedir();
12055
+ const homeDir2 = import_os24.default.homedir();
11702
12056
  const claudeSettings = readJson2(
11703
- import_path29.default.join(homeDir2, ".claude", "settings.json")
12057
+ import_path30.default.join(homeDir2, ".claude", "settings.json")
11704
12058
  );
11705
- const claudeConfig = readJson2(import_path29.default.join(homeDir2, ".claude.json"));
12059
+ const claudeConfig = readJson2(import_path30.default.join(homeDir2, ".claude.json"));
11706
12060
  const geminiSettings = readJson2(
11707
- import_path29.default.join(homeDir2, ".gemini", "settings.json")
12061
+ import_path30.default.join(homeDir2, ".gemini", "settings.json")
11708
12062
  );
11709
- const cursorConfig = readJson2(import_path29.default.join(homeDir2, ".cursor", "mcp.json"));
12063
+ const cursorConfig = readJson2(import_path30.default.join(homeDir2, ".cursor", "mcp.json"));
11710
12064
  const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
11711
12065
  if (agentFound) {
11712
12066
  console.log("");
@@ -11765,9 +12119,9 @@ function registerStatusCommand(program2) {
11765
12119
 
11766
12120
  // src/cli/commands/init.ts
11767
12121
  var import_chalk12 = __toESM(require("chalk"));
11768
- var import_fs28 = __toESM(require("fs"));
11769
- var import_path30 = __toESM(require("path"));
11770
- var import_os24 = __toESM(require("os"));
12122
+ var import_fs29 = __toESM(require("fs"));
12123
+ var import_path31 = __toESM(require("path"));
12124
+ var import_os25 = __toESM(require("os"));
11771
12125
  var import_https3 = __toESM(require("https"));
11772
12126
  init_core();
11773
12127
  init_shields();
@@ -11828,15 +12182,15 @@ function registerInitCommand(program2) {
11828
12182
  }
11829
12183
  console.log("");
11830
12184
  }
11831
- const configPath = import_path30.default.join(import_os24.default.homedir(), ".node9", "config.json");
11832
- if (import_fs28.default.existsSync(configPath) && !options.force) {
12185
+ const configPath = import_path31.default.join(import_os25.default.homedir(), ".node9", "config.json");
12186
+ if (import_fs29.default.existsSync(configPath) && !options.force) {
11833
12187
  try {
11834
- const existing = JSON.parse(import_fs28.default.readFileSync(configPath, "utf-8"));
12188
+ const existing = JSON.parse(import_fs29.default.readFileSync(configPath, "utf-8"));
11835
12189
  const settings = existing.settings ?? {};
11836
12190
  if (settings.mode !== chosenMode) {
11837
12191
  settings.mode = chosenMode;
11838
12192
  existing.settings = settings;
11839
- import_fs28.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
12193
+ import_fs29.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
11840
12194
  console.log(import_chalk12.default.green(`\u2705 Mode updated: ${chosenMode}`));
11841
12195
  } else {
11842
12196
  console.log(import_chalk12.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
@@ -11849,9 +12203,9 @@ function registerInitCommand(program2) {
11849
12203
  ...DEFAULT_CONFIG,
11850
12204
  settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
11851
12205
  };
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");
12206
+ const dir = import_path31.default.dirname(configPath);
12207
+ if (!import_fs29.default.existsSync(dir)) import_fs29.default.mkdirSync(dir, { recursive: true });
12208
+ import_fs29.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
11855
12209
  console.log(import_chalk12.default.green(`\u2705 Config created: ${configPath}`));
11856
12210
  console.log(import_chalk12.default.gray(` Mode: ${chosenMode}`));
11857
12211
  }
@@ -11935,7 +12289,7 @@ function registerInitCommand(program2) {
11935
12289
  }
11936
12290
 
11937
12291
  // src/cli/commands/undo.ts
11938
- var import_path31 = __toESM(require("path"));
12292
+ var import_path32 = __toESM(require("path"));
11939
12293
  var import_chalk14 = __toESM(require("chalk"));
11940
12294
 
11941
12295
  // src/tui/undo-navigator.ts
@@ -12094,7 +12448,7 @@ function findMatchingCwd(startDir, history) {
12094
12448
  let dir = startDir;
12095
12449
  while (true) {
12096
12450
  if (cwds.has(dir)) return dir;
12097
- const parent = import_path31.default.dirname(dir);
12451
+ const parent = import_path32.default.dirname(dir);
12098
12452
  if (parent === dir) return null;
12099
12453
  dir = parent;
12100
12454
  }
@@ -12290,12 +12644,12 @@ init_orchestrator();
12290
12644
  init_provenance();
12291
12645
 
12292
12646
  // src/mcp-pin.ts
12293
- var import_fs29 = __toESM(require("fs"));
12294
- var import_path32 = __toESM(require("path"));
12295
- var import_os25 = __toESM(require("os"));
12296
- var import_crypto9 = __toESM(require("crypto"));
12297
- function getPinsFilePath() {
12298
- return import_path32.default.join(import_os25.default.homedir(), ".node9", "mcp-pins.json");
12647
+ var import_fs30 = __toESM(require("fs"));
12648
+ var import_path33 = __toESM(require("path"));
12649
+ var import_os26 = __toESM(require("os"));
12650
+ var import_crypto10 = __toESM(require("crypto"));
12651
+ function getPinsFilePath2() {
12652
+ return import_path33.default.join(import_os26.default.homedir(), ".node9", "mcp-pins.json");
12299
12653
  }
12300
12654
  function hashToolDefinitions(tools) {
12301
12655
  const sorted = [...tools].sort((a, b) => {
@@ -12304,15 +12658,15 @@ function hashToolDefinitions(tools) {
12304
12658
  return nameA.localeCompare(nameB);
12305
12659
  });
12306
12660
  const canonical = JSON.stringify(sorted);
12307
- return import_crypto9.default.createHash("sha256").update(canonical).digest("hex");
12661
+ return import_crypto10.default.createHash("sha256").update(canonical).digest("hex");
12308
12662
  }
12309
12663
  function getServerKey(upstreamCommand) {
12310
- return import_crypto9.default.createHash("sha256").update(upstreamCommand).digest("hex").slice(0, 16);
12664
+ return import_crypto10.default.createHash("sha256").update(upstreamCommand).digest("hex").slice(0, 16);
12311
12665
  }
12312
12666
  function readMcpPinsSafe() {
12313
- const filePath = getPinsFilePath();
12667
+ const filePath = getPinsFilePath2();
12314
12668
  try {
12315
- const raw = import_fs29.default.readFileSync(filePath, "utf-8");
12669
+ const raw = import_fs30.default.readFileSync(filePath, "utf-8");
12316
12670
  if (!raw.trim()) {
12317
12671
  return { ok: false, reason: "corrupt", detail: "empty file" };
12318
12672
  }
@@ -12335,11 +12689,11 @@ function readMcpPins() {
12335
12689
  throw new Error(`[node9] MCP pin file is corrupt: ${result.detail}`);
12336
12690
  }
12337
12691
  function writeMcpPins(data) {
12338
- const filePath = getPinsFilePath();
12339
- import_fs29.default.mkdirSync(import_path32.default.dirname(filePath), { recursive: true });
12340
- const tmp = `${filePath}.${import_crypto9.default.randomBytes(6).toString("hex")}.tmp`;
12341
- import_fs29.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
12342
- import_fs29.default.renameSync(tmp, filePath);
12692
+ const filePath = getPinsFilePath2();
12693
+ import_fs30.default.mkdirSync(import_path33.default.dirname(filePath), { recursive: true });
12694
+ const tmp = `${filePath}.${import_crypto10.default.randomBytes(6).toString("hex")}.tmp`;
12695
+ import_fs30.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
12696
+ import_fs30.default.renameSync(tmp, filePath);
12343
12697
  }
12344
12698
  function checkPin(serverKey, currentHash) {
12345
12699
  const result = readMcpPinsSafe();
@@ -12362,12 +12716,12 @@ function updatePin(serverKey, label, toolsHash, toolNames) {
12362
12716
  };
12363
12717
  writeMcpPins(pins);
12364
12718
  }
12365
- function removePin(serverKey) {
12719
+ function removePin2(serverKey) {
12366
12720
  const pins = readMcpPins();
12367
12721
  delete pins.servers[serverKey];
12368
12722
  writeMcpPins(pins);
12369
12723
  }
12370
- function clearAllPins() {
12724
+ function clearAllPins2() {
12371
12725
  writeMcpPins({ servers: {} });
12372
12726
  }
12373
12727
 
@@ -12711,9 +13065,9 @@ function registerMcpGatewayCommand(program2) {
12711
13065
 
12712
13066
  // src/mcp-server/index.ts
12713
13067
  var import_readline4 = __toESM(require("readline"));
12714
- var import_fs30 = __toESM(require("fs"));
12715
- var import_os26 = __toESM(require("os"));
12716
- var import_path33 = __toESM(require("path"));
13068
+ var import_fs31 = __toESM(require("fs"));
13069
+ var import_os27 = __toESM(require("os"));
13070
+ var import_path34 = __toESM(require("path"));
12717
13071
  init_core();
12718
13072
  init_daemon();
12719
13073
  init_shields();
@@ -12888,13 +13242,13 @@ function handleStatus() {
12888
13242
  lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
12889
13243
  lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
12890
13244
  lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
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");
13245
+ const projectConfig = import_path34.default.join(process.cwd(), "node9.config.json");
13246
+ const globalConfig = import_path34.default.join(import_os27.default.homedir(), ".node9", "config.json");
12893
13247
  lines.push(
12894
- `Project config (node9.config.json): ${import_fs30.default.existsSync(projectConfig) ? "present" : "not found"}`
13248
+ `Project config (node9.config.json): ${import_fs31.default.existsSync(projectConfig) ? "present" : "not found"}`
12895
13249
  );
12896
13250
  lines.push(
12897
- `Global config (~/.node9/config.json): ${import_fs30.default.existsSync(globalConfig) ? "present" : "not found"}`
13251
+ `Global config (~/.node9/config.json): ${import_fs31.default.existsSync(globalConfig) ? "present" : "not found"}`
12898
13252
  );
12899
13253
  return lines.join("\n");
12900
13254
  }
@@ -12968,21 +13322,21 @@ function handleShieldDisable(args) {
12968
13322
  writeActiveShields(active.filter((s) => s !== name));
12969
13323
  return `Shield "${name}" disabled.`;
12970
13324
  }
12971
- var GLOBAL_CONFIG_PATH2 = import_path33.default.join(import_os26.default.homedir(), ".node9", "config.json");
13325
+ var GLOBAL_CONFIG_PATH2 = import_path34.default.join(import_os27.default.homedir(), ".node9", "config.json");
12972
13326
  var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
12973
13327
  function readGlobalConfigRaw() {
12974
13328
  try {
12975
- if (import_fs30.default.existsSync(GLOBAL_CONFIG_PATH2)) {
12976
- return JSON.parse(import_fs30.default.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
13329
+ if (import_fs31.default.existsSync(GLOBAL_CONFIG_PATH2)) {
13330
+ return JSON.parse(import_fs31.default.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
12977
13331
  }
12978
13332
  } catch {
12979
13333
  }
12980
13334
  return {};
12981
13335
  }
12982
13336
  function writeGlobalConfigRaw(data) {
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");
13337
+ const dir = import_path34.default.dirname(GLOBAL_CONFIG_PATH2);
13338
+ if (!import_fs31.default.existsSync(dir)) import_fs31.default.mkdirSync(dir, { recursive: true });
13339
+ import_fs31.default.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
12986
13340
  }
12987
13341
  function handleApproverList() {
12988
13342
  const config = getConfig();
@@ -13025,9 +13379,9 @@ function handleApproverSet(args) {
13025
13379
  }
13026
13380
  function handleAuditGet(args) {
13027
13381
  const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
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);
13382
+ const auditPath = import_path34.default.join(import_os27.default.homedir(), ".node9", "audit.log");
13383
+ if (!import_fs31.default.existsSync(auditPath)) return "No audit log found.";
13384
+ const lines = import_fs31.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
13031
13385
  const recent = lines.slice(-limit);
13032
13386
  const entries = recent.map((line) => {
13033
13387
  try {
@@ -13325,7 +13679,7 @@ function registerMcpPinCommand(program2) {
13325
13679
  process.exit(1);
13326
13680
  }
13327
13681
  const label = pins.servers[serverKey].label;
13328
- removePin(serverKey);
13682
+ removePin2(serverKey);
13329
13683
  console.log(import_chalk18.default.green(`
13330
13684
  \u{1F513} Pin removed for ${import_chalk18.default.cyan(serverKey)}`));
13331
13685
  console.log(import_chalk18.default.gray(` Server: ${label}`));
@@ -13338,7 +13692,7 @@ function registerMcpPinCommand(program2) {
13338
13692
  return;
13339
13693
  }
13340
13694
  const count = result.ok ? Object.keys(result.pins.servers).length : "?";
13341
- clearAllPins();
13695
+ clearAllPins2();
13342
13696
  console.log(import_chalk18.default.green(`
13343
13697
  \u{1F513} Cleared ${count} MCP pin(s).`));
13344
13698
  console.log(import_chalk18.default.gray(" Next connection to each server will re-pin.\n"));
@@ -13485,9 +13839,9 @@ function registerAgentsCommand(program2) {
13485
13839
 
13486
13840
  // src/cli/commands/scan.ts
13487
13841
  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"));
13842
+ var import_fs32 = __toESM(require("fs"));
13843
+ var import_path35 = __toESM(require("path"));
13844
+ var import_os28 = __toESM(require("os"));
13491
13845
  init_shields();
13492
13846
  init_config();
13493
13847
  init_policy();
@@ -13559,7 +13913,7 @@ function buildRuleSources() {
13559
13913
  return sources;
13560
13914
  }
13561
13915
  function scanClaudeHistory(startDate) {
13562
- const projectsDir = import_path34.default.join(import_os27.default.homedir(), ".claude", "projects");
13916
+ const projectsDir = import_path35.default.join(import_os28.default.homedir(), ".claude", "projects");
13563
13917
  const result = {
13564
13918
  filesScanned: 0,
13565
13919
  sessions: 0,
@@ -13571,25 +13925,25 @@ function scanClaudeHistory(startDate) {
13571
13925
  firstDate: null,
13572
13926
  lastDate: null
13573
13927
  };
13574
- if (!import_fs31.default.existsSync(projectsDir)) return result;
13928
+ if (!import_fs32.default.existsSync(projectsDir)) return result;
13575
13929
  let projDirs;
13576
13930
  try {
13577
- projDirs = import_fs31.default.readdirSync(projectsDir);
13931
+ projDirs = import_fs32.default.readdirSync(projectsDir);
13578
13932
  } catch {
13579
13933
  return result;
13580
13934
  }
13581
13935
  const ruleSources = buildRuleSources();
13582
13936
  for (const proj of projDirs) {
13583
- const projPath = import_path34.default.join(projectsDir, proj);
13937
+ const projPath = import_path35.default.join(projectsDir, proj);
13584
13938
  try {
13585
- if (!import_fs31.default.statSync(projPath).isDirectory()) continue;
13939
+ if (!import_fs32.default.statSync(projPath).isDirectory()) continue;
13586
13940
  } catch {
13587
13941
  continue;
13588
13942
  }
13589
- const projLabel = decodeURIComponent(proj).replace(import_os27.default.homedir(), "~").slice(0, 40);
13943
+ const projLabel = decodeURIComponent(proj).replace(import_os28.default.homedir(), "~").slice(0, 40);
13590
13944
  let files;
13591
13945
  try {
13592
- files = import_fs31.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
13946
+ files = import_fs32.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
13593
13947
  } catch {
13594
13948
  continue;
13595
13949
  }
@@ -13598,7 +13952,7 @@ function scanClaudeHistory(startDate) {
13598
13952
  result.sessions++;
13599
13953
  let raw;
13600
13954
  try {
13601
- raw = import_fs31.default.readFileSync(import_path34.default.join(projPath, file), "utf-8");
13955
+ raw = import_fs32.default.readFileSync(import_path35.default.join(projPath, file), "utf-8");
13602
13956
  } catch {
13603
13957
  continue;
13604
13958
  }
@@ -13691,8 +14045,8 @@ function registerScanCommand(program2) {
13691
14045
  console.log("");
13692
14046
  console.log(import_chalk21.default.cyan.bold("\u{1F50D} node9 scan") + import_chalk21.default.dim(" \u2014 what would node9 catch?"));
13693
14047
  console.log("");
13694
- const projectsDir = import_path34.default.join(import_os27.default.homedir(), ".claude", "projects");
13695
- if (!import_fs31.default.existsSync(projectsDir)) {
14048
+ const projectsDir = import_path35.default.join(import_os28.default.homedir(), ".claude", "projects");
14049
+ if (!import_fs32.default.existsSync(projectsDir)) {
13696
14050
  console.log(import_chalk21.default.yellow(" No Claude history found at ~/.claude/projects/"));
13697
14051
  console.log(import_chalk21.default.gray(" Install Claude Code, run a few sessions, then try again.\n"));
13698
14052
  return;
@@ -13804,8 +14158,8 @@ function registerScanCommand(program2) {
13804
14158
  );
13805
14159
  console.log("");
13806
14160
  }
13807
- const auditLog = import_path34.default.join(import_os27.default.homedir(), ".node9", "audit.log");
13808
- if (import_fs31.default.existsSync(auditLog)) {
14161
+ const auditLog = import_path35.default.join(import_os28.default.homedir(), ".node9", "audit.log");
14162
+ if (import_fs32.default.existsSync(auditLog)) {
13809
14163
  console.log(import_chalk21.default.green(" \u2705 node9 is active \u2014 future sessions are protected."));
13810
14164
  console.log(
13811
14165
  import_chalk21.default.dim(" Run ") + import_chalk21.default.cyan("node9 report") + import_chalk21.default.dim(" to see live stats.")
@@ -13822,9 +14176,9 @@ function registerScanCommand(program2) {
13822
14176
 
13823
14177
  // src/cli/commands/sessions.ts
13824
14178
  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"));
14179
+ var import_fs33 = __toESM(require("fs"));
14180
+ var import_path36 = __toESM(require("path"));
14181
+ var import_os29 = __toESM(require("os"));
13828
14182
  var CLAUDE_PRICING3 = {
13829
14183
  "claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
13830
14184
  "claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
@@ -13849,10 +14203,10 @@ function encodeProjectPath(projectPath) {
13849
14203
  }
13850
14204
  function sessionJsonlPath(projectPath, sessionId) {
13851
14205
  const encoded = encodeProjectPath(projectPath);
13852
- return import_path35.default.join(import_os28.default.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
14206
+ return import_path36.default.join(import_os29.default.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
13853
14207
  }
13854
14208
  function projectLabel(projectPath) {
13855
- return projectPath.replace(import_os28.default.homedir(), "~");
14209
+ return projectPath.replace(import_os29.default.homedir(), "~");
13856
14210
  }
13857
14211
  function parseHistoryLines(lines) {
13858
14212
  const entries = [];
@@ -13921,10 +14275,10 @@ function parseSessionLines(lines) {
13921
14275
  return { toolCalls, costUSD, hasSnapshot, modifiedFiles };
13922
14276
  }
13923
14277
  function loadAuditEntries(auditPath) {
13924
- const aPath = auditPath ?? import_path35.default.join(import_os28.default.homedir(), ".node9", "audit.log");
14278
+ const aPath = auditPath ?? import_path36.default.join(import_os29.default.homedir(), ".node9", "audit.log");
13925
14279
  let raw;
13926
14280
  try {
13927
- raw = import_fs32.default.readFileSync(aPath, "utf-8");
14281
+ raw = import_fs33.default.readFileSync(aPath, "utf-8");
13928
14282
  } catch {
13929
14283
  return [];
13930
14284
  }
@@ -13960,10 +14314,10 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
13960
14314
  return result;
13961
14315
  }
13962
14316
  function buildSessions(days, historyPath) {
13963
- const hPath = historyPath ?? import_path35.default.join(import_os28.default.homedir(), ".claude", "history.jsonl");
14317
+ const hPath = historyPath ?? import_path36.default.join(import_os29.default.homedir(), ".claude", "history.jsonl");
13964
14318
  let historyRaw;
13965
14319
  try {
13966
- historyRaw = import_fs32.default.readFileSync(hPath, "utf-8");
14320
+ historyRaw = import_fs33.default.readFileSync(hPath, "utf-8");
13967
14321
  } catch {
13968
14322
  return [];
13969
14323
  }
@@ -13988,7 +14342,7 @@ function buildSessions(days, historyPath) {
13988
14342
  const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
13989
14343
  let sessionLines = [];
13990
14344
  try {
13991
- sessionLines = import_fs32.default.readFileSync(jsonlFile, "utf-8").split("\n");
14345
+ sessionLines = import_fs33.default.readFileSync(jsonlFile, "utf-8").split("\n");
13992
14346
  } catch {
13993
14347
  }
13994
14348
  const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
@@ -14239,8 +14593,8 @@ function registerSessionsCommand(program2) {
14239
14593
  console.log("");
14240
14594
  console.log(import_chalk22.default.cyan.bold("\u{1F4CB} node9 sessions") + import_chalk22.default.dim(" \u2014 what your AI agent did"));
14241
14595
  console.log("");
14242
- const historyPath = import_path35.default.join(import_os28.default.homedir(), ".claude", "history.jsonl");
14243
- if (!import_fs32.default.existsSync(historyPath)) {
14596
+ const historyPath = import_path36.default.join(import_os29.default.homedir(), ".claude", "history.jsonl");
14597
+ if (!import_fs33.default.existsSync(historyPath)) {
14244
14598
  console.log(import_chalk22.default.yellow(" No Claude session history found at ~/.claude/history.jsonl"));
14245
14599
  console.log(import_chalk22.default.gray(" Install Claude Code, run a few sessions, then try again.\n"));
14246
14600
  return;
@@ -14270,22 +14624,110 @@ function registerSessionsCommand(program2) {
14270
14624
  });
14271
14625
  }
14272
14626
 
14627
+ // src/cli/commands/skill-pin.ts
14628
+ var import_chalk23 = __toESM(require("chalk"));
14629
+ var import_fs34 = __toESM(require("fs"));
14630
+ var import_os30 = __toESM(require("os"));
14631
+ var import_path37 = __toESM(require("path"));
14632
+ function wipeSkillSessions() {
14633
+ try {
14634
+ import_fs34.default.rmSync(import_path37.default.join(import_os30.default.homedir(), ".node9", "skill-sessions"), {
14635
+ recursive: true,
14636
+ force: true
14637
+ });
14638
+ } catch {
14639
+ }
14640
+ }
14641
+ function registerSkillPinCommand(program2) {
14642
+ const skillCmd = program2.command("skill").description("Manage skill pinning (supply chain & update drift defense, AST 02 + AST 07)");
14643
+ const pinSubCmd = skillCmd.command("pin").description("Manage pinned skill roots");
14644
+ pinSubCmd.command("list").description("Show all pinned skill roots and their content hashes").action(() => {
14645
+ const result = readSkillPinsSafe();
14646
+ if (!result.ok) {
14647
+ if (result.reason === "missing") {
14648
+ console.log(import_chalk23.default.gray("\nNo skill roots are pinned yet."));
14649
+ console.log(
14650
+ import_chalk23.default.gray("Pins are created automatically on the first tool call of each session.\n")
14651
+ );
14652
+ return;
14653
+ }
14654
+ console.error(import_chalk23.default.red(`
14655
+ \u274C Pin file is corrupt: ${result.detail}`));
14656
+ console.error(import_chalk23.default.yellow(" Run: node9 skill pin reset\n"));
14657
+ process.exit(1);
14658
+ }
14659
+ const entries = Object.entries(result.pins.roots);
14660
+ if (entries.length === 0) {
14661
+ console.log(import_chalk23.default.gray("\nNo skill roots are pinned yet.\n"));
14662
+ return;
14663
+ }
14664
+ console.log(import_chalk23.default.bold("\n\u{1F512} Pinned Skill Roots\n"));
14665
+ for (const [key, entry] of entries) {
14666
+ const missing = entry.exists ? "" : import_chalk23.default.yellow(" (not present at pin time)");
14667
+ console.log(` ${import_chalk23.default.cyan(key)} ${import_chalk23.default.gray(entry.rootPath)}${missing}`);
14668
+ console.log(` Files (${entry.fileCount})`);
14669
+ console.log(` Hash: ${import_chalk23.default.gray(entry.contentHash.slice(0, 16))}...`);
14670
+ console.log(` Pinned: ${import_chalk23.default.gray(entry.pinnedAt)}
14671
+ `);
14672
+ }
14673
+ });
14674
+ pinSubCmd.command("update <rootKey>").description("Remove a pin so the next session re-pins with current state").action((rootKey) => {
14675
+ let pins;
14676
+ try {
14677
+ pins = readSkillPins();
14678
+ } catch {
14679
+ console.error(import_chalk23.default.red("\n\u274C Pin file is corrupt."));
14680
+ console.error(import_chalk23.default.yellow(" Run: node9 skill pin reset\n"));
14681
+ process.exit(1);
14682
+ }
14683
+ if (!pins.roots[rootKey]) {
14684
+ console.error(import_chalk23.default.red(`
14685
+ \u274C No pin found for root key "${rootKey}"
14686
+ `));
14687
+ console.error(`Run ${import_chalk23.default.cyan("node9 skill pin list")} to see pinned roots.
14688
+ `);
14689
+ process.exit(1);
14690
+ }
14691
+ const rootPath = pins.roots[rootKey].rootPath;
14692
+ removePin(rootKey);
14693
+ wipeSkillSessions();
14694
+ console.log(import_chalk23.default.green(`
14695
+ \u{1F513} Pin removed for ${import_chalk23.default.cyan(rootKey)}`));
14696
+ console.log(import_chalk23.default.gray(` ${rootPath}`));
14697
+ console.log(import_chalk23.default.gray(" Next session will re-pin with current state.\n"));
14698
+ });
14699
+ pinSubCmd.command("reset").description("Clear all skill pins and wipe session verification flags").action(() => {
14700
+ const result = readSkillPinsSafe();
14701
+ if (!result.ok && result.reason === "missing") {
14702
+ wipeSkillSessions();
14703
+ console.log(import_chalk23.default.gray("\nNo pins to clear.\n"));
14704
+ return;
14705
+ }
14706
+ const count = result.ok ? Object.keys(result.pins.roots).length : "?";
14707
+ clearAllPins();
14708
+ wipeSkillSessions();
14709
+ console.log(import_chalk23.default.green(`
14710
+ \u{1F513} Cleared ${count} skill pin(s).`));
14711
+ console.log(import_chalk23.default.gray(" Next session will re-pin with current state.\n"));
14712
+ });
14713
+ }
14714
+
14273
14715
  // src/cli.ts
14274
14716
  var { version } = JSON.parse(
14275
- import_fs35.default.readFileSync(import_path38.default.join(__dirname, "../package.json"), "utf-8")
14717
+ import_fs37.default.readFileSync(import_path40.default.join(__dirname, "../package.json"), "utf-8")
14276
14718
  );
14277
14719
  var program = new import_commander.Command();
14278
14720
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
14279
14721
  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) => {
14280
14722
  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 });
14723
+ const credPath = import_path40.default.join(import_os33.default.homedir(), ".node9", "credentials.json");
14724
+ if (!import_fs37.default.existsSync(import_path40.default.dirname(credPath)))
14725
+ import_fs37.default.mkdirSync(import_path40.default.dirname(credPath), { recursive: true });
14284
14726
  const profileName = options.profile || "default";
14285
14727
  let existingCreds = {};
14286
14728
  try {
14287
- if (import_fs35.default.existsSync(credPath)) {
14288
- const raw = JSON.parse(import_fs35.default.readFileSync(credPath, "utf-8"));
14729
+ if (import_fs37.default.existsSync(credPath)) {
14730
+ const raw = JSON.parse(import_fs37.default.readFileSync(credPath, "utf-8"));
14289
14731
  if (raw.apiKey) {
14290
14732
  existingCreds = {
14291
14733
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
@@ -14297,13 +14739,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
14297
14739
  } catch {
14298
14740
  }
14299
14741
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
14300
- import_fs35.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
14742
+ import_fs37.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
14301
14743
  if (profileName === "default") {
14302
- const configPath = import_path38.default.join(import_os31.default.homedir(), ".node9", "config.json");
14744
+ const configPath = import_path40.default.join(import_os33.default.homedir(), ".node9", "config.json");
14303
14745
  let config = {};
14304
14746
  try {
14305
- if (import_fs35.default.existsSync(configPath))
14306
- config = JSON.parse(import_fs35.default.readFileSync(configPath, "utf-8"));
14747
+ if (import_fs37.default.existsSync(configPath))
14748
+ config = JSON.parse(import_fs37.default.readFileSync(configPath, "utf-8"));
14307
14749
  } catch {
14308
14750
  }
14309
14751
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -14318,19 +14760,19 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
14318
14760
  approvers.cloud = false;
14319
14761
  }
14320
14762
  s.approvers = approvers;
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 });
14763
+ if (!import_fs37.default.existsSync(import_path40.default.dirname(configPath)))
14764
+ import_fs37.default.mkdirSync(import_path40.default.dirname(configPath), { recursive: true });
14765
+ import_fs37.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
14324
14766
  }
14325
14767
  if (options.profile && profileName !== "default") {
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`));
14768
+ console.log(import_chalk25.default.green(`\u2705 Profile "${profileName}" saved`));
14769
+ console.log(import_chalk25.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
14328
14770
  } else if (options.local) {
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.`));
14771
+ console.log(import_chalk25.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
14772
+ console.log(import_chalk25.default.gray(` All decisions stay on this machine.`));
14331
14773
  } else {
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.`));
14774
+ console.log(import_chalk25.default.green(`\u2705 Logged in \u2014 agent mode`));
14775
+ console.log(import_chalk25.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
14334
14776
  }
14335
14777
  });
14336
14778
  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) => {
@@ -14341,7 +14783,7 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
14341
14783
  if (target === "vscode") return await setupVSCode();
14342
14784
  if (target === "hud") return setupHud();
14343
14785
  console.error(
14344
- import_chalk24.default.red(
14786
+ import_chalk25.default.red(
14345
14787
  `Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
14346
14788
  )
14347
14789
  );
@@ -14349,16 +14791,16 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
14349
14791
  });
14350
14792
  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) => {
14351
14793
  if (!target) {
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");
14794
+ console.log(import_chalk25.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
14795
+ console.log(" Usage: " + import_chalk25.default.white("node9 setup <target>") + "\n");
14354
14796
  console.log(" Targets:");
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)");
14797
+ console.log(" " + import_chalk25.default.green("claude") + " \u2014 Claude Code (hook mode)");
14798
+ console.log(" " + import_chalk25.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
14799
+ console.log(" " + import_chalk25.default.green("cursor") + " \u2014 Cursor (MCP proxy)");
14800
+ console.log(" " + import_chalk25.default.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
14801
+ console.log(" " + import_chalk25.default.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
14360
14802
  process.stdout.write(
14361
- " " + import_chalk24.default.green("hud") + " \u2014 Claude Code security statusline\n"
14803
+ " " + import_chalk25.default.green("hud") + " \u2014 Claude Code security statusline\n"
14362
14804
  );
14363
14805
  console.log("");
14364
14806
  return;
@@ -14371,7 +14813,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
14371
14813
  if (t === "vscode") return await setupVSCode();
14372
14814
  if (t === "hud") return setupHud();
14373
14815
  console.error(
14374
- import_chalk24.default.red(
14816
+ import_chalk25.default.red(
14375
14817
  `Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
14376
14818
  )
14377
14819
  );
@@ -14390,33 +14832,33 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
14390
14832
  else if (target === "hud") fn = teardownHud;
14391
14833
  else {
14392
14834
  console.error(
14393
- import_chalk24.default.red(
14835
+ import_chalk25.default.red(
14394
14836
  `Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
14395
14837
  )
14396
14838
  );
14397
14839
  process.exit(1);
14398
14840
  }
14399
- console.log(import_chalk24.default.cyan(`
14841
+ console.log(import_chalk25.default.cyan(`
14400
14842
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
14401
14843
  `));
14402
14844
  try {
14403
14845
  fn();
14404
14846
  } catch (err2) {
14405
- console.error(import_chalk24.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
14847
+ console.error(import_chalk25.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
14406
14848
  process.exit(1);
14407
14849
  }
14408
- console.log(import_chalk24.default.gray("\n Restart the agent for changes to take effect."));
14850
+ console.log(import_chalk25.default.gray("\n Restart the agent for changes to take effect."));
14409
14851
  });
14410
14852
  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) => {
14411
- console.log(import_chalk24.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
14412
- console.log(import_chalk24.default.bold("Stopping daemon..."));
14853
+ console.log(import_chalk25.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
14854
+ console.log(import_chalk25.default.bold("Stopping daemon..."));
14413
14855
  try {
14414
14856
  stopDaemon();
14415
- console.log(import_chalk24.default.green(" \u2705 Daemon stopped"));
14857
+ console.log(import_chalk25.default.green(" \u2705 Daemon stopped"));
14416
14858
  } catch {
14417
- console.log(import_chalk24.default.blue(" \u2139\uFE0F Daemon was not running"));
14859
+ console.log(import_chalk25.default.blue(" \u2139\uFE0F Daemon was not running"));
14418
14860
  }
14419
- console.log(import_chalk24.default.bold("\nRemoving hooks..."));
14861
+ console.log(import_chalk25.default.bold("\nRemoving hooks..."));
14420
14862
  let teardownFailed = false;
14421
14863
  for (const [label, fn] of [
14422
14864
  ["Claude", teardownClaude],
@@ -14430,45 +14872,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
14430
14872
  } catch (err2) {
14431
14873
  teardownFailed = true;
14432
14874
  console.error(
14433
- import_chalk24.default.red(
14875
+ import_chalk25.default.red(
14434
14876
  ` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
14435
14877
  )
14436
14878
  );
14437
14879
  }
14438
14880
  }
14439
14881
  if (options.purge) {
14440
- const node9Dir = import_path38.default.join(import_os31.default.homedir(), ".node9");
14441
- if (import_fs35.default.existsSync(node9Dir)) {
14882
+ const node9Dir = import_path40.default.join(import_os33.default.homedir(), ".node9");
14883
+ if (import_fs37.default.existsSync(node9Dir)) {
14442
14884
  const confirmed = await (0, import_prompts2.confirm)({
14443
14885
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
14444
14886
  default: false
14445
14887
  });
14446
14888
  if (confirmed) {
14447
- import_fs35.default.rmSync(node9Dir, { recursive: true });
14448
- if (import_fs35.default.existsSync(node9Dir)) {
14889
+ import_fs37.default.rmSync(node9Dir, { recursive: true });
14890
+ if (import_fs37.default.existsSync(node9Dir)) {
14449
14891
  console.error(
14450
- import_chalk24.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
14892
+ import_chalk25.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
14451
14893
  );
14452
14894
  } else {
14453
- console.log(import_chalk24.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
14895
+ console.log(import_chalk25.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
14454
14896
  }
14455
14897
  } else {
14456
- console.log(import_chalk24.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
14898
+ console.log(import_chalk25.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
14457
14899
  }
14458
14900
  } else {
14459
- console.log(import_chalk24.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
14901
+ console.log(import_chalk25.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
14460
14902
  }
14461
14903
  } else {
14462
14904
  console.log(
14463
- import_chalk24.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
14905
+ import_chalk25.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
14464
14906
  );
14465
14907
  }
14466
14908
  if (teardownFailed) {
14467
- console.error(import_chalk24.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
14909
+ console.error(import_chalk25.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
14468
14910
  process.exit(1);
14469
14911
  }
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"));
14912
+ console.log(import_chalk25.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
14913
+ console.log(import_chalk25.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
14472
14914
  });
14473
14915
  registerDoctorCommand(program, version);
14474
14916
  program.command("explain").description(
@@ -14481,7 +14923,7 @@ program.command("explain").description(
14481
14923
  try {
14482
14924
  args = JSON.parse(trimmed);
14483
14925
  } catch {
14484
- console.error(import_chalk24.default.red(`
14926
+ console.error(import_chalk25.default.red(`
14485
14927
  \u274C Invalid JSON: ${trimmed}
14486
14928
  `));
14487
14929
  process.exit(1);
@@ -14492,54 +14934,54 @@ program.command("explain").description(
14492
14934
  }
14493
14935
  const result = await explainPolicy(tool, args);
14494
14936
  console.log("");
14495
- console.log(import_chalk24.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
14937
+ console.log(import_chalk25.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
14496
14938
  console.log("");
14497
- console.log(` ${import_chalk24.default.bold("Tool:")} ${import_chalk24.default.white(result.tool)}`);
14939
+ console.log(` ${import_chalk25.default.bold("Tool:")} ${import_chalk25.default.white(result.tool)}`);
14498
14940
  if (argsRaw) {
14499
14941
  const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
14500
- console.log(` ${import_chalk24.default.bold("Input:")} ${import_chalk24.default.gray(preview2)}`);
14942
+ console.log(` ${import_chalk25.default.bold("Input:")} ${import_chalk25.default.gray(preview2)}`);
14501
14943
  }
14502
14944
  console.log("");
14503
- console.log(import_chalk24.default.bold("Config Sources (Waterfall):"));
14945
+ console.log(import_chalk25.default.bold("Config Sources (Waterfall):"));
14504
14946
  for (const tier of result.waterfall) {
14505
- const num3 = import_chalk24.default.gray(` ${tier.tier}.`);
14947
+ const num3 = import_chalk25.default.gray(` ${tier.tier}.`);
14506
14948
  const label = tier.label.padEnd(16);
14507
14949
  let statusStr;
14508
14950
  if (tier.tier === 1) {
14509
- statusStr = import_chalk24.default.gray(tier.note ?? "");
14951
+ statusStr = import_chalk25.default.gray(tier.note ?? "");
14510
14952
  } else if (tier.status === "active") {
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 : "");
14953
+ const loc = tier.path ? import_chalk25.default.gray(tier.path) : "";
14954
+ const note = tier.note ? import_chalk25.default.gray(`(${tier.note})`) : "";
14955
+ statusStr = import_chalk25.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
14514
14956
  } else {
14515
- statusStr = import_chalk24.default.gray("\u25CB " + (tier.note ?? "not found"));
14957
+ statusStr = import_chalk25.default.gray("\u25CB " + (tier.note ?? "not found"));
14516
14958
  }
14517
- console.log(`${num3} ${import_chalk24.default.white(label)} ${statusStr}`);
14959
+ console.log(`${num3} ${import_chalk25.default.white(label)} ${statusStr}`);
14518
14960
  }
14519
14961
  console.log("");
14520
- console.log(import_chalk24.default.bold("Policy Evaluation:"));
14962
+ console.log(import_chalk25.default.bold("Policy Evaluation:"));
14521
14963
  for (const step of result.steps) {
14522
14964
  const isFinal = step.isFinal;
14523
14965
  let icon;
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 ");
14966
+ if (step.outcome === "allow") icon = import_chalk25.default.green(" \u2705");
14967
+ else if (step.outcome === "review") icon = import_chalk25.default.red(" \u{1F534}");
14968
+ else if (step.outcome === "skip") icon = import_chalk25.default.gray(" \u2500 ");
14969
+ else icon = import_chalk25.default.gray(" \u25CB ");
14528
14970
  const name = step.name.padEnd(18);
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") : "";
14971
+ const nameStr = isFinal ? import_chalk25.default.white.bold(name) : import_chalk25.default.white(name);
14972
+ const detail = isFinal ? import_chalk25.default.white(step.detail) : import_chalk25.default.gray(step.detail);
14973
+ const arrow = isFinal ? import_chalk25.default.yellow(" \u2190 STOP") : "";
14532
14974
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
14533
14975
  }
14534
14976
  console.log("");
14535
14977
  if (result.decision === "allow") {
14536
- console.log(import_chalk24.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk24.default.gray(" \u2014 no approval needed"));
14978
+ console.log(import_chalk25.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk25.default.gray(" \u2014 no approval needed"));
14537
14979
  } else {
14538
14980
  console.log(
14539
- import_chalk24.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk24.default.gray(" \u2014 human approval required")
14981
+ import_chalk25.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk25.default.gray(" \u2014 human approval required")
14540
14982
  );
14541
14983
  if (result.blockedByLabel) {
14542
- console.log(import_chalk24.default.gray(` Reason: ${result.blockedByLabel}`));
14984
+ console.log(import_chalk25.default.gray(` Reason: ${result.blockedByLabel}`));
14543
14985
  }
14544
14986
  }
14545
14987
  console.log("");
@@ -14554,7 +14996,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
14554
14996
  try {
14555
14997
  await startTail2(options);
14556
14998
  } catch (err2) {
14557
- console.error(import_chalk24.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
14999
+ console.error(import_chalk25.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
14558
15000
  process.exit(1);
14559
15001
  }
14560
15002
  });
@@ -14562,6 +15004,7 @@ registerWatchCommand(program);
14562
15004
  registerMcpGatewayCommand(program);
14563
15005
  registerMcpServerCommand(program);
14564
15006
  registerMcpPinCommand(program);
15007
+ registerSkillPinCommand(program);
14565
15008
  registerCheckCommand(program);
14566
15009
  registerLogCommand(program);
14567
15010
  program.command("hud").description("Render node9 security statusline (spawned by Claude Code statusLine)").addHelpText(
@@ -14586,14 +15029,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
14586
15029
  Run "node9 addto claude" to register it as the statusLine.`
14587
15030
  ).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
14588
15031
  if (subcommand === "debug") {
14589
- const flagFile = import_path38.default.join(import_os31.default.homedir(), ".node9", "hud-debug");
15032
+ const flagFile = import_path40.default.join(import_os33.default.homedir(), ".node9", "hud-debug");
14590
15033
  if (state === "on") {
14591
- import_fs35.default.mkdirSync(import_path38.default.dirname(flagFile), { recursive: true });
14592
- import_fs35.default.writeFileSync(flagFile, "");
15034
+ import_fs37.default.mkdirSync(import_path40.default.dirname(flagFile), { recursive: true });
15035
+ import_fs37.default.writeFileSync(flagFile, "");
14593
15036
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
14594
15037
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
14595
15038
  } else if (state === "off") {
14596
- if (import_fs35.default.existsSync(flagFile)) import_fs35.default.unlinkSync(flagFile);
15039
+ if (import_fs37.default.existsSync(flagFile)) import_fs37.default.unlinkSync(flagFile);
14597
15040
  console.log("HUD debug logging disabled.");
14598
15041
  } else {
14599
15042
  console.error("Usage: node9 hud debug on|off");
@@ -14608,7 +15051,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
14608
15051
  const ms = parseDuration(options.duration);
14609
15052
  if (ms === null) {
14610
15053
  console.error(
14611
- import_chalk24.default.red(`
15054
+ import_chalk25.default.red(`
14612
15055
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
14613
15056
  `)
14614
15057
  );
@@ -14616,20 +15059,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
14616
15059
  }
14617
15060
  pauseNode9(ms, options.duration);
14618
15061
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
14619
- console.log(import_chalk24.default.yellow(`
15062
+ console.log(import_chalk25.default.yellow(`
14620
15063
  \u23F8 Node9 paused until ${expiresAt}`));
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.
15064
+ console.log(import_chalk25.default.gray(` All tool calls will be allowed without review.`));
15065
+ console.log(import_chalk25.default.gray(` Run "node9 resume" to re-enable early.
14623
15066
  `));
14624
15067
  });
14625
15068
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
14626
15069
  const { paused } = checkPause();
14627
15070
  if (!paused) {
14628
- console.log(import_chalk24.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
15071
+ console.log(import_chalk25.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
14629
15072
  return;
14630
15073
  }
14631
15074
  resumeNode9();
14632
- console.log(import_chalk24.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
15075
+ console.log(import_chalk25.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
14633
15076
  });
14634
15077
  var HOOK_BASED_AGENTS = {
14635
15078
  claude: "claude",
@@ -14642,15 +15085,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
14642
15085
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
14643
15086
  const target = HOOK_BASED_AGENTS[firstArg2];
14644
15087
  console.error(
14645
- import_chalk24.default.yellow(`
15088
+ import_chalk25.default.yellow(`
14646
15089
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
14647
15090
  );
14648
- console.error(import_chalk24.default.white(`
15091
+ console.error(import_chalk25.default.white(`
14649
15092
  "${target}" uses its own hook system. Use:`));
14650
15093
  console.error(
14651
- import_chalk24.default.green(` node9 addto ${target} `) + import_chalk24.default.gray("# one-time setup")
15094
+ import_chalk25.default.green(` node9 addto ${target} `) + import_chalk25.default.gray("# one-time setup")
14652
15095
  );
14653
- console.error(import_chalk24.default.green(` ${target} `) + import_chalk24.default.gray("# run normally"));
15096
+ console.error(import_chalk25.default.green(` ${target} `) + import_chalk25.default.gray("# run normally"));
14654
15097
  process.exit(1);
14655
15098
  }
14656
15099
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -14667,7 +15110,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
14667
15110
  }
14668
15111
  );
14669
15112
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
14670
- console.error(import_chalk24.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
15113
+ console.error(import_chalk25.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
14671
15114
  const daemonReady = await autoStartDaemonAndWait();
14672
15115
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
14673
15116
  }
@@ -14680,12 +15123,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
14680
15123
  }
14681
15124
  if (!result.approved) {
14682
15125
  console.error(
14683
- import_chalk24.default.red(`
15126
+ import_chalk25.default.red(`
14684
15127
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
14685
15128
  );
14686
15129
  process.exit(1);
14687
15130
  }
14688
- console.error(import_chalk24.default.green("\n\u2705 Approved \u2014 running command...\n"));
15131
+ console.error(import_chalk25.default.green("\n\u2705 Approved \u2014 running command...\n"));
14689
15132
  await runProxy(fullCommand);
14690
15133
  } else {
14691
15134
  program.help();
@@ -14704,9 +15147,9 @@ if (process.argv[2] !== "daemon") {
14704
15147
  const isCheckHook = process.argv[2] === "check";
14705
15148
  if (isCheckHook) {
14706
15149
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
14707
- const logPath = import_path38.default.join(import_os31.default.homedir(), ".node9", "hook-debug.log");
15150
+ const logPath = import_path40.default.join(import_os33.default.homedir(), ".node9", "hook-debug.log");
14708
15151
  const msg = reason instanceof Error ? reason.message : String(reason);
14709
- import_fs35.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
15152
+ import_fs37.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
14710
15153
  `);
14711
15154
  }
14712
15155
  process.exit(0);