@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.mjs CHANGED
@@ -147,8 +147,8 @@ function sanitizeConfig(raw) {
147
147
  }
148
148
  }
149
149
  const lines = result.error.issues.map((issue) => {
150
- const path44 = issue.path.length > 0 ? issue.path.join(".") : "root";
151
- return ` \u2022 ${path44}: ${issue.message}`;
150
+ const path45 = issue.path.length > 0 ? issue.path.join(".") : "root";
151
+ return ` \u2022 ${path45}: ${issue.message}`;
152
152
  });
153
153
  return {
154
154
  sanitized,
@@ -2066,9 +2066,9 @@ function matchesPattern(text, patterns) {
2066
2066
  const withoutDotSlash = text.replace(/^\.\//, "");
2067
2067
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
2068
2068
  }
2069
- function getNestedValue(obj, path44) {
2069
+ function getNestedValue(obj, path45) {
2070
2070
  if (!obj || typeof obj !== "object") return null;
2071
- return path44.split(".").reduce((prev, curr) => prev?.[curr], obj);
2071
+ return path45.split(".").reduce((prev, curr) => prev?.[curr], obj);
2072
2072
  }
2073
2073
  function normalizeCommandForPolicy(command) {
2074
2074
  try {
@@ -5243,6 +5243,10 @@ var init_ui = __esm({
5243
5243
  border-color: rgba(83, 155, 245, 0.35);
5244
5244
  background: rgba(83, 155, 245, 0.04);
5245
5245
  }
5246
+ .pending-card.discovery-card {
5247
+ border-color: rgba(120, 100, 255, 0.45);
5248
+ background: rgba(100, 80, 255, 0.06);
5249
+ }
5246
5250
  .pending-card-header {
5247
5251
  display: flex;
5248
5252
  align-items: center;
@@ -5712,6 +5716,12 @@ var init_ui = __esm({
5712
5716
  text-align: center;
5713
5717
  }
5714
5718
  /* results */
5719
+ .scan-date-range {
5720
+ text-align: center;
5721
+ font-size: 11px;
5722
+ color: var(--muted);
5723
+ padding: 6px 0 10px;
5724
+ }
5715
5725
  .scan-summary-row {
5716
5726
  display: grid;
5717
5727
  grid-template-columns: repeat(4, 1fr);
@@ -7423,7 +7433,7 @@ var init_ui = __esm({
7423
7433
  });
7424
7434
 
7425
7435
  try {
7426
- await fetch('/mcp/approve', {
7436
+ const res = await fetch('/mcp/approve', {
7427
7437
  method: 'POST',
7428
7438
  headers: {
7429
7439
  'Content-Type': 'application/json',
@@ -7431,9 +7441,19 @@ var init_ui = __esm({
7431
7441
  },
7432
7442
  body: JSON.stringify({ id, serverKey, disabledTools }),
7433
7443
  });
7444
+ if (!res.ok) {
7445
+ showToast(
7446
+ '\u26A0\uFE0F',
7447
+ 'Approval failed',
7448
+ \`Server responded with \${res.status}\`,
7449
+ 'toast-block'
7450
+ );
7451
+ return;
7452
+ }
7434
7453
  closeMcpReview();
7435
7454
  refreshMcpTools();
7436
7455
  } catch (err) {
7456
+ showToast('\u26A0\uFE0F', 'Approval failed', 'Network error \u2014 check the daemon', 'toast-block');
7437
7457
  console.error('Failed to approve MCP server:', err);
7438
7458
  }
7439
7459
  }
@@ -7604,13 +7624,10 @@ var init_ui = __esm({
7604
7624
  const res = await fetch('/scan', { headers: { 'X-Node9-Token': CSRF_TOKEN } });
7605
7625
  if (!res.ok) return;
7606
7626
  const data = await res.json();
7607
- if (data.status !== 'complete') return;
7627
+ if (data.status !== 'complete' || !data.summary) return;
7608
7628
  _scanData = data;
7609
- const sources = data.sources || [];
7610
- const total =
7611
- sources.reduce((n, s) => n + (s.findings || []).length, 0) +
7612
- sources.reduce((n, s) => n + (s.leaks || []).length, 0) +
7613
- sources.reduce((n, s) => n + (s.loops || []).length, 0);
7629
+ const v = data.summary.byVerdict || {};
7630
+ const total = (v.blocked || 0) + (v.supervised || 0) + (v.leaks || 0) + (v.loops || 0);
7614
7631
  if (total > 0) {
7615
7632
  const btn = document.getElementById('btn-scan-history');
7616
7633
  if (btn && !btn.querySelector('.scan-badge')) {
@@ -7715,42 +7732,26 @@ var init_ui = __esm({
7715
7732
  }
7716
7733
 
7717
7734
  function renderScanResults(data) {
7718
- if (!data || data.status !== 'complete') {
7735
+ if (!data || data.status !== 'complete' || !data.summary) {
7719
7736
  return '<div style="text-align:center;color:var(--muted);padding:32px">No data returned.</div>';
7720
7737
  }
7721
- const s = data.summary || {};
7722
- const sources = data.sources || [];
7723
-
7724
- // Flatten all findings across sources
7725
- const allFindings = sources.flatMap((src) => src.findings || []);
7726
- const allLeaks = sources.flatMap((src) => src.leaks || []);
7727
- const allLoops = sources.flatMap((src) => src.loops || []);
7728
-
7729
- // Split findings into three distinct buckets
7730
- const blocked = allFindings.filter((f) => f.verdict === 'block' && f.ruleSource !== 'user');
7731
- const supervised = allFindings.filter(
7732
- (f) => f.verdict !== 'block' && f.ruleSource !== 'user'
7733
- );
7734
- const yourRules = allFindings.filter((f) => f.ruleSource === 'user');
7735
-
7736
- // Loop cost estimate: each wasted iteration ~2K tokens at Sonnet pricing
7737
- const LOOP_THRESHOLD = 3;
7738
- const COST_PER_ITER = 0.006;
7739
- const wastedIters = allLoops.reduce(
7740
- (sum, l) => sum + Math.max(0, (l.count || 0) - LOOP_THRESHOLD),
7741
- 0
7742
- );
7743
- const loopWastedUSD = wastedIters * COST_PER_ITER;
7744
-
7745
- const totalCost = s.totalCostUSD || 0;
7738
+ // Server-computed ScanSummary (see src/scan-summary.ts).
7739
+ // This renderer is a pure presentation layer \u2014 no categorization here.
7740
+ const summary = data.summary;
7741
+ const stats = summary.stats || {};
7742
+ const byVerdict = summary.byVerdict || { blocked: 0, supervised: 0, leaks: 0, loops: 0 };
7743
+ const byAgent = summary.byAgent || [];
7744
+ const sections = summary.sections || [];
7745
+ const leaks = summary.leaks || [];
7746
+ const loops = summary.loops || [];
7747
+ const loopWastedUSD = summary.loopWastedUSD || 0;
7748
+ const totalCost = stats.totalCostUSD || 0;
7746
7749
 
7747
7750
  function fmtUSD(n) {
7748
7751
  if (!n || n < 0.001) return null;
7749
7752
  if (n < 1) return '$' + n.toFixed(3);
7750
7753
  return '$' + n.toFixed(2);
7751
7754
  }
7752
-
7753
- // \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
7754
7755
  const costStr = fmtUSD(totalCost);
7755
7756
  const savingsStr = fmtUSD(loopWastedUSD);
7756
7757
 
@@ -7772,10 +7773,46 @@ var init_ui = __esm({
7772
7773
  );
7773
7774
  }
7774
7775
 
7775
- const stats =
7776
+ // \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
7777
+ function fmtShort(iso) {
7778
+ if (!iso) return null;
7779
+ try {
7780
+ return new Date(iso).toLocaleDateString([], {
7781
+ month: 'short',
7782
+ day: 'numeric',
7783
+ year: 'numeric',
7784
+ });
7785
+ } catch {
7786
+ return null;
7787
+ }
7788
+ }
7789
+ const firstLabel = fmtShort(stats.firstDate);
7790
+ const lastLabel = fmtShort(stats.lastDate);
7791
+ let daySpan = '';
7792
+ if (stats.firstDate && stats.lastDate) {
7793
+ const days =
7794
+ Math.round(
7795
+ (new Date(stats.lastDate).getTime() - new Date(stats.firstDate).getTime()) /
7796
+ (1000 * 60 * 60 * 24)
7797
+ ) + 1;
7798
+ if (days > 1) daySpan = ' \xB7 ' + days + ' day' + (days !== 1 ? 's' : '');
7799
+ }
7800
+ const dateRangeHtml =
7801
+ firstLabel && lastLabel
7802
+ ? '<div class="scan-date-range">\u{1F4C5} ' +
7803
+ esc(firstLabel) +
7804
+ ' \u2013 ' +
7805
+ esc(lastLabel) +
7806
+ esc(daySpan) +
7807
+ '</div>'
7808
+ : '';
7809
+
7810
+ // \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
7811
+ const statsHtml =
7812
+ dateRangeHtml +
7776
7813
  '<div class="scan-summary-row">' +
7777
7814
  '<div class="scan-stat">' +
7778
- statVal(s.sessions || 0, '') +
7815
+ statVal(stats.sessions || 0, '') +
7779
7816
  '<div class="scan-stat-label">Sessions</div></div>' +
7780
7817
  (totalCost > 0
7781
7818
  ? '<div class="scan-stat">' +
@@ -7783,19 +7820,16 @@ var init_ui = __esm({
7783
7820
  '<div class="scan-stat-label">AI Spend</div></div>'
7784
7821
  : '') +
7785
7822
  '<div class="scan-stat">' +
7786
- statVal(blocked.length, blocked.length ? 'danger' : 'ok') +
7823
+ statVal(byVerdict.blocked || 0, byVerdict.blocked ? 'danger' : 'ok') +
7787
7824
  '<div class="scan-stat-label">Blocked</div></div>' +
7788
7825
  '<div class="scan-stat">' +
7789
- statVal(supervised.length, supervised.length ? 'warning' : 'ok') +
7826
+ statVal(byVerdict.supervised || 0, byVerdict.supervised ? 'warning' : 'ok') +
7790
7827
  '<div class="scan-stat-label">Supervised</div></div>' +
7791
7828
  '<div class="scan-stat">' +
7792
- statVal(yourRules.length, '', yourRules.length ? 'color:#58a6ff' : '') +
7793
- '<div class="scan-stat-label">Your Rules</div></div>' +
7794
- '<div class="scan-stat">' +
7795
- statVal(allLeaks.length, allLeaks.length ? 'danger' : 'ok') +
7829
+ statVal(byVerdict.leaks || 0, byVerdict.leaks ? 'danger' : 'ok') +
7796
7830
  '<div class="scan-stat-label">Leaks</div></div>' +
7797
7831
  '<div class="scan-stat">' +
7798
- statVal(allLoops.length, allLoops.length ? 'warning' : 'ok') +
7832
+ statVal(byVerdict.loops || 0, byVerdict.loops ? 'warning' : 'ok') +
7799
7833
  '<div class="scan-stat-label">Loops</div></div>' +
7800
7834
  '</div>' +
7801
7835
  (savingsStr
@@ -7804,56 +7838,103 @@ var init_ui = __esm({
7804
7838
  '</span> in unnecessary LLM turns</div>'
7805
7839
  : '');
7806
7840
 
7807
- if (!allFindings.length && !allLeaks.length && !allLoops.length) {
7841
+ const hasNothing = !sections.length && !leaks.length && !loops.length;
7842
+ if (hasNothing) {
7808
7843
  return (
7809
- stats +
7844
+ statsHtml +
7810
7845
  '<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 ' +
7811
- (s.sessions || 0) +
7812
- ' sessions across ' +
7813
- sources.length +
7814
- ' AI source(s)</span></div>'
7846
+ (stats.sessions || 0) +
7847
+ ' sessions</span></div>'
7815
7848
  );
7816
7849
  }
7817
7850
 
7818
- // \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
7819
- function groupByKey(items, keyFn) {
7820
- const map = new Map();
7821
- for (const item of items) {
7822
- const k = keyFn(item);
7823
- if (!map.has(k)) map.set(k, []);
7824
- map.get(k).push(item);
7851
+ // \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
7852
+ const agentBreakdown = byAgent.length
7853
+ ? '<div class="scan-agent-group">' +
7854
+ byAgent
7855
+ .map(
7856
+ (a) =>
7857
+ '<div class="scan-agent-row">' +
7858
+ '<span class="scan-agent-name">' +
7859
+ esc(a.icon || '') +
7860
+ ' ' +
7861
+ esc(a.label) +
7862
+ '</span>' +
7863
+ '<span class="scan-agent-stat">' +
7864
+ a.sessions +
7865
+ ' session' +
7866
+ (a.sessions !== 1 ? 's' : '') +
7867
+ '</span>' +
7868
+ '<span class="scan-agent-sep">\xB7</span>' +
7869
+ '<span class="scan-agent-stat">' +
7870
+ a.findings +
7871
+ ' finding' +
7872
+ (a.findings !== 1 ? 's' : '') +
7873
+ '</span>' +
7874
+ '</div>'
7875
+ )
7876
+ .join('') +
7877
+ '</div>'
7878
+ : '';
7879
+
7880
+ // \u2500\u2500 Rule row renderer \u2014 shared between rule sections + leaks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7881
+ // Determines bar-max across all sections so relative counts stay honest.
7882
+ let maxBar = 1;
7883
+ for (const section of sections) {
7884
+ for (const rule of section.rules || []) {
7885
+ if (rule.findings.length > maxBar) maxBar = rule.findings.length;
7825
7886
  }
7826
- return [...map.entries()].sort((a, b) => b[1].length - a[1].length);
7827
7887
  }
7888
+ // Group leaks by patternName for display
7889
+ const leaksByPattern = (function () {
7890
+ const m = new Map();
7891
+ for (const l of leaks) {
7892
+ const k = l.patternName || 'DLP';
7893
+ if (!m.has(k)) m.set(k, []);
7894
+ m.get(k).push(l);
7895
+ }
7896
+ for (const [, group] of m) if (group.length > maxBar) maxBar = group.length;
7897
+ return [...m.entries()].sort((a, b) => b[1].length - a[1].length);
7898
+ })();
7828
7899
 
7829
- function findingRow(item, type) {
7830
- const date = new Date(item.timestamp).toLocaleDateString([], {
7900
+ function findingRow(timestamp, text) {
7901
+ const date = new Date(timestamp).toLocaleDateString([], {
7831
7902
  month: 'short',
7832
7903
  day: 'numeric',
7833
7904
  });
7834
- const cmdText =
7835
- type === 'leak' ? esc(item.sample || '') : esc(truncate(item.command || '', 120));
7836
7905
  return (
7837
7906
  '<div class="scan-finding-row"><span class="scan-finding-ts">' +
7838
7907
  date +
7839
7908
  '</span><span class="scan-finding-cmd">' +
7840
- cmdText +
7909
+ text +
7841
7910
  '</span></div>'
7842
7911
  );
7843
7912
  }
7844
7913
 
7845
- function ruleSection(items, type, badgeClass, badgeLabel, maxBar) {
7846
- const keyFn =
7847
- type === 'leak'
7848
- ? (i) => i.pattern || 'DLP'
7849
- : (i) => (i.rule || 'unknown').replace(/^shield:[^:]+:/, '');
7850
- const grouped = groupByKey(items, keyFn);
7851
- return grouped
7852
- .map(([rule, group]) => {
7853
- const count = group.length;
7914
+ function ruleCardsForSection(section) {
7915
+ return (section.rules || [])
7916
+ .map((rule) => {
7917
+ const count = rule.findings.length;
7854
7918
  const barPct = Math.round((count / maxBar) * 100);
7855
7919
  const detailId = 'detail-' + Math.random().toString(36).slice(2);
7856
- const rows = group.map((i) => findingRow(i, type)).join('');
7920
+ const badgeClass =
7921
+ section.sourceType === 'user'
7922
+ ? 'badge-user'
7923
+ : rule.verdict === 'block'
7924
+ ? 'badge-block'
7925
+ : 'badge-review';
7926
+ const badgeLabel =
7927
+ section.sourceType === 'user'
7928
+ ? rule.verdict === 'block'
7929
+ ? 'YOUR BLOCK'
7930
+ : 'YOUR RULE'
7931
+ : rule.verdict === 'block'
7932
+ ? 'BLOCK'
7933
+ : 'REVIEW';
7934
+ const barColor = section.sourceType === 'user' ? ';background:#388bfd33' : '';
7935
+ const rows = rule.findings
7936
+ .map((f) => findingRow(f.timestamp, esc(truncate(f.command || '', 120))))
7937
+ .join('');
7857
7938
  return (
7858
7939
  '<div class="scan-rule-row" onclick="var d=document.getElementById(\\'' +
7859
7940
  detailId +
@@ -7864,12 +7945,12 @@ var init_ui = __esm({
7864
7945
  badgeLabel +
7865
7946
  '</span>' +
7866
7947
  '<span class="scan-rule-name">' +
7867
- esc(rule) +
7948
+ esc(rule.name) +
7868
7949
  '</span>' +
7869
7950
  '<span class="scan-rule-bar-wrap"><span class="scan-rule-bar" style="width:' +
7870
7951
  barPct +
7871
7952
  '%' +
7872
- (badgeClass === 'badge-user' ? ';background:#388bfd33' : '') +
7953
+ barColor +
7873
7954
  '"></span></span>' +
7874
7955
  '<span class="scan-rule-count">' +
7875
7956
  count +
@@ -7885,98 +7966,95 @@ var init_ui = __esm({
7885
7966
  .join('');
7886
7967
  }
7887
7968
 
7888
- // \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
7889
- const maxBar = Math.max(
7890
- ...allFindings.map((f) => 1),
7891
- ...groupByKey(allFindings, (f) => (f.rule || '').replace(/^shield:[^:]+:/, '')).map(
7892
- ([, v]) => v.length
7893
- ),
7894
- ...groupByKey(allLeaks, (f) => f.pattern || 'DLP').map(([, v]) => v.length),
7895
- 1
7896
- );
7969
+ // \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
7970
+ function sectionHeading(section) {
7971
+ if (section.id === 'default') {
7972
+ return '\u{1F441} ' + esc(section.label) + ' \u2014 built-in, always on';
7973
+ }
7974
+ if (section.sourceType === 'shield') {
7975
+ return (
7976
+ '\u{1F6E1} ' +
7977
+ esc(section.label) +
7978
+ ' shield' +
7979
+ (section.subtitle ? ' \u2014 ' + esc(section.subtitle) : '')
7980
+ );
7981
+ }
7982
+ if (section.id === 'cloud') {
7983
+ return '\u2601 Cloud Policy \u2014 synced from node9';
7984
+ }
7985
+ return '\u2705 Your Rules \u2014 working as configured';
7986
+ }
7987
+ function sectionColor(section) {
7988
+ if (section.blockedCount > 0) return '#e5534b';
7989
+ if (section.sourceType === 'user') return '#58a6ff';
7990
+ return 'var(--muted)';
7991
+ }
7897
7992
 
7898
- // Per-agent breakdown \u2014 one row per active agent (only shows active ones)
7899
- const agentBreakdown = (function () {
7900
- const rows = sources
7901
- .filter(
7902
- (src) =>
7903
- (src.sessions || 0) > 0 ||
7904
- (src.findings || []).length > 0 ||
7905
- (src.leaks || []).length > 0 ||
7906
- (src.loops || []).length > 0
7907
- )
7908
- .map((src) => {
7909
- const f = (src.findings || []).length;
7910
- const l = (src.leaks || []).length;
7911
- const lp = (src.loops || []).length;
7912
- const findingsTotal = f + l + lp;
7993
+ let html = statsHtml + agentBreakdown;
7994
+
7995
+ // \u2500\u2500 Leaks (shown first: credential exposure is highest-severity) \u2500\u2500\u2500\u2500\u2500
7996
+ if (leaksByPattern.length) {
7997
+ html +=
7998
+ '<div class="scan-rule-section-label" style="color:#e5534b">\u{1F511} Credential Leaks \u2014 secrets found in tool calls</div>';
7999
+ html += leaksByPattern
8000
+ .map(([pattern, group]) => {
8001
+ const count = group.length;
8002
+ const barPct = Math.round((count / maxBar) * 100);
8003
+ const detailId = 'detail-' + Math.random().toString(36).slice(2);
8004
+ const rows = group
8005
+ .map((l) => findingRow(l.timestamp, esc(l.redactedSample || '')))
8006
+ .join('');
7913
8007
  return (
7914
- '<div class="scan-agent-row">' +
7915
- '<span class="scan-agent-name">' +
7916
- esc(src.icon || '') +
7917
- ' ' +
7918
- esc(src.label || src.id) +
7919
- '</span>' +
7920
- '<span class="scan-agent-stat">' +
7921
- (src.sessions || 0) +
7922
- ' session' +
7923
- ((src.sessions || 0) !== 1 ? 's' : '') +
8008
+ '<div class="scan-rule-row" onclick="var d=document.getElementById(\\'' +
8009
+ detailId +
8010
+ "');var a=this.querySelector('.scan-rule-arrow');d.style.display=d.style.display===''?'none':'';a.textContent=d.style.display===''?'\u25B2':'\u25BC';\\">" +
8011
+ '<span class="scan-verdict-badge badge-dlp">DLP</span>' +
8012
+ '<span class="scan-rule-name">' +
8013
+ esc(pattern) +
7924
8014
  '</span>' +
7925
- '<span class="scan-agent-sep">\xB7</span>' +
7926
- '<span class="scan-agent-stat">' +
7927
- findingsTotal +
7928
- ' finding' +
7929
- (findingsTotal !== 1 ? 's' : '') +
8015
+ '<span class="scan-rule-bar-wrap"><span class="scan-rule-bar" style="width:' +
8016
+ barPct +
8017
+ '%"></span></span>' +
8018
+ '<span class="scan-rule-count">' +
8019
+ count +
7930
8020
  '</span>' +
8021
+ '<span class="scan-rule-arrow">\u25BC</span>' +
8022
+ '</div><div id="' +
8023
+ detailId +
8024
+ '" style="display:none" class="scan-rule-detail">' +
8025
+ rows +
7931
8026
  '</div>'
7932
8027
  );
7933
- });
7934
- if (!rows.length) return '';
7935
- return '<div class="scan-agent-group">' + rows.join('') + '</div>';
7936
- })();
7937
-
7938
- let html = stats + agentBreakdown;
7939
-
7940
- if (allLeaks.length) {
7941
- html +=
7942
- '<div class="scan-rule-section-label" style="color:#e5534b">\u{1F511} Credential Leaks \u2014 secrets found in tool calls</div>';
7943
- html += ruleSection(allLeaks, 'leak', 'badge-dlp', 'DLP', maxBar);
7944
- }
7945
-
7946
- if (blocked.length) {
7947
- html +=
7948
- '<div class="scan-rule-section-label" style="color:#e5534b">\u{1F6D1} Blocked \u2014 Node9 would have stopped these</div>';
7949
- html += ruleSection(blocked, 'risk', 'badge-block', 'BLOCK', maxBar);
7950
- }
7951
-
7952
- if (supervised.length) {
7953
- html +=
7954
- '<div class="scan-rule-section-label">\u{1F441} Supervised \u2014 Node9 would have asked you first</div>';
7955
- html += ruleSection(supervised, 'risk', 'badge-review', 'REVIEW', maxBar);
8028
+ })
8029
+ .join('');
7956
8030
  }
7957
8031
 
7958
- if (yourRules.length) {
8032
+ // \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
8033
+ for (const section of sections) {
7959
8034
  html +=
7960
- '<div class="scan-rule-section-label" style="color:#58a6ff">\u2705 Your Rules \u2014 working as configured</div>';
7961
- html += ruleSection(yourRules, 'risk', 'badge-user', 'YOUR RULE', maxBar);
8035
+ '<div class="scan-rule-section-label" style="color:' +
8036
+ sectionColor(section) +
8037
+ '">' +
8038
+ sectionHeading(section) +
8039
+ '</div>';
8040
+ html += ruleCardsForSection(section);
7962
8041
  }
7963
8042
 
7964
- // Loops: single collapsed summary card, expand to see all groups
7965
- if (allLoops.length) {
7966
- const totalReps = allLoops.reduce((sum, l) => sum + (l.count || 0), 0);
8043
+ // \u2500\u2500 Loops: single collapsed summary card, expand to see all groups \u2500\u2500\u2500
8044
+ if (loops.length) {
8045
+ const totalReps = loops.reduce((sum, l) => sum + (l.count || 0), 0);
7967
8046
  const loopsDetailId = 'loops-detail-' + Math.random().toString(36).slice(2);
7968
8047
  const savingNote = savingsStr
7969
8048
  ? ' \xB7 <span style="color:var(--warning)">~' + savingsStr + ' wasted</span>'
7970
8049
  : '';
7971
- const loopGroupRows = allLoops
8050
+ const maxLoopCount = Math.max(...loops.map((l) => l.count || 0), 1);
8051
+ const loopGroupRows = loops
7972
8052
  .map((item) => {
7973
8053
  const date = new Date(item.timestamp).toLocaleDateString([], {
7974
8054
  month: 'short',
7975
8055
  day: 'numeric',
7976
8056
  });
7977
- const barPct = Math.round(
7978
- (item.count / Math.max(...allLoops.map((l) => l.count), 1)) * 100
7979
- );
8057
+ const barPct = Math.round((item.count / maxLoopCount) * 100);
7980
8058
  const detailId = 'detail-' + Math.random().toString(36).slice(2);
7981
8059
  return (
7982
8060
  '<div class="scan-rule-row" onclick="event.stopPropagation();var d=document.getElementById(\\'' +
@@ -8014,9 +8092,9 @@ var init_ui = __esm({
8014
8092
  "');var a=this.querySelector('.scan-rule-arrow');d.style.display=d.style.display===''?'none':'';a.textContent=d.style.display===''?'\u25B2':'\u25BC';\\">" +
8015
8093
  '<span class="scan-verdict-badge badge-review">LOOP</span>' +
8016
8094
  '<span class="scan-rule-name">' +
8017
- allLoops.length +
8095
+ loops.length +
8018
8096
  ' pattern' +
8019
- (allLoops.length !== 1 ? 's' : '') +
8097
+ (loops.length !== 1 ? 's' : '') +
8020
8098
  ' \xB7 ' +
8021
8099
  totalReps +
8022
8100
  ' total repetitions' +
@@ -8090,10 +8168,20 @@ var init_ui = __esm({
8090
8168
  const data = await res.json();
8091
8169
  results.style.display = 'block';
8092
8170
 
8093
- if (data.status === 'complete') {
8094
- const sources = data.sources || [];
8095
- const allRisks = sources.flatMap((s) => s.findings || []);
8096
- const allLeaks = sources.flatMap((s) => s.leaks || []);
8171
+ if (data.status === 'complete' && data.summary) {
8172
+ const summary = data.summary;
8173
+ const byVerdict = summary.byVerdict || {};
8174
+ const sections = summary.sections || [];
8175
+ const leaks = summary.leaks || [];
8176
+ // Flatten rule findings across all sections so we can show a Top-N list
8177
+ const allRisks = [];
8178
+ for (const section of sections) {
8179
+ for (const rule of section.rules || []) {
8180
+ for (const f of rule.findings) {
8181
+ allRisks.push({ rule: rule.name, verdict: rule.verdict, ...f });
8182
+ }
8183
+ }
8184
+ }
8097
8185
 
8098
8186
  let risksHtml = '';
8099
8187
  if (allRisks.length > 0) {
@@ -8106,7 +8194,7 @@ var init_ui = __esm({
8106
8194
  (r) => \`
8107
8195
  <div class="finding-item">
8108
8196
  <span class="finding-ts">\${new Date(r.timestamp).toLocaleDateString([], { month: 'short', day: 'numeric' })}</span>
8109
- <span class="finding-rule">\${r.rule.replace(/^shield:[^:]+:/, '')}</span>
8197
+ <span class="finding-rule">\${esc(r.rule)}</span>
8110
8198
  <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>
8111
8199
  </div>
8112
8200
  \`
@@ -8117,18 +8205,18 @@ var init_ui = __esm({
8117
8205
  }
8118
8206
 
8119
8207
  let leaksHtml = '';
8120
- if (allLeaks.length > 0) {
8208
+ if (leaks.length > 0) {
8121
8209
  leaksHtml = \`
8122
8210
  <div class="findings-list" style="border-color: rgba(201, 60, 55, 0.3);">
8123
8211
  <div style="font-size: 10px; font-weight: 700; color: #f87171; margin-bottom: 8px; text-transform: uppercase;">Credential Leaks Identified</div>
8124
- \${allLeaks
8212
+ \${leaks
8125
8213
  .slice(0, 5)
8126
8214
  .map(
8127
8215
  (l) => \`
8128
8216
  <div class="finding-item">
8129
8217
  <span class="finding-ts">\${new Date(l.timestamp).toLocaleDateString([], { month: 'short', day: 'numeric' })}</span>
8130
- <span class="finding-rule">\${esc(l.pattern)}</span>
8131
- <span class="finding-cmd"><span class="finding-badge badge-leak">\u{1F511}</span>\${esc(l.sample)}</span>
8218
+ <span class="finding-rule">\${esc(l.patternName)}</span>
8219
+ <span class="finding-cmd"><span class="finding-badge badge-leak">\u{1F511}</span>\${esc(l.redactedSample)}</span>
8132
8220
  </div>
8133
8221
  \`
8134
8222
  )
@@ -8137,10 +8225,11 @@ var init_ui = __esm({
8137
8225
  \`;
8138
8226
  }
8139
8227
 
8228
+ const riskCount = (byVerdict.blocked || 0) + (byVerdict.supervised || 0);
8140
8229
  results.innerHTML = \`
8141
8230
  <div class="insight-hint" style="color: #57ab5a; border-color: rgba(87, 171, 90, 0.4); background: rgba(87, 171, 90, 0.08);">
8142
8231
  <strong>\u2705 History Audit Complete</strong><br>
8143
- Scanned \${data.summary.sessions} sessions. Found <strong>\${data.summary.findings} risky operations</strong> and <strong>\${data.summary.dlp} secret leaks</strong>.
8232
+ Scanned \${summary.stats?.sessions || 0} sessions. Found <strong>\${riskCount} risky operations</strong> and <strong>\${byVerdict.leaks || 0} secret leaks</strong>.
8144
8233
  Node9 is now actively protecting you from these types of events.
8145
8234
  </div>
8146
8235
  \${leaksHtml}
@@ -8179,6 +8268,10 @@ var init_ui2 = __esm({
8179
8268
 
8180
8269
  // src/cli/daemon-starter.ts
8181
8270
  import { spawn as spawn2, execSync } from "child_process";
8271
+ import path16 from "path";
8272
+ function isTestingMode() {
8273
+ return /^(1|true|yes)$/i.test(process.env.NODE9_TESTING ?? "");
8274
+ }
8182
8275
  function openBrowserLocal() {
8183
8276
  const url = `http://${DAEMON_HOST}:${DAEMON_PORT}/`;
8184
8277
  try {
@@ -8189,8 +8282,9 @@ function openBrowserLocal() {
8189
8282
  } catch {
8190
8283
  }
8191
8284
  }
8192
- async function autoStartDaemonAndWait() {
8193
- if (process.env.NODE9_TESTING === "1") return false;
8285
+ async function autoStartDaemonAndWait(openBrowser2 = true) {
8286
+ if (isTestingMode()) return false;
8287
+ if (!path16.isAbsolute(process.argv[1])) return false;
8194
8288
  try {
8195
8289
  const child = spawn2(process.execPath, [process.argv[1], "daemon"], {
8196
8290
  detached: true,
@@ -8208,7 +8302,9 @@ async function autoStartDaemonAndWait() {
8208
8302
  signal: AbortSignal.timeout(500)
8209
8303
  });
8210
8304
  if (res.ok) {
8211
- openBrowserLocal();
8305
+ if (openBrowser2) {
8306
+ openBrowserLocal();
8307
+ }
8212
8308
  return true;
8213
8309
  }
8214
8310
  } catch {
@@ -8225,10 +8321,199 @@ var init_daemon_starter = __esm({
8225
8321
  }
8226
8322
  });
8227
8323
 
8324
+ // src/scan-summary.ts
8325
+ function buildScanSummary(agents) {
8326
+ const stats = {
8327
+ sessions: 0,
8328
+ totalToolCalls: 0,
8329
+ bashCalls: 0,
8330
+ totalCostUSD: 0,
8331
+ firstDate: null,
8332
+ lastDate: null
8333
+ };
8334
+ for (const a of agents) {
8335
+ stats.sessions += a.scan.sessions;
8336
+ stats.totalToolCalls += a.scan.totalToolCalls;
8337
+ stats.bashCalls += a.scan.bashCalls;
8338
+ stats.totalCostUSD += a.scan.totalCostUSD;
8339
+ if (a.scan.firstDate && (!stats.firstDate || a.scan.firstDate < stats.firstDate)) {
8340
+ stats.firstDate = a.scan.firstDate;
8341
+ }
8342
+ if (a.scan.lastDate && (!stats.lastDate || a.scan.lastDate > stats.lastDate)) {
8343
+ stats.lastDate = a.scan.lastDate;
8344
+ }
8345
+ }
8346
+ const allFindings = agents.flatMap((a) => a.scan.findings);
8347
+ const allLeaks = agents.flatMap(
8348
+ (a) => a.scan.dlpFindings.map((f) => ({
8349
+ patternName: f.patternName,
8350
+ redactedSample: f.redactedSample,
8351
+ toolName: f.toolName,
8352
+ timestamp: f.timestamp,
8353
+ project: f.project,
8354
+ sessionId: f.sessionId,
8355
+ agent: f.agent
8356
+ }))
8357
+ );
8358
+ const allLoops = agents.flatMap(
8359
+ (a) => a.scan.loopFindings.map((f) => ({
8360
+ toolName: f.toolName,
8361
+ commandPreview: f.commandPreview,
8362
+ count: f.count,
8363
+ timestamp: f.timestamp,
8364
+ project: f.project,
8365
+ sessionId: f.sessionId,
8366
+ agent: f.agent
8367
+ }))
8368
+ );
8369
+ const byVerdict = {
8370
+ blocked: allFindings.filter((f) => f.source.rule.verdict === "block").length,
8371
+ supervised: allFindings.filter((f) => f.source.rule.verdict === "review").length,
8372
+ leaks: allLeaks.length,
8373
+ loops: allLoops.length
8374
+ };
8375
+ const byAgent = agents.map((a) => ({
8376
+ id: a.id,
8377
+ label: a.label,
8378
+ icon: a.icon,
8379
+ sessions: a.scan.sessions,
8380
+ findings: a.scan.findings.length + a.scan.dlpFindings.length + a.scan.loopFindings.length,
8381
+ costUSD: a.scan.totalCostUSD
8382
+ })).filter((s) => s.sessions > 0 || s.findings > 0);
8383
+ const sections = buildSections(allFindings);
8384
+ const wastedIters = allLoops.reduce(
8385
+ (sum, l) => sum + Math.max(0, l.count - LOOP_THRESHOLD_FOR_WASTE),
8386
+ 0
8387
+ );
8388
+ const loopWastedUSD = wastedIters * COST_PER_LOOP_ITER_USD;
8389
+ return {
8390
+ stats,
8391
+ byVerdict,
8392
+ byAgent,
8393
+ sections,
8394
+ leaks: allLeaks,
8395
+ loops: allLoops,
8396
+ loopWastedUSD
8397
+ };
8398
+ }
8399
+ function buildSections(findings) {
8400
+ const sectionMap = /* @__PURE__ */ new Map();
8401
+ function ensureSection(id, label, subtitle, sourceType, shieldKey) {
8402
+ let s = sectionMap.get(id);
8403
+ if (!s) {
8404
+ s = {
8405
+ id,
8406
+ label,
8407
+ subtitle,
8408
+ sourceType,
8409
+ shieldKey,
8410
+ blockedCount: 0,
8411
+ reviewCount: 0,
8412
+ rules: []
8413
+ };
8414
+ sectionMap.set(id, s);
8415
+ }
8416
+ return s;
8417
+ }
8418
+ const ruleMap = /* @__PURE__ */ new Map();
8419
+ for (const f of findings) {
8420
+ const src = f.source;
8421
+ const sourceType = src.sourceType;
8422
+ const shieldName = src.shieldName;
8423
+ const verdict = src.rule.verdict === "block" ? "block" : "review";
8424
+ let sectionId;
8425
+ let sectionLabel;
8426
+ let sectionSubtitle;
8427
+ let shieldKey;
8428
+ if (sourceType === "default") {
8429
+ sectionId = "default";
8430
+ sectionLabel = "Default Rules";
8431
+ sectionSubtitle = "built-in, always on";
8432
+ } else if (sourceType === "shield") {
8433
+ sectionId = `shield:${shieldName}`;
8434
+ sectionLabel = shieldName;
8435
+ sectionSubtitle = SHIELDS[shieldName]?.description ?? "";
8436
+ shieldKey = shieldName;
8437
+ } else if (shieldName === "cloud") {
8438
+ sectionId = "cloud";
8439
+ sectionLabel = "Cloud Policy";
8440
+ sectionSubtitle = "synced from node9 cloud";
8441
+ } else {
8442
+ sectionId = "user";
8443
+ sectionLabel = "Your Rules";
8444
+ sectionSubtitle = "added in node9.config.json";
8445
+ }
8446
+ const section = ensureSection(sectionId, sectionLabel, sectionSubtitle, sourceType, shieldKey);
8447
+ const ruleDisplayName = (src.rule.name ?? "unnamed").replace(/^shield:[^:]+:/, "");
8448
+ const ruleKey = sectionId + "::" + ruleDisplayName;
8449
+ let rule = ruleMap.get(ruleKey);
8450
+ if (!rule) {
8451
+ rule = {
8452
+ name: ruleDisplayName,
8453
+ verdict,
8454
+ reason: src.rule.reason ?? "",
8455
+ findings: []
8456
+ };
8457
+ ruleMap.set(ruleKey, rule);
8458
+ section.rules.push(rule);
8459
+ }
8460
+ const cmdPreview = previewCommand(f.input, 120);
8461
+ const fullCmd = fullCommandOf(f.input);
8462
+ const isDupe = rule.findings.some((x) => x.project === f.project && x.command === cmdPreview);
8463
+ if (!isDupe) {
8464
+ rule.findings.push({
8465
+ timestamp: f.timestamp ?? "",
8466
+ command: cmdPreview,
8467
+ fullCommand: fullCmd,
8468
+ project: f.project,
8469
+ sessionId: f.sessionId,
8470
+ agent: f.agent,
8471
+ toolName: f.toolName
8472
+ });
8473
+ }
8474
+ if (verdict === "block") section.blockedCount++;
8475
+ else section.reviewCount++;
8476
+ }
8477
+ const sections = [...sectionMap.values()];
8478
+ sections.sort((a, b) => {
8479
+ const aTotal = a.blockedCount + a.reviewCount;
8480
+ const bTotal = b.blockedCount + b.reviewCount;
8481
+ if (b.blockedCount !== a.blockedCount) return b.blockedCount - a.blockedCount;
8482
+ return bTotal - aTotal;
8483
+ });
8484
+ for (const s of sections) {
8485
+ s.rules.sort((a, b) => {
8486
+ const aBlock = a.verdict === "block" ? 1 : 0;
8487
+ const bBlock = b.verdict === "block" ? 1 : 0;
8488
+ if (bBlock !== aBlock) return bBlock - aBlock;
8489
+ return b.findings.length - a.findings.length;
8490
+ });
8491
+ }
8492
+ return sections;
8493
+ }
8494
+ function previewCommand(input, max) {
8495
+ const raw = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
8496
+ const s = String(raw).replace(/\s+/g, " ").trim();
8497
+ return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
8498
+ }
8499
+ function fullCommandOf(input) {
8500
+ const raw = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
8501
+ return String(raw).replace(/\s+/g, " ").trim();
8502
+ }
8503
+ var LOOP_THRESHOLD_FOR_WASTE, COST_PER_LOOP_ITER_USD;
8504
+ var init_scan_summary = __esm({
8505
+ "src/scan-summary.ts"() {
8506
+ "use strict";
8507
+ init_shields();
8508
+ LOOP_THRESHOLD_FOR_WASTE = 3;
8509
+ COST_PER_LOOP_ITER_USD = 6e-3;
8510
+ }
8511
+ });
8512
+
8228
8513
  // src/cli/commands/scan.ts
8229
8514
  import chalk2 from "chalk";
8230
8515
  import fs13 from "fs";
8231
- import path16 from "path";
8516
+ import path17 from "path";
8232
8517
  import os12 from "os";
8233
8518
  function claudeModelPrice(model) {
8234
8519
  const base = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
@@ -8269,10 +8554,6 @@ function preview(input, max) {
8269
8554
  const s = String(cmd).replace(/\s+/g, " ").trim();
8270
8555
  return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
8271
8556
  }
8272
- function fullCommand(input) {
8273
- const cmd = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
8274
- return String(cmd).replace(/\s+/g, " ").trim();
8275
- }
8276
8557
  function detectLoops(calls, project, sessionId, agent) {
8277
8558
  const counts = /* @__PURE__ */ new Map();
8278
8559
  for (const call of calls) {
@@ -8332,11 +8613,11 @@ function buildRuleSources() {
8332
8613
  }
8333
8614
  function countScanFiles() {
8334
8615
  let total = 0;
8335
- const claudeDir = path16.join(os12.homedir(), ".claude", "projects");
8616
+ const claudeDir = path17.join(os12.homedir(), ".claude", "projects");
8336
8617
  if (fs13.existsSync(claudeDir)) {
8337
8618
  try {
8338
8619
  for (const proj of fs13.readdirSync(claudeDir)) {
8339
- const p = path16.join(claudeDir, proj);
8620
+ const p = path17.join(claudeDir, proj);
8340
8621
  try {
8341
8622
  if (!fs13.statSync(p).isDirectory()) continue;
8342
8623
  total += fs13.readdirSync(p).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-")).length;
@@ -8347,14 +8628,14 @@ function countScanFiles() {
8347
8628
  } catch {
8348
8629
  }
8349
8630
  }
8350
- const geminiDir = path16.join(os12.homedir(), ".gemini", "tmp");
8631
+ const geminiDir = path17.join(os12.homedir(), ".gemini", "tmp");
8351
8632
  if (fs13.existsSync(geminiDir)) {
8352
8633
  try {
8353
8634
  for (const slug of fs13.readdirSync(geminiDir)) {
8354
- const p = path16.join(geminiDir, slug);
8635
+ const p = path17.join(geminiDir, slug);
8355
8636
  try {
8356
8637
  if (!fs13.statSync(p).isDirectory()) continue;
8357
- const chatsDir = path16.join(p, "chats");
8638
+ const chatsDir = path17.join(p, "chats");
8358
8639
  if (fs13.existsSync(chatsDir)) {
8359
8640
  try {
8360
8641
  total += fs13.readdirSync(chatsDir).filter((f) => f.endsWith(".json")).length;
@@ -8368,19 +8649,19 @@ function countScanFiles() {
8368
8649
  } catch {
8369
8650
  }
8370
8651
  }
8371
- const codexDir = path16.join(os12.homedir(), ".codex", "sessions");
8652
+ const codexDir = path17.join(os12.homedir(), ".codex", "sessions");
8372
8653
  if (fs13.existsSync(codexDir)) {
8373
8654
  try {
8374
8655
  for (const year of fs13.readdirSync(codexDir)) {
8375
- const yp = path16.join(codexDir, year);
8656
+ const yp = path17.join(codexDir, year);
8376
8657
  try {
8377
8658
  if (!fs13.statSync(yp).isDirectory()) continue;
8378
8659
  for (const month of fs13.readdirSync(yp)) {
8379
- const mp = path16.join(yp, month);
8660
+ const mp = path17.join(yp, month);
8380
8661
  try {
8381
8662
  if (!fs13.statSync(mp).isDirectory()) continue;
8382
8663
  for (const day of fs13.readdirSync(mp)) {
8383
- const dp = path16.join(mp, day);
8664
+ const dp = path17.join(mp, day);
8384
8665
  try {
8385
8666
  if (!fs13.statSync(dp).isDirectory()) continue;
8386
8667
  total += fs13.readdirSync(dp).filter((f) => f.endsWith(".jsonl")).length;
@@ -8412,7 +8693,7 @@ function renderProgressBar(done, total) {
8412
8693
  );
8413
8694
  }
8414
8695
  function scanClaudeHistory(startDate, onProgress) {
8415
- const projectsDir = path16.join(os12.homedir(), ".claude", "projects");
8696
+ const projectsDir = path17.join(os12.homedir(), ".claude", "projects");
8416
8697
  const result = {
8417
8698
  filesScanned: 0,
8418
8699
  sessions: 0,
@@ -8434,7 +8715,7 @@ function scanClaudeHistory(startDate, onProgress) {
8434
8715
  }
8435
8716
  const ruleSources = buildRuleSources();
8436
8717
  for (const proj of projDirs) {
8437
- const projPath = path16.join(projectsDir, proj);
8718
+ const projPath = path17.join(projectsDir, proj);
8438
8719
  try {
8439
8720
  if (!fs13.statSync(projPath).isDirectory()) continue;
8440
8721
  } catch {
@@ -8454,7 +8735,7 @@ function scanClaudeHistory(startDate, onProgress) {
8454
8735
  const sessionId = file.replace(/\.jsonl$/, "");
8455
8736
  let raw;
8456
8737
  try {
8457
- raw = fs13.readFileSync(path16.join(projPath, file), "utf-8");
8738
+ raw = fs13.readFileSync(path17.join(projPath, file), "utf-8");
8458
8739
  } catch {
8459
8740
  continue;
8460
8741
  }
@@ -8607,7 +8888,7 @@ function scanClaudeHistory(startDate, onProgress) {
8607
8888
  return result;
8608
8889
  }
8609
8890
  function scanGeminiHistory(startDate, onProgress) {
8610
- const tmpDir = path16.join(os12.homedir(), ".gemini", "tmp");
8891
+ const tmpDir = path17.join(os12.homedir(), ".gemini", "tmp");
8611
8892
  const result = {
8612
8893
  filesScanned: 0,
8613
8894
  sessions: 0,
@@ -8629,7 +8910,7 @@ function scanGeminiHistory(startDate, onProgress) {
8629
8910
  }
8630
8911
  const ruleSources = buildRuleSources();
8631
8912
  for (const slug of slugDirs) {
8632
- const slugPath = path16.join(tmpDir, slug);
8913
+ const slugPath = path17.join(tmpDir, slug);
8633
8914
  try {
8634
8915
  if (!fs13.statSync(slugPath).isDirectory()) continue;
8635
8916
  } catch {
@@ -8637,10 +8918,10 @@ function scanGeminiHistory(startDate, onProgress) {
8637
8918
  }
8638
8919
  let projLabel = slug;
8639
8920
  try {
8640
- projLabel = fs13.readFileSync(path16.join(slugPath, ".project_root"), "utf-8").trim().replace(os12.homedir(), "~").slice(0, 40);
8921
+ projLabel = fs13.readFileSync(path17.join(slugPath, ".project_root"), "utf-8").trim().replace(os12.homedir(), "~").slice(0, 40);
8641
8922
  } catch {
8642
8923
  }
8643
- const chatsDir = path16.join(slugPath, "chats");
8924
+ const chatsDir = path17.join(slugPath, "chats");
8644
8925
  if (!fs13.existsSync(chatsDir)) continue;
8645
8926
  let chatFiles;
8646
8927
  try {
@@ -8654,7 +8935,7 @@ function scanGeminiHistory(startDate, onProgress) {
8654
8935
  const sessionId = chatFile.replace(/\.json$/, "");
8655
8936
  let raw;
8656
8937
  try {
8657
- raw = fs13.readFileSync(path16.join(chatsDir, chatFile), "utf-8");
8938
+ raw = fs13.readFileSync(path17.join(chatsDir, chatFile), "utf-8");
8658
8939
  } catch {
8659
8940
  continue;
8660
8941
  }
@@ -8803,7 +9084,7 @@ function scanGeminiHistory(startDate, onProgress) {
8803
9084
  return result;
8804
9085
  }
8805
9086
  function scanCodexHistory(startDate, onProgress) {
8806
- const sessionsBase = path16.join(os12.homedir(), ".codex", "sessions");
9087
+ const sessionsBase = path17.join(os12.homedir(), ".codex", "sessions");
8807
9088
  const result = {
8808
9089
  filesScanned: 0,
8809
9090
  sessions: 0,
@@ -8820,28 +9101,28 @@ function scanCodexHistory(startDate, onProgress) {
8820
9101
  const jsonlFiles = [];
8821
9102
  try {
8822
9103
  for (const year of fs13.readdirSync(sessionsBase)) {
8823
- const yearPath = path16.join(sessionsBase, year);
9104
+ const yearPath = path17.join(sessionsBase, year);
8824
9105
  try {
8825
9106
  if (!fs13.statSync(yearPath).isDirectory()) continue;
8826
9107
  } catch {
8827
9108
  continue;
8828
9109
  }
8829
9110
  for (const month of fs13.readdirSync(yearPath)) {
8830
- const monthPath = path16.join(yearPath, month);
9111
+ const monthPath = path17.join(yearPath, month);
8831
9112
  try {
8832
9113
  if (!fs13.statSync(monthPath).isDirectory()) continue;
8833
9114
  } catch {
8834
9115
  continue;
8835
9116
  }
8836
9117
  for (const day of fs13.readdirSync(monthPath)) {
8837
- const dayPath = path16.join(monthPath, day);
9118
+ const dayPath = path17.join(monthPath, day);
8838
9119
  try {
8839
9120
  if (!fs13.statSync(dayPath).isDirectory()) continue;
8840
9121
  } catch {
8841
9122
  continue;
8842
9123
  }
8843
9124
  for (const file of fs13.readdirSync(dayPath)) {
8844
- if (file.endsWith(".jsonl")) jsonlFiles.push(path16.join(dayPath, file));
9125
+ if (file.endsWith(".jsonl")) jsonlFiles.push(path17.join(dayPath, file));
8845
9126
  }
8846
9127
  }
8847
9128
  }
@@ -9043,26 +9324,32 @@ function printFindingRow(f, drillDown, showSessionId, previewWidth) {
9043
9324
  const ts = f.timestamp ? chalk2.dim(fmtTs(f.timestamp) + " ") : "";
9044
9325
  const proj = chalk2.dim(f.project.slice(0, 22).padEnd(22) + " ");
9045
9326
  const agentBadge = f.agent === "gemini" ? chalk2.blue("[Gemini] ") : f.agent === "codex" ? chalk2.magenta("[Codex] ") : chalk2.cyan("[Claude] ");
9046
- const cmd = drillDown ? chalk2.gray(fullCommand(f.input)) : chalk2.gray(preview(f.input, previewWidth));
9327
+ let cmdText;
9328
+ if (drillDown) {
9329
+ cmdText = f.fullCommand;
9330
+ } else {
9331
+ cmdText = f.command;
9332
+ if (cmdText.length > previewWidth) cmdText = cmdText.slice(0, previewWidth - 1) + "\u2026";
9333
+ }
9334
+ const cmd = chalk2.gray(cmdText);
9047
9335
  const sessionSuffix = showSessionId && f.sessionId ? chalk2.dim(` \u2192 ${f.sessionId.slice(0, 8)}`) : "";
9048
9336
  console.log(` ${ts}${proj}${agentBadge}${cmd}${sessionSuffix}`);
9049
9337
  }
9050
- function printRuleGroup(ruleFindings, topN, drillDown, previewWidth) {
9051
- const rule = ruleFindings[0].source.rule;
9052
- const ruleCount = ruleFindings.length;
9338
+ function printRuleGroup(rule, topN, drillDown, previewWidth) {
9339
+ const findings = rule.findings;
9340
+ const ruleCount = findings.length;
9053
9341
  const countBadge = ruleCount > 1 ? chalk2.white(` \xD7${ruleCount}`) : "";
9054
- const shortName = (rule.name ?? "unnamed").replace(/^shield:[^:]+:/, "");
9055
- const icon = verdictIcon(rule.verdict ?? "review");
9342
+ const icon = verdictIcon(rule.verdict);
9056
9343
  console.log(
9057
- " " + icon + " " + chalk2.white(shortName) + countBadge + (rule.reason ? chalk2.dim(` \u2014 ${rule.reason}`) : "")
9344
+ " " + icon + " " + chalk2.white(rule.name) + countBadge + (rule.reason ? chalk2.dim(` \u2014 ${rule.reason}`) : "")
9058
9345
  );
9059
- const shown = drillDown ? ruleFindings : ruleFindings.slice(0, topN);
9346
+ const shown = drillDown ? findings : findings.slice(0, topN);
9060
9347
  for (const f of shown) {
9061
9348
  printFindingRow(f, drillDown, drillDown, previewWidth);
9062
9349
  }
9063
- if (!drillDown && ruleFindings.length > topN) {
9350
+ if (!drillDown && findings.length > topN) {
9064
9351
  console.log(
9065
- chalk2.dim(` \u2026 and ${ruleFindings.length - topN} more (--drill-down for full list)`)
9352
+ chalk2.dim(` \u2026 and ${findings.length - topN} more (--drill-down for full list)`)
9066
9353
  );
9067
9354
  }
9068
9355
  }
@@ -9077,7 +9364,7 @@ function registerScanCommand(program2) {
9077
9364
  d.setHours(0, 0, 0, 0);
9078
9365
  return d;
9079
9366
  })();
9080
- const isInstalled = fs13.existsSync(path16.join(os12.homedir(), ".node9", "audit.log"));
9367
+ const isInstalled = fs13.existsSync(path17.join(os12.homedir(), ".node9", "audit.log"));
9081
9368
  console.log("");
9082
9369
  if (!isInstalled) {
9083
9370
  console.log(
@@ -9109,6 +9396,11 @@ function registerScanCommand(program2) {
9109
9396
  (done) => onProgress(claudeScan.filesScanned + geminiScan.filesScanned + done)
9110
9397
  );
9111
9398
  const scan = mergeScans(mergeScans(claudeScan, geminiScan), codexScan);
9399
+ const summary = buildScanSummary([
9400
+ { id: "claude", label: "Claude", icon: "\u{1F916}", scan: claudeScan },
9401
+ { id: "gemini", label: "Gemini", icon: "\u264A", scan: geminiScan },
9402
+ { id: "codex", label: "Codex", icon: "\u{1F52E}", scan: codexScan }
9403
+ ]);
9112
9404
  process.stdout.write("\r" + " ".repeat(60) + "\r");
9113
9405
  if (scan.filesScanned === 0) {
9114
9406
  console.log(chalk2.yellow(" No session history found."));
@@ -9171,76 +9463,27 @@ function registerScanCommand(program2) {
9171
9463
  );
9172
9464
  }
9173
9465
  console.log("");
9174
- const sections = [];
9175
- const defaultFindings = scan.findings.filter((f) => f.source.sourceType === "default");
9176
- if (defaultFindings.length > 0) {
9177
- sections.push({
9178
- label: "Default Rules",
9179
- subtitle: "built-in, always on",
9180
- findings: defaultFindings
9181
- });
9182
- }
9183
- const byShield = /* @__PURE__ */ new Map();
9184
- for (const f of scan.findings.filter((f2) => f2.source.sourceType === "shield")) {
9185
- const arr = byShield.get(f.source.shieldName) ?? [];
9186
- arr.push(f);
9187
- byShield.set(f.source.shieldName, arr);
9188
- }
9189
- const shieldsWithFindings = [...byShield.entries()].sort(
9190
- (a, b) => b[1].length - a[1].length
9191
- );
9192
- for (const [shieldName, findings] of shieldsWithFindings) {
9193
- const description = SHIELDS[shieldName]?.description ?? "";
9194
- sections.push({
9195
- label: shieldName,
9196
- subtitle: description,
9197
- shieldKey: shieldName,
9198
- findings
9199
- });
9200
- }
9201
- const userFindings = scan.findings.filter(
9202
- (f) => f.source.sourceType === "user" || f.source.shieldName === "cloud"
9203
- );
9204
- if (userFindings.length > 0) {
9205
- sections.push({
9206
- label: "Your Rules",
9207
- subtitle: "added in node9.config.json",
9208
- findings: userFindings
9209
- });
9210
- }
9211
- for (const section of sections) {
9212
- const sectionBlocked = section.findings.filter(
9213
- (f) => f.source.rule.verdict === "block"
9214
- ).length;
9215
- const sectionReview = section.findings.length - sectionBlocked;
9466
+ for (const section of summary.sections) {
9216
9467
  const countParts = [];
9217
- if (sectionBlocked > 0) countParts.push(chalk2.red(`${sectionBlocked} blocked`));
9218
- if (sectionReview > 0) countParts.push(chalk2.yellow(`${sectionReview} review`));
9468
+ if (section.blockedCount > 0)
9469
+ countParts.push(chalk2.red(`${section.blockedCount} blocked`));
9470
+ if (section.reviewCount > 0)
9471
+ countParts.push(chalk2.yellow(`${section.reviewCount} review`));
9219
9472
  const countStr = countParts.join(chalk2.dim(" \xB7 "));
9220
9473
  const enableHint = section.shieldKey ? chalk2.dim(` \u2192 node9 shield enable ${section.shieldKey}`) : "";
9221
9474
  console.log(" " + chalk2.dim("\u2500".repeat(70)));
9222
9475
  console.log(
9223
9476
  " " + chalk2.bold(section.label) + (section.subtitle ? chalk2.dim(` \xB7 ${section.subtitle}`) : "") + " " + countStr + enableHint
9224
9477
  );
9225
- const byRule = /* @__PURE__ */ new Map();
9226
- for (const f of section.findings) {
9227
- const ruleKey = f.source.rule.name ?? "unnamed";
9228
- const arr = byRule.get(ruleKey) ?? [];
9229
- arr.push(f);
9230
- byRule.set(ruleKey, arr);
9231
- }
9232
- const sortedRules = [...byRule.entries()].sort((a, b) => {
9233
- const aBlock = a[1][0].source.rule.verdict === "block" ? 1 : 0;
9234
- const bBlock = b[1][0].source.rule.verdict === "block" ? 1 : 0;
9235
- if (bBlock !== aBlock) return bBlock - aBlock;
9236
- return b[1].length - a[1].length;
9237
- });
9238
- for (const [, ruleFindings] of sortedRules) {
9239
- printRuleGroup(ruleFindings, topN, drillDown, previewWidth);
9478
+ for (const rule of section.rules) {
9479
+ printRuleGroup(rule, topN, drillDown, previewWidth);
9240
9480
  }
9241
9481
  console.log("");
9242
9482
  }
9243
- const emptyShields = Object.keys(SHIELDS).filter((n) => !byShield.has(n)).sort();
9483
+ const activeShieldIds = new Set(
9484
+ summary.sections.filter((s) => s.sourceType === "shield" && s.shieldKey).map((s) => s.shieldKey)
9485
+ );
9486
+ const emptyShields = Object.keys(SHIELDS).filter((n) => !activeShieldIds.has(n)).sort();
9244
9487
  if (emptyShields.length > 0) {
9245
9488
  console.log(" " + chalk2.dim("\u2500".repeat(70)));
9246
9489
  console.log(
@@ -9346,82 +9589,16 @@ function registerScanCommand(program2) {
9346
9589
  console.log(" " + chalk2.dim("\u2192 ") + chalk2.underline("https://node9.ai"));
9347
9590
  }
9348
9591
  console.log("");
9349
- if (isDaemonRunning() && process.env.NODE9_TESTING !== "1") {
9592
+ if (!isTestingMode() && isDaemonRunning()) {
9350
9593
  const internalToken = getInternalToken();
9351
9594
  if (internalToken) {
9352
9595
  try {
9353
- const mapFindings = (arr, src) => (
9354
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
9355
- arr.map((f) => ({
9356
- timestamp: f.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
9357
- rule: f.source?.rule?.name ?? f.source?.shieldLabel ?? "unnamed",
9358
- command: f.input?.command ?? f.input?.cmd ?? f.input?.file_path ?? f.toolName ?? "unknown",
9359
- verdict: f.source?.rule?.verdict ?? f.source?.rule?.action ?? "review",
9360
- ruleSource: f.source?.sourceType ?? "default",
9361
- source: src
9362
- }))
9363
- );
9364
- const mapLeaks = (arr, src) => (
9365
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
9366
- arr.map((f) => ({
9367
- timestamp: f.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
9368
- pattern: f.patternName || "DLP",
9369
- sample: f.redactedSample || "********",
9370
- source: src
9371
- }))
9372
- );
9373
- const mapLoops = (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
- toolName: f.toolName || "unknown",
9378
- commandPreview: f.commandPreview || "",
9379
- count: f.count || 0,
9380
- source: src
9381
- }))
9382
- );
9383
- const sources = [
9384
- {
9385
- id: "claude",
9386
- label: "Claude",
9387
- icon: "\u{1F916}",
9388
- sessions: claudeScan.sessions,
9389
- findings: mapFindings(claudeScan.findings, "claude"),
9390
- leaks: mapLeaks(claudeScan.dlpFindings, "claude"),
9391
- loops: mapLoops(claudeScan.loopFindings, "claude")
9392
- },
9393
- {
9394
- id: "gemini",
9395
- label: "Gemini",
9396
- icon: "\u264A",
9397
- sessions: geminiScan.sessions,
9398
- findings: mapFindings(geminiScan.findings, "gemini"),
9399
- leaks: mapLeaks(geminiScan.dlpFindings, "gemini"),
9400
- loops: mapLoops(geminiScan.loopFindings, "gemini")
9401
- },
9402
- {
9403
- id: "codex",
9404
- label: "Codex",
9405
- icon: "\u{1F52E}",
9406
- sessions: codexScan.sessions,
9407
- findings: mapFindings(codexScan.findings, "codex"),
9408
- leaks: mapLeaks(codexScan.dlpFindings, "codex"),
9409
- loops: mapLoops(codexScan.loopFindings, "codex")
9410
- }
9411
- ].filter(
9412
- (s) => s.sessions > 0 || s.findings.length > 0 || s.leaks.length > 0 || s.loops.length > 0
9413
- );
9414
- const payload = {
9415
- status: "complete",
9416
- summary: {
9417
- sessions: scan.sessions,
9418
- findings: scan.findings.length,
9419
- dlp: scan.dlpFindings.length,
9420
- loops: scan.loopFindings.length,
9421
- totalCostUSD: scan.totalCostUSD
9422
- },
9423
- sources
9424
- };
9596
+ const summary2 = buildScanSummary([
9597
+ { id: "claude", label: "Claude", icon: "\u{1F916}", scan: claudeScan },
9598
+ { id: "gemini", label: "Gemini", icon: "\u264A", scan: geminiScan },
9599
+ { id: "codex", label: "Codex", icon: "\u{1F52E}", scan: codexScan }
9600
+ ]);
9601
+ const payload = { status: "complete", summary: summary2 };
9425
9602
  await fetch(`http://${DAEMON_HOST}:${DAEMON_PORT}/scan/push`, {
9426
9603
  method: "POST",
9427
9604
  headers: { "Content-Type": "application/json", "x-node9-internal": internalToken },
@@ -9448,6 +9625,7 @@ var init_scan = __esm({
9448
9625
  init_dlp();
9449
9626
  init_daemon();
9450
9627
  init_daemon_starter();
9628
+ init_scan_summary();
9451
9629
  CLAUDE_PRICING = {
9452
9630
  "claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
9453
9631
  "claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
@@ -9577,7 +9755,7 @@ var init_suggestion_tracker = __esm({
9577
9755
 
9578
9756
  // src/daemon/taint-store.ts
9579
9757
  import fs14 from "fs";
9580
- import path17 from "path";
9758
+ import path18 from "path";
9581
9759
  var DEFAULT_TTL_MS, TaintStore;
9582
9760
  var init_taint_store = __esm({
9583
9761
  "src/daemon/taint-store.ts"() {
@@ -9646,9 +9824,9 @@ var init_taint_store = __esm({
9646
9824
  /** Resolve to absolute path, falling back to path.resolve if file doesn't exist yet. */
9647
9825
  _resolve(filePath) {
9648
9826
  try {
9649
- return fs14.realpathSync.native(path17.resolve(filePath));
9827
+ return fs14.realpathSync.native(path18.resolve(filePath));
9650
9828
  } catch {
9651
- return path17.resolve(filePath);
9829
+ return path18.resolve(filePath);
9652
9830
  }
9653
9831
  }
9654
9832
  };
@@ -9766,7 +9944,7 @@ var init_session_history = __esm({
9766
9944
  // src/daemon/state.ts
9767
9945
  import net2 from "net";
9768
9946
  import fs15 from "fs";
9769
- import path18 from "path";
9947
+ import path19 from "path";
9770
9948
  import os13 from "os";
9771
9949
  import { spawn as spawn3 } from "child_process";
9772
9950
  import { randomUUID as randomUUID3 } from "crypto";
@@ -9816,7 +9994,7 @@ function setCachedScanResult(result) {
9816
9994
  cachedScanTs = Date.now();
9817
9995
  }
9818
9996
  function atomicWriteSync2(filePath, data, options) {
9819
- const dir = path18.dirname(filePath);
9997
+ const dir = path19.dirname(filePath);
9820
9998
  if (!fs15.existsSync(dir)) fs15.mkdirSync(dir, { recursive: true });
9821
9999
  const tmpPath = `${filePath}.${randomUUID3()}.tmp`;
9822
10000
  try {
@@ -9856,7 +10034,7 @@ function appendAuditLog(data) {
9856
10034
  decision: data.decision,
9857
10035
  source: "daemon"
9858
10036
  };
9859
- const dir = path18.dirname(AUDIT_LOG_FILE);
10037
+ const dir = path19.dirname(AUDIT_LOG_FILE);
9860
10038
  if (!fs15.existsSync(dir)) fs15.mkdirSync(dir, { recursive: true });
9861
10039
  fs15.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
9862
10040
  } catch {
@@ -10120,13 +10298,13 @@ var init_state2 = __esm({
10120
10298
  init_session_counters();
10121
10299
  init_session_history();
10122
10300
  homeDir = os13.homedir();
10123
- DAEMON_PID_FILE = path18.join(homeDir, ".node9", "daemon.pid");
10124
- DECISIONS_FILE = path18.join(homeDir, ".node9", "decisions.json");
10125
- AUDIT_LOG_FILE = path18.join(homeDir, ".node9", "audit.log");
10126
- TRUST_FILE2 = path18.join(homeDir, ".node9", "trust.json");
10127
- GLOBAL_CONFIG_FILE = path18.join(homeDir, ".node9", "config.json");
10128
- CREDENTIALS_FILE = path18.join(homeDir, ".node9", "credentials.json");
10129
- INSIGHT_COUNTS_FILE = path18.join(homeDir, ".node9", "insight-counts.json");
10301
+ DAEMON_PID_FILE = path19.join(homeDir, ".node9", "daemon.pid");
10302
+ DECISIONS_FILE = path19.join(homeDir, ".node9", "decisions.json");
10303
+ AUDIT_LOG_FILE = path19.join(homeDir, ".node9", "audit.log");
10304
+ TRUST_FILE2 = path19.join(homeDir, ".node9", "trust.json");
10305
+ GLOBAL_CONFIG_FILE = path19.join(homeDir, ".node9", "config.json");
10306
+ CREDENTIALS_FILE = path19.join(homeDir, ".node9", "credentials.json");
10307
+ INSIGHT_COUNTS_FILE = path19.join(homeDir, ".node9", "insight-counts.json");
10130
10308
  pending = /* @__PURE__ */ new Map();
10131
10309
  sseClients = /* @__PURE__ */ new Set();
10132
10310
  suggestionTracker = new SuggestionTracker(3);
@@ -10144,7 +10322,7 @@ var init_state2 = __esm({
10144
10322
  "2h": 2 * 60 * 6e4
10145
10323
  };
10146
10324
  autoStarted = process.env.NODE9_AUTO_STARTED === "1";
10147
- ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path18.join(os13.tmpdir(), "node9-activity.sock");
10325
+ ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path19.join(os13.tmpdir(), "node9-activity.sock");
10148
10326
  ACTIVITY_RING_SIZE = 100;
10149
10327
  activityRing = [];
10150
10328
  LARGE_RESPONSE_RING_SIZE = 20;
@@ -10172,7 +10350,7 @@ var init_state2 = __esm({
10172
10350
 
10173
10351
  // src/config/patch.ts
10174
10352
  import fs16 from "fs";
10175
- import path19 from "path";
10353
+ import path20 from "path";
10176
10354
  import os14 from "os";
10177
10355
  function patchConfig(configPath, patch) {
10178
10356
  let config = {};
@@ -10197,7 +10375,7 @@ function patchConfig(configPath, patch) {
10197
10375
  ignored.push(patch.toolName);
10198
10376
  }
10199
10377
  }
10200
- const dir = path19.dirname(configPath);
10378
+ const dir = path20.dirname(configPath);
10201
10379
  fs16.mkdirSync(dir, { recursive: true });
10202
10380
  const tmp = configPath + ".node9-tmp";
10203
10381
  try {
@@ -10223,13 +10401,13 @@ var GLOBAL_CONFIG_PATH;
10223
10401
  var init_patch = __esm({
10224
10402
  "src/config/patch.ts"() {
10225
10403
  "use strict";
10226
- GLOBAL_CONFIG_PATH = path19.join(os14.homedir(), ".node9", "config.json");
10404
+ GLOBAL_CONFIG_PATH = path20.join(os14.homedir(), ".node9", "config.json");
10227
10405
  }
10228
10406
  });
10229
10407
 
10230
10408
  // src/costSync.ts
10231
10409
  import fs17 from "fs";
10232
- import path20 from "path";
10410
+ import path21 from "path";
10233
10411
  import os15 from "os";
10234
10412
  function normalizeModel(raw) {
10235
10413
  return raw.replace(/-\d{8}$/, "");
@@ -10298,7 +10476,7 @@ function parseJSONLFile(filePath) {
10298
10476
  return daily;
10299
10477
  }
10300
10478
  function collectEntries() {
10301
- const projectsDir = path20.join(os15.homedir(), ".claude", "projects");
10479
+ const projectsDir = path21.join(os15.homedir(), ".claude", "projects");
10302
10480
  if (!fs17.existsSync(projectsDir)) return [];
10303
10481
  const combined = /* @__PURE__ */ new Map();
10304
10482
  let dirs;
@@ -10308,7 +10486,7 @@ function collectEntries() {
10308
10486
  return [];
10309
10487
  }
10310
10488
  for (const dir of dirs) {
10311
- const dirPath = path20.join(projectsDir, dir);
10489
+ const dirPath = path21.join(projectsDir, dir);
10312
10490
  try {
10313
10491
  if (!fs17.statSync(dirPath).isDirectory()) continue;
10314
10492
  } catch {
@@ -10321,7 +10499,7 @@ function collectEntries() {
10321
10499
  continue;
10322
10500
  }
10323
10501
  for (const file of files) {
10324
- const entries = parseJSONLFile(path20.join(dirPath, file));
10502
+ const entries = parseJSONLFile(path21.join(dirPath, file));
10325
10503
  for (const [key, e] of entries) {
10326
10504
  const prev = combined.get(key);
10327
10505
  if (prev) {
@@ -10397,7 +10575,7 @@ var init_costSync = __esm({
10397
10575
  import fs18 from "fs";
10398
10576
  import https from "https";
10399
10577
  import os16 from "os";
10400
- import path21 from "path";
10578
+ import path22 from "path";
10401
10579
  function readCredentials() {
10402
10580
  if (process.env.NODE9_API_KEY) {
10403
10581
  return {
@@ -10406,7 +10584,7 @@ function readCredentials() {
10406
10584
  };
10407
10585
  }
10408
10586
  try {
10409
- const credPath = path21.join(os16.homedir(), ".node9", "credentials.json");
10587
+ const credPath = path22.join(os16.homedir(), ".node9", "credentials.json");
10410
10588
  const creds = JSON.parse(fs18.readFileSync(credPath, "utf-8"));
10411
10589
  const profileName = process.env.NODE9_PROFILE ?? "default";
10412
10590
  const profile = creds[profileName];
@@ -10469,7 +10647,7 @@ async function syncOnce() {
10469
10647
  try {
10470
10648
  const rules = await fetchCloudRules(creds.apiKey, creds.apiUrl);
10471
10649
  const cache = { fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), rules };
10472
- const dir = path21.dirname(rulesCacheFile());
10650
+ const dir = path22.dirname(rulesCacheFile());
10473
10651
  if (!fs18.existsSync(dir)) fs18.mkdirSync(dir, { recursive: true });
10474
10652
  fs18.writeFileSync(rulesCacheFile(), JSON.stringify(cache, null, 2) + "\n", "utf-8");
10475
10653
  } catch {
@@ -10483,7 +10661,7 @@ async function runCloudSync() {
10483
10661
  try {
10484
10662
  const rules = await fetchCloudRules(creds.apiKey, creds.apiUrl);
10485
10663
  const cache = { fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), rules };
10486
- const dir = path21.dirname(rulesCacheFile());
10664
+ const dir = path22.dirname(rulesCacheFile());
10487
10665
  if (!fs18.existsSync(dir)) fs18.mkdirSync(dir, { recursive: true });
10488
10666
  fs18.writeFileSync(rulesCacheFile(), JSON.stringify(cache, null, 2) + "\n", "utf-8");
10489
10667
  return { ok: true, rules: rules.length, fetchedAt: cache.fetchedAt };
@@ -10522,7 +10700,7 @@ var init_sync = __esm({
10522
10700
  "src/daemon/sync.ts"() {
10523
10701
  "use strict";
10524
10702
  init_config();
10525
- rulesCacheFile = () => path21.join(os16.homedir(), ".node9", "rules-cache.json");
10703
+ rulesCacheFile = () => path22.join(os16.homedir(), ".node9", "rules-cache.json");
10526
10704
  DEFAULT_API_URL = "https://api.node9.ai/api/v1/policy";
10527
10705
  DEFAULT_INTERVAL_HOURS = 5;
10528
10706
  MIN_INTERVAL_HOURS = 1;
@@ -10531,7 +10709,7 @@ var init_sync = __esm({
10531
10709
 
10532
10710
  // src/daemon/dlp-scanner.ts
10533
10711
  import fs19 from "fs";
10534
- import path22 from "path";
10712
+ import path23 from "path";
10535
10713
  import os17 from "os";
10536
10714
  function loadIndex() {
10537
10715
  try {
@@ -10563,11 +10741,11 @@ function runDlpScan() {
10563
10741
  return;
10564
10742
  }
10565
10743
  for (const proj of projDirs) {
10566
- const projPath = path22.join(PROJECTS_DIR, proj);
10744
+ const projPath = path23.join(PROJECTS_DIR, proj);
10567
10745
  try {
10568
10746
  if (!fs19.lstatSync(projPath).isDirectory()) continue;
10569
10747
  const real = fs19.realpathSync(projPath);
10570
- if (!real.startsWith(PROJECTS_DIR + path22.sep) && real !== PROJECTS_DIR) continue;
10748
+ if (!real.startsWith(PROJECTS_DIR + path23.sep) && real !== PROJECTS_DIR) continue;
10571
10749
  } catch {
10572
10750
  continue;
10573
10751
  }
@@ -10578,7 +10756,7 @@ function runDlpScan() {
10578
10756
  continue;
10579
10757
  }
10580
10758
  for (const file of files) {
10581
- const filePath = path22.join(projPath, file);
10759
+ const filePath = path23.join(projPath, file);
10582
10760
  const lastOffset = index[filePath] ?? 0;
10583
10761
  let size;
10584
10762
  try {
@@ -10674,17 +10852,17 @@ var init_dlp_scanner = __esm({
10674
10852
  init_dlp();
10675
10853
  init_native();
10676
10854
  init_state2();
10677
- INDEX_FILE = path22.join(os17.homedir(), ".node9", "dlp-index.json");
10678
- PROJECTS_DIR = path22.join(os17.homedir(), ".claude", "projects");
10855
+ INDEX_FILE = path23.join(os17.homedir(), ".node9", "dlp-index.json");
10856
+ PROJECTS_DIR = path23.join(os17.homedir(), ".claude", "projects");
10679
10857
  }
10680
10858
  });
10681
10859
 
10682
10860
  // src/daemon/mcp-tools.ts
10683
10861
  import fs20 from "fs";
10684
- import path23 from "path";
10862
+ import path24 from "path";
10685
10863
  import os18 from "os";
10686
10864
  function getMcpToolsFile() {
10687
- return path23.join(os18.homedir(), ".node9", "mcp-tools.json");
10865
+ return path24.join(os18.homedir(), ".node9", "mcp-tools.json");
10688
10866
  }
10689
10867
  function readMcpToolsConfig() {
10690
10868
  try {
@@ -10699,7 +10877,7 @@ function readMcpToolsConfig() {
10699
10877
  function writeMcpToolsConfig(config) {
10700
10878
  try {
10701
10879
  const file = getMcpToolsFile();
10702
- const dir = path23.dirname(file);
10880
+ const dir = path24.dirname(file);
10703
10881
  if (!fs20.existsSync(dir)) fs20.mkdirSync(dir, { recursive: true });
10704
10882
  const tmpPath = `${file}.${os18.hostname()}.${process.pid}.tmp`;
10705
10883
  fs20.writeFileSync(tmpPath, JSON.stringify(config, null, 2));
@@ -10751,7 +10929,7 @@ var init_mcp_tools = __esm({
10751
10929
  // src/daemon/server.ts
10752
10930
  import http from "http";
10753
10931
  import fs21 from "fs";
10754
- import path24 from "path";
10932
+ import path25 from "path";
10755
10933
  import os19 from "os";
10756
10934
  import { randomUUID as randomUUID4 } from "crypto";
10757
10935
  import { spawnSync as spawnSync2 } from "child_process";
@@ -10946,7 +11124,7 @@ data: ${JSON.stringify(item.data)}
10946
11124
  status: "pending"
10947
11125
  });
10948
11126
  }
10949
- const projectCwd = typeof cwd === "string" && path24.isAbsolute(cwd) ? cwd : void 0;
11127
+ const projectCwd = typeof cwd === "string" && path25.isAbsolute(cwd) ? cwd : void 0;
10950
11128
  const projectConfig = getConfig(projectCwd);
10951
11129
  const browserEnabled = projectConfig.settings.approvers?.browser !== false;
10952
11130
  const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
@@ -11310,7 +11488,7 @@ data: ${JSON.stringify(item.data)}
11310
11488
  if (!validToken(req)) return res.writeHead(403).end();
11311
11489
  const periodParam = reqUrl.searchParams.get("period") || "7d";
11312
11490
  const period = ["today", "7d", "30d", "month"].includes(periodParam) ? periodParam : "7d";
11313
- const logPath = path24.join(os19.homedir(), ".node9", "audit.log");
11491
+ const logPath = path25.join(os19.homedir(), ".node9", "audit.log");
11314
11492
  if (!fs21.existsSync(logPath)) {
11315
11493
  res.writeHead(200, { "Content-Type": "application/json" });
11316
11494
  return res.end(
@@ -11430,9 +11608,21 @@ data: ${JSON.stringify(item.data)}
11430
11608
  const d = /* @__PURE__ */ new Date();
11431
11609
  d.setDate(d.getDate() - 90);
11432
11610
  d.setHours(0, 0, 0, 0);
11433
- let claude = { sessions: 0, findings: [], dlpFindings: [], loopFindings: [] };
11434
- let gemini = { sessions: 0, findings: [], dlpFindings: [], loopFindings: [] };
11435
- let codex = { sessions: 0, findings: [], dlpFindings: [], loopFindings: [] };
11611
+ const EMPTY_SCAN = {
11612
+ filesScanned: 0,
11613
+ sessions: 0,
11614
+ totalToolCalls: 0,
11615
+ bashCalls: 0,
11616
+ findings: [],
11617
+ dlpFindings: [],
11618
+ loopFindings: [],
11619
+ totalCostUSD: 0,
11620
+ firstDate: null,
11621
+ lastDate: null
11622
+ };
11623
+ let claude = EMPTY_SCAN;
11624
+ let gemini = EMPTY_SCAN;
11625
+ let codex = EMPTY_SCAN;
11436
11626
  try {
11437
11627
  claude = scanClaudeHistory(d);
11438
11628
  } catch (e) {
@@ -11448,86 +11638,13 @@ data: ${JSON.stringify(item.data)}
11448
11638
  } catch (e) {
11449
11639
  console.error("Codex scan failed:", e);
11450
11640
  }
11451
- const mapFindings = (arr, src) => (
11452
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11453
- arr.map((f) => ({
11454
- timestamp: f.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
11455
- rule: f.source?.rule?.name ?? f.source?.shieldLabel ?? "unnamed",
11456
- command: f.input?.command ?? f.input?.cmd ?? f.input?.file_path ?? f.toolName ?? "unknown",
11457
- verdict: f.source?.rule?.verdict ?? f.source?.rule?.action ?? "review",
11458
- ruleSource: f.source?.sourceType ?? "default",
11459
- source: src
11460
- }))
11461
- );
11462
- const mapLeaks = (arr, src) => (
11463
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11464
- arr.map((f) => ({
11465
- timestamp: f.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
11466
- pattern: f.patternName || "DLP",
11467
- sample: f.redactedSample || "********",
11468
- source: src
11469
- }))
11470
- );
11471
- const mapLoops = (arr, src) => (
11472
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11473
- arr.map((f) => ({
11474
- timestamp: f.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
11475
- toolName: f.toolName || "unknown",
11476
- commandPreview: f.commandPreview || "",
11477
- count: f.count || 0,
11478
- source: src
11479
- }))
11480
- );
11481
- const sources = [
11482
- {
11483
- id: "claude",
11484
- label: "Claude",
11485
- icon: "\u{1F916}",
11486
- sessions: claude.sessions,
11487
- findings: mapFindings(claude.findings, "claude"),
11488
- leaks: mapLeaks(claude.dlpFindings, "claude"),
11489
- loops: mapLoops(claude.loopFindings, "claude")
11490
- },
11491
- {
11492
- id: "gemini",
11493
- label: "Gemini",
11494
- icon: "\u264A",
11495
- sessions: gemini.sessions,
11496
- findings: mapFindings(gemini.findings, "gemini"),
11497
- leaks: mapLeaks(gemini.dlpFindings, "gemini"),
11498
- loops: mapLoops(gemini.loopFindings, "gemini")
11499
- },
11500
- {
11501
- id: "codex",
11502
- label: "Codex",
11503
- icon: "\u{1F52E}",
11504
- sessions: codex.sessions,
11505
- findings: mapFindings(codex.findings, "codex"),
11506
- leaks: mapLeaks(codex.dlpFindings, "codex"),
11507
- loops: mapLoops(codex.loopFindings, "codex")
11508
- }
11509
- ].filter(
11510
- (s) => s.sessions > 0 || s.findings.length > 0 || s.leaks.length > 0 || s.loops.length > 0
11511
- );
11512
- const totalSessions = claude.sessions + gemini.sessions + codex.sessions;
11513
- const totalFindings = claude.findings.length + gemini.findings.length + codex.findings.length;
11514
- const totalDlp = claude.dlpFindings.length + gemini.dlpFindings.length + codex.dlpFindings.length;
11515
- const totalLoops = claude.loopFindings.length + gemini.loopFindings.length + codex.loopFindings.length;
11516
- const totalCostUSD = (claude.totalCostUSD || 0) + (gemini.totalCostUSD || 0) + (codex.totalCostUSD || 0);
11641
+ const summary = buildScanSummary([
11642
+ { id: "claude", label: "Claude", icon: "\u{1F916}", scan: claude },
11643
+ { id: "gemini", label: "Gemini", icon: "\u264A", scan: gemini },
11644
+ { id: "codex", label: "Codex", icon: "\u{1F52E}", scan: codex }
11645
+ ]);
11517
11646
  res.writeHead(200, { "Content-Type": "application/json" });
11518
- return res.end(
11519
- JSON.stringify({
11520
- status: "complete",
11521
- summary: {
11522
- sessions: totalSessions,
11523
- totalCostUSD,
11524
- findings: totalFindings,
11525
- dlp: totalDlp,
11526
- loops: totalLoops
11527
- },
11528
- sources
11529
- })
11530
- );
11647
+ return res.end(JSON.stringify({ status: "complete", summary }));
11531
11648
  } catch (err2) {
11532
11649
  console.error("Scan failed:", err2);
11533
11650
  res.writeHead(500, { "Content-Type": "application/json" });
@@ -11565,8 +11682,8 @@ data: ${JSON.stringify(item.data)}
11565
11682
  const body = await readBody(req);
11566
11683
  const data = body ? JSON.parse(body) : {};
11567
11684
  const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
11568
- const node9Dir = path24.dirname(GLOBAL_CONFIG_PATH);
11569
- if (!path24.resolve(configPath).startsWith(node9Dir + path24.sep)) {
11685
+ const node9Dir = path25.dirname(GLOBAL_CONFIG_PATH);
11686
+ if (!path25.resolve(configPath).startsWith(node9Dir + path25.sep)) {
11570
11687
  res.writeHead(400, { "Content-Type": "application/json" });
11571
11688
  return res.end(
11572
11689
  JSON.stringify({ error: "configPath must be within the node9 config directory" })
@@ -11685,6 +11802,11 @@ data: ${JSON.stringify(item.data)}
11685
11802
  return;
11686
11803
  }
11687
11804
  approveServer(serverKey, disabledTools);
11805
+ appendAuditLog({
11806
+ toolName: `mcp-server:${serverKey}`,
11807
+ args: { disabledTools },
11808
+ decision: "allow"
11809
+ });
11688
11810
  broadcast("mcp-tools-updated", { serverKey, disabledTools });
11689
11811
  res.writeHead(200).end();
11690
11812
  return;
@@ -11703,6 +11825,11 @@ data: ${JSON.stringify(item.data)}
11703
11825
  }
11704
11826
  const status = updateServerDiscovery(serverKey, tools);
11705
11827
  if (status === "new" || status === "drift") {
11828
+ appendAuditLog({
11829
+ toolName: `mcp-server:${serverKey}`,
11830
+ args: { toolCount: tools.length, status },
11831
+ decision: "mcp-discovered"
11832
+ });
11706
11833
  const id = randomUUID4();
11707
11834
  const entry = {
11708
11835
  id,
@@ -11771,6 +11898,11 @@ data: ${JSON.stringify(item.data)}
11771
11898
  }
11772
11899
  clearTimeout(entry.timer);
11773
11900
  approveServer(serverKey, disabledTools);
11901
+ appendAuditLog({
11902
+ toolName: `mcp-server:${serverKey}`,
11903
+ args: { disabledTools },
11904
+ decision: "allow"
11905
+ });
11774
11906
  pending.delete(id);
11775
11907
  broadcast("remove", { id, decision: "allow" });
11776
11908
  res.writeHead(200).end();
@@ -11866,6 +11998,7 @@ var init_server = __esm({
11866
11998
  init_shields();
11867
11999
  init_ui2();
11868
12000
  init_scan();
12001
+ init_scan_summary();
11869
12002
  init_state2();
11870
12003
  init_state();
11871
12004
  init_patch();
@@ -11879,13 +12012,13 @@ var init_server = __esm({
11879
12012
 
11880
12013
  // src/daemon/service.ts
11881
12014
  import fs22 from "fs";
11882
- import path25 from "path";
12015
+ import path26 from "path";
11883
12016
  import os20 from "os";
11884
12017
  import { spawnSync as spawnSync3, execFileSync } from "child_process";
11885
12018
  function resolveNode9Binary() {
11886
12019
  try {
11887
12020
  const script = process.argv[1];
11888
- if (typeof script === "string" && path25.isAbsolute(script) && fs22.existsSync(script)) {
12021
+ if (typeof script === "string" && path26.isAbsolute(script) && fs22.existsSync(script)) {
11889
12022
  return fs22.realpathSync(script);
11890
12023
  }
11891
12024
  } catch {
@@ -11904,11 +12037,11 @@ function xmlEscape(s) {
11904
12037
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
11905
12038
  }
11906
12039
  function launchdPlist(binaryPath) {
11907
- const logDir = path25.join(os20.homedir(), ".node9");
12040
+ const logDir = path26.join(os20.homedir(), ".node9");
11908
12041
  const nodePath = xmlEscape(process.execPath);
11909
12042
  const scriptPath = xmlEscape(binaryPath);
11910
- const outLog = xmlEscape(path25.join(logDir, "daemon.log"));
11911
- const errLog = xmlEscape(path25.join(logDir, "daemon-error.log"));
12043
+ const outLog = xmlEscape(path26.join(logDir, "daemon.log"));
12044
+ const errLog = xmlEscape(path26.join(logDir, "daemon-error.log"));
11912
12045
  return `<?xml version="1.0" encoding="UTF-8"?>
11913
12046
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
11914
12047
  <plist version="1.0">
@@ -11943,7 +12076,7 @@ function launchdPlist(binaryPath) {
11943
12076
  `;
11944
12077
  }
11945
12078
  function installLaunchd(binaryPath) {
11946
- const dir = path25.dirname(LAUNCHD_PLIST);
12079
+ const dir = path26.dirname(LAUNCHD_PLIST);
11947
12080
  if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
11948
12081
  fs22.writeFileSync(LAUNCHD_PLIST, launchdPlist(binaryPath), "utf-8");
11949
12082
  spawnSync3("launchctl", ["unload", LAUNCHD_PLIST], { encoding: "utf8" });
@@ -12020,7 +12153,7 @@ function isSystemdInstalled() {
12020
12153
  return fs22.existsSync(SYSTEMD_UNIT);
12021
12154
  }
12022
12155
  function stopRunningDaemon() {
12023
- const pidFile = path25.join(os20.homedir(), ".node9", "daemon.pid");
12156
+ const pidFile = path26.join(os20.homedir(), ".node9", "daemon.pid");
12024
12157
  if (!fs22.existsSync(pidFile)) return;
12025
12158
  try {
12026
12159
  const data = JSON.parse(fs22.readFileSync(pidFile, "utf-8"));
@@ -12118,9 +12251,9 @@ var init_service = __esm({
12118
12251
  "src/daemon/service.ts"() {
12119
12252
  "use strict";
12120
12253
  LAUNCHD_LABEL = "ai.node9.daemon";
12121
- LAUNCHD_PLIST = path25.join(os20.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
12122
- SYSTEMD_UNIT_DIR = path25.join(os20.homedir(), ".config", "systemd", "user");
12123
- SYSTEMD_UNIT = path25.join(SYSTEMD_UNIT_DIR, "node9-daemon.service");
12254
+ LAUNCHD_PLIST = path26.join(os20.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
12255
+ SYSTEMD_UNIT_DIR = path26.join(os20.homedir(), ".config", "systemd", "user");
12256
+ SYSTEMD_UNIT = path26.join(SYSTEMD_UNIT_DIR, "node9-daemon.service");
12124
12257
  }
12125
12258
  });
12126
12259
 
@@ -12206,7 +12339,7 @@ import http2 from "http";
12206
12339
  import chalk25 from "chalk";
12207
12340
  import fs38 from "fs";
12208
12341
  import os35 from "os";
12209
- import path41 from "path";
12342
+ import path42 from "path";
12210
12343
  import readline5 from "readline";
12211
12344
  import { spawn as spawn10, execSync as execSync3 } from "child_process";
12212
12345
  function getIcon(tool) {
@@ -12224,18 +12357,18 @@ function getModelContextLimit(model) {
12224
12357
  return 2e5;
12225
12358
  }
12226
12359
  function readSessionUsage() {
12227
- const projectsDir = path41.join(os35.homedir(), ".claude", "projects");
12360
+ const projectsDir = path42.join(os35.homedir(), ".claude", "projects");
12228
12361
  if (!fs38.existsSync(projectsDir)) return null;
12229
12362
  let latestFile = null;
12230
12363
  let latestMtime = 0;
12231
12364
  try {
12232
12365
  for (const dir of fs38.readdirSync(projectsDir)) {
12233
- const dirPath = path41.join(projectsDir, dir);
12366
+ const dirPath = path42.join(projectsDir, dir);
12234
12367
  try {
12235
12368
  if (!fs38.statSync(dirPath).isDirectory()) continue;
12236
12369
  for (const file of fs38.readdirSync(dirPath)) {
12237
12370
  if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
12238
- const filePath = path41.join(dirPath, file);
12371
+ const filePath = path42.join(dirPath, file);
12239
12372
  try {
12240
12373
  const mtime = fs38.statSync(filePath).mtimeMs;
12241
12374
  if (mtime > latestMtime) {
@@ -12496,7 +12629,7 @@ function buildRecoveryCardLines(req) {
12496
12629
  ];
12497
12630
  }
12498
12631
  function readApproversFromDisk() {
12499
- const configPath = path41.join(os35.homedir(), ".node9", "config.json");
12632
+ const configPath = path42.join(os35.homedir(), ".node9", "config.json");
12500
12633
  try {
12501
12634
  const raw = JSON.parse(fs38.readFileSync(configPath, "utf-8"));
12502
12635
  const settings = raw.settings ?? {};
@@ -12514,7 +12647,7 @@ function approverStatusLine() {
12514
12647
  return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
12515
12648
  }
12516
12649
  function toggleApprover(channel) {
12517
- const configPath = path41.join(os35.homedir(), ".node9", "config.json");
12650
+ const configPath = path42.join(os35.homedir(), ".node9", "config.json");
12518
12651
  try {
12519
12652
  const raw = JSON.parse(fs38.readFileSync(configPath, "utf-8"));
12520
12653
  const settings = raw.settings ?? {};
@@ -12693,7 +12826,7 @@ async function startTail(options = {}) {
12693
12826
  postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
12694
12827
  try {
12695
12828
  fs38.appendFileSync(
12696
- path41.join(os35.homedir(), ".node9", "hook-debug.log"),
12829
+ path42.join(os35.homedir(), ".node9", "hook-debug.log"),
12697
12830
  `[tail] POST /decision failed: ${String(err2)}
12698
12831
  `
12699
12832
  );
@@ -12774,7 +12907,7 @@ async function startTail(options = {}) {
12774
12907
  }
12775
12908
  } catch {
12776
12909
  }
12777
- const auditLog = path41.join(os35.homedir(), ".node9", "audit.log");
12910
+ const auditLog = path42.join(os35.homedir(), ".node9", "audit.log");
12778
12911
  try {
12779
12912
  const unackedDlp = fs38.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
12780
12913
  if (unackedDlp > 0) {
@@ -12968,7 +13101,7 @@ var init_tail = __esm({
12968
13101
  init_daemon2();
12969
13102
  init_daemon();
12970
13103
  init_core();
12971
- PID_FILE = path41.join(os35.homedir(), ".node9", "daemon.pid");
13104
+ PID_FILE = path42.join(os35.homedir(), ".node9", "daemon.pid");
12972
13105
  ICONS = {
12973
13106
  bash: "\u{1F4BB}",
12974
13107
  shell: "\u{1F4BB}",
@@ -13017,7 +13150,7 @@ __export(hud_exports, {
13017
13150
  renderEnvironmentLine: () => renderEnvironmentLine
13018
13151
  });
13019
13152
  import fs39 from "fs";
13020
- import path42 from "path";
13153
+ import path43 from "path";
13021
13154
  import os36 from "os";
13022
13155
  import http3 from "http";
13023
13156
  async function readStdin() {
@@ -13122,7 +13255,7 @@ function countRulesInDir(rulesDir) {
13122
13255
  try {
13123
13256
  for (const entry of fs39.readdirSync(rulesDir, { withFileTypes: true })) {
13124
13257
  if (entry.isDirectory()) {
13125
- count += countRulesInDir(path42.join(rulesDir, entry.name));
13258
+ count += countRulesInDir(path43.join(rulesDir, entry.name));
13126
13259
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
13127
13260
  count++;
13128
13261
  }
@@ -13133,46 +13266,46 @@ function countRulesInDir(rulesDir) {
13133
13266
  }
13134
13267
  function isSamePath(a, b) {
13135
13268
  try {
13136
- return path42.resolve(a) === path42.resolve(b);
13269
+ return path43.resolve(a) === path43.resolve(b);
13137
13270
  } catch {
13138
13271
  return false;
13139
13272
  }
13140
13273
  }
13141
13274
  function countConfigs(cwd) {
13142
13275
  const homeDir2 = os36.homedir();
13143
- const claudeDir = path42.join(homeDir2, ".claude");
13276
+ const claudeDir = path43.join(homeDir2, ".claude");
13144
13277
  let claudeMdCount = 0;
13145
13278
  let rulesCount = 0;
13146
13279
  let hooksCount = 0;
13147
13280
  const userMcpServers = /* @__PURE__ */ new Set();
13148
13281
  const projectMcpServers = /* @__PURE__ */ new Set();
13149
- if (fs39.existsSync(path42.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
13150
- rulesCount += countRulesInDir(path42.join(claudeDir, "rules"));
13151
- const userSettings = path42.join(claudeDir, "settings.json");
13282
+ if (fs39.existsSync(path43.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
13283
+ rulesCount += countRulesInDir(path43.join(claudeDir, "rules"));
13284
+ const userSettings = path43.join(claudeDir, "settings.json");
13152
13285
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
13153
13286
  hooksCount += countHooksInFile(userSettings);
13154
- const userClaudeJson = path42.join(homeDir2, ".claude.json");
13287
+ const userClaudeJson = path43.join(homeDir2, ".claude.json");
13155
13288
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
13156
13289
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
13157
13290
  userMcpServers.delete(name);
13158
13291
  }
13159
13292
  if (cwd) {
13160
- if (fs39.existsSync(path42.join(cwd, "CLAUDE.md"))) claudeMdCount++;
13161
- if (fs39.existsSync(path42.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
13162
- const projectClaudeDir = path42.join(cwd, ".claude");
13293
+ if (fs39.existsSync(path43.join(cwd, "CLAUDE.md"))) claudeMdCount++;
13294
+ if (fs39.existsSync(path43.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
13295
+ const projectClaudeDir = path43.join(cwd, ".claude");
13163
13296
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
13164
13297
  if (!overlapsUserScope) {
13165
- if (fs39.existsSync(path42.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
13166
- rulesCount += countRulesInDir(path42.join(projectClaudeDir, "rules"));
13167
- const projSettings = path42.join(projectClaudeDir, "settings.json");
13298
+ if (fs39.existsSync(path43.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
13299
+ rulesCount += countRulesInDir(path43.join(projectClaudeDir, "rules"));
13300
+ const projSettings = path43.join(projectClaudeDir, "settings.json");
13168
13301
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
13169
13302
  hooksCount += countHooksInFile(projSettings);
13170
13303
  }
13171
- if (fs39.existsSync(path42.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
13172
- const localSettings = path42.join(projectClaudeDir, "settings.local.json");
13304
+ if (fs39.existsSync(path43.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
13305
+ const localSettings = path43.join(projectClaudeDir, "settings.local.json");
13173
13306
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
13174
13307
  hooksCount += countHooksInFile(localSettings);
13175
- const mcpJsonServers = getMcpServerNames(path42.join(cwd, ".mcp.json"));
13308
+ const mcpJsonServers = getMcpServerNames(path43.join(cwd, ".mcp.json"));
13176
13309
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
13177
13310
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
13178
13311
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -13205,7 +13338,7 @@ function readActiveShieldsHud() {
13205
13338
  return shieldsCache.value;
13206
13339
  }
13207
13340
  try {
13208
- const shieldsPath = path42.join(os36.homedir(), ".node9", "shields.json");
13341
+ const shieldsPath = path43.join(os36.homedir(), ".node9", "shields.json");
13209
13342
  if (!fs39.existsSync(shieldsPath)) {
13210
13343
  shieldsCache = { value: [], ts: now };
13211
13344
  return [];
@@ -13312,9 +13445,9 @@ function renderContextLine(stdin) {
13312
13445
  async function main() {
13313
13446
  try {
13314
13447
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
13315
- if (fs39.existsSync(path42.join(os36.homedir(), ".node9", "hud-debug"))) {
13448
+ if (fs39.existsSync(path43.join(os36.homedir(), ".node9", "hud-debug"))) {
13316
13449
  try {
13317
- const logPath = path42.join(os36.homedir(), ".node9", "hud-debug.log");
13450
+ const logPath = path43.join(os36.homedir(), ".node9", "hud-debug.log");
13318
13451
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
13319
13452
  let size = 0;
13320
13453
  try {
@@ -13343,8 +13476,8 @@ async function main() {
13343
13476
  try {
13344
13477
  const cwd = stdin.cwd ?? process.cwd();
13345
13478
  for (const configPath of [
13346
- path42.join(cwd, "node9.config.json"),
13347
- path42.join(os36.homedir(), ".node9", "config.json")
13479
+ path43.join(cwd, "node9.config.json"),
13480
+ path43.join(os36.homedir(), ".node9", "config.json")
13348
13481
  ]) {
13349
13482
  if (!fs39.existsSync(configPath)) continue;
13350
13483
  const cfg = JSON.parse(fs39.readFileSync(configPath, "utf-8"));
@@ -14416,7 +14549,7 @@ function getAgentsStatus(homeDir2 = os11.homedir()) {
14416
14549
  init_daemon2();
14417
14550
  import chalk26 from "chalk";
14418
14551
  import fs40 from "fs";
14419
- import path43 from "path";
14552
+ import path44 from "path";
14420
14553
  import os37 from "os";
14421
14554
  import { confirm as confirm2 } from "@inquirer/prompts";
14422
14555
 
@@ -14608,7 +14741,7 @@ init_policy();
14608
14741
  import chalk6 from "chalk";
14609
14742
  import fs26 from "fs";
14610
14743
  import { spawn as spawn6 } from "child_process";
14611
- import path28 from "path";
14744
+ import path29 from "path";
14612
14745
  import os23 from "os";
14613
14746
 
14614
14747
  // src/undo.ts
@@ -14616,9 +14749,9 @@ import { spawnSync as spawnSync5, spawn as spawn5 } from "child_process";
14616
14749
  import crypto3 from "crypto";
14617
14750
  import fs24 from "fs";
14618
14751
  import net3 from "net";
14619
- import path26 from "path";
14752
+ import path27 from "path";
14620
14753
  import os21 from "os";
14621
- var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path26.join(os21.tmpdir(), "node9-activity.sock");
14754
+ var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path27.join(os21.tmpdir(), "node9-activity.sock");
14622
14755
  function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
14623
14756
  try {
14624
14757
  const payload = JSON.stringify({
@@ -14638,8 +14771,8 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
14638
14771
  } catch {
14639
14772
  }
14640
14773
  }
14641
- var SNAPSHOT_STACK_PATH = path26.join(os21.homedir(), ".node9", "snapshots.json");
14642
- var UNDO_LATEST_PATH = path26.join(os21.homedir(), ".node9", "undo_latest.txt");
14774
+ var SNAPSHOT_STACK_PATH = path27.join(os21.homedir(), ".node9", "snapshots.json");
14775
+ var UNDO_LATEST_PATH = path27.join(os21.homedir(), ".node9", "undo_latest.txt");
14643
14776
  var MAX_SNAPSHOTS = 10;
14644
14777
  var GIT_TIMEOUT = 15e3;
14645
14778
  function readStack() {
@@ -14651,7 +14784,7 @@ function readStack() {
14651
14784
  return [];
14652
14785
  }
14653
14786
  function writeStack(stack) {
14654
- const dir = path26.dirname(SNAPSHOT_STACK_PATH);
14787
+ const dir = path27.dirname(SNAPSHOT_STACK_PATH);
14655
14788
  if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
14656
14789
  fs24.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
14657
14790
  }
@@ -14673,12 +14806,12 @@ function buildArgsSummary(tool, args) {
14673
14806
  return "";
14674
14807
  }
14675
14808
  function findProjectRoot(filePath) {
14676
- let dir = path26.dirname(filePath);
14809
+ let dir = path27.dirname(filePath);
14677
14810
  while (true) {
14678
- if (fs24.existsSync(path26.join(dir, ".git")) || fs24.existsSync(path26.join(dir, "package.json"))) {
14811
+ if (fs24.existsSync(path27.join(dir, ".git")) || fs24.existsSync(path27.join(dir, "package.json"))) {
14679
14812
  return dir;
14680
14813
  }
14681
- const parent = path26.dirname(dir);
14814
+ const parent = path27.dirname(dir);
14682
14815
  if (parent === dir) return process.cwd();
14683
14816
  dir = parent;
14684
14817
  }
@@ -14696,14 +14829,14 @@ function normalizeCwdForHash(cwd) {
14696
14829
  }
14697
14830
  function getShadowRepoDir(cwd) {
14698
14831
  const hash = crypto3.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
14699
- return path26.join(os21.homedir(), ".node9", "snapshots", hash);
14832
+ return path27.join(os21.homedir(), ".node9", "snapshots", hash);
14700
14833
  }
14701
14834
  function cleanOrphanedIndexFiles(shadowDir) {
14702
14835
  try {
14703
14836
  const cutoff = Date.now() - 6e4;
14704
14837
  for (const f of fs24.readdirSync(shadowDir)) {
14705
14838
  if (f.startsWith("index_")) {
14706
- const fp = path26.join(shadowDir, f);
14839
+ const fp = path27.join(shadowDir, f);
14707
14840
  try {
14708
14841
  if (fs24.statSync(fp).mtimeMs < cutoff) fs24.unlinkSync(fp);
14709
14842
  } catch {
@@ -14717,7 +14850,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
14717
14850
  const hardcoded = [".git", ".node9"];
14718
14851
  const lines = [...hardcoded, ...ignorePaths].join("\n");
14719
14852
  try {
14720
- fs24.writeFileSync(path26.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
14853
+ fs24.writeFileSync(path27.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
14721
14854
  } catch {
14722
14855
  }
14723
14856
  }
@@ -14730,7 +14863,7 @@ function ensureShadowRepo(shadowDir, cwd) {
14730
14863
  timeout: 3e3
14731
14864
  });
14732
14865
  if (check.status === 0) {
14733
- const ptPath = path26.join(shadowDir, "project-path.txt");
14866
+ const ptPath = path27.join(shadowDir, "project-path.txt");
14734
14867
  try {
14735
14868
  const stored = fs24.readFileSync(ptPath, "utf8").trim();
14736
14869
  if (stored === normalizedCwd) return true;
@@ -14757,7 +14890,7 @@ function ensureShadowRepo(shadowDir, cwd) {
14757
14890
  if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
14758
14891
  return false;
14759
14892
  }
14760
- const configFile = path26.join(shadowDir, "config");
14893
+ const configFile = path27.join(shadowDir, "config");
14761
14894
  spawnSync5("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
14762
14895
  timeout: 3e3
14763
14896
  });
@@ -14765,7 +14898,7 @@ function ensureShadowRepo(shadowDir, cwd) {
14765
14898
  timeout: 3e3
14766
14899
  });
14767
14900
  try {
14768
- fs24.writeFileSync(path26.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
14901
+ fs24.writeFileSync(path27.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
14769
14902
  } catch {
14770
14903
  }
14771
14904
  return true;
@@ -14785,12 +14918,12 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
14785
14918
  let indexFile = null;
14786
14919
  try {
14787
14920
  const rawFilePath = extractFilePath(args);
14788
- const absFilePath = rawFilePath && path26.isAbsolute(rawFilePath) ? rawFilePath : null;
14921
+ const absFilePath = rawFilePath && path27.isAbsolute(rawFilePath) ? rawFilePath : null;
14789
14922
  const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
14790
14923
  const shadowDir = getShadowRepoDir(cwd);
14791
14924
  if (!ensureShadowRepo(shadowDir, cwd)) return null;
14792
14925
  writeShadowExcludes(shadowDir, ignorePaths);
14793
- indexFile = path26.join(shadowDir, `index_${process.pid}_${Date.now()}`);
14926
+ indexFile = path27.join(shadowDir, `index_${process.pid}_${Date.now()}`);
14794
14927
  const shadowEnv = {
14795
14928
  ...process.env,
14796
14929
  GIT_DIR: shadowDir,
@@ -14949,7 +15082,7 @@ function applyUndo(hash, cwd) {
14949
15082
  timeout: GIT_TIMEOUT
14950
15083
  }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
14951
15084
  for (const file of [...tracked, ...untracked]) {
14952
- const fullPath = path26.join(dir, file);
15085
+ const fullPath = path27.join(dir, file);
14953
15086
  if (!snapshotFiles.has(file) && fs24.existsSync(fullPath)) {
14954
15087
  fs24.unlinkSync(fullPath);
14955
15088
  }
@@ -14965,11 +15098,11 @@ init_daemon_starter();
14965
15098
 
14966
15099
  // src/skill-pin.ts
14967
15100
  import fs25 from "fs";
14968
- import path27 from "path";
15101
+ import path28 from "path";
14969
15102
  import os22 from "os";
14970
15103
  import crypto4 from "crypto";
14971
15104
  function getPinsFilePath() {
14972
- return path27.join(os22.homedir(), ".node9", "skill-pins.json");
15105
+ return path28.join(os22.homedir(), ".node9", "skill-pins.json");
14973
15106
  }
14974
15107
  var MAX_FILES = 5e3;
14975
15108
  var MAX_TOTAL_BYTES = 50 * 1024 * 1024;
@@ -14990,8 +15123,8 @@ function walkDir(root) {
14990
15123
  entries.sort((a, b) => a.name.localeCompare(b.name));
14991
15124
  for (const entry of entries) {
14992
15125
  if (out.length >= MAX_FILES) return;
14993
- const full = path27.join(dir, entry.name);
14994
- const rel = relDir ? path27.posix.join(relDir, entry.name) : entry.name;
15126
+ const full = path28.join(dir, entry.name);
15127
+ const rel = relDir ? path28.posix.join(relDir, entry.name) : entry.name;
14995
15128
  let lst;
14996
15129
  try {
14997
15130
  lst = fs25.lstatSync(full);
@@ -15065,7 +15198,7 @@ function readSkillPins() {
15065
15198
  }
15066
15199
  function writeSkillPins(data) {
15067
15200
  const filePath = getPinsFilePath();
15068
- fs25.mkdirSync(path27.dirname(filePath), { recursive: true });
15201
+ fs25.mkdirSync(path28.dirname(filePath), { recursive: true });
15069
15202
  const tmp = `${filePath}.${crypto4.randomBytes(6).toString("hex")}.tmp`;
15070
15203
  fs25.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
15071
15204
  fs25.renameSync(tmp, filePath);
@@ -15112,7 +15245,7 @@ function verifyAndPinRoots(roots) {
15112
15245
  return { kind: "verified" };
15113
15246
  }
15114
15247
  function defaultSkillRoots(_cwd) {
15115
- const marketplaces = path27.join(os22.homedir(), ".claude", "plugins", "marketplaces");
15248
+ const marketplaces = path28.join(os22.homedir(), ".claude", "plugins", "marketplaces");
15116
15249
  const roots = [];
15117
15250
  let registries;
15118
15251
  try {
@@ -15122,7 +15255,7 @@ function defaultSkillRoots(_cwd) {
15122
15255
  }
15123
15256
  for (const registry of registries) {
15124
15257
  if (!registry.isDirectory()) continue;
15125
- const pluginsDir = path27.join(marketplaces, registry.name, "plugins");
15258
+ const pluginsDir = path28.join(marketplaces, registry.name, "plugins");
15126
15259
  let plugins;
15127
15260
  try {
15128
15261
  plugins = fs25.readdirSync(pluginsDir, { withFileTypes: true });
@@ -15131,17 +15264,17 @@ function defaultSkillRoots(_cwd) {
15131
15264
  }
15132
15265
  for (const plugin of plugins) {
15133
15266
  if (!plugin.isDirectory()) continue;
15134
- roots.push(path27.join(pluginsDir, plugin.name));
15267
+ roots.push(path28.join(pluginsDir, plugin.name));
15135
15268
  }
15136
15269
  }
15137
15270
  return roots;
15138
15271
  }
15139
15272
  function resolveUserSkillRoot(entry, cwd) {
15140
15273
  if (!entry) return null;
15141
- if (entry.startsWith("~/") || entry === "~") return path27.join(os22.homedir(), entry.slice(1));
15142
- if (path27.isAbsolute(entry)) return entry;
15143
- if (!cwd || !path27.isAbsolute(cwd)) return null;
15144
- return path27.join(cwd, entry);
15274
+ if (entry.startsWith("~/") || entry === "~") return path28.join(os22.homedir(), entry.slice(1));
15275
+ if (path28.isAbsolute(entry)) return entry;
15276
+ if (!cwd || !path28.isAbsolute(cwd)) return null;
15277
+ return path28.join(cwd, entry);
15145
15278
  }
15146
15279
 
15147
15280
  // src/cli/commands/check.ts
@@ -15159,7 +15292,7 @@ function registerCheckCommand(program2) {
15159
15292
  } catch (err2) {
15160
15293
  const tempConfig = getConfig();
15161
15294
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
15162
- const logPath = path28.join(os23.homedir(), ".node9", "hook-debug.log");
15295
+ const logPath = path29.join(os23.homedir(), ".node9", "hook-debug.log");
15163
15296
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
15164
15297
  fs26.appendFileSync(
15165
15298
  logPath,
@@ -15174,11 +15307,11 @@ RAW: ${raw}
15174
15307
  if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
15175
15308
  try {
15176
15309
  const scriptPath = process.argv[1];
15177
- if (typeof scriptPath !== "string" || !path28.isAbsolute(scriptPath))
15310
+ if (typeof scriptPath !== "string" || !path29.isAbsolute(scriptPath))
15178
15311
  throw new Error("node9: argv[1] is not an absolute path");
15179
15312
  const resolvedScript = fs26.realpathSync(scriptPath);
15180
- const packageDist = fs26.realpathSync(path28.resolve(__dirname, "../.."));
15181
- if (!resolvedScript.startsWith(packageDist + path28.sep) && resolvedScript !== packageDist)
15313
+ const packageDist = fs26.realpathSync(path29.resolve(__dirname, "../.."));
15314
+ if (!resolvedScript.startsWith(packageDist + path29.sep) && resolvedScript !== packageDist)
15182
15315
  throw new Error(
15183
15316
  `node9: daemon spawn aborted \u2014 argv[1] (${resolvedScript}) is outside package dist (${packageDist})`
15184
15317
  );
@@ -15200,7 +15333,7 @@ RAW: ${raw}
15200
15333
  });
15201
15334
  d.unref();
15202
15335
  } catch (spawnErr) {
15203
- const logPath = path28.join(os23.homedir(), ".node9", "hook-debug.log");
15336
+ const logPath = path29.join(os23.homedir(), ".node9", "hook-debug.log");
15204
15337
  const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
15205
15338
  try {
15206
15339
  fs26.appendFileSync(
@@ -15213,9 +15346,9 @@ RAW: ${raw}
15213
15346
  }
15214
15347
  }
15215
15348
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
15216
- const logPath = path28.join(os23.homedir(), ".node9", "hook-debug.log");
15217
- if (!fs26.existsSync(path28.dirname(logPath)))
15218
- fs26.mkdirSync(path28.dirname(logPath), { recursive: true });
15349
+ const logPath = path29.join(os23.homedir(), ".node9", "hook-debug.log");
15350
+ if (!fs26.existsSync(path29.dirname(logPath)))
15351
+ fs26.mkdirSync(path29.dirname(logPath), { recursive: true });
15219
15352
  fs26.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
15220
15353
  `);
15221
15354
  }
@@ -15283,8 +15416,8 @@ RAW: ${raw}
15283
15416
  const safeSessionId = /^[A-Za-z0-9_\-]{1,128}$/.test(rawSessionId) ? rawSessionId : "";
15284
15417
  if (skillPinCfg.enabled && safeSessionId) {
15285
15418
  try {
15286
- const sessionsDir = path28.join(os23.homedir(), ".node9", "skill-sessions");
15287
- const flagPath = path28.join(sessionsDir, `${safeSessionId}.json`);
15419
+ const sessionsDir = path29.join(os23.homedir(), ".node9", "skill-sessions");
15420
+ const flagPath = path29.join(sessionsDir, `${safeSessionId}.json`);
15288
15421
  let flag = null;
15289
15422
  try {
15290
15423
  flag = JSON.parse(fs26.readFileSync(flagPath, "utf-8"));
@@ -15336,7 +15469,7 @@ RAW: ${raw}
15336
15469
  return;
15337
15470
  }
15338
15471
  if (!flag || flag.state !== "verified" && flag.state !== "warned") {
15339
- const absoluteCwd = typeof payload.cwd === "string" && path28.isAbsolute(payload.cwd) ? payload.cwd : void 0;
15472
+ const absoluteCwd = typeof payload.cwd === "string" && path29.isAbsolute(payload.cwd) ? payload.cwd : void 0;
15340
15473
  const extraRoots = skillPinCfg.roots;
15341
15474
  const resolvedExtra = extraRoots.map((r) => resolveUserSkillRoot(r, absoluteCwd)).filter((r) => typeof r === "string");
15342
15475
  const roots = [...defaultSkillRoots(absoluteCwd), ...resolvedExtra];
@@ -15378,7 +15511,7 @@ RAW: ${raw}
15378
15511
  try {
15379
15512
  const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
15380
15513
  for (const name of fs26.readdirSync(sessionsDir)) {
15381
- const p = path28.join(sessionsDir, name);
15514
+ const p = path29.join(sessionsDir, name);
15382
15515
  try {
15383
15516
  if (fs26.statSync(p).mtimeMs < cutoff) fs26.unlinkSync(p);
15384
15517
  } catch {
@@ -15390,7 +15523,7 @@ RAW: ${raw}
15390
15523
  } catch (err2) {
15391
15524
  if (process.env.NODE9_DEBUG === "1") {
15392
15525
  try {
15393
- const dbg = path28.join(os23.homedir(), ".node9", "hook-debug.log");
15526
+ const dbg = path29.join(os23.homedir(), ".node9", "hook-debug.log");
15394
15527
  const msg = err2 instanceof Error ? err2.message : String(err2);
15395
15528
  fs26.appendFileSync(dbg, `[${(/* @__PURE__ */ new Date()).toISOString()}] SKILL_PIN_ERROR: ${msg}
15396
15529
  `);
@@ -15402,7 +15535,7 @@ RAW: ${raw}
15402
15535
  if (shouldSnapshot(toolName, toolInput, config)) {
15403
15536
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
15404
15537
  }
15405
- const safeCwdForAuth = typeof payload.cwd === "string" && path28.isAbsolute(payload.cwd) ? payload.cwd : void 0;
15538
+ const safeCwdForAuth = typeof payload.cwd === "string" && path29.isAbsolute(payload.cwd) ? payload.cwd : void 0;
15406
15539
  const result = await authorizeHeadless(toolName, toolInput, meta, {
15407
15540
  cwd: safeCwdForAuth
15408
15541
  });
@@ -15446,7 +15579,7 @@ RAW: ${raw}
15446
15579
  });
15447
15580
  } catch (err2) {
15448
15581
  if (process.env.NODE9_DEBUG === "1") {
15449
- const logPath = path28.join(os23.homedir(), ".node9", "hook-debug.log");
15582
+ const logPath = path29.join(os23.homedir(), ".node9", "hook-debug.log");
15450
15583
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
15451
15584
  fs26.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
15452
15585
  `);
@@ -15485,7 +15618,7 @@ RAW: ${raw}
15485
15618
  init_audit();
15486
15619
  init_config();
15487
15620
  import fs27 from "fs";
15488
- import path29 from "path";
15621
+ import path30 from "path";
15489
15622
  import os24 from "os";
15490
15623
  init_daemon();
15491
15624
 
@@ -15559,9 +15692,9 @@ function registerLogCommand(program2) {
15559
15692
  decision: "allowed",
15560
15693
  source: "post-hook"
15561
15694
  };
15562
- const logPath = path29.join(os24.homedir(), ".node9", "audit.log");
15563
- if (!fs27.existsSync(path29.dirname(logPath)))
15564
- fs27.mkdirSync(path29.dirname(logPath), { recursive: true });
15695
+ const logPath = path30.join(os24.homedir(), ".node9", "audit.log");
15696
+ if (!fs27.existsSync(path30.dirname(logPath)))
15697
+ fs27.mkdirSync(path30.dirname(logPath), { recursive: true });
15565
15698
  fs27.appendFileSync(logPath, JSON.stringify(entry) + "\n");
15566
15699
  if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
15567
15700
  const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
@@ -15595,7 +15728,7 @@ function registerLogCommand(program2) {
15595
15728
  }
15596
15729
  }
15597
15730
  }
15598
- const safeCwd = typeof payload.cwd === "string" && path29.isAbsolute(payload.cwd) ? payload.cwd : void 0;
15731
+ const safeCwd = typeof payload.cwd === "string" && path30.isAbsolute(payload.cwd) ? payload.cwd : void 0;
15599
15732
  const config = getConfig(safeCwd);
15600
15733
  if ((tool === "Bash" || tool === "bash") && config.settings.enableUndo !== false) {
15601
15734
  const bashCommand = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
@@ -15616,7 +15749,7 @@ function registerLogCommand(program2) {
15616
15749
  const msg = err2 instanceof Error ? err2.message : String(err2);
15617
15750
  process.stderr.write(`[Node9] audit log error: ${msg}
15618
15751
  `);
15619
- const debugPath = path29.join(os24.homedir(), ".node9", "hook-debug.log");
15752
+ const debugPath = path30.join(os24.homedir(), ".node9", "hook-debug.log");
15620
15753
  try {
15621
15754
  fs27.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
15622
15755
  `);
@@ -16020,7 +16153,7 @@ function registerConfigShowCommand(program2) {
16020
16153
  init_daemon();
16021
16154
  import chalk8 from "chalk";
16022
16155
  import fs28 from "fs";
16023
- import path30 from "path";
16156
+ import path31 from "path";
16024
16157
  import os25 from "os";
16025
16158
  import { execSync as execSync2 } from "child_process";
16026
16159
  function registerDoctorCommand(program2, version2) {
@@ -16074,7 +16207,7 @@ function registerDoctorCommand(program2, version2) {
16074
16207
  );
16075
16208
  }
16076
16209
  section("Configuration");
16077
- const globalConfigPath = path30.join(homeDir2, ".node9", "config.json");
16210
+ const globalConfigPath = path31.join(homeDir2, ".node9", "config.json");
16078
16211
  if (fs28.existsSync(globalConfigPath)) {
16079
16212
  try {
16080
16213
  JSON.parse(fs28.readFileSync(globalConfigPath, "utf-8"));
@@ -16085,7 +16218,7 @@ function registerDoctorCommand(program2, version2) {
16085
16218
  } else {
16086
16219
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
16087
16220
  }
16088
- const projectConfigPath = path30.join(process.cwd(), "node9.config.json");
16221
+ const projectConfigPath = path31.join(process.cwd(), "node9.config.json");
16089
16222
  if (fs28.existsSync(projectConfigPath)) {
16090
16223
  try {
16091
16224
  JSON.parse(fs28.readFileSync(projectConfigPath, "utf-8"));
@@ -16097,7 +16230,7 @@ function registerDoctorCommand(program2, version2) {
16097
16230
  );
16098
16231
  }
16099
16232
  }
16100
- const credsPath = path30.join(homeDir2, ".node9", "credentials.json");
16233
+ const credsPath = path31.join(homeDir2, ".node9", "credentials.json");
16101
16234
  if (fs28.existsSync(credsPath)) {
16102
16235
  pass("Cloud credentials found (~/.node9/credentials.json)");
16103
16236
  } else {
@@ -16107,7 +16240,7 @@ function registerDoctorCommand(program2, version2) {
16107
16240
  );
16108
16241
  }
16109
16242
  section("Agent Hooks");
16110
- const claudeSettingsPath = path30.join(homeDir2, ".claude", "settings.json");
16243
+ const claudeSettingsPath = path31.join(homeDir2, ".claude", "settings.json");
16111
16244
  if (fs28.existsSync(claudeSettingsPath)) {
16112
16245
  try {
16113
16246
  const cs = JSON.parse(fs28.readFileSync(claudeSettingsPath, "utf-8"));
@@ -16126,7 +16259,7 @@ function registerDoctorCommand(program2, version2) {
16126
16259
  } else {
16127
16260
  warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
16128
16261
  }
16129
- const geminiSettingsPath = path30.join(homeDir2, ".gemini", "settings.json");
16262
+ const geminiSettingsPath = path31.join(homeDir2, ".gemini", "settings.json");
16130
16263
  if (fs28.existsSync(geminiSettingsPath)) {
16131
16264
  try {
16132
16265
  const gs = JSON.parse(fs28.readFileSync(geminiSettingsPath, "utf-8"));
@@ -16145,7 +16278,7 @@ function registerDoctorCommand(program2, version2) {
16145
16278
  } else {
16146
16279
  warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
16147
16280
  }
16148
- const cursorHooksPath = path30.join(homeDir2, ".cursor", "hooks.json");
16281
+ const cursorHooksPath = path31.join(homeDir2, ".cursor", "hooks.json");
16149
16282
  if (fs28.existsSync(cursorHooksPath)) {
16150
16283
  try {
16151
16284
  const cur = JSON.parse(fs28.readFileSync(cursorHooksPath, "utf-8"));
@@ -16187,7 +16320,7 @@ function registerDoctorCommand(program2, version2) {
16187
16320
  // src/cli/commands/audit.ts
16188
16321
  import chalk9 from "chalk";
16189
16322
  import fs29 from "fs";
16190
- import path31 from "path";
16323
+ import path32 from "path";
16191
16324
  import os26 from "os";
16192
16325
  function formatRelativeTime(timestamp) {
16193
16326
  const diff = Date.now() - new Date(timestamp).getTime();
@@ -16201,7 +16334,7 @@ function formatRelativeTime(timestamp) {
16201
16334
  }
16202
16335
  function registerAuditCommand(program2) {
16203
16336
  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) => {
16204
- const logPath = path31.join(os26.homedir(), ".node9", "audit.log");
16337
+ const logPath = path32.join(os26.homedir(), ".node9", "audit.log");
16205
16338
  if (!fs29.existsSync(logPath)) {
16206
16339
  console.log(
16207
16340
  chalk9.yellow("No audit logs found. Run node9 with an agent to generate entries.")
@@ -16263,7 +16396,7 @@ function registerAuditCommand(program2) {
16263
16396
  // src/cli/commands/report.ts
16264
16397
  import chalk10 from "chalk";
16265
16398
  import fs30 from "fs";
16266
- import path32 from "path";
16399
+ import path33 from "path";
16267
16400
  import os27 from "os";
16268
16401
  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;
16269
16402
  function buildTestTimestamps(allEntries) {
@@ -16391,7 +16524,7 @@ function loadClaudeCost(start, end) {
16391
16524
  cacheWriteTokens: 0,
16392
16525
  cacheReadTokens: 0
16393
16526
  };
16394
- const projectsDir = path32.join(os27.homedir(), ".claude", "projects");
16527
+ const projectsDir = path33.join(os27.homedir(), ".claude", "projects");
16395
16528
  if (!fs30.existsSync(projectsDir)) return empty;
16396
16529
  let dirs;
16397
16530
  try {
@@ -16407,7 +16540,7 @@ function loadClaudeCost(start, end) {
16407
16540
  const byDay = /* @__PURE__ */ new Map();
16408
16541
  const byModel = /* @__PURE__ */ new Map();
16409
16542
  for (const proj of dirs) {
16410
- const projPath = path32.join(projectsDir, proj);
16543
+ const projPath = path33.join(projectsDir, proj);
16411
16544
  let files;
16412
16545
  try {
16413
16546
  const stat = fs30.statSync(projPath);
@@ -16418,7 +16551,7 @@ function loadClaudeCost(start, end) {
16418
16551
  }
16419
16552
  for (const file of files) {
16420
16553
  try {
16421
- const raw = fs30.readFileSync(path32.join(projPath, file), "utf-8");
16554
+ const raw = fs30.readFileSync(path33.join(projPath, file), "utf-8");
16422
16555
  for (const line of raw.split("\n")) {
16423
16556
  if (!line.trim()) continue;
16424
16557
  let entry;
@@ -16459,7 +16592,7 @@ function loadClaudeCost(start, end) {
16459
16592
  return { total, byDay, byModel, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens };
16460
16593
  }
16461
16594
  function loadCodexCost(start, end) {
16462
- const sessionsBase = path32.join(os27.homedir(), ".codex", "sessions");
16595
+ const sessionsBase = path33.join(os27.homedir(), ".codex", "sessions");
16463
16596
  const byDay = /* @__PURE__ */ new Map();
16464
16597
  let total = 0;
16465
16598
  let toolCalls = 0;
@@ -16467,28 +16600,28 @@ function loadCodexCost(start, end) {
16467
16600
  const jsonlFiles = [];
16468
16601
  try {
16469
16602
  for (const year of fs30.readdirSync(sessionsBase)) {
16470
- const yearPath = path32.join(sessionsBase, year);
16603
+ const yearPath = path33.join(sessionsBase, year);
16471
16604
  try {
16472
16605
  if (!fs30.statSync(yearPath).isDirectory()) continue;
16473
16606
  } catch {
16474
16607
  continue;
16475
16608
  }
16476
16609
  for (const month of fs30.readdirSync(yearPath)) {
16477
- const monthPath = path32.join(yearPath, month);
16610
+ const monthPath = path33.join(yearPath, month);
16478
16611
  try {
16479
16612
  if (!fs30.statSync(monthPath).isDirectory()) continue;
16480
16613
  } catch {
16481
16614
  continue;
16482
16615
  }
16483
16616
  for (const day of fs30.readdirSync(monthPath)) {
16484
- const dayPath = path32.join(monthPath, day);
16617
+ const dayPath = path33.join(monthPath, day);
16485
16618
  try {
16486
16619
  if (!fs30.statSync(dayPath).isDirectory()) continue;
16487
16620
  } catch {
16488
16621
  continue;
16489
16622
  }
16490
16623
  for (const file of fs30.readdirSync(dayPath)) {
16491
- if (file.endsWith(".jsonl")) jsonlFiles.push(path32.join(dayPath, file));
16624
+ if (file.endsWith(".jsonl")) jsonlFiles.push(path33.join(dayPath, file));
16492
16625
  }
16493
16626
  }
16494
16627
  }
@@ -16549,7 +16682,7 @@ function registerReportCommand(program2) {
16549
16682
  const period = ["today", "7d", "30d", "month"].includes(
16550
16683
  options.period
16551
16684
  ) ? options.period : "7d";
16552
- const logPath = path32.join(os27.homedir(), ".node9", "audit.log");
16685
+ const logPath = path33.join(os27.homedir(), ".node9", "audit.log");
16553
16686
  const allEntries = parseAuditLog(logPath);
16554
16687
  const unackedDlp = allEntries.filter((e) => e.source === "response-dlp");
16555
16688
  if (unackedDlp.length > 0) {
@@ -17057,7 +17190,7 @@ init_core();
17057
17190
  init_daemon();
17058
17191
  import chalk12 from "chalk";
17059
17192
  import fs31 from "fs";
17060
- import path33 from "path";
17193
+ import path34 from "path";
17061
17194
  import os28 from "os";
17062
17195
  function readJson2(filePath) {
17063
17196
  try {
@@ -17126,8 +17259,8 @@ function registerStatusCommand(program2) {
17126
17259
  console.log("");
17127
17260
  const modeLabel = settings.mode === "audit" ? chalk12.blue("audit") : settings.mode === "strict" ? chalk12.red("strict") : chalk12.white("standard");
17128
17261
  console.log(` Mode: ${modeLabel}`);
17129
- const projectConfig = path33.join(process.cwd(), "node9.config.json");
17130
- const globalConfig = path33.join(os28.homedir(), ".node9", "config.json");
17262
+ const projectConfig = path34.join(process.cwd(), "node9.config.json");
17263
+ const globalConfig = path34.join(os28.homedir(), ".node9", "config.json");
17131
17264
  console.log(
17132
17265
  ` Local: ${fs31.existsSync(projectConfig) ? chalk12.green("Active (node9.config.json)") : chalk12.gray("Not present")}`
17133
17266
  );
@@ -17141,13 +17274,13 @@ function registerStatusCommand(program2) {
17141
17274
  }
17142
17275
  const homeDir2 = os28.homedir();
17143
17276
  const claudeSettings = readJson2(
17144
- path33.join(homeDir2, ".claude", "settings.json")
17277
+ path34.join(homeDir2, ".claude", "settings.json")
17145
17278
  );
17146
- const claudeConfig = readJson2(path33.join(homeDir2, ".claude.json"));
17279
+ const claudeConfig = readJson2(path34.join(homeDir2, ".claude.json"));
17147
17280
  const geminiSettings = readJson2(
17148
- path33.join(homeDir2, ".gemini", "settings.json")
17281
+ path34.join(homeDir2, ".gemini", "settings.json")
17149
17282
  );
17150
- const cursorConfig = readJson2(path33.join(homeDir2, ".cursor", "mcp.json"));
17283
+ const cursorConfig = readJson2(path34.join(homeDir2, ".cursor", "mcp.json"));
17151
17284
  const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
17152
17285
  if (agentFound) {
17153
17286
  console.log("");
@@ -17208,7 +17341,7 @@ function registerStatusCommand(program2) {
17208
17341
  init_core();
17209
17342
  import chalk13 from "chalk";
17210
17343
  import fs32 from "fs";
17211
- import path34 from "path";
17344
+ import path35 from "path";
17212
17345
  import os29 from "os";
17213
17346
  import https3 from "https";
17214
17347
  init_shields();
@@ -17269,7 +17402,7 @@ function registerInitCommand(program2) {
17269
17402
  }
17270
17403
  console.log("");
17271
17404
  }
17272
- const configPath = path34.join(os29.homedir(), ".node9", "config.json");
17405
+ const configPath = path35.join(os29.homedir(), ".node9", "config.json");
17273
17406
  if (fs32.existsSync(configPath) && !options.force) {
17274
17407
  try {
17275
17408
  const existing = JSON.parse(fs32.readFileSync(configPath, "utf-8"));
@@ -17290,7 +17423,7 @@ function registerInitCommand(program2) {
17290
17423
  ...DEFAULT_CONFIG,
17291
17424
  settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
17292
17425
  };
17293
- const dir = path34.dirname(configPath);
17426
+ const dir = path35.dirname(configPath);
17294
17427
  if (!fs32.existsSync(dir)) fs32.mkdirSync(dir, { recursive: true });
17295
17428
  fs32.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
17296
17429
  console.log(chalk13.green(`\u2705 Config created: ${configPath}`));
@@ -17377,7 +17510,7 @@ function registerInitCommand(program2) {
17377
17510
  }
17378
17511
 
17379
17512
  // src/cli/commands/undo.ts
17380
- import path35 from "path";
17513
+ import path36 from "path";
17381
17514
  import chalk15 from "chalk";
17382
17515
 
17383
17516
  // src/tui/undo-navigator.ts
@@ -17536,7 +17669,7 @@ function findMatchingCwd(startDir, history) {
17536
17669
  let dir = startDir;
17537
17670
  while (true) {
17538
17671
  if (cwds.has(dir)) return dir;
17539
- const parent = path35.dirname(dir);
17672
+ const parent = path36.dirname(dir);
17540
17673
  if (parent === dir) return null;
17541
17674
  dir = parent;
17542
17675
  }
@@ -17733,11 +17866,11 @@ init_provenance();
17733
17866
 
17734
17867
  // src/mcp-pin.ts
17735
17868
  import fs33 from "fs";
17736
- import path36 from "path";
17869
+ import path37 from "path";
17737
17870
  import os30 from "os";
17738
17871
  import crypto5 from "crypto";
17739
17872
  function getPinsFilePath2() {
17740
- return path36.join(os30.homedir(), ".node9", "mcp-pins.json");
17873
+ return path37.join(os30.homedir(), ".node9", "mcp-pins.json");
17741
17874
  }
17742
17875
  function hashToolDefinitions(tools) {
17743
17876
  const sorted = [...tools].sort((a, b) => {
@@ -17778,7 +17911,7 @@ function readMcpPins() {
17778
17911
  }
17779
17912
  function writeMcpPins(data) {
17780
17913
  const filePath = getPinsFilePath2();
17781
- fs33.mkdirSync(path36.dirname(filePath), { recursive: true });
17914
+ fs33.mkdirSync(path37.dirname(filePath), { recursive: true });
17782
17915
  const tmp = `${filePath}.${crypto5.randomBytes(6).toString("hex")}.tmp`;
17783
17916
  fs33.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
17784
17917
  fs33.renameSync(tmp, filePath);
@@ -18231,7 +18364,7 @@ function registerMcpGatewayCommand(program2) {
18231
18364
  import readline4 from "readline";
18232
18365
  import fs34 from "fs";
18233
18366
  import os31 from "os";
18234
- import path37 from "path";
18367
+ import path38 from "path";
18235
18368
  import { spawnSync as spawnSync7 } from "child_process";
18236
18369
  init_core();
18237
18370
  init_daemon();
@@ -18482,8 +18615,8 @@ function handleStatus() {
18482
18615
  lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
18483
18616
  lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
18484
18617
  lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
18485
- const projectConfig = path37.join(process.cwd(), "node9.config.json");
18486
- const globalConfig = path37.join(os31.homedir(), ".node9", "config.json");
18618
+ const projectConfig = path38.join(process.cwd(), "node9.config.json");
18619
+ const globalConfig = path38.join(os31.homedir(), ".node9", "config.json");
18487
18620
  lines.push(
18488
18621
  `Project config (node9.config.json): ${fs34.existsSync(projectConfig) ? "present" : "not found"}`
18489
18622
  );
@@ -18562,7 +18695,7 @@ function handleShieldDisable(args) {
18562
18695
  writeActiveShields(active.filter((s) => s !== name));
18563
18696
  return `Shield "${name}" disabled.`;
18564
18697
  }
18565
- var GLOBAL_CONFIG_PATH2 = path37.join(os31.homedir(), ".node9", "config.json");
18698
+ var GLOBAL_CONFIG_PATH2 = path38.join(os31.homedir(), ".node9", "config.json");
18566
18699
  var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
18567
18700
  function readGlobalConfigRaw() {
18568
18701
  try {
@@ -18574,7 +18707,7 @@ function readGlobalConfigRaw() {
18574
18707
  return {};
18575
18708
  }
18576
18709
  function writeGlobalConfigRaw(data) {
18577
- const dir = path37.dirname(GLOBAL_CONFIG_PATH2);
18710
+ const dir = path38.dirname(GLOBAL_CONFIG_PATH2);
18578
18711
  if (!fs34.existsSync(dir)) fs34.mkdirSync(dir, { recursive: true });
18579
18712
  fs34.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
18580
18713
  }
@@ -18620,7 +18753,7 @@ function handleApproverSet(args) {
18620
18753
  function handleAuditGet(args) {
18621
18754
  const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
18622
18755
  const filter = typeof args.filter === "string" && args.filter !== "all" ? args.filter : null;
18623
- const auditPath = path37.join(os31.homedir(), ".node9", "audit.log");
18756
+ const auditPath = path38.join(os31.homedir(), ".node9", "audit.log");
18624
18757
  if (!fs34.existsSync(auditPath)) return "No audit log found.";
18625
18758
  const rawLines = fs34.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
18626
18759
  const parsed = [];
@@ -19177,7 +19310,7 @@ init_scan();
19177
19310
  // src/cli/commands/sessions.ts
19178
19311
  import chalk22 from "chalk";
19179
19312
  import fs35 from "fs";
19180
- import path38 from "path";
19313
+ import path39 from "path";
19181
19314
  import os32 from "os";
19182
19315
  var CLAUDE_PRICING3 = {
19183
19316
  "claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
@@ -19219,7 +19352,7 @@ function encodeProjectPath(projectPath) {
19219
19352
  }
19220
19353
  function sessionJsonlPath(projectPath, sessionId) {
19221
19354
  const encoded = encodeProjectPath(projectPath);
19222
- return path38.join(os32.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
19355
+ return path39.join(os32.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
19223
19356
  }
19224
19357
  function projectLabel(projectPath) {
19225
19358
  return projectPath.replace(os32.homedir(), "~");
@@ -19291,7 +19424,7 @@ function parseSessionLines(lines) {
19291
19424
  return { toolCalls, costUSD, hasSnapshot, modifiedFiles };
19292
19425
  }
19293
19426
  function loadAuditEntries(auditPath) {
19294
- const aPath = auditPath ?? path38.join(os32.homedir(), ".node9", "audit.log");
19427
+ const aPath = auditPath ?? path39.join(os32.homedir(), ".node9", "audit.log");
19295
19428
  let raw;
19296
19429
  try {
19297
19430
  raw = fs35.readFileSync(aPath, "utf-8");
@@ -19330,7 +19463,7 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
19330
19463
  return result;
19331
19464
  }
19332
19465
  function buildGeminiSessions(days, allAuditEntries) {
19333
- const tmpDir = path38.join(os32.homedir(), ".gemini", "tmp");
19466
+ const tmpDir = path39.join(os32.homedir(), ".gemini", "tmp");
19334
19467
  if (!fs35.existsSync(tmpDir)) return [];
19335
19468
  const cutoff = days !== null ? (() => {
19336
19469
  const d = /* @__PURE__ */ new Date();
@@ -19346,18 +19479,18 @@ function buildGeminiSessions(days, allAuditEntries) {
19346
19479
  }
19347
19480
  const summaries = [];
19348
19481
  for (const slug of slugDirs) {
19349
- const slugPath = path38.join(tmpDir, slug);
19482
+ const slugPath = path39.join(tmpDir, slug);
19350
19483
  try {
19351
19484
  if (!fs35.statSync(slugPath).isDirectory()) continue;
19352
19485
  } catch {
19353
19486
  continue;
19354
19487
  }
19355
- let projectRoot = path38.join(os32.homedir(), slug);
19488
+ let projectRoot = path39.join(os32.homedir(), slug);
19356
19489
  try {
19357
- projectRoot = fs35.readFileSync(path38.join(slugPath, ".project_root"), "utf-8").trim();
19490
+ projectRoot = fs35.readFileSync(path39.join(slugPath, ".project_root"), "utf-8").trim();
19358
19491
  } catch {
19359
19492
  }
19360
- const chatsDir = path38.join(slugPath, "chats");
19493
+ const chatsDir = path39.join(slugPath, "chats");
19361
19494
  if (!fs35.existsSync(chatsDir)) continue;
19362
19495
  let chatFiles;
19363
19496
  try {
@@ -19368,7 +19501,7 @@ function buildGeminiSessions(days, allAuditEntries) {
19368
19501
  for (const chatFile of chatFiles) {
19369
19502
  let raw;
19370
19503
  try {
19371
- raw = fs35.readFileSync(path38.join(chatsDir, chatFile), "utf-8");
19504
+ raw = fs35.readFileSync(path39.join(chatsDir, chatFile), "utf-8");
19372
19505
  } catch {
19373
19506
  continue;
19374
19507
  }
@@ -19448,7 +19581,7 @@ function buildGeminiSessions(days, allAuditEntries) {
19448
19581
  return summaries;
19449
19582
  }
19450
19583
  function buildCodexSessions(days, allAuditEntries) {
19451
- const sessionsBase = path38.join(os32.homedir(), ".codex", "sessions");
19584
+ const sessionsBase = path39.join(os32.homedir(), ".codex", "sessions");
19452
19585
  if (!fs35.existsSync(sessionsBase)) return [];
19453
19586
  const cutoff = days !== null ? (() => {
19454
19587
  const d = /* @__PURE__ */ new Date();
@@ -19459,28 +19592,28 @@ function buildCodexSessions(days, allAuditEntries) {
19459
19592
  const jsonlFiles = [];
19460
19593
  try {
19461
19594
  for (const year of fs35.readdirSync(sessionsBase)) {
19462
- const yearPath = path38.join(sessionsBase, year);
19595
+ const yearPath = path39.join(sessionsBase, year);
19463
19596
  try {
19464
19597
  if (!fs35.statSync(yearPath).isDirectory()) continue;
19465
19598
  } catch {
19466
19599
  continue;
19467
19600
  }
19468
19601
  for (const month of fs35.readdirSync(yearPath)) {
19469
- const monthPath = path38.join(yearPath, month);
19602
+ const monthPath = path39.join(yearPath, month);
19470
19603
  try {
19471
19604
  if (!fs35.statSync(monthPath).isDirectory()) continue;
19472
19605
  } catch {
19473
19606
  continue;
19474
19607
  }
19475
19608
  for (const day of fs35.readdirSync(monthPath)) {
19476
- const dayPath = path38.join(monthPath, day);
19609
+ const dayPath = path39.join(monthPath, day);
19477
19610
  try {
19478
19611
  if (!fs35.statSync(dayPath).isDirectory()) continue;
19479
19612
  } catch {
19480
19613
  continue;
19481
19614
  }
19482
19615
  for (const file of fs35.readdirSync(dayPath)) {
19483
- if (file.endsWith(".jsonl")) jsonlFiles.push(path38.join(dayPath, file));
19616
+ if (file.endsWith(".jsonl")) jsonlFiles.push(path39.join(dayPath, file));
19484
19617
  }
19485
19618
  }
19486
19619
  }
@@ -19570,7 +19703,7 @@ function buildCodexSessions(days, allAuditEntries) {
19570
19703
  return summaries;
19571
19704
  }
19572
19705
  function buildSessions(days, historyPath) {
19573
- const hPath = historyPath ?? path38.join(os32.homedir(), ".claude", "history.jsonl");
19706
+ const hPath = historyPath ?? path39.join(os32.homedir(), ".claude", "history.jsonl");
19574
19707
  let historyRaw;
19575
19708
  try {
19576
19709
  historyRaw = fs35.readFileSync(hPath, "utf-8");
@@ -19864,7 +19997,7 @@ function registerSessionsCommand(program2) {
19864
19997
  console.log("");
19865
19998
  console.log(chalk22.cyan.bold("\u{1F4CB} node9 sessions") + chalk22.dim(" \u2014 what your AI agent did"));
19866
19999
  console.log("");
19867
- const historyPath = path38.join(os32.homedir(), ".claude", "history.jsonl");
20000
+ const historyPath = path39.join(os32.homedir(), ".claude", "history.jsonl");
19868
20001
  if (!fs35.existsSync(historyPath)) {
19869
20002
  console.log(chalk22.yellow(" No Claude session history found at ~/.claude/history.jsonl"));
19870
20003
  console.log(chalk22.gray(" Install Claude Code, run a few sessions, then try again.\n"));
@@ -19904,10 +20037,10 @@ function registerSessionsCommand(program2) {
19904
20037
  import chalk23 from "chalk";
19905
20038
  import fs36 from "fs";
19906
20039
  import os33 from "os";
19907
- import path39 from "path";
20040
+ import path40 from "path";
19908
20041
  function wipeSkillSessions() {
19909
20042
  try {
19910
- fs36.rmSync(path39.join(os33.homedir(), ".node9", "skill-sessions"), {
20043
+ fs36.rmSync(path40.join(os33.homedir(), ".node9", "skill-sessions"), {
19911
20044
  recursive: true,
19912
20045
  force: true
19913
20046
  });
@@ -19991,10 +20124,10 @@ function registerSkillPinCommand(program2) {
19991
20124
  // src/cli/commands/dlp.ts
19992
20125
  import chalk24 from "chalk";
19993
20126
  import fs37 from "fs";
19994
- import path40 from "path";
20127
+ import path41 from "path";
19995
20128
  import os34 from "os";
19996
- var AUDIT_LOG = path40.join(os34.homedir(), ".node9", "audit.log");
19997
- var RESOLVED_FILE = path40.join(os34.homedir(), ".node9", "dlp-resolved.json");
20129
+ var AUDIT_LOG = path41.join(os34.homedir(), ".node9", "audit.log");
20130
+ var RESOLVED_FILE = path41.join(os34.homedir(), ".node9", "dlp-resolved.json");
19998
20131
  var ANSI_RE = /\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g;
19999
20132
  function stripAnsi(s) {
20000
20133
  return s.replace(ANSI_RE, "");
@@ -20113,15 +20246,15 @@ function registerDlpCommand(program2) {
20113
20246
 
20114
20247
  // src/cli.ts
20115
20248
  var { version } = JSON.parse(
20116
- fs40.readFileSync(path43.join(__dirname, "../package.json"), "utf-8")
20249
+ fs40.readFileSync(path44.join(__dirname, "../package.json"), "utf-8")
20117
20250
  );
20118
20251
  var program = new Command();
20119
20252
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
20120
20253
  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) => {
20121
20254
  const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
20122
- const credPath = path43.join(os37.homedir(), ".node9", "credentials.json");
20123
- if (!fs40.existsSync(path43.dirname(credPath)))
20124
- fs40.mkdirSync(path43.dirname(credPath), { recursive: true });
20255
+ const credPath = path44.join(os37.homedir(), ".node9", "credentials.json");
20256
+ if (!fs40.existsSync(path44.dirname(credPath)))
20257
+ fs40.mkdirSync(path44.dirname(credPath), { recursive: true });
20125
20258
  const profileName = options.profile || "default";
20126
20259
  let existingCreds = {};
20127
20260
  try {
@@ -20140,7 +20273,7 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
20140
20273
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
20141
20274
  fs40.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
20142
20275
  if (profileName === "default") {
20143
- const configPath = path43.join(os37.homedir(), ".node9", "config.json");
20276
+ const configPath = path44.join(os37.homedir(), ".node9", "config.json");
20144
20277
  let config = {};
20145
20278
  try {
20146
20279
  if (fs40.existsSync(configPath))
@@ -20159,8 +20292,8 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
20159
20292
  approvers.cloud = false;
20160
20293
  }
20161
20294
  s.approvers = approvers;
20162
- if (!fs40.existsSync(path43.dirname(configPath)))
20163
- fs40.mkdirSync(path43.dirname(configPath), { recursive: true });
20295
+ if (!fs40.existsSync(path44.dirname(configPath)))
20296
+ fs40.mkdirSync(path44.dirname(configPath), { recursive: true });
20164
20297
  fs40.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
20165
20298
  }
20166
20299
  if (options.profile && profileName !== "default") {
@@ -20298,7 +20431,7 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
20298
20431
  }
20299
20432
  }
20300
20433
  if (options.purge) {
20301
- const node9Dir = path43.join(os37.homedir(), ".node9");
20434
+ const node9Dir = path44.join(os37.homedir(), ".node9");
20302
20435
  if (fs40.existsSync(node9Dir)) {
20303
20436
  const confirmed = await confirm2({
20304
20437
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
@@ -20448,9 +20581,9 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
20448
20581
  Run "node9 addto claude" to register it as the statusLine.`
20449
20582
  ).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
20450
20583
  if (subcommand === "debug") {
20451
- const flagFile = path43.join(os37.homedir(), ".node9", "hud-debug");
20584
+ const flagFile = path44.join(os37.homedir(), ".node9", "hud-debug");
20452
20585
  if (state === "on") {
20453
- fs40.mkdirSync(path43.dirname(flagFile), { recursive: true });
20586
+ fs40.mkdirSync(path44.dirname(flagFile), { recursive: true });
20454
20587
  fs40.writeFileSync(flagFile, "");
20455
20588
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
20456
20589
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
@@ -20520,10 +20653,10 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
20520
20653
  program.help();
20521
20654
  return;
20522
20655
  }
20523
- const fullCommand2 = runArgs.join(" ");
20656
+ const fullCommand = runArgs.join(" ");
20524
20657
  let result = await authorizeHeadless(
20525
20658
  "shell",
20526
- { command: fullCommand2 },
20659
+ { command: fullCommand },
20527
20660
  {
20528
20661
  agent: "Terminal"
20529
20662
  }
@@ -20531,11 +20664,11 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
20531
20664
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
20532
20665
  console.error(chalk26.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
20533
20666
  const daemonReady = await autoStartDaemonAndWait();
20534
- if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand2 });
20667
+ if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
20535
20668
  }
20536
20669
  if (result.noApprovalMechanism && process.stdout.isTTY) {
20537
20670
  const approved = await confirm2({
20538
- message: `\u{1F6E1}\uFE0F Node9: Allow "${fullCommand2}"?`,
20671
+ message: `\u{1F6E1}\uFE0F Node9: Allow "${fullCommand}"?`,
20539
20672
  default: false
20540
20673
  });
20541
20674
  result = { approved, reason: approved ? void 0 : "Denied by user at terminal." };
@@ -20548,7 +20681,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
20548
20681
  process.exit(1);
20549
20682
  }
20550
20683
  console.error(chalk26.green("\n\u2705 Approved \u2014 running command...\n"));
20551
- await runProxy(fullCommand2);
20684
+ await runProxy(fullCommand);
20552
20685
  } else {
20553
20686
  program.help();
20554
20687
  }
@@ -20567,7 +20700,7 @@ if (process.argv[2] !== "daemon") {
20567
20700
  const isCheckHook = process.argv[2] === "check";
20568
20701
  if (isCheckHook) {
20569
20702
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
20570
- const logPath = path43.join(os37.homedir(), ".node9", "hook-debug.log");
20703
+ const logPath = path44.join(os37.homedir(), ".node9", "hook-debug.log");
20571
20704
  const msg = reason instanceof Error ? reason.message : String(reason);
20572
20705
  fs40.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
20573
20706
  `);