@node9/proxy 1.11.12 → 1.12.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.
Files changed (4) hide show
  1. package/README.md +142 -244
  2. package/dist/cli.js +775 -642
  3. package/dist/cli.mjs +762 -629
  4. package/package.json +1 -1
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 path44 = issue.path.length > 0 ? issue.path.join(".") : "root";
172
- return ` \u2022 ${path44}: ${issue.message}`;
171
+ const path45 = issue.path.length > 0 ? issue.path.join(".") : "root";
172
+ return ` \u2022 ${path45}: ${issue.message}`;
173
173
  });
174
174
  return {
175
175
  sanitized,
@@ -2083,9 +2083,9 @@ function matchesPattern(text, patterns) {
2083
2083
  const withoutDotSlash = text.replace(/^\.\//, "");
2084
2084
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
2085
2085
  }
2086
- function getNestedValue(obj, path44) {
2086
+ function getNestedValue(obj, path45) {
2087
2087
  if (!obj || typeof obj !== "object") return null;
2088
- return path44.split(".").reduce((prev, curr) => prev?.[curr], obj);
2088
+ return path45.split(".").reduce((prev, curr) => prev?.[curr], obj);
2089
2089
  }
2090
2090
  function normalizeCommandForPolicy(command) {
2091
2091
  try {
@@ -5266,6 +5266,10 @@ var init_ui = __esm({
5266
5266
  border-color: rgba(83, 155, 245, 0.35);
5267
5267
  background: rgba(83, 155, 245, 0.04);
5268
5268
  }
5269
+ .pending-card.discovery-card {
5270
+ border-color: rgba(120, 100, 255, 0.45);
5271
+ background: rgba(100, 80, 255, 0.06);
5272
+ }
5269
5273
  .pending-card-header {
5270
5274
  display: flex;
5271
5275
  align-items: center;
@@ -5735,6 +5739,12 @@ var init_ui = __esm({
5735
5739
  text-align: center;
5736
5740
  }
5737
5741
  /* results */
5742
+ .scan-date-range {
5743
+ text-align: center;
5744
+ font-size: 11px;
5745
+ color: var(--muted);
5746
+ padding: 6px 0 10px;
5747
+ }
5738
5748
  .scan-summary-row {
5739
5749
  display: grid;
5740
5750
  grid-template-columns: repeat(4, 1fr);
@@ -7446,7 +7456,7 @@ var init_ui = __esm({
7446
7456
  });
7447
7457
 
7448
7458
  try {
7449
- await fetch('/mcp/approve', {
7459
+ const res = await fetch('/mcp/approve', {
7450
7460
  method: 'POST',
7451
7461
  headers: {
7452
7462
  'Content-Type': 'application/json',
@@ -7454,9 +7464,19 @@ var init_ui = __esm({
7454
7464
  },
7455
7465
  body: JSON.stringify({ id, serverKey, disabledTools }),
7456
7466
  });
7467
+ if (!res.ok) {
7468
+ showToast(
7469
+ '\u26A0\uFE0F',
7470
+ 'Approval failed',
7471
+ \`Server responded with \${res.status}\`,
7472
+ 'toast-block'
7473
+ );
7474
+ return;
7475
+ }
7457
7476
  closeMcpReview();
7458
7477
  refreshMcpTools();
7459
7478
  } catch (err) {
7479
+ showToast('\u26A0\uFE0F', 'Approval failed', 'Network error \u2014 check the daemon', 'toast-block');
7460
7480
  console.error('Failed to approve MCP server:', err);
7461
7481
  }
7462
7482
  }
@@ -7627,13 +7647,10 @@ var init_ui = __esm({
7627
7647
  const res = await fetch('/scan', { headers: { 'X-Node9-Token': CSRF_TOKEN } });
7628
7648
  if (!res.ok) return;
7629
7649
  const data = await res.json();
7630
- if (data.status !== 'complete') return;
7650
+ if (data.status !== 'complete' || !data.summary) return;
7631
7651
  _scanData = data;
7632
- const sources = data.sources || [];
7633
- const total =
7634
- sources.reduce((n, s) => n + (s.findings || []).length, 0) +
7635
- sources.reduce((n, s) => n + (s.leaks || []).length, 0) +
7636
- sources.reduce((n, s) => n + (s.loops || []).length, 0);
7652
+ const v = data.summary.byVerdict || {};
7653
+ const total = (v.blocked || 0) + (v.supervised || 0) + (v.leaks || 0) + (v.loops || 0);
7637
7654
  if (total > 0) {
7638
7655
  const btn = document.getElementById('btn-scan-history');
7639
7656
  if (btn && !btn.querySelector('.scan-badge')) {
@@ -7738,42 +7755,26 @@ var init_ui = __esm({
7738
7755
  }
7739
7756
 
7740
7757
  function renderScanResults(data) {
7741
- if (!data || data.status !== 'complete') {
7758
+ if (!data || data.status !== 'complete' || !data.summary) {
7742
7759
  return '<div style="text-align:center;color:var(--muted);padding:32px">No data returned.</div>';
7743
7760
  }
7744
- const s = data.summary || {};
7745
- const sources = data.sources || [];
7746
-
7747
- // Flatten all findings across sources
7748
- const allFindings = sources.flatMap((src) => src.findings || []);
7749
- const allLeaks = sources.flatMap((src) => src.leaks || []);
7750
- const allLoops = sources.flatMap((src) => src.loops || []);
7751
-
7752
- // Split findings into three distinct buckets
7753
- const blocked = allFindings.filter((f) => f.verdict === 'block' && f.ruleSource !== 'user');
7754
- const supervised = allFindings.filter(
7755
- (f) => f.verdict !== 'block' && f.ruleSource !== 'user'
7756
- );
7757
- const yourRules = allFindings.filter((f) => f.ruleSource === 'user');
7758
-
7759
- // Loop cost estimate: each wasted iteration ~2K tokens at Sonnet pricing
7760
- const LOOP_THRESHOLD = 3;
7761
- const COST_PER_ITER = 0.006;
7762
- const wastedIters = allLoops.reduce(
7763
- (sum, l) => sum + Math.max(0, (l.count || 0) - LOOP_THRESHOLD),
7764
- 0
7765
- );
7766
- const loopWastedUSD = wastedIters * COST_PER_ITER;
7767
-
7768
- const totalCost = s.totalCostUSD || 0;
7761
+ // Server-computed ScanSummary (see src/scan-summary.ts).
7762
+ // This renderer is a pure presentation layer \u2014 no categorization here.
7763
+ const summary = data.summary;
7764
+ const stats = summary.stats || {};
7765
+ const byVerdict = summary.byVerdict || { blocked: 0, supervised: 0, leaks: 0, loops: 0 };
7766
+ const byAgent = summary.byAgent || [];
7767
+ const sections = summary.sections || [];
7768
+ const leaks = summary.leaks || [];
7769
+ const loops = summary.loops || [];
7770
+ const loopWastedUSD = summary.loopWastedUSD || 0;
7771
+ const totalCost = stats.totalCostUSD || 0;
7769
7772
 
7770
7773
  function fmtUSD(n) {
7771
7774
  if (!n || n < 0.001) return null;
7772
7775
  if (n < 1) return '$' + n.toFixed(3);
7773
7776
  return '$' + n.toFixed(2);
7774
7777
  }
7775
-
7776
- // \u2500\u2500 Hero stats row \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7777
7778
  const costStr = fmtUSD(totalCost);
7778
7779
  const savingsStr = fmtUSD(loopWastedUSD);
7779
7780
 
@@ -7795,10 +7796,46 @@ var init_ui = __esm({
7795
7796
  );
7796
7797
  }
7797
7798
 
7798
- const stats =
7799
+ // \u2500\u2500 Date range header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7800
+ function fmtShort(iso) {
7801
+ if (!iso) return null;
7802
+ try {
7803
+ return new Date(iso).toLocaleDateString([], {
7804
+ month: 'short',
7805
+ day: 'numeric',
7806
+ year: 'numeric',
7807
+ });
7808
+ } catch {
7809
+ return null;
7810
+ }
7811
+ }
7812
+ const firstLabel = fmtShort(stats.firstDate);
7813
+ const lastLabel = fmtShort(stats.lastDate);
7814
+ let daySpan = '';
7815
+ if (stats.firstDate && stats.lastDate) {
7816
+ const days =
7817
+ Math.round(
7818
+ (new Date(stats.lastDate).getTime() - new Date(stats.firstDate).getTime()) /
7819
+ (1000 * 60 * 60 * 24)
7820
+ ) + 1;
7821
+ if (days > 1) daySpan = ' \xB7 ' + days + ' day' + (days !== 1 ? 's' : '');
7822
+ }
7823
+ const dateRangeHtml =
7824
+ firstLabel && lastLabel
7825
+ ? '<div class="scan-date-range">\u{1F4C5} ' +
7826
+ esc(firstLabel) +
7827
+ ' \u2013 ' +
7828
+ esc(lastLabel) +
7829
+ esc(daySpan) +
7830
+ '</div>'
7831
+ : '';
7832
+
7833
+ // \u2500\u2500 Hero stats row (by VERDICT \u2014 matches terminal) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7834
+ const statsHtml =
7835
+ dateRangeHtml +
7799
7836
  '<div class="scan-summary-row">' +
7800
7837
  '<div class="scan-stat">' +
7801
- statVal(s.sessions || 0, '') +
7838
+ statVal(stats.sessions || 0, '') +
7802
7839
  '<div class="scan-stat-label">Sessions</div></div>' +
7803
7840
  (totalCost > 0
7804
7841
  ? '<div class="scan-stat">' +
@@ -7806,19 +7843,16 @@ var init_ui = __esm({
7806
7843
  '<div class="scan-stat-label">AI Spend</div></div>'
7807
7844
  : '') +
7808
7845
  '<div class="scan-stat">' +
7809
- statVal(blocked.length, blocked.length ? 'danger' : 'ok') +
7846
+ statVal(byVerdict.blocked || 0, byVerdict.blocked ? 'danger' : 'ok') +
7810
7847
  '<div class="scan-stat-label">Blocked</div></div>' +
7811
7848
  '<div class="scan-stat">' +
7812
- statVal(supervised.length, supervised.length ? 'warning' : 'ok') +
7849
+ statVal(byVerdict.supervised || 0, byVerdict.supervised ? 'warning' : 'ok') +
7813
7850
  '<div class="scan-stat-label">Supervised</div></div>' +
7814
7851
  '<div class="scan-stat">' +
7815
- statVal(yourRules.length, '', yourRules.length ? 'color:#58a6ff' : '') +
7816
- '<div class="scan-stat-label">Your Rules</div></div>' +
7817
- '<div class="scan-stat">' +
7818
- statVal(allLeaks.length, allLeaks.length ? 'danger' : 'ok') +
7852
+ statVal(byVerdict.leaks || 0, byVerdict.leaks ? 'danger' : 'ok') +
7819
7853
  '<div class="scan-stat-label">Leaks</div></div>' +
7820
7854
  '<div class="scan-stat">' +
7821
- statVal(allLoops.length, allLoops.length ? 'warning' : 'ok') +
7855
+ statVal(byVerdict.loops || 0, byVerdict.loops ? 'warning' : 'ok') +
7822
7856
  '<div class="scan-stat-label">Loops</div></div>' +
7823
7857
  '</div>' +
7824
7858
  (savingsStr
@@ -7827,56 +7861,103 @@ var init_ui = __esm({
7827
7861
  '</span> in unnecessary LLM turns</div>'
7828
7862
  : '');
7829
7863
 
7830
- if (!allFindings.length && !allLeaks.length && !allLoops.length) {
7864
+ const hasNothing = !sections.length && !leaks.length && !loops.length;
7865
+ if (hasNothing) {
7831
7866
  return (
7832
- stats +
7867
+ statsHtml +
7833
7868
  '<div style="text-align:center;color:#57ab5a;padding:28px 0;font-size:13px;line-height:1.8">\u2705 <strong>No issues found</strong><br><span style="font-size:11px;color:var(--muted)">Scanned ' +
7834
- (s.sessions || 0) +
7835
- ' sessions across ' +
7836
- sources.length +
7837
- ' AI source(s)</span></div>'
7869
+ (stats.sessions || 0) +
7870
+ ' sessions</span></div>'
7838
7871
  );
7839
7872
  }
7840
7873
 
7841
- // \u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7842
- function groupByKey(items, keyFn) {
7843
- const map = new Map();
7844
- for (const item of items) {
7845
- const k = keyFn(item);
7846
- if (!map.has(k)) map.set(k, []);
7847
- map.get(k).push(item);
7874
+ // \u2500\u2500 Per-agent breakdown (server-computed byAgent) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7875
+ const agentBreakdown = byAgent.length
7876
+ ? '<div class="scan-agent-group">' +
7877
+ byAgent
7878
+ .map(
7879
+ (a) =>
7880
+ '<div class="scan-agent-row">' +
7881
+ '<span class="scan-agent-name">' +
7882
+ esc(a.icon || '') +
7883
+ ' ' +
7884
+ esc(a.label) +
7885
+ '</span>' +
7886
+ '<span class="scan-agent-stat">' +
7887
+ a.sessions +
7888
+ ' session' +
7889
+ (a.sessions !== 1 ? 's' : '') +
7890
+ '</span>' +
7891
+ '<span class="scan-agent-sep">\xB7</span>' +
7892
+ '<span class="scan-agent-stat">' +
7893
+ a.findings +
7894
+ ' finding' +
7895
+ (a.findings !== 1 ? 's' : '') +
7896
+ '</span>' +
7897
+ '</div>'
7898
+ )
7899
+ .join('') +
7900
+ '</div>'
7901
+ : '';
7902
+
7903
+ // \u2500\u2500 Rule row renderer \u2014 shared between rule sections + leaks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7904
+ // Determines bar-max across all sections so relative counts stay honest.
7905
+ let maxBar = 1;
7906
+ for (const section of sections) {
7907
+ for (const rule of section.rules || []) {
7908
+ if (rule.findings.length > maxBar) maxBar = rule.findings.length;
7848
7909
  }
7849
- return [...map.entries()].sort((a, b) => b[1].length - a[1].length);
7850
7910
  }
7911
+ // Group leaks by patternName for display
7912
+ const leaksByPattern = (function () {
7913
+ const m = new Map();
7914
+ for (const l of leaks) {
7915
+ const k = l.patternName || 'DLP';
7916
+ if (!m.has(k)) m.set(k, []);
7917
+ m.get(k).push(l);
7918
+ }
7919
+ for (const [, group] of m) if (group.length > maxBar) maxBar = group.length;
7920
+ return [...m.entries()].sort((a, b) => b[1].length - a[1].length);
7921
+ })();
7851
7922
 
7852
- function findingRow(item, type) {
7853
- const date = new Date(item.timestamp).toLocaleDateString([], {
7923
+ function findingRow(timestamp, text) {
7924
+ const date = new Date(timestamp).toLocaleDateString([], {
7854
7925
  month: 'short',
7855
7926
  day: 'numeric',
7856
7927
  });
7857
- const cmdText =
7858
- type === 'leak' ? esc(item.sample || '') : esc(truncate(item.command || '', 120));
7859
7928
  return (
7860
7929
  '<div class="scan-finding-row"><span class="scan-finding-ts">' +
7861
7930
  date +
7862
7931
  '</span><span class="scan-finding-cmd">' +
7863
- cmdText +
7932
+ text +
7864
7933
  '</span></div>'
7865
7934
  );
7866
7935
  }
7867
7936
 
7868
- function ruleSection(items, type, badgeClass, badgeLabel, maxBar) {
7869
- const keyFn =
7870
- type === 'leak'
7871
- ? (i) => i.pattern || 'DLP'
7872
- : (i) => (i.rule || 'unknown').replace(/^shield:[^:]+:/, '');
7873
- const grouped = groupByKey(items, keyFn);
7874
- return grouped
7875
- .map(([rule, group]) => {
7876
- const count = group.length;
7937
+ function ruleCardsForSection(section) {
7938
+ return (section.rules || [])
7939
+ .map((rule) => {
7940
+ const count = rule.findings.length;
7877
7941
  const barPct = Math.round((count / maxBar) * 100);
7878
7942
  const detailId = 'detail-' + Math.random().toString(36).slice(2);
7879
- const rows = group.map((i) => findingRow(i, type)).join('');
7943
+ const badgeClass =
7944
+ section.sourceType === 'user'
7945
+ ? 'badge-user'
7946
+ : rule.verdict === 'block'
7947
+ ? 'badge-block'
7948
+ : 'badge-review';
7949
+ const badgeLabel =
7950
+ section.sourceType === 'user'
7951
+ ? rule.verdict === 'block'
7952
+ ? 'YOUR BLOCK'
7953
+ : 'YOUR RULE'
7954
+ : rule.verdict === 'block'
7955
+ ? 'BLOCK'
7956
+ : 'REVIEW';
7957
+ const barColor = section.sourceType === 'user' ? ';background:#388bfd33' : '';
7958
+ const rows = rule.findings
7959
+ .map((f) => findingRow(f.timestamp, esc(truncate(f.command || '', 120))))
7960
+ .join('');
7880
7961
  return (
7881
7962
  '<div class="scan-rule-row" onclick="var d=document.getElementById(\\'' +
7882
7963
  detailId +
@@ -7887,12 +7968,12 @@ var init_ui = __esm({
7887
7968
  badgeLabel +
7888
7969
  '</span>' +
7889
7970
  '<span class="scan-rule-name">' +
7890
- esc(rule) +
7971
+ esc(rule.name) +
7891
7972
  '</span>' +
7892
7973
  '<span class="scan-rule-bar-wrap"><span class="scan-rule-bar" style="width:' +
7893
7974
  barPct +
7894
7975
  '%' +
7895
- (badgeClass === 'badge-user' ? ';background:#388bfd33' : '') +
7976
+ barColor +
7896
7977
  '"></span></span>' +
7897
7978
  '<span class="scan-rule-count">' +
7898
7979
  count +
@@ -7908,98 +7989,95 @@ var init_ui = __esm({
7908
7989
  .join('');
7909
7990
  }
7910
7991
 
7911
- // \u2500\u2500 Sections \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7912
- const maxBar = Math.max(
7913
- ...allFindings.map((f) => 1),
7914
- ...groupByKey(allFindings, (f) => (f.rule || '').replace(/^shield:[^:]+:/, '')).map(
7915
- ([, v]) => v.length
7916
- ),
7917
- ...groupByKey(allLeaks, (f) => f.pattern || 'DLP').map(([, v]) => v.length),
7918
- 1
7919
- );
7992
+ // \u2500\u2500 Section headings by sourceType / id \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7993
+ function sectionHeading(section) {
7994
+ if (section.id === 'default') {
7995
+ return '\u{1F441} ' + esc(section.label) + ' \u2014 built-in, always on';
7996
+ }
7997
+ if (section.sourceType === 'shield') {
7998
+ return (
7999
+ '\u{1F6E1} ' +
8000
+ esc(section.label) +
8001
+ ' shield' +
8002
+ (section.subtitle ? ' \u2014 ' + esc(section.subtitle) : '')
8003
+ );
8004
+ }
8005
+ if (section.id === 'cloud') {
8006
+ return '\u2601 Cloud Policy \u2014 synced from node9';
8007
+ }
8008
+ return '\u2705 Your Rules \u2014 working as configured';
8009
+ }
8010
+ function sectionColor(section) {
8011
+ if (section.blockedCount > 0) return '#e5534b';
8012
+ if (section.sourceType === 'user') return '#58a6ff';
8013
+ return 'var(--muted)';
8014
+ }
7920
8015
 
7921
- // Per-agent breakdown \u2014 one row per active agent (only shows active ones)
7922
- const agentBreakdown = (function () {
7923
- const rows = sources
7924
- .filter(
7925
- (src) =>
7926
- (src.sessions || 0) > 0 ||
7927
- (src.findings || []).length > 0 ||
7928
- (src.leaks || []).length > 0 ||
7929
- (src.loops || []).length > 0
7930
- )
7931
- .map((src) => {
7932
- const f = (src.findings || []).length;
7933
- const l = (src.leaks || []).length;
7934
- const lp = (src.loops || []).length;
7935
- const findingsTotal = f + l + lp;
8016
+ let html = statsHtml + agentBreakdown;
8017
+
8018
+ // \u2500\u2500 Leaks (shown first: credential exposure is highest-severity) \u2500\u2500\u2500\u2500\u2500
8019
+ if (leaksByPattern.length) {
8020
+ html +=
8021
+ '<div class="scan-rule-section-label" style="color:#e5534b">\u{1F511} Credential Leaks \u2014 secrets found in tool calls</div>';
8022
+ html += leaksByPattern
8023
+ .map(([pattern, group]) => {
8024
+ const count = group.length;
8025
+ const barPct = Math.round((count / maxBar) * 100);
8026
+ const detailId = 'detail-' + Math.random().toString(36).slice(2);
8027
+ const rows = group
8028
+ .map((l) => findingRow(l.timestamp, esc(l.redactedSample || '')))
8029
+ .join('');
7936
8030
  return (
7937
- '<div class="scan-agent-row">' +
7938
- '<span class="scan-agent-name">' +
7939
- esc(src.icon || '') +
7940
- ' ' +
7941
- esc(src.label || src.id) +
7942
- '</span>' +
7943
- '<span class="scan-agent-stat">' +
7944
- (src.sessions || 0) +
7945
- ' session' +
7946
- ((src.sessions || 0) !== 1 ? 's' : '') +
8031
+ '<div class="scan-rule-row" onclick="var d=document.getElementById(\\'' +
8032
+ detailId +
8033
+ "');var a=this.querySelector('.scan-rule-arrow');d.style.display=d.style.display===''?'none':'';a.textContent=d.style.display===''?'\u25B2':'\u25BC';\\">" +
8034
+ '<span class="scan-verdict-badge badge-dlp">DLP</span>' +
8035
+ '<span class="scan-rule-name">' +
8036
+ esc(pattern) +
7947
8037
  '</span>' +
7948
- '<span class="scan-agent-sep">\xB7</span>' +
7949
- '<span class="scan-agent-stat">' +
7950
- findingsTotal +
7951
- ' finding' +
7952
- (findingsTotal !== 1 ? 's' : '') +
8038
+ '<span class="scan-rule-bar-wrap"><span class="scan-rule-bar" style="width:' +
8039
+ barPct +
8040
+ '%"></span></span>' +
8041
+ '<span class="scan-rule-count">' +
8042
+ count +
7953
8043
  '</span>' +
8044
+ '<span class="scan-rule-arrow">\u25BC</span>' +
8045
+ '</div><div id="' +
8046
+ detailId +
8047
+ '" style="display:none" class="scan-rule-detail">' +
8048
+ rows +
7954
8049
  '</div>'
7955
8050
  );
7956
- });
7957
- if (!rows.length) return '';
7958
- return '<div class="scan-agent-group">' + rows.join('') + '</div>';
7959
- })();
7960
-
7961
- let html = stats + agentBreakdown;
7962
-
7963
- if (allLeaks.length) {
7964
- html +=
7965
- '<div class="scan-rule-section-label" style="color:#e5534b">\u{1F511} Credential Leaks \u2014 secrets found in tool calls</div>';
7966
- html += ruleSection(allLeaks, 'leak', 'badge-dlp', 'DLP', maxBar);
7967
- }
7968
-
7969
- if (blocked.length) {
7970
- html +=
7971
- '<div class="scan-rule-section-label" style="color:#e5534b">\u{1F6D1} Blocked \u2014 Node9 would have stopped these</div>';
7972
- html += ruleSection(blocked, 'risk', 'badge-block', 'BLOCK', maxBar);
7973
- }
7974
-
7975
- if (supervised.length) {
7976
- html +=
7977
- '<div class="scan-rule-section-label">\u{1F441} Supervised \u2014 Node9 would have asked you first</div>';
7978
- html += ruleSection(supervised, 'risk', 'badge-review', 'REVIEW', maxBar);
8051
+ })
8052
+ .join('');
7979
8053
  }
7980
8054
 
7981
- if (yourRules.length) {
8055
+ // \u2500\u2500 Sections (pre-grouped by source on the server) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
8056
+ for (const section of sections) {
7982
8057
  html +=
7983
- '<div class="scan-rule-section-label" style="color:#58a6ff">\u2705 Your Rules \u2014 working as configured</div>';
7984
- html += ruleSection(yourRules, 'risk', 'badge-user', 'YOUR RULE', maxBar);
8058
+ '<div class="scan-rule-section-label" style="color:' +
8059
+ sectionColor(section) +
8060
+ '">' +
8061
+ sectionHeading(section) +
8062
+ '</div>';
8063
+ html += ruleCardsForSection(section);
7985
8064
  }
7986
8065
 
7987
- // Loops: single collapsed summary card, expand to see all groups
7988
- if (allLoops.length) {
7989
- const totalReps = allLoops.reduce((sum, l) => sum + (l.count || 0), 0);
8066
+ // \u2500\u2500 Loops: single collapsed summary card, expand to see all groups \u2500\u2500\u2500
8067
+ if (loops.length) {
8068
+ const totalReps = loops.reduce((sum, l) => sum + (l.count || 0), 0);
7990
8069
  const loopsDetailId = 'loops-detail-' + Math.random().toString(36).slice(2);
7991
8070
  const savingNote = savingsStr
7992
8071
  ? ' \xB7 <span style="color:var(--warning)">~' + savingsStr + ' wasted</span>'
7993
8072
  : '';
7994
- const loopGroupRows = allLoops
8073
+ const maxLoopCount = Math.max(...loops.map((l) => l.count || 0), 1);
8074
+ const loopGroupRows = loops
7995
8075
  .map((item) => {
7996
8076
  const date = new Date(item.timestamp).toLocaleDateString([], {
7997
8077
  month: 'short',
7998
8078
  day: 'numeric',
7999
8079
  });
8000
- const barPct = Math.round(
8001
- (item.count / Math.max(...allLoops.map((l) => l.count), 1)) * 100
8002
- );
8080
+ const barPct = Math.round((item.count / maxLoopCount) * 100);
8003
8081
  const detailId = 'detail-' + Math.random().toString(36).slice(2);
8004
8082
  return (
8005
8083
  '<div class="scan-rule-row" onclick="event.stopPropagation();var d=document.getElementById(\\'' +
@@ -8037,9 +8115,9 @@ var init_ui = __esm({
8037
8115
  "');var a=this.querySelector('.scan-rule-arrow');d.style.display=d.style.display===''?'none':'';a.textContent=d.style.display===''?'\u25B2':'\u25BC';\\">" +
8038
8116
  '<span class="scan-verdict-badge badge-review">LOOP</span>' +
8039
8117
  '<span class="scan-rule-name">' +
8040
- allLoops.length +
8118
+ loops.length +
8041
8119
  ' pattern' +
8042
- (allLoops.length !== 1 ? 's' : '') +
8120
+ (loops.length !== 1 ? 's' : '') +
8043
8121
  ' \xB7 ' +
8044
8122
  totalReps +
8045
8123
  ' total repetitions' +
@@ -8113,10 +8191,20 @@ var init_ui = __esm({
8113
8191
  const data = await res.json();
8114
8192
  results.style.display = 'block';
8115
8193
 
8116
- if (data.status === 'complete') {
8117
- const sources = data.sources || [];
8118
- const allRisks = sources.flatMap((s) => s.findings || []);
8119
- const allLeaks = sources.flatMap((s) => s.leaks || []);
8194
+ if (data.status === 'complete' && data.summary) {
8195
+ const summary = data.summary;
8196
+ const byVerdict = summary.byVerdict || {};
8197
+ const sections = summary.sections || [];
8198
+ const leaks = summary.leaks || [];
8199
+ // Flatten rule findings across all sections so we can show a Top-N list
8200
+ const allRisks = [];
8201
+ for (const section of sections) {
8202
+ for (const rule of section.rules || []) {
8203
+ for (const f of rule.findings) {
8204
+ allRisks.push({ rule: rule.name, verdict: rule.verdict, ...f });
8205
+ }
8206
+ }
8207
+ }
8120
8208
 
8121
8209
  let risksHtml = '';
8122
8210
  if (allRisks.length > 0) {
@@ -8129,7 +8217,7 @@ var init_ui = __esm({
8129
8217
  (r) => \`
8130
8218
  <div class="finding-item">
8131
8219
  <span class="finding-ts">\${new Date(r.timestamp).toLocaleDateString([], { month: 'short', day: 'numeric' })}</span>
8132
- <span class="finding-rule">\${r.rule.replace(/^shield:[^:]+:/, '')}</span>
8220
+ <span class="finding-rule">\${esc(r.rule)}</span>
8133
8221
  <span class="finding-cmd"><span class="finding-badge \${r.verdict === 'block' ? 'badge-block' : ''}">\${r.verdict === 'block' ? '\u{1F6D1}' : '\u{1F441}\uFE0F'}</span>\${esc(truncate(r.command, 120))}</span>
8134
8222
  </div>
8135
8223
  \`
@@ -8140,18 +8228,18 @@ var init_ui = __esm({
8140
8228
  }
8141
8229
 
8142
8230
  let leaksHtml = '';
8143
- if (allLeaks.length > 0) {
8231
+ if (leaks.length > 0) {
8144
8232
  leaksHtml = \`
8145
8233
  <div class="findings-list" style="border-color: rgba(201, 60, 55, 0.3);">
8146
8234
  <div style="font-size: 10px; font-weight: 700; color: #f87171; margin-bottom: 8px; text-transform: uppercase;">Credential Leaks Identified</div>
8147
- \${allLeaks
8235
+ \${leaks
8148
8236
  .slice(0, 5)
8149
8237
  .map(
8150
8238
  (l) => \`
8151
8239
  <div class="finding-item">
8152
8240
  <span class="finding-ts">\${new Date(l.timestamp).toLocaleDateString([], { month: 'short', day: 'numeric' })}</span>
8153
- <span class="finding-rule">\${esc(l.pattern)}</span>
8154
- <span class="finding-cmd"><span class="finding-badge badge-leak">\u{1F511}</span>\${esc(l.sample)}</span>
8241
+ <span class="finding-rule">\${esc(l.patternName)}</span>
8242
+ <span class="finding-cmd"><span class="finding-badge badge-leak">\u{1F511}</span>\${esc(l.redactedSample)}</span>
8155
8243
  </div>
8156
8244
  \`
8157
8245
  )
@@ -8160,10 +8248,11 @@ var init_ui = __esm({
8160
8248
  \`;
8161
8249
  }
8162
8250
 
8251
+ const riskCount = (byVerdict.blocked || 0) + (byVerdict.supervised || 0);
8163
8252
  results.innerHTML = \`
8164
8253
  <div class="insight-hint" style="color: #57ab5a; border-color: rgba(87, 171, 90, 0.4); background: rgba(87, 171, 90, 0.08);">
8165
8254
  <strong>\u2705 History Audit Complete</strong><br>
8166
- Scanned \${data.summary.sessions} sessions. Found <strong>\${data.summary.findings} risky operations</strong> and <strong>\${data.summary.dlp} secret leaks</strong>.
8255
+ Scanned \${summary.stats?.sessions || 0} sessions. Found <strong>\${riskCount} risky operations</strong> and <strong>\${byVerdict.leaks || 0} secret leaks</strong>.
8167
8256
  Node9 is now actively protecting you from these types of events.
8168
8257
  </div>
8169
8258
  \${leaksHtml}
@@ -8201,6 +8290,9 @@ var init_ui2 = __esm({
8201
8290
  });
8202
8291
 
8203
8292
  // src/cli/daemon-starter.ts
8293
+ function isTestingMode() {
8294
+ return /^(1|true|yes)$/i.test(process.env.NODE9_TESTING ?? "");
8295
+ }
8204
8296
  function openBrowserLocal() {
8205
8297
  const url = `http://${DAEMON_HOST}:${DAEMON_PORT}/`;
8206
8298
  try {
@@ -8211,8 +8303,9 @@ function openBrowserLocal() {
8211
8303
  } catch {
8212
8304
  }
8213
8305
  }
8214
- async function autoStartDaemonAndWait() {
8215
- if (process.env.NODE9_TESTING === "1") return false;
8306
+ async function autoStartDaemonAndWait(openBrowser2 = true) {
8307
+ if (isTestingMode()) return false;
8308
+ if (!import_path16.default.isAbsolute(process.argv[1])) return false;
8216
8309
  try {
8217
8310
  const child = (0, import_child_process3.spawn)(process.execPath, [process.argv[1], "daemon"], {
8218
8311
  detached: true,
@@ -8230,7 +8323,9 @@ async function autoStartDaemonAndWait() {
8230
8323
  signal: AbortSignal.timeout(500)
8231
8324
  });
8232
8325
  if (res.ok) {
8233
- openBrowserLocal();
8326
+ if (openBrowser2) {
8327
+ openBrowserLocal();
8328
+ }
8234
8329
  return true;
8235
8330
  }
8236
8331
  } catch {
@@ -8240,15 +8335,205 @@ async function autoStartDaemonAndWait() {
8240
8335
  }
8241
8336
  return false;
8242
8337
  }
8243
- var import_child_process3;
8338
+ var import_child_process3, import_path16;
8244
8339
  var init_daemon_starter = __esm({
8245
8340
  "src/cli/daemon-starter.ts"() {
8246
8341
  "use strict";
8247
8342
  import_child_process3 = require("child_process");
8343
+ import_path16 = __toESM(require("path"));
8248
8344
  init_daemon();
8249
8345
  }
8250
8346
  });
8251
8347
 
8348
+ // src/scan-summary.ts
8349
+ function buildScanSummary(agents) {
8350
+ const stats = {
8351
+ sessions: 0,
8352
+ totalToolCalls: 0,
8353
+ bashCalls: 0,
8354
+ totalCostUSD: 0,
8355
+ firstDate: null,
8356
+ lastDate: null
8357
+ };
8358
+ for (const a of agents) {
8359
+ stats.sessions += a.scan.sessions;
8360
+ stats.totalToolCalls += a.scan.totalToolCalls;
8361
+ stats.bashCalls += a.scan.bashCalls;
8362
+ stats.totalCostUSD += a.scan.totalCostUSD;
8363
+ if (a.scan.firstDate && (!stats.firstDate || a.scan.firstDate < stats.firstDate)) {
8364
+ stats.firstDate = a.scan.firstDate;
8365
+ }
8366
+ if (a.scan.lastDate && (!stats.lastDate || a.scan.lastDate > stats.lastDate)) {
8367
+ stats.lastDate = a.scan.lastDate;
8368
+ }
8369
+ }
8370
+ const allFindings = agents.flatMap((a) => a.scan.findings);
8371
+ const allLeaks = agents.flatMap(
8372
+ (a) => a.scan.dlpFindings.map((f) => ({
8373
+ patternName: f.patternName,
8374
+ redactedSample: f.redactedSample,
8375
+ toolName: f.toolName,
8376
+ timestamp: f.timestamp,
8377
+ project: f.project,
8378
+ sessionId: f.sessionId,
8379
+ agent: f.agent
8380
+ }))
8381
+ );
8382
+ const allLoops = agents.flatMap(
8383
+ (a) => a.scan.loopFindings.map((f) => ({
8384
+ toolName: f.toolName,
8385
+ commandPreview: f.commandPreview,
8386
+ count: f.count,
8387
+ timestamp: f.timestamp,
8388
+ project: f.project,
8389
+ sessionId: f.sessionId,
8390
+ agent: f.agent
8391
+ }))
8392
+ );
8393
+ const byVerdict = {
8394
+ blocked: allFindings.filter((f) => f.source.rule.verdict === "block").length,
8395
+ supervised: allFindings.filter((f) => f.source.rule.verdict === "review").length,
8396
+ leaks: allLeaks.length,
8397
+ loops: allLoops.length
8398
+ };
8399
+ const byAgent = agents.map((a) => ({
8400
+ id: a.id,
8401
+ label: a.label,
8402
+ icon: a.icon,
8403
+ sessions: a.scan.sessions,
8404
+ findings: a.scan.findings.length + a.scan.dlpFindings.length + a.scan.loopFindings.length,
8405
+ costUSD: a.scan.totalCostUSD
8406
+ })).filter((s) => s.sessions > 0 || s.findings > 0);
8407
+ const sections = buildSections(allFindings);
8408
+ const wastedIters = allLoops.reduce(
8409
+ (sum, l) => sum + Math.max(0, l.count - LOOP_THRESHOLD_FOR_WASTE),
8410
+ 0
8411
+ );
8412
+ const loopWastedUSD = wastedIters * COST_PER_LOOP_ITER_USD;
8413
+ return {
8414
+ stats,
8415
+ byVerdict,
8416
+ byAgent,
8417
+ sections,
8418
+ leaks: allLeaks,
8419
+ loops: allLoops,
8420
+ loopWastedUSD
8421
+ };
8422
+ }
8423
+ function buildSections(findings) {
8424
+ const sectionMap = /* @__PURE__ */ new Map();
8425
+ function ensureSection(id, label, subtitle, sourceType, shieldKey) {
8426
+ let s = sectionMap.get(id);
8427
+ if (!s) {
8428
+ s = {
8429
+ id,
8430
+ label,
8431
+ subtitle,
8432
+ sourceType,
8433
+ shieldKey,
8434
+ blockedCount: 0,
8435
+ reviewCount: 0,
8436
+ rules: []
8437
+ };
8438
+ sectionMap.set(id, s);
8439
+ }
8440
+ return s;
8441
+ }
8442
+ const ruleMap = /* @__PURE__ */ new Map();
8443
+ for (const f of findings) {
8444
+ const src = f.source;
8445
+ const sourceType = src.sourceType;
8446
+ const shieldName = src.shieldName;
8447
+ const verdict = src.rule.verdict === "block" ? "block" : "review";
8448
+ let sectionId;
8449
+ let sectionLabel;
8450
+ let sectionSubtitle;
8451
+ let shieldKey;
8452
+ if (sourceType === "default") {
8453
+ sectionId = "default";
8454
+ sectionLabel = "Default Rules";
8455
+ sectionSubtitle = "built-in, always on";
8456
+ } else if (sourceType === "shield") {
8457
+ sectionId = `shield:${shieldName}`;
8458
+ sectionLabel = shieldName;
8459
+ sectionSubtitle = SHIELDS[shieldName]?.description ?? "";
8460
+ shieldKey = shieldName;
8461
+ } else if (shieldName === "cloud") {
8462
+ sectionId = "cloud";
8463
+ sectionLabel = "Cloud Policy";
8464
+ sectionSubtitle = "synced from node9 cloud";
8465
+ } else {
8466
+ sectionId = "user";
8467
+ sectionLabel = "Your Rules";
8468
+ sectionSubtitle = "added in node9.config.json";
8469
+ }
8470
+ const section = ensureSection(sectionId, sectionLabel, sectionSubtitle, sourceType, shieldKey);
8471
+ const ruleDisplayName = (src.rule.name ?? "unnamed").replace(/^shield:[^:]+:/, "");
8472
+ const ruleKey = sectionId + "::" + ruleDisplayName;
8473
+ let rule = ruleMap.get(ruleKey);
8474
+ if (!rule) {
8475
+ rule = {
8476
+ name: ruleDisplayName,
8477
+ verdict,
8478
+ reason: src.rule.reason ?? "",
8479
+ findings: []
8480
+ };
8481
+ ruleMap.set(ruleKey, rule);
8482
+ section.rules.push(rule);
8483
+ }
8484
+ const cmdPreview = previewCommand(f.input, 120);
8485
+ const fullCmd = fullCommandOf(f.input);
8486
+ const isDupe = rule.findings.some((x) => x.project === f.project && x.command === cmdPreview);
8487
+ if (!isDupe) {
8488
+ rule.findings.push({
8489
+ timestamp: f.timestamp ?? "",
8490
+ command: cmdPreview,
8491
+ fullCommand: fullCmd,
8492
+ project: f.project,
8493
+ sessionId: f.sessionId,
8494
+ agent: f.agent,
8495
+ toolName: f.toolName
8496
+ });
8497
+ }
8498
+ if (verdict === "block") section.blockedCount++;
8499
+ else section.reviewCount++;
8500
+ }
8501
+ const sections = [...sectionMap.values()];
8502
+ sections.sort((a, b) => {
8503
+ const aTotal = a.blockedCount + a.reviewCount;
8504
+ const bTotal = b.blockedCount + b.reviewCount;
8505
+ if (b.blockedCount !== a.blockedCount) return b.blockedCount - a.blockedCount;
8506
+ return bTotal - aTotal;
8507
+ });
8508
+ for (const s of sections) {
8509
+ s.rules.sort((a, b) => {
8510
+ const aBlock = a.verdict === "block" ? 1 : 0;
8511
+ const bBlock = b.verdict === "block" ? 1 : 0;
8512
+ if (bBlock !== aBlock) return bBlock - aBlock;
8513
+ return b.findings.length - a.findings.length;
8514
+ });
8515
+ }
8516
+ return sections;
8517
+ }
8518
+ function previewCommand(input, max) {
8519
+ const raw = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
8520
+ const s = String(raw).replace(/\s+/g, " ").trim();
8521
+ return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
8522
+ }
8523
+ function fullCommandOf(input) {
8524
+ const raw = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
8525
+ return String(raw).replace(/\s+/g, " ").trim();
8526
+ }
8527
+ var LOOP_THRESHOLD_FOR_WASTE, COST_PER_LOOP_ITER_USD;
8528
+ var init_scan_summary = __esm({
8529
+ "src/scan-summary.ts"() {
8530
+ "use strict";
8531
+ init_shields();
8532
+ LOOP_THRESHOLD_FOR_WASTE = 3;
8533
+ COST_PER_LOOP_ITER_USD = 6e-3;
8534
+ }
8535
+ });
8536
+
8252
8537
  // src/cli/commands/scan.ts
8253
8538
  function claudeModelPrice(model) {
8254
8539
  const base = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
@@ -8289,10 +8574,6 @@ function preview(input, max) {
8289
8574
  const s = String(cmd).replace(/\s+/g, " ").trim();
8290
8575
  return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
8291
8576
  }
8292
- function fullCommand(input) {
8293
- const cmd = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
8294
- return String(cmd).replace(/\s+/g, " ").trim();
8295
- }
8296
8577
  function detectLoops(calls, project, sessionId, agent) {
8297
8578
  const counts = /* @__PURE__ */ new Map();
8298
8579
  for (const call of calls) {
@@ -8352,11 +8633,11 @@ function buildRuleSources() {
8352
8633
  }
8353
8634
  function countScanFiles() {
8354
8635
  let total = 0;
8355
- const claudeDir = import_path16.default.join(import_os12.default.homedir(), ".claude", "projects");
8636
+ const claudeDir = import_path17.default.join(import_os12.default.homedir(), ".claude", "projects");
8356
8637
  if (import_fs13.default.existsSync(claudeDir)) {
8357
8638
  try {
8358
8639
  for (const proj of import_fs13.default.readdirSync(claudeDir)) {
8359
- const p = import_path16.default.join(claudeDir, proj);
8640
+ const p = import_path17.default.join(claudeDir, proj);
8360
8641
  try {
8361
8642
  if (!import_fs13.default.statSync(p).isDirectory()) continue;
8362
8643
  total += import_fs13.default.readdirSync(p).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-")).length;
@@ -8367,14 +8648,14 @@ function countScanFiles() {
8367
8648
  } catch {
8368
8649
  }
8369
8650
  }
8370
- const geminiDir = import_path16.default.join(import_os12.default.homedir(), ".gemini", "tmp");
8651
+ const geminiDir = import_path17.default.join(import_os12.default.homedir(), ".gemini", "tmp");
8371
8652
  if (import_fs13.default.existsSync(geminiDir)) {
8372
8653
  try {
8373
8654
  for (const slug of import_fs13.default.readdirSync(geminiDir)) {
8374
- const p = import_path16.default.join(geminiDir, slug);
8655
+ const p = import_path17.default.join(geminiDir, slug);
8375
8656
  try {
8376
8657
  if (!import_fs13.default.statSync(p).isDirectory()) continue;
8377
- const chatsDir = import_path16.default.join(p, "chats");
8658
+ const chatsDir = import_path17.default.join(p, "chats");
8378
8659
  if (import_fs13.default.existsSync(chatsDir)) {
8379
8660
  try {
8380
8661
  total += import_fs13.default.readdirSync(chatsDir).filter((f) => f.endsWith(".json")).length;
@@ -8388,19 +8669,19 @@ function countScanFiles() {
8388
8669
  } catch {
8389
8670
  }
8390
8671
  }
8391
- const codexDir = import_path16.default.join(import_os12.default.homedir(), ".codex", "sessions");
8672
+ const codexDir = import_path17.default.join(import_os12.default.homedir(), ".codex", "sessions");
8392
8673
  if (import_fs13.default.existsSync(codexDir)) {
8393
8674
  try {
8394
8675
  for (const year of import_fs13.default.readdirSync(codexDir)) {
8395
- const yp = import_path16.default.join(codexDir, year);
8676
+ const yp = import_path17.default.join(codexDir, year);
8396
8677
  try {
8397
8678
  if (!import_fs13.default.statSync(yp).isDirectory()) continue;
8398
8679
  for (const month of import_fs13.default.readdirSync(yp)) {
8399
- const mp = import_path16.default.join(yp, month);
8680
+ const mp = import_path17.default.join(yp, month);
8400
8681
  try {
8401
8682
  if (!import_fs13.default.statSync(mp).isDirectory()) continue;
8402
8683
  for (const day of import_fs13.default.readdirSync(mp)) {
8403
- const dp = import_path16.default.join(mp, day);
8684
+ const dp = import_path17.default.join(mp, day);
8404
8685
  try {
8405
8686
  if (!import_fs13.default.statSync(dp).isDirectory()) continue;
8406
8687
  total += import_fs13.default.readdirSync(dp).filter((f) => f.endsWith(".jsonl")).length;
@@ -8432,7 +8713,7 @@ function renderProgressBar(done, total) {
8432
8713
  );
8433
8714
  }
8434
8715
  function scanClaudeHistory(startDate, onProgress) {
8435
- const projectsDir = import_path16.default.join(import_os12.default.homedir(), ".claude", "projects");
8716
+ const projectsDir = import_path17.default.join(import_os12.default.homedir(), ".claude", "projects");
8436
8717
  const result = {
8437
8718
  filesScanned: 0,
8438
8719
  sessions: 0,
@@ -8454,7 +8735,7 @@ function scanClaudeHistory(startDate, onProgress) {
8454
8735
  }
8455
8736
  const ruleSources = buildRuleSources();
8456
8737
  for (const proj of projDirs) {
8457
- const projPath = import_path16.default.join(projectsDir, proj);
8738
+ const projPath = import_path17.default.join(projectsDir, proj);
8458
8739
  try {
8459
8740
  if (!import_fs13.default.statSync(projPath).isDirectory()) continue;
8460
8741
  } catch {
@@ -8474,7 +8755,7 @@ function scanClaudeHistory(startDate, onProgress) {
8474
8755
  const sessionId = file.replace(/\.jsonl$/, "");
8475
8756
  let raw;
8476
8757
  try {
8477
- raw = import_fs13.default.readFileSync(import_path16.default.join(projPath, file), "utf-8");
8758
+ raw = import_fs13.default.readFileSync(import_path17.default.join(projPath, file), "utf-8");
8478
8759
  } catch {
8479
8760
  continue;
8480
8761
  }
@@ -8627,7 +8908,7 @@ function scanClaudeHistory(startDate, onProgress) {
8627
8908
  return result;
8628
8909
  }
8629
8910
  function scanGeminiHistory(startDate, onProgress) {
8630
- const tmpDir = import_path16.default.join(import_os12.default.homedir(), ".gemini", "tmp");
8911
+ const tmpDir = import_path17.default.join(import_os12.default.homedir(), ".gemini", "tmp");
8631
8912
  const result = {
8632
8913
  filesScanned: 0,
8633
8914
  sessions: 0,
@@ -8649,7 +8930,7 @@ function scanGeminiHistory(startDate, onProgress) {
8649
8930
  }
8650
8931
  const ruleSources = buildRuleSources();
8651
8932
  for (const slug of slugDirs) {
8652
- const slugPath = import_path16.default.join(tmpDir, slug);
8933
+ const slugPath = import_path17.default.join(tmpDir, slug);
8653
8934
  try {
8654
8935
  if (!import_fs13.default.statSync(slugPath).isDirectory()) continue;
8655
8936
  } catch {
@@ -8657,10 +8938,10 @@ function scanGeminiHistory(startDate, onProgress) {
8657
8938
  }
8658
8939
  let projLabel = slug;
8659
8940
  try {
8660
- projLabel = import_fs13.default.readFileSync(import_path16.default.join(slugPath, ".project_root"), "utf-8").trim().replace(import_os12.default.homedir(), "~").slice(0, 40);
8941
+ projLabel = import_fs13.default.readFileSync(import_path17.default.join(slugPath, ".project_root"), "utf-8").trim().replace(import_os12.default.homedir(), "~").slice(0, 40);
8661
8942
  } catch {
8662
8943
  }
8663
- const chatsDir = import_path16.default.join(slugPath, "chats");
8944
+ const chatsDir = import_path17.default.join(slugPath, "chats");
8664
8945
  if (!import_fs13.default.existsSync(chatsDir)) continue;
8665
8946
  let chatFiles;
8666
8947
  try {
@@ -8674,7 +8955,7 @@ function scanGeminiHistory(startDate, onProgress) {
8674
8955
  const sessionId = chatFile.replace(/\.json$/, "");
8675
8956
  let raw;
8676
8957
  try {
8677
- raw = import_fs13.default.readFileSync(import_path16.default.join(chatsDir, chatFile), "utf-8");
8958
+ raw = import_fs13.default.readFileSync(import_path17.default.join(chatsDir, chatFile), "utf-8");
8678
8959
  } catch {
8679
8960
  continue;
8680
8961
  }
@@ -8823,7 +9104,7 @@ function scanGeminiHistory(startDate, onProgress) {
8823
9104
  return result;
8824
9105
  }
8825
9106
  function scanCodexHistory(startDate, onProgress) {
8826
- const sessionsBase = import_path16.default.join(import_os12.default.homedir(), ".codex", "sessions");
9107
+ const sessionsBase = import_path17.default.join(import_os12.default.homedir(), ".codex", "sessions");
8827
9108
  const result = {
8828
9109
  filesScanned: 0,
8829
9110
  sessions: 0,
@@ -8840,28 +9121,28 @@ function scanCodexHistory(startDate, onProgress) {
8840
9121
  const jsonlFiles = [];
8841
9122
  try {
8842
9123
  for (const year of import_fs13.default.readdirSync(sessionsBase)) {
8843
- const yearPath = import_path16.default.join(sessionsBase, year);
9124
+ const yearPath = import_path17.default.join(sessionsBase, year);
8844
9125
  try {
8845
9126
  if (!import_fs13.default.statSync(yearPath).isDirectory()) continue;
8846
9127
  } catch {
8847
9128
  continue;
8848
9129
  }
8849
9130
  for (const month of import_fs13.default.readdirSync(yearPath)) {
8850
- const monthPath = import_path16.default.join(yearPath, month);
9131
+ const monthPath = import_path17.default.join(yearPath, month);
8851
9132
  try {
8852
9133
  if (!import_fs13.default.statSync(monthPath).isDirectory()) continue;
8853
9134
  } catch {
8854
9135
  continue;
8855
9136
  }
8856
9137
  for (const day of import_fs13.default.readdirSync(monthPath)) {
8857
- const dayPath = import_path16.default.join(monthPath, day);
9138
+ const dayPath = import_path17.default.join(monthPath, day);
8858
9139
  try {
8859
9140
  if (!import_fs13.default.statSync(dayPath).isDirectory()) continue;
8860
9141
  } catch {
8861
9142
  continue;
8862
9143
  }
8863
9144
  for (const file of import_fs13.default.readdirSync(dayPath)) {
8864
- if (file.endsWith(".jsonl")) jsonlFiles.push(import_path16.default.join(dayPath, file));
9145
+ if (file.endsWith(".jsonl")) jsonlFiles.push(import_path17.default.join(dayPath, file));
8865
9146
  }
8866
9147
  }
8867
9148
  }
@@ -9063,26 +9344,32 @@ function printFindingRow(f, drillDown, showSessionId, previewWidth) {
9063
9344
  const ts = f.timestamp ? import_chalk2.default.dim(fmtTs(f.timestamp) + " ") : "";
9064
9345
  const proj = import_chalk2.default.dim(f.project.slice(0, 22).padEnd(22) + " ");
9065
9346
  const agentBadge = f.agent === "gemini" ? import_chalk2.default.blue("[Gemini] ") : f.agent === "codex" ? import_chalk2.default.magenta("[Codex] ") : import_chalk2.default.cyan("[Claude] ");
9066
- const cmd = drillDown ? import_chalk2.default.gray(fullCommand(f.input)) : import_chalk2.default.gray(preview(f.input, previewWidth));
9347
+ let cmdText;
9348
+ if (drillDown) {
9349
+ cmdText = f.fullCommand;
9350
+ } else {
9351
+ cmdText = f.command;
9352
+ if (cmdText.length > previewWidth) cmdText = cmdText.slice(0, previewWidth - 1) + "\u2026";
9353
+ }
9354
+ const cmd = import_chalk2.default.gray(cmdText);
9067
9355
  const sessionSuffix = showSessionId && f.sessionId ? import_chalk2.default.dim(` \u2192 ${f.sessionId.slice(0, 8)}`) : "";
9068
9356
  console.log(` ${ts}${proj}${agentBadge}${cmd}${sessionSuffix}`);
9069
9357
  }
9070
- function printRuleGroup(ruleFindings, topN, drillDown, previewWidth) {
9071
- const rule = ruleFindings[0].source.rule;
9072
- const ruleCount = ruleFindings.length;
9358
+ function printRuleGroup(rule, topN, drillDown, previewWidth) {
9359
+ const findings = rule.findings;
9360
+ const ruleCount = findings.length;
9073
9361
  const countBadge = ruleCount > 1 ? import_chalk2.default.white(` \xD7${ruleCount}`) : "";
9074
- const shortName = (rule.name ?? "unnamed").replace(/^shield:[^:]+:/, "");
9075
- const icon = verdictIcon(rule.verdict ?? "review");
9362
+ const icon = verdictIcon(rule.verdict);
9076
9363
  console.log(
9077
- " " + icon + " " + import_chalk2.default.white(shortName) + countBadge + (rule.reason ? import_chalk2.default.dim(` \u2014 ${rule.reason}`) : "")
9364
+ " " + icon + " " + import_chalk2.default.white(rule.name) + countBadge + (rule.reason ? import_chalk2.default.dim(` \u2014 ${rule.reason}`) : "")
9078
9365
  );
9079
- const shown = drillDown ? ruleFindings : ruleFindings.slice(0, topN);
9366
+ const shown = drillDown ? findings : findings.slice(0, topN);
9080
9367
  for (const f of shown) {
9081
9368
  printFindingRow(f, drillDown, drillDown, previewWidth);
9082
9369
  }
9083
- if (!drillDown && ruleFindings.length > topN) {
9370
+ if (!drillDown && findings.length > topN) {
9084
9371
  console.log(
9085
- import_chalk2.default.dim(` \u2026 and ${ruleFindings.length - topN} more (--drill-down for full list)`)
9372
+ import_chalk2.default.dim(` \u2026 and ${findings.length - topN} more (--drill-down for full list)`)
9086
9373
  );
9087
9374
  }
9088
9375
  }
@@ -9097,7 +9384,7 @@ function registerScanCommand(program2) {
9097
9384
  d.setHours(0, 0, 0, 0);
9098
9385
  return d;
9099
9386
  })();
9100
- const isInstalled = import_fs13.default.existsSync(import_path16.default.join(import_os12.default.homedir(), ".node9", "audit.log"));
9387
+ const isInstalled = import_fs13.default.existsSync(import_path17.default.join(import_os12.default.homedir(), ".node9", "audit.log"));
9101
9388
  console.log("");
9102
9389
  if (!isInstalled) {
9103
9390
  console.log(
@@ -9129,6 +9416,11 @@ function registerScanCommand(program2) {
9129
9416
  (done) => onProgress(claudeScan.filesScanned + geminiScan.filesScanned + done)
9130
9417
  );
9131
9418
  const scan = mergeScans(mergeScans(claudeScan, geminiScan), codexScan);
9419
+ const summary = buildScanSummary([
9420
+ { id: "claude", label: "Claude", icon: "\u{1F916}", scan: claudeScan },
9421
+ { id: "gemini", label: "Gemini", icon: "\u264A", scan: geminiScan },
9422
+ { id: "codex", label: "Codex", icon: "\u{1F52E}", scan: codexScan }
9423
+ ]);
9132
9424
  process.stdout.write("\r" + " ".repeat(60) + "\r");
9133
9425
  if (scan.filesScanned === 0) {
9134
9426
  console.log(import_chalk2.default.yellow(" No session history found."));
@@ -9191,76 +9483,27 @@ function registerScanCommand(program2) {
9191
9483
  );
9192
9484
  }
9193
9485
  console.log("");
9194
- const sections = [];
9195
- const defaultFindings = scan.findings.filter((f) => f.source.sourceType === "default");
9196
- if (defaultFindings.length > 0) {
9197
- sections.push({
9198
- label: "Default Rules",
9199
- subtitle: "built-in, always on",
9200
- findings: defaultFindings
9201
- });
9202
- }
9203
- const byShield = /* @__PURE__ */ new Map();
9204
- for (const f of scan.findings.filter((f2) => f2.source.sourceType === "shield")) {
9205
- const arr = byShield.get(f.source.shieldName) ?? [];
9206
- arr.push(f);
9207
- byShield.set(f.source.shieldName, arr);
9208
- }
9209
- const shieldsWithFindings = [...byShield.entries()].sort(
9210
- (a, b) => b[1].length - a[1].length
9211
- );
9212
- for (const [shieldName, findings] of shieldsWithFindings) {
9213
- const description = SHIELDS[shieldName]?.description ?? "";
9214
- sections.push({
9215
- label: shieldName,
9216
- subtitle: description,
9217
- shieldKey: shieldName,
9218
- findings
9219
- });
9220
- }
9221
- const userFindings = scan.findings.filter(
9222
- (f) => f.source.sourceType === "user" || f.source.shieldName === "cloud"
9223
- );
9224
- if (userFindings.length > 0) {
9225
- sections.push({
9226
- label: "Your Rules",
9227
- subtitle: "added in node9.config.json",
9228
- findings: userFindings
9229
- });
9230
- }
9231
- for (const section of sections) {
9232
- const sectionBlocked = section.findings.filter(
9233
- (f) => f.source.rule.verdict === "block"
9234
- ).length;
9235
- const sectionReview = section.findings.length - sectionBlocked;
9486
+ for (const section of summary.sections) {
9236
9487
  const countParts = [];
9237
- if (sectionBlocked > 0) countParts.push(import_chalk2.default.red(`${sectionBlocked} blocked`));
9238
- if (sectionReview > 0) countParts.push(import_chalk2.default.yellow(`${sectionReview} review`));
9488
+ if (section.blockedCount > 0)
9489
+ countParts.push(import_chalk2.default.red(`${section.blockedCount} blocked`));
9490
+ if (section.reviewCount > 0)
9491
+ countParts.push(import_chalk2.default.yellow(`${section.reviewCount} review`));
9239
9492
  const countStr = countParts.join(import_chalk2.default.dim(" \xB7 "));
9240
9493
  const enableHint = section.shieldKey ? import_chalk2.default.dim(` \u2192 node9 shield enable ${section.shieldKey}`) : "";
9241
9494
  console.log(" " + import_chalk2.default.dim("\u2500".repeat(70)));
9242
9495
  console.log(
9243
9496
  " " + import_chalk2.default.bold(section.label) + (section.subtitle ? import_chalk2.default.dim(` \xB7 ${section.subtitle}`) : "") + " " + countStr + enableHint
9244
9497
  );
9245
- const byRule = /* @__PURE__ */ new Map();
9246
- for (const f of section.findings) {
9247
- const ruleKey = f.source.rule.name ?? "unnamed";
9248
- const arr = byRule.get(ruleKey) ?? [];
9249
- arr.push(f);
9250
- byRule.set(ruleKey, arr);
9251
- }
9252
- const sortedRules = [...byRule.entries()].sort((a, b) => {
9253
- const aBlock = a[1][0].source.rule.verdict === "block" ? 1 : 0;
9254
- const bBlock = b[1][0].source.rule.verdict === "block" ? 1 : 0;
9255
- if (bBlock !== aBlock) return bBlock - aBlock;
9256
- return b[1].length - a[1].length;
9257
- });
9258
- for (const [, ruleFindings] of sortedRules) {
9259
- printRuleGroup(ruleFindings, topN, drillDown, previewWidth);
9498
+ for (const rule of section.rules) {
9499
+ printRuleGroup(rule, topN, drillDown, previewWidth);
9260
9500
  }
9261
9501
  console.log("");
9262
9502
  }
9263
- const emptyShields = Object.keys(SHIELDS).filter((n) => !byShield.has(n)).sort();
9503
+ const activeShieldIds = new Set(
9504
+ summary.sections.filter((s) => s.sourceType === "shield" && s.shieldKey).map((s) => s.shieldKey)
9505
+ );
9506
+ const emptyShields = Object.keys(SHIELDS).filter((n) => !activeShieldIds.has(n)).sort();
9264
9507
  if (emptyShields.length > 0) {
9265
9508
  console.log(" " + import_chalk2.default.dim("\u2500".repeat(70)));
9266
9509
  console.log(
@@ -9366,82 +9609,16 @@ function registerScanCommand(program2) {
9366
9609
  console.log(" " + import_chalk2.default.dim("\u2192 ") + import_chalk2.default.underline("https://node9.ai"));
9367
9610
  }
9368
9611
  console.log("");
9369
- if (isDaemonRunning() && process.env.NODE9_TESTING !== "1") {
9612
+ if (!isTestingMode() && isDaemonRunning()) {
9370
9613
  const internalToken = getInternalToken();
9371
9614
  if (internalToken) {
9372
9615
  try {
9373
- const mapFindings = (arr, src) => (
9374
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
9375
- arr.map((f) => ({
9376
- timestamp: f.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
9377
- rule: f.source?.rule?.name ?? f.source?.shieldLabel ?? "unnamed",
9378
- command: f.input?.command ?? f.input?.cmd ?? f.input?.file_path ?? f.toolName ?? "unknown",
9379
- verdict: f.source?.rule?.verdict ?? f.source?.rule?.action ?? "review",
9380
- ruleSource: f.source?.sourceType ?? "default",
9381
- source: src
9382
- }))
9383
- );
9384
- const mapLeaks = (arr, src) => (
9385
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
9386
- arr.map((f) => ({
9387
- timestamp: f.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
9388
- pattern: f.patternName || "DLP",
9389
- sample: f.redactedSample || "********",
9390
- source: src
9391
- }))
9392
- );
9393
- const mapLoops = (arr, src) => (
9394
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
9395
- arr.map((f) => ({
9396
- timestamp: f.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
9397
- toolName: f.toolName || "unknown",
9398
- commandPreview: f.commandPreview || "",
9399
- count: f.count || 0,
9400
- source: src
9401
- }))
9402
- );
9403
- const sources = [
9404
- {
9405
- id: "claude",
9406
- label: "Claude",
9407
- icon: "\u{1F916}",
9408
- sessions: claudeScan.sessions,
9409
- findings: mapFindings(claudeScan.findings, "claude"),
9410
- leaks: mapLeaks(claudeScan.dlpFindings, "claude"),
9411
- loops: mapLoops(claudeScan.loopFindings, "claude")
9412
- },
9413
- {
9414
- id: "gemini",
9415
- label: "Gemini",
9416
- icon: "\u264A",
9417
- sessions: geminiScan.sessions,
9418
- findings: mapFindings(geminiScan.findings, "gemini"),
9419
- leaks: mapLeaks(geminiScan.dlpFindings, "gemini"),
9420
- loops: mapLoops(geminiScan.loopFindings, "gemini")
9421
- },
9422
- {
9423
- id: "codex",
9424
- label: "Codex",
9425
- icon: "\u{1F52E}",
9426
- sessions: codexScan.sessions,
9427
- findings: mapFindings(codexScan.findings, "codex"),
9428
- leaks: mapLeaks(codexScan.dlpFindings, "codex"),
9429
- loops: mapLoops(codexScan.loopFindings, "codex")
9430
- }
9431
- ].filter(
9432
- (s) => s.sessions > 0 || s.findings.length > 0 || s.leaks.length > 0 || s.loops.length > 0
9433
- );
9434
- const payload = {
9435
- status: "complete",
9436
- summary: {
9437
- sessions: scan.sessions,
9438
- findings: scan.findings.length,
9439
- dlp: scan.dlpFindings.length,
9440
- loops: scan.loopFindings.length,
9441
- totalCostUSD: scan.totalCostUSD
9442
- },
9443
- sources
9444
- };
9616
+ const summary2 = buildScanSummary([
9617
+ { id: "claude", label: "Claude", icon: "\u{1F916}", scan: claudeScan },
9618
+ { id: "gemini", label: "Gemini", icon: "\u264A", scan: geminiScan },
9619
+ { id: "codex", label: "Codex", icon: "\u{1F52E}", scan: codexScan }
9620
+ ]);
9621
+ const payload = { status: "complete", summary: summary2 };
9445
9622
  await fetch(`http://${DAEMON_HOST}:${DAEMON_PORT}/scan/push`, {
9446
9623
  method: "POST",
9447
9624
  headers: { "Content-Type": "application/json", "x-node9-internal": internalToken },
@@ -9458,13 +9635,13 @@ function registerScanCommand(program2) {
9458
9635
  }
9459
9636
  });
9460
9637
  }
9461
- var import_chalk2, import_fs13, import_path16, import_os12, CLAUDE_PRICING, GEMINI_PRICING, LOOP_TOOLS, LOOP_THRESHOLD, DEFAULT_RULE_NAMES;
9638
+ var import_chalk2, import_fs13, import_path17, import_os12, CLAUDE_PRICING, GEMINI_PRICING, LOOP_TOOLS, LOOP_THRESHOLD, DEFAULT_RULE_NAMES;
9462
9639
  var init_scan = __esm({
9463
9640
  "src/cli/commands/scan.ts"() {
9464
9641
  "use strict";
9465
9642
  import_chalk2 = __toESM(require("chalk"));
9466
9643
  import_fs13 = __toESM(require("fs"));
9467
- import_path16 = __toESM(require("path"));
9644
+ import_path17 = __toESM(require("path"));
9468
9645
  import_os12 = __toESM(require("os"));
9469
9646
  init_shields();
9470
9647
  init_config();
@@ -9472,6 +9649,7 @@ var init_scan = __esm({
9472
9649
  init_dlp();
9473
9650
  init_daemon();
9474
9651
  init_daemon_starter();
9652
+ init_scan_summary();
9475
9653
  CLAUDE_PRICING = {
9476
9654
  "claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
9477
9655
  "claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
@@ -9600,12 +9778,12 @@ var init_suggestion_tracker = __esm({
9600
9778
  });
9601
9779
 
9602
9780
  // src/daemon/taint-store.ts
9603
- var import_fs14, import_path17, DEFAULT_TTL_MS, TaintStore;
9781
+ var import_fs14, import_path18, DEFAULT_TTL_MS, TaintStore;
9604
9782
  var init_taint_store = __esm({
9605
9783
  "src/daemon/taint-store.ts"() {
9606
9784
  "use strict";
9607
9785
  import_fs14 = __toESM(require("fs"));
9608
- import_path17 = __toESM(require("path"));
9786
+ import_path18 = __toESM(require("path"));
9609
9787
  DEFAULT_TTL_MS = 60 * 60 * 1e3;
9610
9788
  TaintStore = class {
9611
9789
  records = /* @__PURE__ */ new Map();
@@ -9670,9 +9848,9 @@ var init_taint_store = __esm({
9670
9848
  /** Resolve to absolute path, falling back to path.resolve if file doesn't exist yet. */
9671
9849
  _resolve(filePath) {
9672
9850
  try {
9673
- return import_fs14.default.realpathSync.native(import_path17.default.resolve(filePath));
9851
+ return import_fs14.default.realpathSync.native(import_path18.default.resolve(filePath));
9674
9852
  } catch {
9675
- return import_path17.default.resolve(filePath);
9853
+ return import_path18.default.resolve(filePath);
9676
9854
  }
9677
9855
  }
9678
9856
  };
@@ -9834,7 +10012,7 @@ function setCachedScanResult(result) {
9834
10012
  cachedScanTs = Date.now();
9835
10013
  }
9836
10014
  function atomicWriteSync2(filePath, data, options) {
9837
- const dir = import_path18.default.dirname(filePath);
10015
+ const dir = import_path19.default.dirname(filePath);
9838
10016
  if (!import_fs15.default.existsSync(dir)) import_fs15.default.mkdirSync(dir, { recursive: true });
9839
10017
  const tmpPath = `${filePath}.${(0, import_crypto6.randomUUID)()}.tmp`;
9840
10018
  try {
@@ -9874,7 +10052,7 @@ function appendAuditLog(data) {
9874
10052
  decision: data.decision,
9875
10053
  source: "daemon"
9876
10054
  };
9877
- const dir = import_path18.default.dirname(AUDIT_LOG_FILE);
10055
+ const dir = import_path19.default.dirname(AUDIT_LOG_FILE);
9878
10056
  if (!import_fs15.default.existsSync(dir)) import_fs15.default.mkdirSync(dir, { recursive: true });
9879
10057
  import_fs15.default.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
9880
10058
  } catch {
@@ -10128,13 +10306,13 @@ function startActivitySocket() {
10128
10306
  }
10129
10307
  });
10130
10308
  }
10131
- var import_net2, import_fs15, import_path18, import_os13, import_child_process4, import_crypto6, homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, pending, sseClients, suggestionTracker, suggestions, taintStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, LARGE_RESPONSE_RING_SIZE, largeResponseRing, cachedScanResult, cachedScanTs, SCAN_CACHE_TTL_MS, SECRET_KEY_RE, INPUT_PRICE_PER_1M, OUTPUT_PRICE_PER_1M, BYTES_PER_TOKEN, WRITE_TOOL_NAMES;
10309
+ var import_net2, import_fs15, import_path19, import_os13, import_child_process4, import_crypto6, homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, pending, sseClients, suggestionTracker, suggestions, taintStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, LARGE_RESPONSE_RING_SIZE, largeResponseRing, cachedScanResult, cachedScanTs, SCAN_CACHE_TTL_MS, SECRET_KEY_RE, INPUT_PRICE_PER_1M, OUTPUT_PRICE_PER_1M, BYTES_PER_TOKEN, WRITE_TOOL_NAMES;
10132
10310
  var init_state2 = __esm({
10133
10311
  "src/daemon/state.ts"() {
10134
10312
  "use strict";
10135
10313
  import_net2 = __toESM(require("net"));
10136
10314
  import_fs15 = __toESM(require("fs"));
10137
- import_path18 = __toESM(require("path"));
10315
+ import_path19 = __toESM(require("path"));
10138
10316
  import_os13 = __toESM(require("os"));
10139
10317
  import_child_process4 = require("child_process");
10140
10318
  import_crypto6 = require("crypto");
@@ -10144,13 +10322,13 @@ var init_state2 = __esm({
10144
10322
  init_session_counters();
10145
10323
  init_session_history();
10146
10324
  homeDir = import_os13.default.homedir();
10147
- DAEMON_PID_FILE = import_path18.default.join(homeDir, ".node9", "daemon.pid");
10148
- DECISIONS_FILE = import_path18.default.join(homeDir, ".node9", "decisions.json");
10149
- AUDIT_LOG_FILE = import_path18.default.join(homeDir, ".node9", "audit.log");
10150
- TRUST_FILE2 = import_path18.default.join(homeDir, ".node9", "trust.json");
10151
- GLOBAL_CONFIG_FILE = import_path18.default.join(homeDir, ".node9", "config.json");
10152
- CREDENTIALS_FILE = import_path18.default.join(homeDir, ".node9", "credentials.json");
10153
- INSIGHT_COUNTS_FILE = import_path18.default.join(homeDir, ".node9", "insight-counts.json");
10325
+ DAEMON_PID_FILE = import_path19.default.join(homeDir, ".node9", "daemon.pid");
10326
+ DECISIONS_FILE = import_path19.default.join(homeDir, ".node9", "decisions.json");
10327
+ AUDIT_LOG_FILE = import_path19.default.join(homeDir, ".node9", "audit.log");
10328
+ TRUST_FILE2 = import_path19.default.join(homeDir, ".node9", "trust.json");
10329
+ GLOBAL_CONFIG_FILE = import_path19.default.join(homeDir, ".node9", "config.json");
10330
+ CREDENTIALS_FILE = import_path19.default.join(homeDir, ".node9", "credentials.json");
10331
+ INSIGHT_COUNTS_FILE = import_path19.default.join(homeDir, ".node9", "insight-counts.json");
10154
10332
  pending = /* @__PURE__ */ new Map();
10155
10333
  sseClients = /* @__PURE__ */ new Set();
10156
10334
  suggestionTracker = new SuggestionTracker(3);
@@ -10168,7 +10346,7 @@ var init_state2 = __esm({
10168
10346
  "2h": 2 * 60 * 6e4
10169
10347
  };
10170
10348
  autoStarted = process.env.NODE9_AUTO_STARTED === "1";
10171
- ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path18.default.join(import_os13.default.tmpdir(), "node9-activity.sock");
10349
+ ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path19.default.join(import_os13.default.tmpdir(), "node9-activity.sock");
10172
10350
  ACTIVITY_RING_SIZE = 100;
10173
10351
  activityRing = [];
10174
10352
  LARGE_RESPONSE_RING_SIZE = 20;
@@ -10218,7 +10396,7 @@ function patchConfig(configPath, patch) {
10218
10396
  ignored.push(patch.toolName);
10219
10397
  }
10220
10398
  }
10221
- const dir = import_path19.default.dirname(configPath);
10399
+ const dir = import_path20.default.dirname(configPath);
10222
10400
  import_fs16.default.mkdirSync(dir, { recursive: true });
10223
10401
  const tmp = configPath + ".node9-tmp";
10224
10402
  try {
@@ -10240,14 +10418,14 @@ function patchConfig(configPath, patch) {
10240
10418
  throw err2;
10241
10419
  }
10242
10420
  }
10243
- var import_fs16, import_path19, import_os14, GLOBAL_CONFIG_PATH;
10421
+ var import_fs16, import_path20, import_os14, GLOBAL_CONFIG_PATH;
10244
10422
  var init_patch = __esm({
10245
10423
  "src/config/patch.ts"() {
10246
10424
  "use strict";
10247
10425
  import_fs16 = __toESM(require("fs"));
10248
- import_path19 = __toESM(require("path"));
10426
+ import_path20 = __toESM(require("path"));
10249
10427
  import_os14 = __toESM(require("os"));
10250
- GLOBAL_CONFIG_PATH = import_path19.default.join(import_os14.default.homedir(), ".node9", "config.json");
10428
+ GLOBAL_CONFIG_PATH = import_path20.default.join(import_os14.default.homedir(), ".node9", "config.json");
10251
10429
  }
10252
10430
  });
10253
10431
 
@@ -10319,7 +10497,7 @@ function parseJSONLFile(filePath) {
10319
10497
  return daily;
10320
10498
  }
10321
10499
  function collectEntries() {
10322
- const projectsDir = import_path20.default.join(import_os15.default.homedir(), ".claude", "projects");
10500
+ const projectsDir = import_path21.default.join(import_os15.default.homedir(), ".claude", "projects");
10323
10501
  if (!import_fs17.default.existsSync(projectsDir)) return [];
10324
10502
  const combined = /* @__PURE__ */ new Map();
10325
10503
  let dirs;
@@ -10329,7 +10507,7 @@ function collectEntries() {
10329
10507
  return [];
10330
10508
  }
10331
10509
  for (const dir of dirs) {
10332
- const dirPath = import_path20.default.join(projectsDir, dir);
10510
+ const dirPath = import_path21.default.join(projectsDir, dir);
10333
10511
  try {
10334
10512
  if (!import_fs17.default.statSync(dirPath).isDirectory()) continue;
10335
10513
  } catch {
@@ -10342,7 +10520,7 @@ function collectEntries() {
10342
10520
  continue;
10343
10521
  }
10344
10522
  for (const file of files) {
10345
- const entries = parseJSONLFile(import_path20.default.join(dirPath, file));
10523
+ const entries = parseJSONLFile(import_path21.default.join(dirPath, file));
10346
10524
  for (const [key, e] of entries) {
10347
10525
  const prev = combined.get(key);
10348
10526
  if (prev) {
@@ -10395,12 +10573,12 @@ function startCostSync() {
10395
10573
  }, SYNC_INTERVAL_MS);
10396
10574
  timer.unref();
10397
10575
  }
10398
- var import_fs17, import_path20, import_os15, SYNC_INTERVAL_MS, PRICING;
10576
+ var import_fs17, import_path21, import_os15, SYNC_INTERVAL_MS, PRICING;
10399
10577
  var init_costSync = __esm({
10400
10578
  "src/costSync.ts"() {
10401
10579
  "use strict";
10402
10580
  import_fs17 = __toESM(require("fs"));
10403
- import_path20 = __toESM(require("path"));
10581
+ import_path21 = __toESM(require("path"));
10404
10582
  import_os15 = __toESM(require("os"));
10405
10583
  init_config();
10406
10584
  init_audit();
@@ -10426,7 +10604,7 @@ function readCredentials() {
10426
10604
  };
10427
10605
  }
10428
10606
  try {
10429
- const credPath = import_path21.default.join(import_os16.default.homedir(), ".node9", "credentials.json");
10607
+ const credPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "credentials.json");
10430
10608
  const creds = JSON.parse(import_fs18.default.readFileSync(credPath, "utf-8"));
10431
10609
  const profileName = process.env.NODE9_PROFILE ?? "default";
10432
10610
  const profile = creds[profileName];
@@ -10489,7 +10667,7 @@ async function syncOnce() {
10489
10667
  try {
10490
10668
  const rules = await fetchCloudRules(creds.apiKey, creds.apiUrl);
10491
10669
  const cache = { fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), rules };
10492
- const dir = import_path21.default.dirname(rulesCacheFile());
10670
+ const dir = import_path22.default.dirname(rulesCacheFile());
10493
10671
  if (!import_fs18.default.existsSync(dir)) import_fs18.default.mkdirSync(dir, { recursive: true });
10494
10672
  import_fs18.default.writeFileSync(rulesCacheFile(), JSON.stringify(cache, null, 2) + "\n", "utf-8");
10495
10673
  } catch {
@@ -10503,7 +10681,7 @@ async function runCloudSync() {
10503
10681
  try {
10504
10682
  const rules = await fetchCloudRules(creds.apiKey, creds.apiUrl);
10505
10683
  const cache = { fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), rules };
10506
- const dir = import_path21.default.dirname(rulesCacheFile());
10684
+ const dir = import_path22.default.dirname(rulesCacheFile());
10507
10685
  if (!import_fs18.default.existsSync(dir)) import_fs18.default.mkdirSync(dir, { recursive: true });
10508
10686
  import_fs18.default.writeFileSync(rulesCacheFile(), JSON.stringify(cache, null, 2) + "\n", "utf-8");
10509
10687
  return { ok: true, rules: rules.length, fetchedAt: cache.fetchedAt };
@@ -10537,16 +10715,16 @@ function startCloudSync() {
10537
10715
  const recurring = setInterval(() => void syncOnce(), intervalMs);
10538
10716
  recurring.unref();
10539
10717
  }
10540
- var import_fs18, import_https, import_os16, import_path21, rulesCacheFile, DEFAULT_API_URL, DEFAULT_INTERVAL_HOURS, MIN_INTERVAL_HOURS;
10718
+ var import_fs18, import_https, import_os16, import_path22, rulesCacheFile, DEFAULT_API_URL, DEFAULT_INTERVAL_HOURS, MIN_INTERVAL_HOURS;
10541
10719
  var init_sync = __esm({
10542
10720
  "src/daemon/sync.ts"() {
10543
10721
  "use strict";
10544
10722
  import_fs18 = __toESM(require("fs"));
10545
10723
  import_https = __toESM(require("https"));
10546
10724
  import_os16 = __toESM(require("os"));
10547
- import_path21 = __toESM(require("path"));
10725
+ import_path22 = __toESM(require("path"));
10548
10726
  init_config();
10549
- rulesCacheFile = () => import_path21.default.join(import_os16.default.homedir(), ".node9", "rules-cache.json");
10727
+ rulesCacheFile = () => import_path22.default.join(import_os16.default.homedir(), ".node9", "rules-cache.json");
10550
10728
  DEFAULT_API_URL = "https://api.node9.ai/api/v1/policy";
10551
10729
  DEFAULT_INTERVAL_HOURS = 5;
10552
10730
  MIN_INTERVAL_HOURS = 1;
@@ -10584,11 +10762,11 @@ function runDlpScan() {
10584
10762
  return;
10585
10763
  }
10586
10764
  for (const proj of projDirs) {
10587
- const projPath = import_path22.default.join(PROJECTS_DIR, proj);
10765
+ const projPath = import_path23.default.join(PROJECTS_DIR, proj);
10588
10766
  try {
10589
10767
  if (!import_fs19.default.lstatSync(projPath).isDirectory()) continue;
10590
10768
  const real = import_fs19.default.realpathSync(projPath);
10591
- if (!real.startsWith(PROJECTS_DIR + import_path22.default.sep) && real !== PROJECTS_DIR) continue;
10769
+ if (!real.startsWith(PROJECTS_DIR + import_path23.default.sep) && real !== PROJECTS_DIR) continue;
10592
10770
  } catch {
10593
10771
  continue;
10594
10772
  }
@@ -10599,7 +10777,7 @@ function runDlpScan() {
10599
10777
  continue;
10600
10778
  }
10601
10779
  for (const file of files) {
10602
- const filePath = import_path22.default.join(projPath, file);
10780
+ const filePath = import_path23.default.join(projPath, file);
10603
10781
  const lastOffset = index[filePath] ?? 0;
10604
10782
  let size;
10605
10783
  try {
@@ -10688,24 +10866,24 @@ function startDlpScanner() {
10688
10866
  );
10689
10867
  timer.unref();
10690
10868
  }
10691
- var import_fs19, import_path22, import_os17, INDEX_FILE, PROJECTS_DIR;
10869
+ var import_fs19, import_path23, import_os17, INDEX_FILE, PROJECTS_DIR;
10692
10870
  var init_dlp_scanner = __esm({
10693
10871
  "src/daemon/dlp-scanner.ts"() {
10694
10872
  "use strict";
10695
10873
  import_fs19 = __toESM(require("fs"));
10696
- import_path22 = __toESM(require("path"));
10874
+ import_path23 = __toESM(require("path"));
10697
10875
  import_os17 = __toESM(require("os"));
10698
10876
  init_dlp();
10699
10877
  init_native();
10700
10878
  init_state2();
10701
- INDEX_FILE = import_path22.default.join(import_os17.default.homedir(), ".node9", "dlp-index.json");
10702
- PROJECTS_DIR = import_path22.default.join(import_os17.default.homedir(), ".claude", "projects");
10879
+ INDEX_FILE = import_path23.default.join(import_os17.default.homedir(), ".node9", "dlp-index.json");
10880
+ PROJECTS_DIR = import_path23.default.join(import_os17.default.homedir(), ".claude", "projects");
10703
10881
  }
10704
10882
  });
10705
10883
 
10706
10884
  // src/daemon/mcp-tools.ts
10707
10885
  function getMcpToolsFile() {
10708
- return import_path23.default.join(import_os18.default.homedir(), ".node9", "mcp-tools.json");
10886
+ return import_path24.default.join(import_os18.default.homedir(), ".node9", "mcp-tools.json");
10709
10887
  }
10710
10888
  function readMcpToolsConfig() {
10711
10889
  try {
@@ -10720,7 +10898,7 @@ function readMcpToolsConfig() {
10720
10898
  function writeMcpToolsConfig(config) {
10721
10899
  try {
10722
10900
  const file = getMcpToolsFile();
10723
- const dir = import_path23.default.dirname(file);
10901
+ const dir = import_path24.default.dirname(file);
10724
10902
  if (!import_fs20.default.existsSync(dir)) import_fs20.default.mkdirSync(dir, { recursive: true });
10725
10903
  const tmpPath = `${file}.${import_os18.default.hostname()}.${process.pid}.tmp`;
10726
10904
  import_fs20.default.writeFileSync(tmpPath, JSON.stringify(config, null, 2));
@@ -10763,12 +10941,12 @@ function approveServer(serverKey, disabledTools) {
10763
10941
  writeMcpToolsConfig(config);
10764
10942
  }
10765
10943
  }
10766
- var import_fs20, import_path23, import_os18;
10944
+ var import_fs20, import_path24, import_os18;
10767
10945
  var init_mcp_tools = __esm({
10768
10946
  "src/daemon/mcp-tools.ts"() {
10769
10947
  "use strict";
10770
10948
  import_fs20 = __toESM(require("fs"));
10771
- import_path23 = __toESM(require("path"));
10949
+ import_path24 = __toESM(require("path"));
10772
10950
  import_os18 = __toESM(require("os"));
10773
10951
  }
10774
10952
  });
@@ -10964,7 +11142,7 @@ data: ${JSON.stringify(item.data)}
10964
11142
  status: "pending"
10965
11143
  });
10966
11144
  }
10967
- const projectCwd = typeof cwd === "string" && import_path24.default.isAbsolute(cwd) ? cwd : void 0;
11145
+ const projectCwd = typeof cwd === "string" && import_path25.default.isAbsolute(cwd) ? cwd : void 0;
10968
11146
  const projectConfig = getConfig(projectCwd);
10969
11147
  const browserEnabled = projectConfig.settings.approvers?.browser !== false;
10970
11148
  const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
@@ -11328,7 +11506,7 @@ data: ${JSON.stringify(item.data)}
11328
11506
  if (!validToken(req)) return res.writeHead(403).end();
11329
11507
  const periodParam = reqUrl.searchParams.get("period") || "7d";
11330
11508
  const period = ["today", "7d", "30d", "month"].includes(periodParam) ? periodParam : "7d";
11331
- const logPath = import_path24.default.join(import_os19.default.homedir(), ".node9", "audit.log");
11509
+ const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "audit.log");
11332
11510
  if (!import_fs21.default.existsSync(logPath)) {
11333
11511
  res.writeHead(200, { "Content-Type": "application/json" });
11334
11512
  return res.end(
@@ -11448,9 +11626,21 @@ data: ${JSON.stringify(item.data)}
11448
11626
  const d = /* @__PURE__ */ new Date();
11449
11627
  d.setDate(d.getDate() - 90);
11450
11628
  d.setHours(0, 0, 0, 0);
11451
- let claude = { sessions: 0, findings: [], dlpFindings: [], loopFindings: [] };
11452
- let gemini = { sessions: 0, findings: [], dlpFindings: [], loopFindings: [] };
11453
- let codex = { sessions: 0, findings: [], dlpFindings: [], loopFindings: [] };
11629
+ const EMPTY_SCAN = {
11630
+ filesScanned: 0,
11631
+ sessions: 0,
11632
+ totalToolCalls: 0,
11633
+ bashCalls: 0,
11634
+ findings: [],
11635
+ dlpFindings: [],
11636
+ loopFindings: [],
11637
+ totalCostUSD: 0,
11638
+ firstDate: null,
11639
+ lastDate: null
11640
+ };
11641
+ let claude = EMPTY_SCAN;
11642
+ let gemini = EMPTY_SCAN;
11643
+ let codex = EMPTY_SCAN;
11454
11644
  try {
11455
11645
  claude = scanClaudeHistory(d);
11456
11646
  } catch (e) {
@@ -11466,86 +11656,13 @@ data: ${JSON.stringify(item.data)}
11466
11656
  } catch (e) {
11467
11657
  console.error("Codex scan failed:", e);
11468
11658
  }
11469
- const mapFindings = (arr, src) => (
11470
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11471
- arr.map((f) => ({
11472
- timestamp: f.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
11473
- rule: f.source?.rule?.name ?? f.source?.shieldLabel ?? "unnamed",
11474
- command: f.input?.command ?? f.input?.cmd ?? f.input?.file_path ?? f.toolName ?? "unknown",
11475
- verdict: f.source?.rule?.verdict ?? f.source?.rule?.action ?? "review",
11476
- ruleSource: f.source?.sourceType ?? "default",
11477
- source: src
11478
- }))
11479
- );
11480
- const mapLeaks = (arr, src) => (
11481
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11482
- arr.map((f) => ({
11483
- timestamp: f.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
11484
- pattern: f.patternName || "DLP",
11485
- sample: f.redactedSample || "********",
11486
- source: src
11487
- }))
11488
- );
11489
- const mapLoops = (arr, src) => (
11490
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11491
- arr.map((f) => ({
11492
- timestamp: f.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
11493
- toolName: f.toolName || "unknown",
11494
- commandPreview: f.commandPreview || "",
11495
- count: f.count || 0,
11496
- source: src
11497
- }))
11498
- );
11499
- const sources = [
11500
- {
11501
- id: "claude",
11502
- label: "Claude",
11503
- icon: "\u{1F916}",
11504
- sessions: claude.sessions,
11505
- findings: mapFindings(claude.findings, "claude"),
11506
- leaks: mapLeaks(claude.dlpFindings, "claude"),
11507
- loops: mapLoops(claude.loopFindings, "claude")
11508
- },
11509
- {
11510
- id: "gemini",
11511
- label: "Gemini",
11512
- icon: "\u264A",
11513
- sessions: gemini.sessions,
11514
- findings: mapFindings(gemini.findings, "gemini"),
11515
- leaks: mapLeaks(gemini.dlpFindings, "gemini"),
11516
- loops: mapLoops(gemini.loopFindings, "gemini")
11517
- },
11518
- {
11519
- id: "codex",
11520
- label: "Codex",
11521
- icon: "\u{1F52E}",
11522
- sessions: codex.sessions,
11523
- findings: mapFindings(codex.findings, "codex"),
11524
- leaks: mapLeaks(codex.dlpFindings, "codex"),
11525
- loops: mapLoops(codex.loopFindings, "codex")
11526
- }
11527
- ].filter(
11528
- (s) => s.sessions > 0 || s.findings.length > 0 || s.leaks.length > 0 || s.loops.length > 0
11529
- );
11530
- const totalSessions = claude.sessions + gemini.sessions + codex.sessions;
11531
- const totalFindings = claude.findings.length + gemini.findings.length + codex.findings.length;
11532
- const totalDlp = claude.dlpFindings.length + gemini.dlpFindings.length + codex.dlpFindings.length;
11533
- const totalLoops = claude.loopFindings.length + gemini.loopFindings.length + codex.loopFindings.length;
11534
- const totalCostUSD = (claude.totalCostUSD || 0) + (gemini.totalCostUSD || 0) + (codex.totalCostUSD || 0);
11659
+ const summary = buildScanSummary([
11660
+ { id: "claude", label: "Claude", icon: "\u{1F916}", scan: claude },
11661
+ { id: "gemini", label: "Gemini", icon: "\u264A", scan: gemini },
11662
+ { id: "codex", label: "Codex", icon: "\u{1F52E}", scan: codex }
11663
+ ]);
11535
11664
  res.writeHead(200, { "Content-Type": "application/json" });
11536
- return res.end(
11537
- JSON.stringify({
11538
- status: "complete",
11539
- summary: {
11540
- sessions: totalSessions,
11541
- totalCostUSD,
11542
- findings: totalFindings,
11543
- dlp: totalDlp,
11544
- loops: totalLoops
11545
- },
11546
- sources
11547
- })
11548
- );
11665
+ return res.end(JSON.stringify({ status: "complete", summary }));
11549
11666
  } catch (err2) {
11550
11667
  console.error("Scan failed:", err2);
11551
11668
  res.writeHead(500, { "Content-Type": "application/json" });
@@ -11583,8 +11700,8 @@ data: ${JSON.stringify(item.data)}
11583
11700
  const body = await readBody(req);
11584
11701
  const data = body ? JSON.parse(body) : {};
11585
11702
  const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
11586
- const node9Dir = import_path24.default.dirname(GLOBAL_CONFIG_PATH);
11587
- if (!import_path24.default.resolve(configPath).startsWith(node9Dir + import_path24.default.sep)) {
11703
+ const node9Dir = import_path25.default.dirname(GLOBAL_CONFIG_PATH);
11704
+ if (!import_path25.default.resolve(configPath).startsWith(node9Dir + import_path25.default.sep)) {
11588
11705
  res.writeHead(400, { "Content-Type": "application/json" });
11589
11706
  return res.end(
11590
11707
  JSON.stringify({ error: "configPath must be within the node9 config directory" })
@@ -11703,6 +11820,11 @@ data: ${JSON.stringify(item.data)}
11703
11820
  return;
11704
11821
  }
11705
11822
  approveServer(serverKey, disabledTools);
11823
+ appendAuditLog({
11824
+ toolName: `mcp-server:${serverKey}`,
11825
+ args: { disabledTools },
11826
+ decision: "allow"
11827
+ });
11706
11828
  broadcast("mcp-tools-updated", { serverKey, disabledTools });
11707
11829
  res.writeHead(200).end();
11708
11830
  return;
@@ -11721,6 +11843,11 @@ data: ${JSON.stringify(item.data)}
11721
11843
  }
11722
11844
  const status = updateServerDiscovery(serverKey, tools);
11723
11845
  if (status === "new" || status === "drift") {
11846
+ appendAuditLog({
11847
+ toolName: `mcp-server:${serverKey}`,
11848
+ args: { toolCount: tools.length, status },
11849
+ decision: "mcp-discovered"
11850
+ });
11724
11851
  const id = (0, import_crypto7.randomUUID)();
11725
11852
  const entry = {
11726
11853
  id,
@@ -11789,6 +11916,11 @@ data: ${JSON.stringify(item.data)}
11789
11916
  }
11790
11917
  clearTimeout(entry.timer);
11791
11918
  approveServer(serverKey, disabledTools);
11919
+ appendAuditLog({
11920
+ toolName: `mcp-server:${serverKey}`,
11921
+ args: { disabledTools },
11922
+ decision: "allow"
11923
+ });
11792
11924
  pending.delete(id);
11793
11925
  broadcast("remove", { id, decision: "allow" });
11794
11926
  res.writeHead(200).end();
@@ -11877,13 +12009,13 @@ data: ${JSON.stringify(item.data)}
11877
12009
  }
11878
12010
  startActivitySocket();
11879
12011
  }
11880
- var import_http, import_fs21, import_path24, import_os19, import_crypto7, import_child_process5, import_chalk3;
12012
+ var import_http, import_fs21, import_path25, import_os19, import_crypto7, import_child_process5, import_chalk3;
11881
12013
  var init_server = __esm({
11882
12014
  "src/daemon/server.ts"() {
11883
12015
  "use strict";
11884
12016
  import_http = __toESM(require("http"));
11885
12017
  import_fs21 = __toESM(require("fs"));
11886
- import_path24 = __toESM(require("path"));
12018
+ import_path25 = __toESM(require("path"));
11887
12019
  import_os19 = __toESM(require("os"));
11888
12020
  import_crypto7 = require("crypto");
11889
12021
  import_child_process5 = require("child_process");
@@ -11892,6 +12024,7 @@ var init_server = __esm({
11892
12024
  init_shields();
11893
12025
  init_ui2();
11894
12026
  init_scan();
12027
+ init_scan_summary();
11895
12028
  init_state2();
11896
12029
  init_state();
11897
12030
  init_patch();
@@ -11907,7 +12040,7 @@ var init_server = __esm({
11907
12040
  function resolveNode9Binary() {
11908
12041
  try {
11909
12042
  const script = process.argv[1];
11910
- if (typeof script === "string" && import_path25.default.isAbsolute(script) && import_fs22.default.existsSync(script)) {
12043
+ if (typeof script === "string" && import_path26.default.isAbsolute(script) && import_fs22.default.existsSync(script)) {
11911
12044
  return import_fs22.default.realpathSync(script);
11912
12045
  }
11913
12046
  } catch {
@@ -11926,11 +12059,11 @@ function xmlEscape(s) {
11926
12059
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
11927
12060
  }
11928
12061
  function launchdPlist(binaryPath) {
11929
- const logDir = import_path25.default.join(import_os20.default.homedir(), ".node9");
12062
+ const logDir = import_path26.default.join(import_os20.default.homedir(), ".node9");
11930
12063
  const nodePath = xmlEscape(process.execPath);
11931
12064
  const scriptPath = xmlEscape(binaryPath);
11932
- const outLog = xmlEscape(import_path25.default.join(logDir, "daemon.log"));
11933
- const errLog = xmlEscape(import_path25.default.join(logDir, "daemon-error.log"));
12065
+ const outLog = xmlEscape(import_path26.default.join(logDir, "daemon.log"));
12066
+ const errLog = xmlEscape(import_path26.default.join(logDir, "daemon-error.log"));
11934
12067
  return `<?xml version="1.0" encoding="UTF-8"?>
11935
12068
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
11936
12069
  <plist version="1.0">
@@ -11965,7 +12098,7 @@ function launchdPlist(binaryPath) {
11965
12098
  `;
11966
12099
  }
11967
12100
  function installLaunchd(binaryPath) {
11968
- const dir = import_path25.default.dirname(LAUNCHD_PLIST);
12101
+ const dir = import_path26.default.dirname(LAUNCHD_PLIST);
11969
12102
  if (!import_fs22.default.existsSync(dir)) import_fs22.default.mkdirSync(dir, { recursive: true });
11970
12103
  import_fs22.default.writeFileSync(LAUNCHD_PLIST, launchdPlist(binaryPath), "utf-8");
11971
12104
  (0, import_child_process6.spawnSync)("launchctl", ["unload", LAUNCHD_PLIST], { encoding: "utf8" });
@@ -12042,7 +12175,7 @@ function isSystemdInstalled() {
12042
12175
  return import_fs22.default.existsSync(SYSTEMD_UNIT);
12043
12176
  }
12044
12177
  function stopRunningDaemon() {
12045
- const pidFile = import_path25.default.join(import_os20.default.homedir(), ".node9", "daemon.pid");
12178
+ const pidFile = import_path26.default.join(import_os20.default.homedir(), ".node9", "daemon.pid");
12046
12179
  if (!import_fs22.default.existsSync(pidFile)) return;
12047
12180
  try {
12048
12181
  const data = JSON.parse(import_fs22.default.readFileSync(pidFile, "utf-8"));
@@ -12135,18 +12268,18 @@ function isDaemonServiceInstalled() {
12135
12268
  if (process.platform === "linux") return isSystemdInstalled();
12136
12269
  return false;
12137
12270
  }
12138
- var import_fs22, import_path25, import_os20, import_child_process6, LAUNCHD_LABEL, LAUNCHD_PLIST, SYSTEMD_UNIT_DIR, SYSTEMD_UNIT;
12271
+ var import_fs22, import_path26, import_os20, import_child_process6, LAUNCHD_LABEL, LAUNCHD_PLIST, SYSTEMD_UNIT_DIR, SYSTEMD_UNIT;
12139
12272
  var init_service = __esm({
12140
12273
  "src/daemon/service.ts"() {
12141
12274
  "use strict";
12142
12275
  import_fs22 = __toESM(require("fs"));
12143
- import_path25 = __toESM(require("path"));
12276
+ import_path26 = __toESM(require("path"));
12144
12277
  import_os20 = __toESM(require("os"));
12145
12278
  import_child_process6 = require("child_process");
12146
12279
  LAUNCHD_LABEL = "ai.node9.daemon";
12147
- LAUNCHD_PLIST = import_path25.default.join(import_os20.default.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
12148
- SYSTEMD_UNIT_DIR = import_path25.default.join(import_os20.default.homedir(), ".config", "systemd", "user");
12149
- SYSTEMD_UNIT = import_path25.default.join(SYSTEMD_UNIT_DIR, "node9-daemon.service");
12280
+ LAUNCHD_PLIST = import_path26.default.join(import_os20.default.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
12281
+ SYSTEMD_UNIT_DIR = import_path26.default.join(import_os20.default.homedir(), ".config", "systemd", "user");
12282
+ SYSTEMD_UNIT = import_path26.default.join(SYSTEMD_UNIT_DIR, "node9-daemon.service");
12150
12283
  }
12151
12284
  });
12152
12285
 
@@ -12243,18 +12376,18 @@ function getModelContextLimit(model) {
12243
12376
  return 2e5;
12244
12377
  }
12245
12378
  function readSessionUsage() {
12246
- const projectsDir = import_path41.default.join(import_os35.default.homedir(), ".claude", "projects");
12379
+ const projectsDir = import_path42.default.join(import_os35.default.homedir(), ".claude", "projects");
12247
12380
  if (!import_fs38.default.existsSync(projectsDir)) return null;
12248
12381
  let latestFile = null;
12249
12382
  let latestMtime = 0;
12250
12383
  try {
12251
12384
  for (const dir of import_fs38.default.readdirSync(projectsDir)) {
12252
- const dirPath = import_path41.default.join(projectsDir, dir);
12385
+ const dirPath = import_path42.default.join(projectsDir, dir);
12253
12386
  try {
12254
12387
  if (!import_fs38.default.statSync(dirPath).isDirectory()) continue;
12255
12388
  for (const file of import_fs38.default.readdirSync(dirPath)) {
12256
12389
  if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
12257
- const filePath = import_path41.default.join(dirPath, file);
12390
+ const filePath = import_path42.default.join(dirPath, file);
12258
12391
  try {
12259
12392
  const mtime = import_fs38.default.statSync(filePath).mtimeMs;
12260
12393
  if (mtime > latestMtime) {
@@ -12515,7 +12648,7 @@ function buildRecoveryCardLines(req) {
12515
12648
  ];
12516
12649
  }
12517
12650
  function readApproversFromDisk() {
12518
- const configPath = import_path41.default.join(import_os35.default.homedir(), ".node9", "config.json");
12651
+ const configPath = import_path42.default.join(import_os35.default.homedir(), ".node9", "config.json");
12519
12652
  try {
12520
12653
  const raw = JSON.parse(import_fs38.default.readFileSync(configPath, "utf-8"));
12521
12654
  const settings = raw.settings ?? {};
@@ -12533,7 +12666,7 @@ function approverStatusLine() {
12533
12666
  return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
12534
12667
  }
12535
12668
  function toggleApprover(channel) {
12536
- const configPath = import_path41.default.join(import_os35.default.homedir(), ".node9", "config.json");
12669
+ const configPath = import_path42.default.join(import_os35.default.homedir(), ".node9", "config.json");
12537
12670
  try {
12538
12671
  const raw = JSON.parse(import_fs38.default.readFileSync(configPath, "utf-8"));
12539
12672
  const settings = raw.settings ?? {};
@@ -12712,7 +12845,7 @@ async function startTail(options = {}) {
12712
12845
  postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
12713
12846
  try {
12714
12847
  import_fs38.default.appendFileSync(
12715
- import_path41.default.join(import_os35.default.homedir(), ".node9", "hook-debug.log"),
12848
+ import_path42.default.join(import_os35.default.homedir(), ".node9", "hook-debug.log"),
12716
12849
  `[tail] POST /decision failed: ${String(err2)}
12717
12850
  `
12718
12851
  );
@@ -12793,7 +12926,7 @@ async function startTail(options = {}) {
12793
12926
  }
12794
12927
  } catch {
12795
12928
  }
12796
- const auditLog = import_path41.default.join(import_os35.default.homedir(), ".node9", "audit.log");
12929
+ const auditLog = import_path42.default.join(import_os35.default.homedir(), ".node9", "audit.log");
12797
12930
  try {
12798
12931
  const unackedDlp = import_fs38.default.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
12799
12932
  if (unackedDlp > 0) {
@@ -12980,7 +13113,7 @@ async function startTail(options = {}) {
12980
13113
  process.exit(1);
12981
13114
  });
12982
13115
  }
12983
- var import_http2, import_chalk25, import_fs38, import_os35, import_path41, import_readline5, import_child_process16, PID_FILE, ICONS, MODEL_CONTEXT_LIMITS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
13116
+ var import_http2, import_chalk25, import_fs38, import_os35, import_path42, import_readline5, import_child_process16, PID_FILE, ICONS, MODEL_CONTEXT_LIMITS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
12984
13117
  var init_tail = __esm({
12985
13118
  "src/tui/tail.ts"() {
12986
13119
  "use strict";
@@ -12988,13 +13121,13 @@ var init_tail = __esm({
12988
13121
  import_chalk25 = __toESM(require("chalk"));
12989
13122
  import_fs38 = __toESM(require("fs"));
12990
13123
  import_os35 = __toESM(require("os"));
12991
- import_path41 = __toESM(require("path"));
13124
+ import_path42 = __toESM(require("path"));
12992
13125
  import_readline5 = __toESM(require("readline"));
12993
13126
  import_child_process16 = require("child_process");
12994
13127
  init_daemon2();
12995
13128
  init_daemon();
12996
13129
  init_core();
12997
- PID_FILE = import_path41.default.join(import_os35.default.homedir(), ".node9", "daemon.pid");
13130
+ PID_FILE = import_path42.default.join(import_os35.default.homedir(), ".node9", "daemon.pid");
12998
13131
  ICONS = {
12999
13132
  bash: "\u{1F4BB}",
13000
13133
  shell: "\u{1F4BB}",
@@ -13144,7 +13277,7 @@ function countRulesInDir(rulesDir) {
13144
13277
  try {
13145
13278
  for (const entry of import_fs39.default.readdirSync(rulesDir, { withFileTypes: true })) {
13146
13279
  if (entry.isDirectory()) {
13147
- count += countRulesInDir(import_path42.default.join(rulesDir, entry.name));
13280
+ count += countRulesInDir(import_path43.default.join(rulesDir, entry.name));
13148
13281
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
13149
13282
  count++;
13150
13283
  }
@@ -13155,46 +13288,46 @@ function countRulesInDir(rulesDir) {
13155
13288
  }
13156
13289
  function isSamePath(a, b) {
13157
13290
  try {
13158
- return import_path42.default.resolve(a) === import_path42.default.resolve(b);
13291
+ return import_path43.default.resolve(a) === import_path43.default.resolve(b);
13159
13292
  } catch {
13160
13293
  return false;
13161
13294
  }
13162
13295
  }
13163
13296
  function countConfigs(cwd) {
13164
13297
  const homeDir2 = import_os36.default.homedir();
13165
- const claudeDir = import_path42.default.join(homeDir2, ".claude");
13298
+ const claudeDir = import_path43.default.join(homeDir2, ".claude");
13166
13299
  let claudeMdCount = 0;
13167
13300
  let rulesCount = 0;
13168
13301
  let hooksCount = 0;
13169
13302
  const userMcpServers = /* @__PURE__ */ new Set();
13170
13303
  const projectMcpServers = /* @__PURE__ */ new Set();
13171
- if (import_fs39.default.existsSync(import_path42.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
13172
- rulesCount += countRulesInDir(import_path42.default.join(claudeDir, "rules"));
13173
- const userSettings = import_path42.default.join(claudeDir, "settings.json");
13304
+ if (import_fs39.default.existsSync(import_path43.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
13305
+ rulesCount += countRulesInDir(import_path43.default.join(claudeDir, "rules"));
13306
+ const userSettings = import_path43.default.join(claudeDir, "settings.json");
13174
13307
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
13175
13308
  hooksCount += countHooksInFile(userSettings);
13176
- const userClaudeJson = import_path42.default.join(homeDir2, ".claude.json");
13309
+ const userClaudeJson = import_path43.default.join(homeDir2, ".claude.json");
13177
13310
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
13178
13311
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
13179
13312
  userMcpServers.delete(name);
13180
13313
  }
13181
13314
  if (cwd) {
13182
- if (import_fs39.default.existsSync(import_path42.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
13183
- if (import_fs39.default.existsSync(import_path42.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
13184
- const projectClaudeDir = import_path42.default.join(cwd, ".claude");
13315
+ if (import_fs39.default.existsSync(import_path43.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
13316
+ if (import_fs39.default.existsSync(import_path43.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
13317
+ const projectClaudeDir = import_path43.default.join(cwd, ".claude");
13185
13318
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
13186
13319
  if (!overlapsUserScope) {
13187
- if (import_fs39.default.existsSync(import_path42.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
13188
- rulesCount += countRulesInDir(import_path42.default.join(projectClaudeDir, "rules"));
13189
- const projSettings = import_path42.default.join(projectClaudeDir, "settings.json");
13320
+ if (import_fs39.default.existsSync(import_path43.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
13321
+ rulesCount += countRulesInDir(import_path43.default.join(projectClaudeDir, "rules"));
13322
+ const projSettings = import_path43.default.join(projectClaudeDir, "settings.json");
13190
13323
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
13191
13324
  hooksCount += countHooksInFile(projSettings);
13192
13325
  }
13193
- if (import_fs39.default.existsSync(import_path42.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
13194
- const localSettings = import_path42.default.join(projectClaudeDir, "settings.local.json");
13326
+ if (import_fs39.default.existsSync(import_path43.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
13327
+ const localSettings = import_path43.default.join(projectClaudeDir, "settings.local.json");
13195
13328
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
13196
13329
  hooksCount += countHooksInFile(localSettings);
13197
- const mcpJsonServers = getMcpServerNames(import_path42.default.join(cwd, ".mcp.json"));
13330
+ const mcpJsonServers = getMcpServerNames(import_path43.default.join(cwd, ".mcp.json"));
13198
13331
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
13199
13332
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
13200
13333
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -13227,7 +13360,7 @@ function readActiveShieldsHud() {
13227
13360
  return shieldsCache.value;
13228
13361
  }
13229
13362
  try {
13230
- const shieldsPath = import_path42.default.join(import_os36.default.homedir(), ".node9", "shields.json");
13363
+ const shieldsPath = import_path43.default.join(import_os36.default.homedir(), ".node9", "shields.json");
13231
13364
  if (!import_fs39.default.existsSync(shieldsPath)) {
13232
13365
  shieldsCache = { value: [], ts: now };
13233
13366
  return [];
@@ -13334,9 +13467,9 @@ function renderContextLine(stdin) {
13334
13467
  async function main() {
13335
13468
  try {
13336
13469
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
13337
- if (import_fs39.default.existsSync(import_path42.default.join(import_os36.default.homedir(), ".node9", "hud-debug"))) {
13470
+ if (import_fs39.default.existsSync(import_path43.default.join(import_os36.default.homedir(), ".node9", "hud-debug"))) {
13338
13471
  try {
13339
- const logPath = import_path42.default.join(import_os36.default.homedir(), ".node9", "hud-debug.log");
13472
+ const logPath = import_path43.default.join(import_os36.default.homedir(), ".node9", "hud-debug.log");
13340
13473
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
13341
13474
  let size = 0;
13342
13475
  try {
@@ -13365,8 +13498,8 @@ async function main() {
13365
13498
  try {
13366
13499
  const cwd = stdin.cwd ?? process.cwd();
13367
13500
  for (const configPath of [
13368
- import_path42.default.join(cwd, "node9.config.json"),
13369
- import_path42.default.join(import_os36.default.homedir(), ".node9", "config.json")
13501
+ import_path43.default.join(cwd, "node9.config.json"),
13502
+ import_path43.default.join(import_os36.default.homedir(), ".node9", "config.json")
13370
13503
  ]) {
13371
13504
  if (!import_fs39.default.existsSync(configPath)) continue;
13372
13505
  const cfg = JSON.parse(import_fs39.default.readFileSync(configPath, "utf-8"));
@@ -13387,12 +13520,12 @@ async function main() {
13387
13520
  renderOffline();
13388
13521
  }
13389
13522
  }
13390
- var import_fs39, import_path42, import_os36, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
13523
+ var import_fs39, import_path43, import_os36, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
13391
13524
  var init_hud = __esm({
13392
13525
  "src/cli/hud.ts"() {
13393
13526
  "use strict";
13394
13527
  import_fs39 = __toESM(require("fs"));
13395
- import_path42 = __toESM(require("path"));
13528
+ import_path43 = __toESM(require("path"));
13396
13529
  import_os36 = __toESM(require("os"));
13397
13530
  import_http3 = __toESM(require("http"));
13398
13531
  init_daemon();
@@ -14442,7 +14575,7 @@ function getAgentsStatus(homeDir2 = import_os11.default.homedir()) {
14442
14575
  init_daemon2();
14443
14576
  var import_chalk26 = __toESM(require("chalk"));
14444
14577
  var import_fs40 = __toESM(require("fs"));
14445
- var import_path43 = __toESM(require("path"));
14578
+ var import_path44 = __toESM(require("path"));
14446
14579
  var import_os37 = __toESM(require("os"));
14447
14580
  var import_prompts2 = require("@inquirer/prompts");
14448
14581
 
@@ -14630,7 +14763,7 @@ init_daemon_starter();
14630
14763
  var import_chalk6 = __toESM(require("chalk"));
14631
14764
  var import_fs26 = __toESM(require("fs"));
14632
14765
  var import_child_process10 = require("child_process");
14633
- var import_path28 = __toESM(require("path"));
14766
+ var import_path29 = __toESM(require("path"));
14634
14767
  var import_os23 = __toESM(require("os"));
14635
14768
  init_orchestrator();
14636
14769
  init_daemon();
@@ -14642,9 +14775,9 @@ var import_child_process9 = require("child_process");
14642
14775
  var import_crypto8 = __toESM(require("crypto"));
14643
14776
  var import_fs24 = __toESM(require("fs"));
14644
14777
  var import_net3 = __toESM(require("net"));
14645
- var import_path26 = __toESM(require("path"));
14778
+ var import_path27 = __toESM(require("path"));
14646
14779
  var import_os21 = __toESM(require("os"));
14647
- var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path26.default.join(import_os21.default.tmpdir(), "node9-activity.sock");
14780
+ var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path27.default.join(import_os21.default.tmpdir(), "node9-activity.sock");
14648
14781
  function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
14649
14782
  try {
14650
14783
  const payload = JSON.stringify({
@@ -14664,8 +14797,8 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
14664
14797
  } catch {
14665
14798
  }
14666
14799
  }
14667
- var SNAPSHOT_STACK_PATH = import_path26.default.join(import_os21.default.homedir(), ".node9", "snapshots.json");
14668
- var UNDO_LATEST_PATH = import_path26.default.join(import_os21.default.homedir(), ".node9", "undo_latest.txt");
14800
+ var SNAPSHOT_STACK_PATH = import_path27.default.join(import_os21.default.homedir(), ".node9", "snapshots.json");
14801
+ var UNDO_LATEST_PATH = import_path27.default.join(import_os21.default.homedir(), ".node9", "undo_latest.txt");
14669
14802
  var MAX_SNAPSHOTS = 10;
14670
14803
  var GIT_TIMEOUT = 15e3;
14671
14804
  function readStack() {
@@ -14677,7 +14810,7 @@ function readStack() {
14677
14810
  return [];
14678
14811
  }
14679
14812
  function writeStack(stack) {
14680
- const dir = import_path26.default.dirname(SNAPSHOT_STACK_PATH);
14813
+ const dir = import_path27.default.dirname(SNAPSHOT_STACK_PATH);
14681
14814
  if (!import_fs24.default.existsSync(dir)) import_fs24.default.mkdirSync(dir, { recursive: true });
14682
14815
  import_fs24.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
14683
14816
  }
@@ -14699,12 +14832,12 @@ function buildArgsSummary(tool, args) {
14699
14832
  return "";
14700
14833
  }
14701
14834
  function findProjectRoot(filePath) {
14702
- let dir = import_path26.default.dirname(filePath);
14835
+ let dir = import_path27.default.dirname(filePath);
14703
14836
  while (true) {
14704
- if (import_fs24.default.existsSync(import_path26.default.join(dir, ".git")) || import_fs24.default.existsSync(import_path26.default.join(dir, "package.json"))) {
14837
+ if (import_fs24.default.existsSync(import_path27.default.join(dir, ".git")) || import_fs24.default.existsSync(import_path27.default.join(dir, "package.json"))) {
14705
14838
  return dir;
14706
14839
  }
14707
- const parent = import_path26.default.dirname(dir);
14840
+ const parent = import_path27.default.dirname(dir);
14708
14841
  if (parent === dir) return process.cwd();
14709
14842
  dir = parent;
14710
14843
  }
@@ -14722,14 +14855,14 @@ function normalizeCwdForHash(cwd) {
14722
14855
  }
14723
14856
  function getShadowRepoDir(cwd) {
14724
14857
  const hash = import_crypto8.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
14725
- return import_path26.default.join(import_os21.default.homedir(), ".node9", "snapshots", hash);
14858
+ return import_path27.default.join(import_os21.default.homedir(), ".node9", "snapshots", hash);
14726
14859
  }
14727
14860
  function cleanOrphanedIndexFiles(shadowDir) {
14728
14861
  try {
14729
14862
  const cutoff = Date.now() - 6e4;
14730
14863
  for (const f of import_fs24.default.readdirSync(shadowDir)) {
14731
14864
  if (f.startsWith("index_")) {
14732
- const fp = import_path26.default.join(shadowDir, f);
14865
+ const fp = import_path27.default.join(shadowDir, f);
14733
14866
  try {
14734
14867
  if (import_fs24.default.statSync(fp).mtimeMs < cutoff) import_fs24.default.unlinkSync(fp);
14735
14868
  } catch {
@@ -14743,7 +14876,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
14743
14876
  const hardcoded = [".git", ".node9"];
14744
14877
  const lines = [...hardcoded, ...ignorePaths].join("\n");
14745
14878
  try {
14746
- import_fs24.default.writeFileSync(import_path26.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
14879
+ import_fs24.default.writeFileSync(import_path27.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
14747
14880
  } catch {
14748
14881
  }
14749
14882
  }
@@ -14756,7 +14889,7 @@ function ensureShadowRepo(shadowDir, cwd) {
14756
14889
  timeout: 3e3
14757
14890
  });
14758
14891
  if (check.status === 0) {
14759
- const ptPath = import_path26.default.join(shadowDir, "project-path.txt");
14892
+ const ptPath = import_path27.default.join(shadowDir, "project-path.txt");
14760
14893
  try {
14761
14894
  const stored = import_fs24.default.readFileSync(ptPath, "utf8").trim();
14762
14895
  if (stored === normalizedCwd) return true;
@@ -14783,7 +14916,7 @@ function ensureShadowRepo(shadowDir, cwd) {
14783
14916
  if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
14784
14917
  return false;
14785
14918
  }
14786
- const configFile = import_path26.default.join(shadowDir, "config");
14919
+ const configFile = import_path27.default.join(shadowDir, "config");
14787
14920
  (0, import_child_process9.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
14788
14921
  timeout: 3e3
14789
14922
  });
@@ -14791,7 +14924,7 @@ function ensureShadowRepo(shadowDir, cwd) {
14791
14924
  timeout: 3e3
14792
14925
  });
14793
14926
  try {
14794
- import_fs24.default.writeFileSync(import_path26.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
14927
+ import_fs24.default.writeFileSync(import_path27.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
14795
14928
  } catch {
14796
14929
  }
14797
14930
  return true;
@@ -14811,12 +14944,12 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
14811
14944
  let indexFile = null;
14812
14945
  try {
14813
14946
  const rawFilePath = extractFilePath(args);
14814
- const absFilePath = rawFilePath && import_path26.default.isAbsolute(rawFilePath) ? rawFilePath : null;
14947
+ const absFilePath = rawFilePath && import_path27.default.isAbsolute(rawFilePath) ? rawFilePath : null;
14815
14948
  const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
14816
14949
  const shadowDir = getShadowRepoDir(cwd);
14817
14950
  if (!ensureShadowRepo(shadowDir, cwd)) return null;
14818
14951
  writeShadowExcludes(shadowDir, ignorePaths);
14819
- indexFile = import_path26.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
14952
+ indexFile = import_path27.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
14820
14953
  const shadowEnv = {
14821
14954
  ...process.env,
14822
14955
  GIT_DIR: shadowDir,
@@ -14975,7 +15108,7 @@ function applyUndo(hash, cwd) {
14975
15108
  timeout: GIT_TIMEOUT
14976
15109
  }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
14977
15110
  for (const file of [...tracked, ...untracked]) {
14978
- const fullPath = import_path26.default.join(dir, file);
15111
+ const fullPath = import_path27.default.join(dir, file);
14979
15112
  if (!snapshotFiles.has(file) && import_fs24.default.existsSync(fullPath)) {
14980
15113
  import_fs24.default.unlinkSync(fullPath);
14981
15114
  }
@@ -14991,11 +15124,11 @@ init_daemon_starter();
14991
15124
 
14992
15125
  // src/skill-pin.ts
14993
15126
  var import_fs25 = __toESM(require("fs"));
14994
- var import_path27 = __toESM(require("path"));
15127
+ var import_path28 = __toESM(require("path"));
14995
15128
  var import_os22 = __toESM(require("os"));
14996
15129
  var import_crypto9 = __toESM(require("crypto"));
14997
15130
  function getPinsFilePath() {
14998
- return import_path27.default.join(import_os22.default.homedir(), ".node9", "skill-pins.json");
15131
+ return import_path28.default.join(import_os22.default.homedir(), ".node9", "skill-pins.json");
14999
15132
  }
15000
15133
  var MAX_FILES = 5e3;
15001
15134
  var MAX_TOTAL_BYTES = 50 * 1024 * 1024;
@@ -15016,8 +15149,8 @@ function walkDir(root) {
15016
15149
  entries.sort((a, b) => a.name.localeCompare(b.name));
15017
15150
  for (const entry of entries) {
15018
15151
  if (out.length >= MAX_FILES) return;
15019
- const full = import_path27.default.join(dir, entry.name);
15020
- const rel = relDir ? import_path27.default.posix.join(relDir, entry.name) : entry.name;
15152
+ const full = import_path28.default.join(dir, entry.name);
15153
+ const rel = relDir ? import_path28.default.posix.join(relDir, entry.name) : entry.name;
15021
15154
  let lst;
15022
15155
  try {
15023
15156
  lst = import_fs25.default.lstatSync(full);
@@ -15091,7 +15224,7 @@ function readSkillPins() {
15091
15224
  }
15092
15225
  function writeSkillPins(data) {
15093
15226
  const filePath = getPinsFilePath();
15094
- import_fs25.default.mkdirSync(import_path27.default.dirname(filePath), { recursive: true });
15227
+ import_fs25.default.mkdirSync(import_path28.default.dirname(filePath), { recursive: true });
15095
15228
  const tmp = `${filePath}.${import_crypto9.default.randomBytes(6).toString("hex")}.tmp`;
15096
15229
  import_fs25.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
15097
15230
  import_fs25.default.renameSync(tmp, filePath);
@@ -15138,7 +15271,7 @@ function verifyAndPinRoots(roots) {
15138
15271
  return { kind: "verified" };
15139
15272
  }
15140
15273
  function defaultSkillRoots(_cwd) {
15141
- const marketplaces = import_path27.default.join(import_os22.default.homedir(), ".claude", "plugins", "marketplaces");
15274
+ const marketplaces = import_path28.default.join(import_os22.default.homedir(), ".claude", "plugins", "marketplaces");
15142
15275
  const roots = [];
15143
15276
  let registries;
15144
15277
  try {
@@ -15148,7 +15281,7 @@ function defaultSkillRoots(_cwd) {
15148
15281
  }
15149
15282
  for (const registry of registries) {
15150
15283
  if (!registry.isDirectory()) continue;
15151
- const pluginsDir = import_path27.default.join(marketplaces, registry.name, "plugins");
15284
+ const pluginsDir = import_path28.default.join(marketplaces, registry.name, "plugins");
15152
15285
  let plugins;
15153
15286
  try {
15154
15287
  plugins = import_fs25.default.readdirSync(pluginsDir, { withFileTypes: true });
@@ -15157,17 +15290,17 @@ function defaultSkillRoots(_cwd) {
15157
15290
  }
15158
15291
  for (const plugin of plugins) {
15159
15292
  if (!plugin.isDirectory()) continue;
15160
- roots.push(import_path27.default.join(pluginsDir, plugin.name));
15293
+ roots.push(import_path28.default.join(pluginsDir, plugin.name));
15161
15294
  }
15162
15295
  }
15163
15296
  return roots;
15164
15297
  }
15165
15298
  function resolveUserSkillRoot(entry, cwd) {
15166
15299
  if (!entry) return null;
15167
- if (entry.startsWith("~/") || entry === "~") return import_path27.default.join(import_os22.default.homedir(), entry.slice(1));
15168
- if (import_path27.default.isAbsolute(entry)) return entry;
15169
- if (!cwd || !import_path27.default.isAbsolute(cwd)) return null;
15170
- return import_path27.default.join(cwd, entry);
15300
+ if (entry.startsWith("~/") || entry === "~") return import_path28.default.join(import_os22.default.homedir(), entry.slice(1));
15301
+ if (import_path28.default.isAbsolute(entry)) return entry;
15302
+ if (!cwd || !import_path28.default.isAbsolute(cwd)) return null;
15303
+ return import_path28.default.join(cwd, entry);
15171
15304
  }
15172
15305
 
15173
15306
  // src/cli/commands/check.ts
@@ -15185,7 +15318,7 @@ function registerCheckCommand(program2) {
15185
15318
  } catch (err2) {
15186
15319
  const tempConfig = getConfig();
15187
15320
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
15188
- const logPath = import_path28.default.join(import_os23.default.homedir(), ".node9", "hook-debug.log");
15321
+ const logPath = import_path29.default.join(import_os23.default.homedir(), ".node9", "hook-debug.log");
15189
15322
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
15190
15323
  import_fs26.default.appendFileSync(
15191
15324
  logPath,
@@ -15200,11 +15333,11 @@ RAW: ${raw}
15200
15333
  if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
15201
15334
  try {
15202
15335
  const scriptPath = process.argv[1];
15203
- if (typeof scriptPath !== "string" || !import_path28.default.isAbsolute(scriptPath))
15336
+ if (typeof scriptPath !== "string" || !import_path29.default.isAbsolute(scriptPath))
15204
15337
  throw new Error("node9: argv[1] is not an absolute path");
15205
15338
  const resolvedScript = import_fs26.default.realpathSync(scriptPath);
15206
- const packageDist = import_fs26.default.realpathSync(import_path28.default.resolve(__dirname, "../.."));
15207
- if (!resolvedScript.startsWith(packageDist + import_path28.default.sep) && resolvedScript !== packageDist)
15339
+ const packageDist = import_fs26.default.realpathSync(import_path29.default.resolve(__dirname, "../.."));
15340
+ if (!resolvedScript.startsWith(packageDist + import_path29.default.sep) && resolvedScript !== packageDist)
15208
15341
  throw new Error(
15209
15342
  `node9: daemon spawn aborted \u2014 argv[1] (${resolvedScript}) is outside package dist (${packageDist})`
15210
15343
  );
@@ -15226,7 +15359,7 @@ RAW: ${raw}
15226
15359
  });
15227
15360
  d.unref();
15228
15361
  } catch (spawnErr) {
15229
- const logPath = import_path28.default.join(import_os23.default.homedir(), ".node9", "hook-debug.log");
15362
+ const logPath = import_path29.default.join(import_os23.default.homedir(), ".node9", "hook-debug.log");
15230
15363
  const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
15231
15364
  try {
15232
15365
  import_fs26.default.appendFileSync(
@@ -15239,9 +15372,9 @@ RAW: ${raw}
15239
15372
  }
15240
15373
  }
15241
15374
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
15242
- const logPath = import_path28.default.join(import_os23.default.homedir(), ".node9", "hook-debug.log");
15243
- if (!import_fs26.default.existsSync(import_path28.default.dirname(logPath)))
15244
- import_fs26.default.mkdirSync(import_path28.default.dirname(logPath), { recursive: true });
15375
+ const logPath = import_path29.default.join(import_os23.default.homedir(), ".node9", "hook-debug.log");
15376
+ if (!import_fs26.default.existsSync(import_path29.default.dirname(logPath)))
15377
+ import_fs26.default.mkdirSync(import_path29.default.dirname(logPath), { recursive: true });
15245
15378
  import_fs26.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
15246
15379
  `);
15247
15380
  }
@@ -15309,8 +15442,8 @@ RAW: ${raw}
15309
15442
  const safeSessionId = /^[A-Za-z0-9_\-]{1,128}$/.test(rawSessionId) ? rawSessionId : "";
15310
15443
  if (skillPinCfg.enabled && safeSessionId) {
15311
15444
  try {
15312
- const sessionsDir = import_path28.default.join(import_os23.default.homedir(), ".node9", "skill-sessions");
15313
- const flagPath = import_path28.default.join(sessionsDir, `${safeSessionId}.json`);
15445
+ const sessionsDir = import_path29.default.join(import_os23.default.homedir(), ".node9", "skill-sessions");
15446
+ const flagPath = import_path29.default.join(sessionsDir, `${safeSessionId}.json`);
15314
15447
  let flag = null;
15315
15448
  try {
15316
15449
  flag = JSON.parse(import_fs26.default.readFileSync(flagPath, "utf-8"));
@@ -15362,7 +15495,7 @@ RAW: ${raw}
15362
15495
  return;
15363
15496
  }
15364
15497
  if (!flag || flag.state !== "verified" && flag.state !== "warned") {
15365
- const absoluteCwd = typeof payload.cwd === "string" && import_path28.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
15498
+ const absoluteCwd = typeof payload.cwd === "string" && import_path29.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
15366
15499
  const extraRoots = skillPinCfg.roots;
15367
15500
  const resolvedExtra = extraRoots.map((r) => resolveUserSkillRoot(r, absoluteCwd)).filter((r) => typeof r === "string");
15368
15501
  const roots = [...defaultSkillRoots(absoluteCwd), ...resolvedExtra];
@@ -15404,7 +15537,7 @@ RAW: ${raw}
15404
15537
  try {
15405
15538
  const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
15406
15539
  for (const name of import_fs26.default.readdirSync(sessionsDir)) {
15407
- const p = import_path28.default.join(sessionsDir, name);
15540
+ const p = import_path29.default.join(sessionsDir, name);
15408
15541
  try {
15409
15542
  if (import_fs26.default.statSync(p).mtimeMs < cutoff) import_fs26.default.unlinkSync(p);
15410
15543
  } catch {
@@ -15416,7 +15549,7 @@ RAW: ${raw}
15416
15549
  } catch (err2) {
15417
15550
  if (process.env.NODE9_DEBUG === "1") {
15418
15551
  try {
15419
- const dbg = import_path28.default.join(import_os23.default.homedir(), ".node9", "hook-debug.log");
15552
+ const dbg = import_path29.default.join(import_os23.default.homedir(), ".node9", "hook-debug.log");
15420
15553
  const msg = err2 instanceof Error ? err2.message : String(err2);
15421
15554
  import_fs26.default.appendFileSync(dbg, `[${(/* @__PURE__ */ new Date()).toISOString()}] SKILL_PIN_ERROR: ${msg}
15422
15555
  `);
@@ -15428,7 +15561,7 @@ RAW: ${raw}
15428
15561
  if (shouldSnapshot(toolName, toolInput, config)) {
15429
15562
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
15430
15563
  }
15431
- const safeCwdForAuth = typeof payload.cwd === "string" && import_path28.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
15564
+ const safeCwdForAuth = typeof payload.cwd === "string" && import_path29.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
15432
15565
  const result = await authorizeHeadless(toolName, toolInput, meta, {
15433
15566
  cwd: safeCwdForAuth
15434
15567
  });
@@ -15472,7 +15605,7 @@ RAW: ${raw}
15472
15605
  });
15473
15606
  } catch (err2) {
15474
15607
  if (process.env.NODE9_DEBUG === "1") {
15475
- const logPath = import_path28.default.join(import_os23.default.homedir(), ".node9", "hook-debug.log");
15608
+ const logPath = import_path29.default.join(import_os23.default.homedir(), ".node9", "hook-debug.log");
15476
15609
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
15477
15610
  import_fs26.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
15478
15611
  `);
@@ -15509,7 +15642,7 @@ RAW: ${raw}
15509
15642
 
15510
15643
  // src/cli/commands/log.ts
15511
15644
  var import_fs27 = __toESM(require("fs"));
15512
- var import_path29 = __toESM(require("path"));
15645
+ var import_path30 = __toESM(require("path"));
15513
15646
  var import_os24 = __toESM(require("os"));
15514
15647
  init_audit();
15515
15648
  init_config();
@@ -15585,9 +15718,9 @@ function registerLogCommand(program2) {
15585
15718
  decision: "allowed",
15586
15719
  source: "post-hook"
15587
15720
  };
15588
- const logPath = import_path29.default.join(import_os24.default.homedir(), ".node9", "audit.log");
15589
- if (!import_fs27.default.existsSync(import_path29.default.dirname(logPath)))
15590
- import_fs27.default.mkdirSync(import_path29.default.dirname(logPath), { recursive: true });
15721
+ const logPath = import_path30.default.join(import_os24.default.homedir(), ".node9", "audit.log");
15722
+ if (!import_fs27.default.existsSync(import_path30.default.dirname(logPath)))
15723
+ import_fs27.default.mkdirSync(import_path30.default.dirname(logPath), { recursive: true });
15591
15724
  import_fs27.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
15592
15725
  if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
15593
15726
  const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
@@ -15621,7 +15754,7 @@ function registerLogCommand(program2) {
15621
15754
  }
15622
15755
  }
15623
15756
  }
15624
- const safeCwd = typeof payload.cwd === "string" && import_path29.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
15757
+ const safeCwd = typeof payload.cwd === "string" && import_path30.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
15625
15758
  const config = getConfig(safeCwd);
15626
15759
  if ((tool === "Bash" || tool === "bash") && config.settings.enableUndo !== false) {
15627
15760
  const bashCommand = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
@@ -15642,7 +15775,7 @@ function registerLogCommand(program2) {
15642
15775
  const msg = err2 instanceof Error ? err2.message : String(err2);
15643
15776
  process.stderr.write(`[Node9] audit log error: ${msg}
15644
15777
  `);
15645
- const debugPath = import_path29.default.join(import_os24.default.homedir(), ".node9", "hook-debug.log");
15778
+ const debugPath = import_path30.default.join(import_os24.default.homedir(), ".node9", "hook-debug.log");
15646
15779
  try {
15647
15780
  import_fs27.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
15648
15781
  `);
@@ -16045,7 +16178,7 @@ function registerConfigShowCommand(program2) {
16045
16178
  // src/cli/commands/doctor.ts
16046
16179
  var import_chalk8 = __toESM(require("chalk"));
16047
16180
  var import_fs28 = __toESM(require("fs"));
16048
- var import_path30 = __toESM(require("path"));
16181
+ var import_path31 = __toESM(require("path"));
16049
16182
  var import_os25 = __toESM(require("os"));
16050
16183
  var import_child_process11 = require("child_process");
16051
16184
  init_daemon();
@@ -16100,7 +16233,7 @@ function registerDoctorCommand(program2, version2) {
16100
16233
  );
16101
16234
  }
16102
16235
  section("Configuration");
16103
- const globalConfigPath = import_path30.default.join(homeDir2, ".node9", "config.json");
16236
+ const globalConfigPath = import_path31.default.join(homeDir2, ".node9", "config.json");
16104
16237
  if (import_fs28.default.existsSync(globalConfigPath)) {
16105
16238
  try {
16106
16239
  JSON.parse(import_fs28.default.readFileSync(globalConfigPath, "utf-8"));
@@ -16111,7 +16244,7 @@ function registerDoctorCommand(program2, version2) {
16111
16244
  } else {
16112
16245
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
16113
16246
  }
16114
- const projectConfigPath = import_path30.default.join(process.cwd(), "node9.config.json");
16247
+ const projectConfigPath = import_path31.default.join(process.cwd(), "node9.config.json");
16115
16248
  if (import_fs28.default.existsSync(projectConfigPath)) {
16116
16249
  try {
16117
16250
  JSON.parse(import_fs28.default.readFileSync(projectConfigPath, "utf-8"));
@@ -16123,7 +16256,7 @@ function registerDoctorCommand(program2, version2) {
16123
16256
  );
16124
16257
  }
16125
16258
  }
16126
- const credsPath = import_path30.default.join(homeDir2, ".node9", "credentials.json");
16259
+ const credsPath = import_path31.default.join(homeDir2, ".node9", "credentials.json");
16127
16260
  if (import_fs28.default.existsSync(credsPath)) {
16128
16261
  pass("Cloud credentials found (~/.node9/credentials.json)");
16129
16262
  } else {
@@ -16133,7 +16266,7 @@ function registerDoctorCommand(program2, version2) {
16133
16266
  );
16134
16267
  }
16135
16268
  section("Agent Hooks");
16136
- const claudeSettingsPath = import_path30.default.join(homeDir2, ".claude", "settings.json");
16269
+ const claudeSettingsPath = import_path31.default.join(homeDir2, ".claude", "settings.json");
16137
16270
  if (import_fs28.default.existsSync(claudeSettingsPath)) {
16138
16271
  try {
16139
16272
  const cs = JSON.parse(import_fs28.default.readFileSync(claudeSettingsPath, "utf-8"));
@@ -16152,7 +16285,7 @@ function registerDoctorCommand(program2, version2) {
16152
16285
  } else {
16153
16286
  warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
16154
16287
  }
16155
- const geminiSettingsPath = import_path30.default.join(homeDir2, ".gemini", "settings.json");
16288
+ const geminiSettingsPath = import_path31.default.join(homeDir2, ".gemini", "settings.json");
16156
16289
  if (import_fs28.default.existsSync(geminiSettingsPath)) {
16157
16290
  try {
16158
16291
  const gs = JSON.parse(import_fs28.default.readFileSync(geminiSettingsPath, "utf-8"));
@@ -16171,7 +16304,7 @@ function registerDoctorCommand(program2, version2) {
16171
16304
  } else {
16172
16305
  warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
16173
16306
  }
16174
- const cursorHooksPath = import_path30.default.join(homeDir2, ".cursor", "hooks.json");
16307
+ const cursorHooksPath = import_path31.default.join(homeDir2, ".cursor", "hooks.json");
16175
16308
  if (import_fs28.default.existsSync(cursorHooksPath)) {
16176
16309
  try {
16177
16310
  const cur = JSON.parse(import_fs28.default.readFileSync(cursorHooksPath, "utf-8"));
@@ -16213,7 +16346,7 @@ function registerDoctorCommand(program2, version2) {
16213
16346
  // src/cli/commands/audit.ts
16214
16347
  var import_chalk9 = __toESM(require("chalk"));
16215
16348
  var import_fs29 = __toESM(require("fs"));
16216
- var import_path31 = __toESM(require("path"));
16349
+ var import_path32 = __toESM(require("path"));
16217
16350
  var import_os26 = __toESM(require("os"));
16218
16351
  function formatRelativeTime(timestamp) {
16219
16352
  const diff = Date.now() - new Date(timestamp).getTime();
@@ -16227,7 +16360,7 @@ function formatRelativeTime(timestamp) {
16227
16360
  }
16228
16361
  function registerAuditCommand(program2) {
16229
16362
  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) => {
16230
- const logPath = import_path31.default.join(import_os26.default.homedir(), ".node9", "audit.log");
16363
+ const logPath = import_path32.default.join(import_os26.default.homedir(), ".node9", "audit.log");
16231
16364
  if (!import_fs29.default.existsSync(logPath)) {
16232
16365
  console.log(
16233
16366
  import_chalk9.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
@@ -16289,7 +16422,7 @@ function registerAuditCommand(program2) {
16289
16422
  // src/cli/commands/report.ts
16290
16423
  var import_chalk10 = __toESM(require("chalk"));
16291
16424
  var import_fs30 = __toESM(require("fs"));
16292
- var import_path32 = __toESM(require("path"));
16425
+ var import_path33 = __toESM(require("path"));
16293
16426
  var import_os27 = __toESM(require("os"));
16294
16427
  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;
16295
16428
  function buildTestTimestamps(allEntries) {
@@ -16417,7 +16550,7 @@ function loadClaudeCost(start, end) {
16417
16550
  cacheWriteTokens: 0,
16418
16551
  cacheReadTokens: 0
16419
16552
  };
16420
- const projectsDir = import_path32.default.join(import_os27.default.homedir(), ".claude", "projects");
16553
+ const projectsDir = import_path33.default.join(import_os27.default.homedir(), ".claude", "projects");
16421
16554
  if (!import_fs30.default.existsSync(projectsDir)) return empty;
16422
16555
  let dirs;
16423
16556
  try {
@@ -16433,7 +16566,7 @@ function loadClaudeCost(start, end) {
16433
16566
  const byDay = /* @__PURE__ */ new Map();
16434
16567
  const byModel = /* @__PURE__ */ new Map();
16435
16568
  for (const proj of dirs) {
16436
- const projPath = import_path32.default.join(projectsDir, proj);
16569
+ const projPath = import_path33.default.join(projectsDir, proj);
16437
16570
  let files;
16438
16571
  try {
16439
16572
  const stat = import_fs30.default.statSync(projPath);
@@ -16444,7 +16577,7 @@ function loadClaudeCost(start, end) {
16444
16577
  }
16445
16578
  for (const file of files) {
16446
16579
  try {
16447
- const raw = import_fs30.default.readFileSync(import_path32.default.join(projPath, file), "utf-8");
16580
+ const raw = import_fs30.default.readFileSync(import_path33.default.join(projPath, file), "utf-8");
16448
16581
  for (const line of raw.split("\n")) {
16449
16582
  if (!line.trim()) continue;
16450
16583
  let entry;
@@ -16485,7 +16618,7 @@ function loadClaudeCost(start, end) {
16485
16618
  return { total, byDay, byModel, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens };
16486
16619
  }
16487
16620
  function loadCodexCost(start, end) {
16488
- const sessionsBase = import_path32.default.join(import_os27.default.homedir(), ".codex", "sessions");
16621
+ const sessionsBase = import_path33.default.join(import_os27.default.homedir(), ".codex", "sessions");
16489
16622
  const byDay = /* @__PURE__ */ new Map();
16490
16623
  let total = 0;
16491
16624
  let toolCalls = 0;
@@ -16493,28 +16626,28 @@ function loadCodexCost(start, end) {
16493
16626
  const jsonlFiles = [];
16494
16627
  try {
16495
16628
  for (const year of import_fs30.default.readdirSync(sessionsBase)) {
16496
- const yearPath = import_path32.default.join(sessionsBase, year);
16629
+ const yearPath = import_path33.default.join(sessionsBase, year);
16497
16630
  try {
16498
16631
  if (!import_fs30.default.statSync(yearPath).isDirectory()) continue;
16499
16632
  } catch {
16500
16633
  continue;
16501
16634
  }
16502
16635
  for (const month of import_fs30.default.readdirSync(yearPath)) {
16503
- const monthPath = import_path32.default.join(yearPath, month);
16636
+ const monthPath = import_path33.default.join(yearPath, month);
16504
16637
  try {
16505
16638
  if (!import_fs30.default.statSync(monthPath).isDirectory()) continue;
16506
16639
  } catch {
16507
16640
  continue;
16508
16641
  }
16509
16642
  for (const day of import_fs30.default.readdirSync(monthPath)) {
16510
- const dayPath = import_path32.default.join(monthPath, day);
16643
+ const dayPath = import_path33.default.join(monthPath, day);
16511
16644
  try {
16512
16645
  if (!import_fs30.default.statSync(dayPath).isDirectory()) continue;
16513
16646
  } catch {
16514
16647
  continue;
16515
16648
  }
16516
16649
  for (const file of import_fs30.default.readdirSync(dayPath)) {
16517
- if (file.endsWith(".jsonl")) jsonlFiles.push(import_path32.default.join(dayPath, file));
16650
+ if (file.endsWith(".jsonl")) jsonlFiles.push(import_path33.default.join(dayPath, file));
16518
16651
  }
16519
16652
  }
16520
16653
  }
@@ -16575,7 +16708,7 @@ function registerReportCommand(program2) {
16575
16708
  const period = ["today", "7d", "30d", "month"].includes(
16576
16709
  options.period
16577
16710
  ) ? options.period : "7d";
16578
- const logPath = import_path32.default.join(import_os27.default.homedir(), ".node9", "audit.log");
16711
+ const logPath = import_path33.default.join(import_os27.default.homedir(), ".node9", "audit.log");
16579
16712
  const allEntries = parseAuditLog(logPath);
16580
16713
  const unackedDlp = allEntries.filter((e) => e.source === "response-dlp");
16581
16714
  if (unackedDlp.length > 0) {
@@ -17081,7 +17214,7 @@ function registerDaemonCommand(program2) {
17081
17214
  // src/cli/commands/status.ts
17082
17215
  var import_chalk12 = __toESM(require("chalk"));
17083
17216
  var import_fs31 = __toESM(require("fs"));
17084
- var import_path33 = __toESM(require("path"));
17217
+ var import_path34 = __toESM(require("path"));
17085
17218
  var import_os28 = __toESM(require("os"));
17086
17219
  init_core();
17087
17220
  init_daemon();
@@ -17152,8 +17285,8 @@ function registerStatusCommand(program2) {
17152
17285
  console.log("");
17153
17286
  const modeLabel = settings.mode === "audit" ? import_chalk12.default.blue("audit") : settings.mode === "strict" ? import_chalk12.default.red("strict") : import_chalk12.default.white("standard");
17154
17287
  console.log(` Mode: ${modeLabel}`);
17155
- const projectConfig = import_path33.default.join(process.cwd(), "node9.config.json");
17156
- const globalConfig = import_path33.default.join(import_os28.default.homedir(), ".node9", "config.json");
17288
+ const projectConfig = import_path34.default.join(process.cwd(), "node9.config.json");
17289
+ const globalConfig = import_path34.default.join(import_os28.default.homedir(), ".node9", "config.json");
17157
17290
  console.log(
17158
17291
  ` Local: ${import_fs31.default.existsSync(projectConfig) ? import_chalk12.default.green("Active (node9.config.json)") : import_chalk12.default.gray("Not present")}`
17159
17292
  );
@@ -17167,13 +17300,13 @@ function registerStatusCommand(program2) {
17167
17300
  }
17168
17301
  const homeDir2 = import_os28.default.homedir();
17169
17302
  const claudeSettings = readJson2(
17170
- import_path33.default.join(homeDir2, ".claude", "settings.json")
17303
+ import_path34.default.join(homeDir2, ".claude", "settings.json")
17171
17304
  );
17172
- const claudeConfig = readJson2(import_path33.default.join(homeDir2, ".claude.json"));
17305
+ const claudeConfig = readJson2(import_path34.default.join(homeDir2, ".claude.json"));
17173
17306
  const geminiSettings = readJson2(
17174
- import_path33.default.join(homeDir2, ".gemini", "settings.json")
17307
+ import_path34.default.join(homeDir2, ".gemini", "settings.json")
17175
17308
  );
17176
- const cursorConfig = readJson2(import_path33.default.join(homeDir2, ".cursor", "mcp.json"));
17309
+ const cursorConfig = readJson2(import_path34.default.join(homeDir2, ".cursor", "mcp.json"));
17177
17310
  const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
17178
17311
  if (agentFound) {
17179
17312
  console.log("");
@@ -17233,7 +17366,7 @@ function registerStatusCommand(program2) {
17233
17366
  // src/cli/commands/init.ts
17234
17367
  var import_chalk13 = __toESM(require("chalk"));
17235
17368
  var import_fs32 = __toESM(require("fs"));
17236
- var import_path34 = __toESM(require("path"));
17369
+ var import_path35 = __toESM(require("path"));
17237
17370
  var import_os29 = __toESM(require("os"));
17238
17371
  var import_https3 = __toESM(require("https"));
17239
17372
  init_core();
@@ -17295,7 +17428,7 @@ function registerInitCommand(program2) {
17295
17428
  }
17296
17429
  console.log("");
17297
17430
  }
17298
- const configPath = import_path34.default.join(import_os29.default.homedir(), ".node9", "config.json");
17431
+ const configPath = import_path35.default.join(import_os29.default.homedir(), ".node9", "config.json");
17299
17432
  if (import_fs32.default.existsSync(configPath) && !options.force) {
17300
17433
  try {
17301
17434
  const existing = JSON.parse(import_fs32.default.readFileSync(configPath, "utf-8"));
@@ -17316,7 +17449,7 @@ function registerInitCommand(program2) {
17316
17449
  ...DEFAULT_CONFIG,
17317
17450
  settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
17318
17451
  };
17319
- const dir = import_path34.default.dirname(configPath);
17452
+ const dir = import_path35.default.dirname(configPath);
17320
17453
  if (!import_fs32.default.existsSync(dir)) import_fs32.default.mkdirSync(dir, { recursive: true });
17321
17454
  import_fs32.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
17322
17455
  console.log(import_chalk13.default.green(`\u2705 Config created: ${configPath}`));
@@ -17403,7 +17536,7 @@ function registerInitCommand(program2) {
17403
17536
  }
17404
17537
 
17405
17538
  // src/cli/commands/undo.ts
17406
- var import_path35 = __toESM(require("path"));
17539
+ var import_path36 = __toESM(require("path"));
17407
17540
  var import_chalk15 = __toESM(require("chalk"));
17408
17541
 
17409
17542
  // src/tui/undo-navigator.ts
@@ -17562,7 +17695,7 @@ function findMatchingCwd(startDir, history) {
17562
17695
  let dir = startDir;
17563
17696
  while (true) {
17564
17697
  if (cwds.has(dir)) return dir;
17565
- const parent = import_path35.default.dirname(dir);
17698
+ const parent = import_path36.default.dirname(dir);
17566
17699
  if (parent === dir) return null;
17567
17700
  dir = parent;
17568
17701
  }
@@ -17759,11 +17892,11 @@ init_provenance();
17759
17892
 
17760
17893
  // src/mcp-pin.ts
17761
17894
  var import_fs33 = __toESM(require("fs"));
17762
- var import_path36 = __toESM(require("path"));
17895
+ var import_path37 = __toESM(require("path"));
17763
17896
  var import_os30 = __toESM(require("os"));
17764
17897
  var import_crypto10 = __toESM(require("crypto"));
17765
17898
  function getPinsFilePath2() {
17766
- return import_path36.default.join(import_os30.default.homedir(), ".node9", "mcp-pins.json");
17899
+ return import_path37.default.join(import_os30.default.homedir(), ".node9", "mcp-pins.json");
17767
17900
  }
17768
17901
  function hashToolDefinitions(tools) {
17769
17902
  const sorted = [...tools].sort((a, b) => {
@@ -17804,7 +17937,7 @@ function readMcpPins() {
17804
17937
  }
17805
17938
  function writeMcpPins(data) {
17806
17939
  const filePath = getPinsFilePath2();
17807
- import_fs33.default.mkdirSync(import_path36.default.dirname(filePath), { recursive: true });
17940
+ import_fs33.default.mkdirSync(import_path37.default.dirname(filePath), { recursive: true });
17808
17941
  const tmp = `${filePath}.${import_crypto10.default.randomBytes(6).toString("hex")}.tmp`;
17809
17942
  import_fs33.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
17810
17943
  import_fs33.default.renameSync(tmp, filePath);
@@ -18257,7 +18390,7 @@ function registerMcpGatewayCommand(program2) {
18257
18390
  var import_readline4 = __toESM(require("readline"));
18258
18391
  var import_fs34 = __toESM(require("fs"));
18259
18392
  var import_os31 = __toESM(require("os"));
18260
- var import_path37 = __toESM(require("path"));
18393
+ var import_path38 = __toESM(require("path"));
18261
18394
  var import_child_process15 = require("child_process");
18262
18395
  init_core();
18263
18396
  init_daemon();
@@ -18508,8 +18641,8 @@ function handleStatus() {
18508
18641
  lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
18509
18642
  lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
18510
18643
  lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
18511
- const projectConfig = import_path37.default.join(process.cwd(), "node9.config.json");
18512
- const globalConfig = import_path37.default.join(import_os31.default.homedir(), ".node9", "config.json");
18644
+ const projectConfig = import_path38.default.join(process.cwd(), "node9.config.json");
18645
+ const globalConfig = import_path38.default.join(import_os31.default.homedir(), ".node9", "config.json");
18513
18646
  lines.push(
18514
18647
  `Project config (node9.config.json): ${import_fs34.default.existsSync(projectConfig) ? "present" : "not found"}`
18515
18648
  );
@@ -18588,7 +18721,7 @@ function handleShieldDisable(args) {
18588
18721
  writeActiveShields(active.filter((s) => s !== name));
18589
18722
  return `Shield "${name}" disabled.`;
18590
18723
  }
18591
- var GLOBAL_CONFIG_PATH2 = import_path37.default.join(import_os31.default.homedir(), ".node9", "config.json");
18724
+ var GLOBAL_CONFIG_PATH2 = import_path38.default.join(import_os31.default.homedir(), ".node9", "config.json");
18592
18725
  var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
18593
18726
  function readGlobalConfigRaw() {
18594
18727
  try {
@@ -18600,7 +18733,7 @@ function readGlobalConfigRaw() {
18600
18733
  return {};
18601
18734
  }
18602
18735
  function writeGlobalConfigRaw(data) {
18603
- const dir = import_path37.default.dirname(GLOBAL_CONFIG_PATH2);
18736
+ const dir = import_path38.default.dirname(GLOBAL_CONFIG_PATH2);
18604
18737
  if (!import_fs34.default.existsSync(dir)) import_fs34.default.mkdirSync(dir, { recursive: true });
18605
18738
  import_fs34.default.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
18606
18739
  }
@@ -18646,7 +18779,7 @@ function handleApproverSet(args) {
18646
18779
  function handleAuditGet(args) {
18647
18780
  const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
18648
18781
  const filter = typeof args.filter === "string" && args.filter !== "all" ? args.filter : null;
18649
- const auditPath = import_path37.default.join(import_os31.default.homedir(), ".node9", "audit.log");
18782
+ const auditPath = import_path38.default.join(import_os31.default.homedir(), ".node9", "audit.log");
18650
18783
  if (!import_fs34.default.existsSync(auditPath)) return "No audit log found.";
18651
18784
  const rawLines = import_fs34.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
18652
18785
  const parsed = [];
@@ -19203,7 +19336,7 @@ init_scan();
19203
19336
  // src/cli/commands/sessions.ts
19204
19337
  var import_chalk22 = __toESM(require("chalk"));
19205
19338
  var import_fs35 = __toESM(require("fs"));
19206
- var import_path38 = __toESM(require("path"));
19339
+ var import_path39 = __toESM(require("path"));
19207
19340
  var import_os32 = __toESM(require("os"));
19208
19341
  var CLAUDE_PRICING3 = {
19209
19342
  "claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
@@ -19245,7 +19378,7 @@ function encodeProjectPath(projectPath) {
19245
19378
  }
19246
19379
  function sessionJsonlPath(projectPath, sessionId) {
19247
19380
  const encoded = encodeProjectPath(projectPath);
19248
- return import_path38.default.join(import_os32.default.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
19381
+ return import_path39.default.join(import_os32.default.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
19249
19382
  }
19250
19383
  function projectLabel(projectPath) {
19251
19384
  return projectPath.replace(import_os32.default.homedir(), "~");
@@ -19317,7 +19450,7 @@ function parseSessionLines(lines) {
19317
19450
  return { toolCalls, costUSD, hasSnapshot, modifiedFiles };
19318
19451
  }
19319
19452
  function loadAuditEntries(auditPath) {
19320
- const aPath = auditPath ?? import_path38.default.join(import_os32.default.homedir(), ".node9", "audit.log");
19453
+ const aPath = auditPath ?? import_path39.default.join(import_os32.default.homedir(), ".node9", "audit.log");
19321
19454
  let raw;
19322
19455
  try {
19323
19456
  raw = import_fs35.default.readFileSync(aPath, "utf-8");
@@ -19356,7 +19489,7 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
19356
19489
  return result;
19357
19490
  }
19358
19491
  function buildGeminiSessions(days, allAuditEntries) {
19359
- const tmpDir = import_path38.default.join(import_os32.default.homedir(), ".gemini", "tmp");
19492
+ const tmpDir = import_path39.default.join(import_os32.default.homedir(), ".gemini", "tmp");
19360
19493
  if (!import_fs35.default.existsSync(tmpDir)) return [];
19361
19494
  const cutoff = days !== null ? (() => {
19362
19495
  const d = /* @__PURE__ */ new Date();
@@ -19372,18 +19505,18 @@ function buildGeminiSessions(days, allAuditEntries) {
19372
19505
  }
19373
19506
  const summaries = [];
19374
19507
  for (const slug of slugDirs) {
19375
- const slugPath = import_path38.default.join(tmpDir, slug);
19508
+ const slugPath = import_path39.default.join(tmpDir, slug);
19376
19509
  try {
19377
19510
  if (!import_fs35.default.statSync(slugPath).isDirectory()) continue;
19378
19511
  } catch {
19379
19512
  continue;
19380
19513
  }
19381
- let projectRoot = import_path38.default.join(import_os32.default.homedir(), slug);
19514
+ let projectRoot = import_path39.default.join(import_os32.default.homedir(), slug);
19382
19515
  try {
19383
- projectRoot = import_fs35.default.readFileSync(import_path38.default.join(slugPath, ".project_root"), "utf-8").trim();
19516
+ projectRoot = import_fs35.default.readFileSync(import_path39.default.join(slugPath, ".project_root"), "utf-8").trim();
19384
19517
  } catch {
19385
19518
  }
19386
- const chatsDir = import_path38.default.join(slugPath, "chats");
19519
+ const chatsDir = import_path39.default.join(slugPath, "chats");
19387
19520
  if (!import_fs35.default.existsSync(chatsDir)) continue;
19388
19521
  let chatFiles;
19389
19522
  try {
@@ -19394,7 +19527,7 @@ function buildGeminiSessions(days, allAuditEntries) {
19394
19527
  for (const chatFile of chatFiles) {
19395
19528
  let raw;
19396
19529
  try {
19397
- raw = import_fs35.default.readFileSync(import_path38.default.join(chatsDir, chatFile), "utf-8");
19530
+ raw = import_fs35.default.readFileSync(import_path39.default.join(chatsDir, chatFile), "utf-8");
19398
19531
  } catch {
19399
19532
  continue;
19400
19533
  }
@@ -19474,7 +19607,7 @@ function buildGeminiSessions(days, allAuditEntries) {
19474
19607
  return summaries;
19475
19608
  }
19476
19609
  function buildCodexSessions(days, allAuditEntries) {
19477
- const sessionsBase = import_path38.default.join(import_os32.default.homedir(), ".codex", "sessions");
19610
+ const sessionsBase = import_path39.default.join(import_os32.default.homedir(), ".codex", "sessions");
19478
19611
  if (!import_fs35.default.existsSync(sessionsBase)) return [];
19479
19612
  const cutoff = days !== null ? (() => {
19480
19613
  const d = /* @__PURE__ */ new Date();
@@ -19485,28 +19618,28 @@ function buildCodexSessions(days, allAuditEntries) {
19485
19618
  const jsonlFiles = [];
19486
19619
  try {
19487
19620
  for (const year of import_fs35.default.readdirSync(sessionsBase)) {
19488
- const yearPath = import_path38.default.join(sessionsBase, year);
19621
+ const yearPath = import_path39.default.join(sessionsBase, year);
19489
19622
  try {
19490
19623
  if (!import_fs35.default.statSync(yearPath).isDirectory()) continue;
19491
19624
  } catch {
19492
19625
  continue;
19493
19626
  }
19494
19627
  for (const month of import_fs35.default.readdirSync(yearPath)) {
19495
- const monthPath = import_path38.default.join(yearPath, month);
19628
+ const monthPath = import_path39.default.join(yearPath, month);
19496
19629
  try {
19497
19630
  if (!import_fs35.default.statSync(monthPath).isDirectory()) continue;
19498
19631
  } catch {
19499
19632
  continue;
19500
19633
  }
19501
19634
  for (const day of import_fs35.default.readdirSync(monthPath)) {
19502
- const dayPath = import_path38.default.join(monthPath, day);
19635
+ const dayPath = import_path39.default.join(monthPath, day);
19503
19636
  try {
19504
19637
  if (!import_fs35.default.statSync(dayPath).isDirectory()) continue;
19505
19638
  } catch {
19506
19639
  continue;
19507
19640
  }
19508
19641
  for (const file of import_fs35.default.readdirSync(dayPath)) {
19509
- if (file.endsWith(".jsonl")) jsonlFiles.push(import_path38.default.join(dayPath, file));
19642
+ if (file.endsWith(".jsonl")) jsonlFiles.push(import_path39.default.join(dayPath, file));
19510
19643
  }
19511
19644
  }
19512
19645
  }
@@ -19596,7 +19729,7 @@ function buildCodexSessions(days, allAuditEntries) {
19596
19729
  return summaries;
19597
19730
  }
19598
19731
  function buildSessions(days, historyPath) {
19599
- const hPath = historyPath ?? import_path38.default.join(import_os32.default.homedir(), ".claude", "history.jsonl");
19732
+ const hPath = historyPath ?? import_path39.default.join(import_os32.default.homedir(), ".claude", "history.jsonl");
19600
19733
  let historyRaw;
19601
19734
  try {
19602
19735
  historyRaw = import_fs35.default.readFileSync(hPath, "utf-8");
@@ -19890,7 +20023,7 @@ function registerSessionsCommand(program2) {
19890
20023
  console.log("");
19891
20024
  console.log(import_chalk22.default.cyan.bold("\u{1F4CB} node9 sessions") + import_chalk22.default.dim(" \u2014 what your AI agent did"));
19892
20025
  console.log("");
19893
- const historyPath = import_path38.default.join(import_os32.default.homedir(), ".claude", "history.jsonl");
20026
+ const historyPath = import_path39.default.join(import_os32.default.homedir(), ".claude", "history.jsonl");
19894
20027
  if (!import_fs35.default.existsSync(historyPath)) {
19895
20028
  console.log(import_chalk22.default.yellow(" No Claude session history found at ~/.claude/history.jsonl"));
19896
20029
  console.log(import_chalk22.default.gray(" Install Claude Code, run a few sessions, then try again.\n"));
@@ -19930,10 +20063,10 @@ function registerSessionsCommand(program2) {
19930
20063
  var import_chalk23 = __toESM(require("chalk"));
19931
20064
  var import_fs36 = __toESM(require("fs"));
19932
20065
  var import_os33 = __toESM(require("os"));
19933
- var import_path39 = __toESM(require("path"));
20066
+ var import_path40 = __toESM(require("path"));
19934
20067
  function wipeSkillSessions() {
19935
20068
  try {
19936
- import_fs36.default.rmSync(import_path39.default.join(import_os33.default.homedir(), ".node9", "skill-sessions"), {
20069
+ import_fs36.default.rmSync(import_path40.default.join(import_os33.default.homedir(), ".node9", "skill-sessions"), {
19937
20070
  recursive: true,
19938
20071
  force: true
19939
20072
  });
@@ -20017,10 +20150,10 @@ function registerSkillPinCommand(program2) {
20017
20150
  // src/cli/commands/dlp.ts
20018
20151
  var import_chalk24 = __toESM(require("chalk"));
20019
20152
  var import_fs37 = __toESM(require("fs"));
20020
- var import_path40 = __toESM(require("path"));
20153
+ var import_path41 = __toESM(require("path"));
20021
20154
  var import_os34 = __toESM(require("os"));
20022
- var AUDIT_LOG = import_path40.default.join(import_os34.default.homedir(), ".node9", "audit.log");
20023
- var RESOLVED_FILE = import_path40.default.join(import_os34.default.homedir(), ".node9", "dlp-resolved.json");
20155
+ var AUDIT_LOG = import_path41.default.join(import_os34.default.homedir(), ".node9", "audit.log");
20156
+ var RESOLVED_FILE = import_path41.default.join(import_os34.default.homedir(), ".node9", "dlp-resolved.json");
20024
20157
  var ANSI_RE = /\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g;
20025
20158
  function stripAnsi(s) {
20026
20159
  return s.replace(ANSI_RE, "");
@@ -20139,15 +20272,15 @@ function registerDlpCommand(program2) {
20139
20272
 
20140
20273
  // src/cli.ts
20141
20274
  var { version } = JSON.parse(
20142
- import_fs40.default.readFileSync(import_path43.default.join(__dirname, "../package.json"), "utf-8")
20275
+ import_fs40.default.readFileSync(import_path44.default.join(__dirname, "../package.json"), "utf-8")
20143
20276
  );
20144
20277
  var program = new import_commander.Command();
20145
20278
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
20146
20279
  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) => {
20147
20280
  const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
20148
- const credPath = import_path43.default.join(import_os37.default.homedir(), ".node9", "credentials.json");
20149
- if (!import_fs40.default.existsSync(import_path43.default.dirname(credPath)))
20150
- import_fs40.default.mkdirSync(import_path43.default.dirname(credPath), { recursive: true });
20281
+ const credPath = import_path44.default.join(import_os37.default.homedir(), ".node9", "credentials.json");
20282
+ if (!import_fs40.default.existsSync(import_path44.default.dirname(credPath)))
20283
+ import_fs40.default.mkdirSync(import_path44.default.dirname(credPath), { recursive: true });
20151
20284
  const profileName = options.profile || "default";
20152
20285
  let existingCreds = {};
20153
20286
  try {
@@ -20166,7 +20299,7 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
20166
20299
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
20167
20300
  import_fs40.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
20168
20301
  if (profileName === "default") {
20169
- const configPath = import_path43.default.join(import_os37.default.homedir(), ".node9", "config.json");
20302
+ const configPath = import_path44.default.join(import_os37.default.homedir(), ".node9", "config.json");
20170
20303
  let config = {};
20171
20304
  try {
20172
20305
  if (import_fs40.default.existsSync(configPath))
@@ -20185,8 +20318,8 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
20185
20318
  approvers.cloud = false;
20186
20319
  }
20187
20320
  s.approvers = approvers;
20188
- if (!import_fs40.default.existsSync(import_path43.default.dirname(configPath)))
20189
- import_fs40.default.mkdirSync(import_path43.default.dirname(configPath), { recursive: true });
20321
+ if (!import_fs40.default.existsSync(import_path44.default.dirname(configPath)))
20322
+ import_fs40.default.mkdirSync(import_path44.default.dirname(configPath), { recursive: true });
20190
20323
  import_fs40.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
20191
20324
  }
20192
20325
  if (options.profile && profileName !== "default") {
@@ -20324,7 +20457,7 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
20324
20457
  }
20325
20458
  }
20326
20459
  if (options.purge) {
20327
- const node9Dir = import_path43.default.join(import_os37.default.homedir(), ".node9");
20460
+ const node9Dir = import_path44.default.join(import_os37.default.homedir(), ".node9");
20328
20461
  if (import_fs40.default.existsSync(node9Dir)) {
20329
20462
  const confirmed = await (0, import_prompts2.confirm)({
20330
20463
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
@@ -20474,9 +20607,9 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
20474
20607
  Run "node9 addto claude" to register it as the statusLine.`
20475
20608
  ).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
20476
20609
  if (subcommand === "debug") {
20477
- const flagFile = import_path43.default.join(import_os37.default.homedir(), ".node9", "hud-debug");
20610
+ const flagFile = import_path44.default.join(import_os37.default.homedir(), ".node9", "hud-debug");
20478
20611
  if (state === "on") {
20479
- import_fs40.default.mkdirSync(import_path43.default.dirname(flagFile), { recursive: true });
20612
+ import_fs40.default.mkdirSync(import_path44.default.dirname(flagFile), { recursive: true });
20480
20613
  import_fs40.default.writeFileSync(flagFile, "");
20481
20614
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
20482
20615
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
@@ -20546,10 +20679,10 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
20546
20679
  program.help();
20547
20680
  return;
20548
20681
  }
20549
- const fullCommand2 = runArgs.join(" ");
20682
+ const fullCommand = runArgs.join(" ");
20550
20683
  let result = await authorizeHeadless(
20551
20684
  "shell",
20552
- { command: fullCommand2 },
20685
+ { command: fullCommand },
20553
20686
  {
20554
20687
  agent: "Terminal"
20555
20688
  }
@@ -20557,11 +20690,11 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
20557
20690
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
20558
20691
  console.error(import_chalk26.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
20559
20692
  const daemonReady = await autoStartDaemonAndWait();
20560
- if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand2 });
20693
+ if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
20561
20694
  }
20562
20695
  if (result.noApprovalMechanism && process.stdout.isTTY) {
20563
20696
  const approved = await (0, import_prompts2.confirm)({
20564
- message: `\u{1F6E1}\uFE0F Node9: Allow "${fullCommand2}"?`,
20697
+ message: `\u{1F6E1}\uFE0F Node9: Allow "${fullCommand}"?`,
20565
20698
  default: false
20566
20699
  });
20567
20700
  result = { approved, reason: approved ? void 0 : "Denied by user at terminal." };
@@ -20574,7 +20707,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
20574
20707
  process.exit(1);
20575
20708
  }
20576
20709
  console.error(import_chalk26.default.green("\n\u2705 Approved \u2014 running command...\n"));
20577
- await runProxy(fullCommand2);
20710
+ await runProxy(fullCommand);
20578
20711
  } else {
20579
20712
  program.help();
20580
20713
  }
@@ -20593,7 +20726,7 @@ if (process.argv[2] !== "daemon") {
20593
20726
  const isCheckHook = process.argv[2] === "check";
20594
20727
  if (isCheckHook) {
20595
20728
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
20596
- const logPath = import_path43.default.join(import_os37.default.homedir(), ".node9", "hook-debug.log");
20729
+ const logPath = import_path44.default.join(import_os37.default.homedir(), ".node9", "hook-debug.log");
20597
20730
  const msg = reason instanceof Error ? reason.message : String(reason);
20598
20731
  import_fs40.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
20599
20732
  `);