@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.
- package/README.md +142 -244
- package/dist/cli.js +775 -642
- package/dist/cli.mjs +762 -629
- 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
|
|
151
|
-
return ` \u2022 ${
|
|
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,
|
|
2069
|
+
function getNestedValue(obj, path45) {
|
|
2070
2070
|
if (!obj || typeof obj !== "object") return null;
|
|
2071
|
-
return
|
|
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
|
|
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
|
-
|
|
7722
|
-
|
|
7723
|
-
|
|
7724
|
-
|
|
7725
|
-
const
|
|
7726
|
-
const
|
|
7727
|
-
const
|
|
7728
|
-
|
|
7729
|
-
|
|
7730
|
-
const
|
|
7731
|
-
const
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
7841
|
+
const hasNothing = !sections.length && !leaks.length && !loops.length;
|
|
7842
|
+
if (hasNothing) {
|
|
7808
7843
|
return (
|
|
7809
|
-
|
|
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
|
-
(
|
|
7812
|
-
' sessions
|
|
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
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
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(
|
|
7830
|
-
const date = new Date(
|
|
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
|
-
|
|
7909
|
+
text +
|
|
7841
7910
|
'</span></div>'
|
|
7842
7911
|
);
|
|
7843
7912
|
}
|
|
7844
7913
|
|
|
7845
|
-
function
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
7889
|
-
|
|
7890
|
-
|
|
7891
|
-
|
|
7892
|
-
|
|
7893
|
-
)
|
|
7894
|
-
|
|
7895
|
-
|
|
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
|
-
|
|
7899
|
-
|
|
7900
|
-
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
const
|
|
7910
|
-
|
|
7911
|
-
|
|
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-
|
|
7915
|
-
|
|
7916
|
-
|
|
7917
|
-
' ' +
|
|
7918
|
-
|
|
7919
|
-
|
|
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-
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
'
|
|
7929
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
7961
|
-
|
|
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 (
|
|
7966
|
-
const totalReps =
|
|
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
|
|
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
|
-
|
|
8095
|
+
loops.length +
|
|
8018
8096
|
' pattern' +
|
|
8019
|
-
(
|
|
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
|
|
8095
|
-
const
|
|
8096
|
-
const
|
|
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
|
|
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 (
|
|
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
|
-
\${
|
|
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.
|
|
8131
|
-
<span class="finding-cmd"><span class="finding-badge badge-leak">\u{1F511}</span>\${esc(l.
|
|
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 \${
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
8635
|
+
const p = path17.join(geminiDir, slug);
|
|
8355
8636
|
try {
|
|
8356
8637
|
if (!fs13.statSync(p).isDirectory()) continue;
|
|
8357
|
-
const chatsDir =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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(
|
|
9051
|
-
const
|
|
9052
|
-
const ruleCount =
|
|
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
|
|
9055
|
-
const icon = verdictIcon(rule.verdict ?? "review");
|
|
9342
|
+
const icon = verdictIcon(rule.verdict);
|
|
9056
9343
|
console.log(
|
|
9057
|
-
" " + icon + " " + chalk2.white(
|
|
9344
|
+
" " + icon + " " + chalk2.white(rule.name) + countBadge + (rule.reason ? chalk2.dim(` \u2014 ${rule.reason}`) : "")
|
|
9058
9345
|
);
|
|
9059
|
-
const shown = drillDown ?
|
|
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 &&
|
|
9350
|
+
if (!drillDown && findings.length > topN) {
|
|
9064
9351
|
console.log(
|
|
9065
|
-
chalk2.dim(` \u2026 and ${
|
|
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(
|
|
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 (
|
|
9218
|
-
|
|
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
|
|
9226
|
-
|
|
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
|
|
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 (
|
|
9592
|
+
if (!isTestingMode() && isDaemonRunning()) {
|
|
9350
9593
|
const internalToken = getInternalToken();
|
|
9351
9594
|
if (internalToken) {
|
|
9352
9595
|
try {
|
|
9353
|
-
const
|
|
9354
|
-
|
|
9355
|
-
|
|
9356
|
-
|
|
9357
|
-
|
|
9358
|
-
|
|
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
|
|
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(
|
|
9827
|
+
return fs14.realpathSync.native(path18.resolve(filePath));
|
|
9650
9828
|
} catch {
|
|
9651
|
-
return
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
10124
|
-
DECISIONS_FILE =
|
|
10125
|
-
AUDIT_LOG_FILE =
|
|
10126
|
-
TRUST_FILE2 =
|
|
10127
|
-
GLOBAL_CONFIG_FILE =
|
|
10128
|
-
CREDENTIALS_FILE =
|
|
10129
|
-
INSIGHT_COUNTS_FILE =
|
|
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" :
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 = () =>
|
|
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
|
|
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 =
|
|
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 +
|
|
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 =
|
|
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 =
|
|
10678
|
-
PROJECTS_DIR =
|
|
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
|
|
10862
|
+
import path24 from "path";
|
|
10685
10863
|
import os18 from "os";
|
|
10686
10864
|
function getMcpToolsFile() {
|
|
10687
|
-
return
|
|
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 =
|
|
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
|
|
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" &&
|
|
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 =
|
|
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
|
-
|
|
11434
|
-
|
|
11435
|
-
|
|
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
|
|
11452
|
-
|
|
11453
|
-
|
|
11454
|
-
|
|
11455
|
-
|
|
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 =
|
|
11569
|
-
if (!
|
|
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
|
|
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" &&
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
11905
12038
|
}
|
|
11906
12039
|
function launchdPlist(binaryPath) {
|
|
11907
|
-
const logDir =
|
|
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(
|
|
11911
|
-
const errLog = xmlEscape(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
12122
|
-
SYSTEMD_UNIT_DIR =
|
|
12123
|
-
SYSTEMD_UNIT =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
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(
|
|
13150
|
-
rulesCount += countRulesInDir(
|
|
13151
|
-
const userSettings =
|
|
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 =
|
|
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(
|
|
13161
|
-
if (fs39.existsSync(
|
|
13162
|
-
const projectClaudeDir =
|
|
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(
|
|
13166
|
-
rulesCount += countRulesInDir(
|
|
13167
|
-
const projSettings =
|
|
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(
|
|
13172
|
-
const localSettings =
|
|
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(
|
|
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 =
|
|
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(
|
|
13448
|
+
if (fs39.existsSync(path43.join(os36.homedir(), ".node9", "hud-debug"))) {
|
|
13316
13449
|
try {
|
|
13317
|
-
const logPath =
|
|
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
|
-
|
|
13347
|
-
|
|
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
|
|
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
|
|
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
|
|
14752
|
+
import path27 from "path";
|
|
14620
14753
|
import os21 from "os";
|
|
14621
|
-
var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
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 =
|
|
14642
|
-
var UNDO_LATEST_PATH =
|
|
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 =
|
|
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 =
|
|
14809
|
+
let dir = path27.dirname(filePath);
|
|
14677
14810
|
while (true) {
|
|
14678
|
-
if (fs24.existsSync(
|
|
14811
|
+
if (fs24.existsSync(path27.join(dir, ".git")) || fs24.existsSync(path27.join(dir, "package.json"))) {
|
|
14679
14812
|
return dir;
|
|
14680
14813
|
}
|
|
14681
|
-
const parent =
|
|
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
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 &&
|
|
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 =
|
|
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 =
|
|
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
|
|
15101
|
+
import path28 from "path";
|
|
14969
15102
|
import os22 from "os";
|
|
14970
15103
|
import crypto4 from "crypto";
|
|
14971
15104
|
function getPinsFilePath() {
|
|
14972
|
-
return
|
|
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 =
|
|
14994
|
-
const rel = relDir ?
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
15142
|
-
if (
|
|
15143
|
-
if (!cwd || !
|
|
15144
|
-
return
|
|
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 =
|
|
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" || !
|
|
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(
|
|
15181
|
-
if (!resolvedScript.startsWith(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 =
|
|
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 =
|
|
15217
|
-
if (!fs26.existsSync(
|
|
15218
|
-
fs26.mkdirSync(
|
|
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 =
|
|
15287
|
-
const flagPath =
|
|
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" &&
|
|
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 =
|
|
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 =
|
|
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" &&
|
|
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 =
|
|
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
|
|
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 =
|
|
15563
|
-
if (!fs27.existsSync(
|
|
15564
|
-
fs27.mkdirSync(
|
|
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" &&
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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
|
|
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 =
|
|
17130
|
-
const globalConfig =
|
|
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
|
-
|
|
17277
|
+
path34.join(homeDir2, ".claude", "settings.json")
|
|
17145
17278
|
);
|
|
17146
|
-
const claudeConfig = readJson2(
|
|
17279
|
+
const claudeConfig = readJson2(path34.join(homeDir2, ".claude.json"));
|
|
17147
17280
|
const geminiSettings = readJson2(
|
|
17148
|
-
|
|
17281
|
+
path34.join(homeDir2, ".gemini", "settings.json")
|
|
17149
17282
|
);
|
|
17150
|
-
const cursorConfig = readJson2(
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
17869
|
+
import path37 from "path";
|
|
17737
17870
|
import os30 from "os";
|
|
17738
17871
|
import crypto5 from "crypto";
|
|
17739
17872
|
function getPinsFilePath2() {
|
|
17740
|
-
return
|
|
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(
|
|
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
|
|
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 =
|
|
18486
|
-
const globalConfig =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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 ??
|
|
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 =
|
|
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 =
|
|
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 =
|
|
19488
|
+
let projectRoot = path39.join(os32.homedir(), slug);
|
|
19356
19489
|
try {
|
|
19357
|
-
projectRoot = fs35.readFileSync(
|
|
19490
|
+
projectRoot = fs35.readFileSync(path39.join(slugPath, ".project_root"), "utf-8").trim();
|
|
19358
19491
|
} catch {
|
|
19359
19492
|
}
|
|
19360
|
-
const chatsDir =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 ??
|
|
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 =
|
|
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
|
|
20040
|
+
import path40 from "path";
|
|
19908
20041
|
function wipeSkillSessions() {
|
|
19909
20042
|
try {
|
|
19910
|
-
fs36.rmSync(
|
|
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
|
|
20127
|
+
import path41 from "path";
|
|
19995
20128
|
import os34 from "os";
|
|
19996
|
-
var AUDIT_LOG =
|
|
19997
|
-
var RESOLVED_FILE =
|
|
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(
|
|
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 =
|
|
20123
|
-
if (!fs40.existsSync(
|
|
20124
|
-
fs40.mkdirSync(
|
|
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 =
|
|
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(
|
|
20163
|
-
fs40.mkdirSync(
|
|
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 =
|
|
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 =
|
|
20584
|
+
const flagFile = path44.join(os37.homedir(), ".node9", "hud-debug");
|
|
20452
20585
|
if (state === "on") {
|
|
20453
|
-
fs40.mkdirSync(
|
|
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
|
|
20656
|
+
const fullCommand = runArgs.join(" ");
|
|
20524
20657
|
let result = await authorizeHeadless(
|
|
20525
20658
|
"shell",
|
|
20526
|
-
{ command:
|
|
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:
|
|
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 "${
|
|
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(
|
|
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 =
|
|
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
|
`);
|