@node9/proxy 1.19.4 → 1.20.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 +53 -98
- package/dist/cli.js +1653 -925
- package/dist/cli.mjs +1653 -924
- package/dist/dashboard.mjs +3501 -1343
- package/dist/index.js +127 -13
- package/dist/index.mjs +127 -13
- package/package.json +2 -1
package/dist/cli.mjs
CHANGED
|
@@ -99,12 +99,14 @@ function appendHookDebug(toolName, args, meta, auditHashArgsEnabled) {
|
|
|
99
99
|
function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashArgsEnabled) {
|
|
100
100
|
const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
|
|
101
101
|
const testRun = isTestCall(toolName, args) || process.env.NODE9_TESTING === "1" ? { testRun: true } : {};
|
|
102
|
+
const ruleNameField = meta?.ruleName ? { ruleName: meta.ruleName } : {};
|
|
102
103
|
appendToLog(LOCAL_AUDIT_LOG, {
|
|
103
104
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
104
105
|
tool: toolName,
|
|
105
106
|
...argsField,
|
|
106
107
|
decision,
|
|
107
108
|
checkedBy,
|
|
109
|
+
...ruleNameField,
|
|
108
110
|
...testRun,
|
|
109
111
|
agent: meta?.agent,
|
|
110
112
|
mcpServer: meta?.mcpServer,
|
|
@@ -691,7 +693,12 @@ function analyzeFsOperationImpl(command) {
|
|
|
691
693
|
for (const p of paths) {
|
|
692
694
|
for (const sp of SENSITIVE_PATH_RULES) {
|
|
693
695
|
if (sp.match(p)) {
|
|
694
|
-
result = {
|
|
696
|
+
result = {
|
|
697
|
+
ruleName: sp.rule,
|
|
698
|
+
verdict: sp.verdict ?? "block",
|
|
699
|
+
reason: sp.reason,
|
|
700
|
+
path: p
|
|
701
|
+
};
|
|
695
702
|
return false;
|
|
696
703
|
}
|
|
697
704
|
}
|
|
@@ -2345,15 +2352,50 @@ var init_dist = __esm({
|
|
|
2345
2352
|
match: (p) => /(^|[\\/])\.aws[\\/]/i.test(p)
|
|
2346
2353
|
},
|
|
2347
2354
|
{
|
|
2355
|
+
// Mirrors the JSON shield's `.env` pattern (project-jail.json's
|
|
2356
|
+
// review-read-env-any-tool) so the AST FS-op path catches the
|
|
2357
|
+
// same set the regex shield does — including Next.js / Vite's
|
|
2358
|
+
// `.env.<env>.local` double-suffix overrides which are commonly
|
|
2359
|
+
// gitignored AND commonly contain real secrets.
|
|
2360
|
+
//
|
|
2361
|
+
// Intentional non-matches (dev fixtures): .env.example, .env.sample,
|
|
2362
|
+
// .env.template, .env.test, .envrc. See shields.test.ts:983-995
|
|
2363
|
+
// for the canonical test-asserted contract.
|
|
2348
2364
|
rule: "shield:project-jail:block-read-env",
|
|
2349
2365
|
reason: "Reading .env files is blocked by project-jail shield",
|
|
2350
|
-
match: (p) => /(?:^|[\\/])\.env(?:\.local
|
|
2366
|
+
match: (p) => /(?:^|[\\/])\.env(?:\.(?:local|production|staging|development|production\.local|staging\.local|development\.local))?$/i.test(
|
|
2367
|
+
p
|
|
2368
|
+
)
|
|
2351
2369
|
},
|
|
2352
2370
|
{
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2371
|
+
// verdict: 'review' (not 'block') is a deliberate design choice
|
|
2372
|
+
// documented in commit 29327a8. SSH keys and AWS credentials are
|
|
2373
|
+
// cryptographic material with no legitimate read use-case for
|
|
2374
|
+
// an AI agent → hard `block`. But .netrc / .npmrc / .docker /
|
|
2375
|
+
// .kube / gcloud are CONFIG files that hold tokens AND have
|
|
2376
|
+
// legitimate diagnostic reads ("which registry am I configured
|
|
2377
|
+
// for", "what cluster am I on"). Hard-blocking those creates
|
|
2378
|
+
// friction without much safety win because the review gate
|
|
2379
|
+
// still catches genuine exfiltration attempts.
|
|
2380
|
+
//
|
|
2381
|
+
// The review gate FAILS CLOSED on timeout (daemon.approvalTimeoutMs
|
|
2382
|
+
// returns a deny verdict via the orchestrator's timeout branch),
|
|
2383
|
+
// so a stuck or unattended approval does NOT silently grant
|
|
2384
|
+
// credential access. If the threat model demands strict block,
|
|
2385
|
+
// a future per-shield strict-mode toggle is the right fix —
|
|
2386
|
+
// not a regex-level upgrade here.
|
|
2387
|
+
rule: "shield:project-jail:review-read-credentials",
|
|
2388
|
+
reason: "Reading credential files requires approval (project-jail shield)",
|
|
2389
|
+
verdict: "review",
|
|
2390
|
+
match: (p) => (
|
|
2391
|
+
// .kube/config holds Kubernetes cluster credentials and was
|
|
2392
|
+
// flagged as missing by the node9-pr-agent review (the comment
|
|
2393
|
+
// above mentioned .kube but the regex didn't include it — a
|
|
2394
|
+
// textbook code-comment vs code drift). The JSON shield's
|
|
2395
|
+
// review-read-credentials-any-tool already had it. Now aligned.
|
|
2396
|
+
/(?:credentials\.json|\.netrc|\.npmrc|\.docker[\\/]config\.json|gcloud[\\/]credentials|\.kube[\\/]config)$/i.test(
|
|
2397
|
+
p
|
|
2398
|
+
)
|
|
2357
2399
|
)
|
|
2358
2400
|
}
|
|
2359
2401
|
];
|
|
@@ -2369,7 +2411,7 @@ var init_dist = __esm({
|
|
|
2369
2411
|
"shield:project-jail:block-read-ssh",
|
|
2370
2412
|
"shield:project-jail:block-read-aws",
|
|
2371
2413
|
"shield:project-jail:block-read-env",
|
|
2372
|
-
"shield:project-jail:
|
|
2414
|
+
"shield:project-jail:review-read-credentials"
|
|
2373
2415
|
]);
|
|
2374
2416
|
FS_OP_CACHE_MAX = 5e3;
|
|
2375
2417
|
fsOpCache = /* @__PURE__ */ new Map();
|
|
@@ -3057,7 +3099,7 @@ var init_dist = __esm({
|
|
|
3057
3099
|
{
|
|
3058
3100
|
field: "command",
|
|
3059
3101
|
op: "matches",
|
|
3060
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s
|
|
3102
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.ssh[\\/\\\\]",
|
|
3061
3103
|
flags: "i"
|
|
3062
3104
|
}
|
|
3063
3105
|
],
|
|
@@ -3071,7 +3113,7 @@ var init_dist = __esm({
|
|
|
3071
3113
|
{
|
|
3072
3114
|
field: "command",
|
|
3073
3115
|
op: "matches",
|
|
3074
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s
|
|
3116
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.aws[\\/\\\\]",
|
|
3075
3117
|
flags: "i"
|
|
3076
3118
|
}
|
|
3077
3119
|
],
|
|
@@ -3093,7 +3135,7 @@ var init_dist = __esm({
|
|
|
3093
3135
|
reason: "Reading .env files is blocked by project-jail shield"
|
|
3094
3136
|
},
|
|
3095
3137
|
{
|
|
3096
|
-
name: "shield:project-jail:
|
|
3138
|
+
name: "shield:project-jail:review-read-credentials",
|
|
3097
3139
|
tool: "bash",
|
|
3098
3140
|
conditions: [
|
|
3099
3141
|
{
|
|
@@ -3103,8 +3145,64 @@ var init_dist = __esm({
|
|
|
3103
3145
|
flags: "i"
|
|
3104
3146
|
}
|
|
3105
3147
|
],
|
|
3148
|
+
verdict: "review",
|
|
3149
|
+
reason: "Reading credential files requires approval (project-jail shield)"
|
|
3150
|
+
},
|
|
3151
|
+
{
|
|
3152
|
+
name: "shield:project-jail:block-read-ssh-any-tool",
|
|
3153
|
+
tool: "*",
|
|
3154
|
+
conditions: [
|
|
3155
|
+
{
|
|
3156
|
+
field: "file_path",
|
|
3157
|
+
op: "matches",
|
|
3158
|
+
value: "(^|[\\/\\\\])\\.ssh[\\/\\\\]",
|
|
3159
|
+
flags: "i"
|
|
3160
|
+
}
|
|
3161
|
+
],
|
|
3162
|
+
verdict: "block",
|
|
3163
|
+
reason: "Reading SSH private keys is blocked by project-jail shield"
|
|
3164
|
+
},
|
|
3165
|
+
{
|
|
3166
|
+
name: "shield:project-jail:block-read-aws-any-tool",
|
|
3167
|
+
tool: "*",
|
|
3168
|
+
conditions: [
|
|
3169
|
+
{
|
|
3170
|
+
field: "file_path",
|
|
3171
|
+
op: "matches",
|
|
3172
|
+
value: "(^|[\\/\\\\])\\.aws[\\/\\\\]",
|
|
3173
|
+
flags: "i"
|
|
3174
|
+
}
|
|
3175
|
+
],
|
|
3106
3176
|
verdict: "block",
|
|
3107
|
-
reason: "Reading
|
|
3177
|
+
reason: "Reading AWS credentials is blocked by project-jail shield"
|
|
3178
|
+
},
|
|
3179
|
+
{
|
|
3180
|
+
name: "shield:project-jail:review-read-env-any-tool",
|
|
3181
|
+
tool: "*",
|
|
3182
|
+
conditions: [
|
|
3183
|
+
{
|
|
3184
|
+
field: "file_path",
|
|
3185
|
+
op: "matches",
|
|
3186
|
+
value: "(^|[\\/\\\\])\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?$",
|
|
3187
|
+
flags: "i"
|
|
3188
|
+
}
|
|
3189
|
+
],
|
|
3190
|
+
verdict: "review",
|
|
3191
|
+
reason: "Reading .env files requires approval (project-jail shield)"
|
|
3192
|
+
},
|
|
3193
|
+
{
|
|
3194
|
+
name: "shield:project-jail:review-read-credentials-any-tool",
|
|
3195
|
+
tool: "*",
|
|
3196
|
+
conditions: [
|
|
3197
|
+
{
|
|
3198
|
+
field: "file_path",
|
|
3199
|
+
op: "matches",
|
|
3200
|
+
value: ".*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials|\\.kube[\\/\\\\]config)",
|
|
3201
|
+
flags: "i"
|
|
3202
|
+
}
|
|
3203
|
+
],
|
|
3204
|
+
verdict: "review",
|
|
3205
|
+
reason: "Reading credential files requires approval (project-jail shield)"
|
|
3108
3206
|
}
|
|
3109
3207
|
],
|
|
3110
3208
|
dangerousWords: []
|
|
@@ -5770,7 +5868,10 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
5770
5868
|
args,
|
|
5771
5869
|
"deny",
|
|
5772
5870
|
"smart-rule-block-override",
|
|
5773
|
-
|
|
5871
|
+
// Same rationale as the smart-rule-block path above —
|
|
5872
|
+
// pass the specific rule name so [2] SHIELDS can
|
|
5873
|
+
// attribute this override-block to its owning shield.
|
|
5874
|
+
{ ...meta, ruleName: policyResult.ruleName },
|
|
5774
5875
|
hashAuditArgs
|
|
5775
5876
|
);
|
|
5776
5877
|
if (approvers.cloud && creds?.apiKey)
|
|
@@ -5800,7 +5901,20 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
5800
5901
|
}
|
|
5801
5902
|
} else {
|
|
5802
5903
|
if (!isManual)
|
|
5803
|
-
appendLocalAudit(
|
|
5904
|
+
appendLocalAudit(
|
|
5905
|
+
toolName,
|
|
5906
|
+
args,
|
|
5907
|
+
"deny",
|
|
5908
|
+
"smart-rule-block",
|
|
5909
|
+
// Include policyResult.ruleName so the [2] Report SHIELDS
|
|
5910
|
+
// panel can attribute this block to its specific shield
|
|
5911
|
+
// (e.g. `shield:project-jail:block-read-ssh`) via the
|
|
5912
|
+
// rule→shield map. checkedBy stays as the generic
|
|
5913
|
+
// `smart-rule-block` for backward compat with existing
|
|
5914
|
+
// log readers.
|
|
5915
|
+
{ ...meta, ruleName: policyResult.ruleName },
|
|
5916
|
+
hashAuditArgs
|
|
5917
|
+
);
|
|
5804
5918
|
if (approvers.cloud && creds?.apiKey)
|
|
5805
5919
|
auditLocalAllow(toolName, args, "smart-rule-block", creds, meta, void 0, false, {
|
|
5806
5920
|
ruleName: policyResult.ruleName,
|
|
@@ -7621,9 +7735,61 @@ function computeLoopWaste(loops, totalToolCalls) {
|
|
|
7621
7735
|
const wastePct = totalToolCalls > 0 ? Math.round(wastedCalls / totalToolCalls * 100) : 0;
|
|
7622
7736
|
return { wastedCalls, wastePct };
|
|
7623
7737
|
}
|
|
7738
|
+
function rollupByShield(sections, topRulesPerShield = 3) {
|
|
7739
|
+
const out = [];
|
|
7740
|
+
for (const section of sections) {
|
|
7741
|
+
if (section.sourceType !== "shield") continue;
|
|
7742
|
+
if (!section.shieldKey) continue;
|
|
7743
|
+
const totalCatches = section.blockedCount + section.reviewCount;
|
|
7744
|
+
const topRuleLabels = [...section.rules].sort((a, b) => b.findings.length - a.findings.length).slice(0, topRulesPerShield).map((r) => r.findings.length > 1 ? `${r.name} \xD7${r.findings.length}` : r.name);
|
|
7745
|
+
out.push({
|
|
7746
|
+
shieldName: section.shieldKey,
|
|
7747
|
+
totalCatches,
|
|
7748
|
+
blockCatches: section.blockedCount,
|
|
7749
|
+
reviewCatches: section.reviewCount,
|
|
7750
|
+
topRuleLabels
|
|
7751
|
+
});
|
|
7752
|
+
}
|
|
7753
|
+
return out.sort((a, b) => b.totalCatches - a.totalCatches);
|
|
7754
|
+
}
|
|
7755
|
+
function boxPanel(title, bodyLines, width = PANEL_WIDTH) {
|
|
7756
|
+
const inner = width - 4;
|
|
7757
|
+
const out = [];
|
|
7758
|
+
const titlePad = ` ${title} `;
|
|
7759
|
+
const titleSegment = titlePad.length <= inner ? titlePad : titlePad.slice(0, inner);
|
|
7760
|
+
const dashFill = "\u2500".repeat(Math.max(0, inner - titleSegment.length));
|
|
7761
|
+
out.push(chalk3.dim("\u256D\u2500") + chalk3.bold(titleSegment) + chalk3.dim(`${dashFill}\u2500\u256E`));
|
|
7762
|
+
for (const line of bodyLines) {
|
|
7763
|
+
const padding = " ".repeat(Math.max(0, inner - line.width));
|
|
7764
|
+
out.push(chalk3.dim("\u2502 ") + line.rendered + padding + chalk3.dim(" \u2502"));
|
|
7765
|
+
}
|
|
7766
|
+
out.push(chalk3.dim("\u2570" + "\u2500".repeat(inner + 2) + "\u256F"));
|
|
7767
|
+
return out;
|
|
7768
|
+
}
|
|
7769
|
+
function relativeDate(timestamp, now = /* @__PURE__ */ new Date()) {
|
|
7770
|
+
const t = new Date(timestamp).getTime();
|
|
7771
|
+
if (Number.isNaN(t)) return "?";
|
|
7772
|
+
const days = Math.floor((now.getTime() - t) / 864e5);
|
|
7773
|
+
if (days < 1) return "today";
|
|
7774
|
+
if (days > 90) return "90d+";
|
|
7775
|
+
return `${days}d`;
|
|
7776
|
+
}
|
|
7777
|
+
var PANEL_WIDTH;
|
|
7624
7778
|
var init_scan_derive = __esm({
|
|
7625
7779
|
"src/cli/render/scan-derive.ts"() {
|
|
7626
7780
|
"use strict";
|
|
7781
|
+
PANEL_WIDTH = 76;
|
|
7782
|
+
}
|
|
7783
|
+
});
|
|
7784
|
+
|
|
7785
|
+
// src/protection.ts
|
|
7786
|
+
var PROTECTIVE_SHIELD_DISCOUNTS;
|
|
7787
|
+
var init_protection = __esm({
|
|
7788
|
+
"src/protection.ts"() {
|
|
7789
|
+
"use strict";
|
|
7790
|
+
PROTECTIVE_SHIELD_DISCOUNTS = {
|
|
7791
|
+
"project-jail": 0.7
|
|
7792
|
+
};
|
|
7627
7793
|
}
|
|
7628
7794
|
});
|
|
7629
7795
|
|
|
@@ -7813,6 +7979,7 @@ async function ensurePricingLoaded() {
|
|
|
7813
7979
|
if (fromDisk && Object.keys(fromDisk).length > 0) {
|
|
7814
7980
|
memCache = fromDisk;
|
|
7815
7981
|
memCacheAt = Date.now();
|
|
7982
|
+
lookupCache.clear();
|
|
7816
7983
|
return;
|
|
7817
7984
|
}
|
|
7818
7985
|
const fetched = await fetchLiteLLMPricing();
|
|
@@ -7820,30 +7987,42 @@ async function ensurePricingLoaded() {
|
|
|
7820
7987
|
memCache = fetched;
|
|
7821
7988
|
memCacheAt = Date.now();
|
|
7822
7989
|
writeCache(fetched);
|
|
7990
|
+
lookupCache.clear();
|
|
7823
7991
|
return;
|
|
7824
7992
|
}
|
|
7825
7993
|
memCache = { ...BUNDLED_PRICING };
|
|
7826
7994
|
memCacheAt = Date.now();
|
|
7995
|
+
lookupCache.clear();
|
|
7827
7996
|
}
|
|
7828
7997
|
function pricingFor(model) {
|
|
7829
7998
|
const norm = normalizeModel(model);
|
|
7999
|
+
const cached = lookupCache.get(norm);
|
|
8000
|
+
if (cached !== void 0) return cached;
|
|
7830
8001
|
const sources = [];
|
|
7831
8002
|
if (memCache) sources.push(memCache);
|
|
7832
8003
|
sources.push(BUNDLED_PRICING);
|
|
8004
|
+
let resolved = null;
|
|
7833
8005
|
for (const source of sources) {
|
|
7834
8006
|
const exact = source[norm];
|
|
7835
|
-
if (exact)
|
|
8007
|
+
if (exact) {
|
|
8008
|
+
resolved = exact;
|
|
8009
|
+
break;
|
|
8010
|
+
}
|
|
7836
8011
|
let best = null;
|
|
7837
8012
|
for (const key of Object.keys(source)) {
|
|
7838
8013
|
if (norm.startsWith(key.toLowerCase()) && (best === null || key.length > best.length)) {
|
|
7839
8014
|
best = key;
|
|
7840
8015
|
}
|
|
7841
8016
|
}
|
|
7842
|
-
if (best)
|
|
8017
|
+
if (best) {
|
|
8018
|
+
resolved = source[best];
|
|
8019
|
+
break;
|
|
8020
|
+
}
|
|
7843
8021
|
}
|
|
7844
|
-
|
|
8022
|
+
lookupCache.set(norm, resolved);
|
|
8023
|
+
return resolved;
|
|
7845
8024
|
}
|
|
7846
|
-
var LITELLM_URL, BUNDLED_PRICING, CACHE_FILE, TTL_MS, memCache, memCacheAt;
|
|
8025
|
+
var LITELLM_URL, BUNDLED_PRICING, CACHE_FILE, TTL_MS, memCache, memCacheAt, lookupCache;
|
|
7847
8026
|
var init_litellm = __esm({
|
|
7848
8027
|
"src/pricing/litellm.ts"() {
|
|
7849
8028
|
"use strict";
|
|
@@ -7877,6 +8056,7 @@ var init_litellm = __esm({
|
|
|
7877
8056
|
TTL_MS = 24 * 60 * 60 * 1e3;
|
|
7878
8057
|
memCache = null;
|
|
7879
8058
|
memCacheAt = 0;
|
|
8059
|
+
lookupCache = /* @__PURE__ */ new Map();
|
|
7880
8060
|
}
|
|
7881
8061
|
});
|
|
7882
8062
|
|
|
@@ -7946,7 +8126,7 @@ function parseJSONLFile(filePath, fallbackWorkingDir) {
|
|
|
7946
8126
|
}
|
|
7947
8127
|
return daily;
|
|
7948
8128
|
}
|
|
7949
|
-
function collectEntries() {
|
|
8129
|
+
function collectEntries(sinceMs) {
|
|
7950
8130
|
const projectsDir = path18.join(os15.homedir(), ".claude", "projects");
|
|
7951
8131
|
if (!fs16.existsSync(projectsDir)) return [];
|
|
7952
8132
|
const combined = /* @__PURE__ */ new Map();
|
|
@@ -7971,7 +8151,15 @@ function collectEntries() {
|
|
|
7971
8151
|
}
|
|
7972
8152
|
const fallbackWorkingDir = decodeProjectDirName(dir);
|
|
7973
8153
|
for (const file of files) {
|
|
7974
|
-
const
|
|
8154
|
+
const filePath = path18.join(dirPath, file);
|
|
8155
|
+
if (sinceMs !== void 0) {
|
|
8156
|
+
try {
|
|
8157
|
+
if (fs16.statSync(filePath).mtimeMs < sinceMs) continue;
|
|
8158
|
+
} catch {
|
|
8159
|
+
continue;
|
|
8160
|
+
}
|
|
8161
|
+
}
|
|
8162
|
+
const entries = parseJSONLFile(filePath, fallbackWorkingDir);
|
|
7975
8163
|
for (const [key, e] of entries) {
|
|
7976
8164
|
const prev = combined.get(key);
|
|
7977
8165
|
if (prev) {
|
|
@@ -8807,7 +8995,16 @@ function buildRecurringPatternSet(findings) {
|
|
|
8807
8995
|
}
|
|
8808
8996
|
return recurring;
|
|
8809
8997
|
}
|
|
8810
|
-
function
|
|
8998
|
+
function emptyScanDedup() {
|
|
8999
|
+
return { findingsKeys: /* @__PURE__ */ new Set(), dlpKeys: /* @__PURE__ */ new Set() };
|
|
9000
|
+
}
|
|
9001
|
+
function findingKey(ruleName, inputPreview, projLabel) {
|
|
9002
|
+
return `${ruleName ?? "<unnamed>"}|${inputPreview}|${projLabel}`;
|
|
9003
|
+
}
|
|
9004
|
+
function dlpKey(patternName, redactedSample, projLabel) {
|
|
9005
|
+
return `${patternName}|${redactedSample}|${projLabel}`;
|
|
9006
|
+
}
|
|
9007
|
+
function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sessionId, agent, result, dedup) {
|
|
8811
9008
|
const fsVerdict = analyzeFsOperation(command);
|
|
8812
9009
|
if (!fsVerdict) return false;
|
|
8813
9010
|
const synthRule = {
|
|
@@ -8830,10 +9027,9 @@ function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sess
|
|
|
8830
9027
|
rule: synthRule
|
|
8831
9028
|
};
|
|
8832
9029
|
const inputPreview = preview(input, 120);
|
|
8833
|
-
const
|
|
8834
|
-
|
|
8835
|
-
|
|
8836
|
-
if (!isDupe) {
|
|
9030
|
+
const k = findingKey(synthRule.name, inputPreview, projLabel);
|
|
9031
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9032
|
+
dedup.findingsKeys.add(k);
|
|
8837
9033
|
result.findings.push({
|
|
8838
9034
|
source: synthSource,
|
|
8839
9035
|
toolName,
|
|
@@ -8918,22 +9114,15 @@ function buildRuleSources() {
|
|
|
8918
9114
|
sources.push({ shieldName, shieldLabel: shieldName, sourceType: "shield", rule });
|
|
8919
9115
|
}
|
|
8920
9116
|
}
|
|
8921
|
-
|
|
8922
|
-
|
|
8923
|
-
|
|
8924
|
-
|
|
8925
|
-
|
|
8926
|
-
|
|
8927
|
-
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
shieldName: isCloud ? "cloud" : isDefault ? "default" : "custom",
|
|
8931
|
-
shieldLabel: isCloud ? "Cloud Policy" : isDefault ? "Default Rules" : "Your Rules",
|
|
8932
|
-
sourceType,
|
|
8933
|
-
rule
|
|
8934
|
-
});
|
|
8935
|
-
}
|
|
8936
|
-
} catch {
|
|
9117
|
+
for (const rule of DEFAULT_CONFIG.policy.smartRules) {
|
|
9118
|
+
if (!rule.name) continue;
|
|
9119
|
+
if (rule.name.startsWith("shield:")) continue;
|
|
9120
|
+
sources.push({
|
|
9121
|
+
shieldName: "default",
|
|
9122
|
+
shieldLabel: "Default Rules",
|
|
9123
|
+
sourceType: "default",
|
|
9124
|
+
rule
|
|
9125
|
+
});
|
|
8937
9126
|
}
|
|
8938
9127
|
return sources;
|
|
8939
9128
|
}
|
|
@@ -9019,178 +9208,53 @@ function renderProgressBar(done, total, lines) {
|
|
|
9019
9208
|
`\r ${chalk5.cyan("Scanning")} [${chalk5.cyan(bar)}] ${chalk5.dim(fileLabel)}${lineLabel} `
|
|
9020
9209
|
);
|
|
9021
9210
|
}
|
|
9022
|
-
function
|
|
9023
|
-
|
|
9024
|
-
|
|
9025
|
-
|
|
9026
|
-
|
|
9027
|
-
|
|
9028
|
-
bashCalls: 0,
|
|
9029
|
-
findings: [],
|
|
9030
|
-
dlpFindings: [],
|
|
9031
|
-
loopFindings: [],
|
|
9032
|
-
totalCostUSD: 0,
|
|
9033
|
-
firstDate: null,
|
|
9034
|
-
lastDate: null,
|
|
9035
|
-
sessionsWithEarlySecrets: 0
|
|
9036
|
-
};
|
|
9037
|
-
if (!fs19.existsSync(projectsDir)) return result;
|
|
9038
|
-
let projDirs;
|
|
9211
|
+
function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, result, dedup, onProgress, onLine) {
|
|
9212
|
+
result.filesScanned++;
|
|
9213
|
+
result.sessions++;
|
|
9214
|
+
onProgress?.(result.filesScanned);
|
|
9215
|
+
const sessionId = file.replace(/\.jsonl$/, "");
|
|
9216
|
+
let raw;
|
|
9039
9217
|
try {
|
|
9040
|
-
|
|
9218
|
+
raw = fs19.readFileSync(path21.join(projPath, file), "utf-8");
|
|
9041
9219
|
} catch {
|
|
9042
|
-
return
|
|
9220
|
+
return;
|
|
9043
9221
|
}
|
|
9044
|
-
const
|
|
9045
|
-
|
|
9046
|
-
|
|
9222
|
+
const sessionCalls = [];
|
|
9223
|
+
const toolUseFilePaths = /* @__PURE__ */ new Map();
|
|
9224
|
+
let firstDlpTs = null;
|
|
9225
|
+
let firstEditTs = null;
|
|
9226
|
+
for (const line of raw.split("\n")) {
|
|
9227
|
+
if (!line.trim()) continue;
|
|
9228
|
+
onLine?.();
|
|
9229
|
+
let entry;
|
|
9047
9230
|
try {
|
|
9048
|
-
|
|
9231
|
+
entry = JSON.parse(line);
|
|
9049
9232
|
} catch {
|
|
9050
9233
|
continue;
|
|
9051
9234
|
}
|
|
9052
|
-
|
|
9053
|
-
|
|
9054
|
-
|
|
9055
|
-
let files;
|
|
9056
|
-
try {
|
|
9057
|
-
files = fs19.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
9058
|
-
} catch {
|
|
9059
|
-
continue;
|
|
9235
|
+
if (entry.type !== "assistant" && entry.type !== "user") continue;
|
|
9236
|
+
if (startDate && entry.timestamp) {
|
|
9237
|
+
if (new Date(entry.timestamp) < startDate) continue;
|
|
9060
9238
|
}
|
|
9061
|
-
|
|
9062
|
-
result.
|
|
9063
|
-
|
|
9064
|
-
|
|
9065
|
-
|
|
9066
|
-
|
|
9067
|
-
|
|
9068
|
-
|
|
9069
|
-
|
|
9070
|
-
|
|
9071
|
-
|
|
9072
|
-
const sessionCalls = [];
|
|
9073
|
-
const toolUseFilePaths = /* @__PURE__ */ new Map();
|
|
9074
|
-
let firstDlpTs = null;
|
|
9075
|
-
let firstEditTs = null;
|
|
9076
|
-
for (const line of raw.split("\n")) {
|
|
9077
|
-
if (!line.trim()) continue;
|
|
9078
|
-
onLine?.();
|
|
9079
|
-
let entry;
|
|
9080
|
-
try {
|
|
9081
|
-
entry = JSON.parse(line);
|
|
9082
|
-
} catch {
|
|
9083
|
-
continue;
|
|
9084
|
-
}
|
|
9085
|
-
if (entry.type !== "assistant" && entry.type !== "user") continue;
|
|
9086
|
-
if (startDate && entry.timestamp) {
|
|
9087
|
-
if (new Date(entry.timestamp) < startDate) continue;
|
|
9088
|
-
}
|
|
9089
|
-
if (entry.timestamp) {
|
|
9090
|
-
if (!result.firstDate || entry.timestamp < result.firstDate)
|
|
9091
|
-
result.firstDate = entry.timestamp;
|
|
9092
|
-
if (!result.lastDate || entry.timestamp > result.lastDate)
|
|
9093
|
-
result.lastDate = entry.timestamp;
|
|
9094
|
-
}
|
|
9095
|
-
if (entry.type === "user") {
|
|
9096
|
-
const content2 = entry.message?.content;
|
|
9097
|
-
if (Array.isArray(content2)) {
|
|
9098
|
-
const text = content2.filter((b) => b.type === "text").map((b) => b["text"] ?? "").join("\n");
|
|
9099
|
-
if (text) {
|
|
9100
|
-
const dlpMatch = scanArgs({ text });
|
|
9101
|
-
if (dlpMatch) {
|
|
9102
|
-
const isDupe = result.dlpFindings.some(
|
|
9103
|
-
(f) => f.patternName === dlpMatch.patternName && f.redactedSample === dlpMatch.redactedSample && f.project === projLabel
|
|
9104
|
-
);
|
|
9105
|
-
if (!isDupe) {
|
|
9106
|
-
result.dlpFindings.push({
|
|
9107
|
-
patternName: dlpMatch.patternName,
|
|
9108
|
-
redactedSample: dlpMatch.redactedSample,
|
|
9109
|
-
toolName: "user-prompt",
|
|
9110
|
-
timestamp: entry.timestamp ?? "",
|
|
9111
|
-
project: projLabel,
|
|
9112
|
-
sessionId,
|
|
9113
|
-
agent: "claude"
|
|
9114
|
-
});
|
|
9115
|
-
}
|
|
9116
|
-
}
|
|
9117
|
-
}
|
|
9118
|
-
for (const block of content2) {
|
|
9119
|
-
if (block.type !== "tool_result") continue;
|
|
9120
|
-
const filePath = block.tool_use_id ? toolUseFilePaths.get(block.tool_use_id) : void 0;
|
|
9121
|
-
if (filePath) {
|
|
9122
|
-
const ext = path21.extname(filePath).toLowerCase();
|
|
9123
|
-
if (CODE_EXTENSIONS.has(ext)) continue;
|
|
9124
|
-
}
|
|
9125
|
-
const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
|
|
9126
|
-
if (!resultText) continue;
|
|
9127
|
-
if (isNode9SelfOutput(resultText)) continue;
|
|
9128
|
-
const dlpMatch = scanArgs({ text: resultText });
|
|
9129
|
-
if (dlpMatch) {
|
|
9130
|
-
if (looksLikeFixtureToken(dlpMatch.redactedSample)) continue;
|
|
9131
|
-
if (firstDlpTs === null) firstDlpTs = entry.timestamp ?? null;
|
|
9132
|
-
const isDupe = result.dlpFindings.some(
|
|
9133
|
-
(f) => f.patternName === dlpMatch.patternName && f.redactedSample === dlpMatch.redactedSample && f.project === projLabel
|
|
9134
|
-
);
|
|
9135
|
-
if (!isDupe) {
|
|
9136
|
-
result.dlpFindings.push({
|
|
9137
|
-
patternName: dlpMatch.patternName,
|
|
9138
|
-
redactedSample: dlpMatch.redactedSample,
|
|
9139
|
-
toolName: "tool-result",
|
|
9140
|
-
timestamp: entry.timestamp ?? "",
|
|
9141
|
-
project: projLabel,
|
|
9142
|
-
sessionId,
|
|
9143
|
-
agent: "claude"
|
|
9144
|
-
});
|
|
9145
|
-
}
|
|
9146
|
-
}
|
|
9147
|
-
}
|
|
9148
|
-
}
|
|
9149
|
-
continue;
|
|
9150
|
-
}
|
|
9151
|
-
const usage = entry.message?.usage;
|
|
9152
|
-
const model = entry.message?.model;
|
|
9153
|
-
if (usage && model) {
|
|
9154
|
-
const p = claudeModelPrice(model);
|
|
9155
|
-
if (p) {
|
|
9156
|
-
result.totalCostUSD += (usage.input_tokens ?? 0) * p.i + (usage.output_tokens ?? 0) * p.o + (usage.cache_creation_input_tokens ?? 0) * p.cw + (usage.cache_read_input_tokens ?? 0) * p.cr;
|
|
9157
|
-
}
|
|
9158
|
-
}
|
|
9159
|
-
const content = entry.message?.content;
|
|
9160
|
-
if (!Array.isArray(content)) continue;
|
|
9161
|
-
for (const block of content) {
|
|
9162
|
-
if (block.type !== "tool_use") continue;
|
|
9163
|
-
result.totalToolCalls++;
|
|
9164
|
-
const toolName = block.name ?? "";
|
|
9165
|
-
const toolNameLower = toolName.toLowerCase();
|
|
9166
|
-
const input = block.input ?? {};
|
|
9167
|
-
if (block.id && typeof input.file_path === "string") {
|
|
9168
|
-
toolUseFilePaths.set(block.id, input.file_path);
|
|
9169
|
-
}
|
|
9170
|
-
sessionCalls.push({ toolName, input, timestamp: entry.timestamp ?? "" });
|
|
9171
|
-
if (toolNameLower === "bash" || toolNameLower === "execute_bash") {
|
|
9172
|
-
result.bashCalls++;
|
|
9173
|
-
}
|
|
9174
|
-
if (firstEditTs === null && (toolNameLower === "edit" || toolNameLower === "write" || toolNameLower === "write_file" || toolNameLower === "edit_file" || toolNameLower === "multiedit")) {
|
|
9175
|
-
firstEditTs = entry.timestamp ?? null;
|
|
9176
|
-
}
|
|
9177
|
-
const rawCmd = String(input.command ?? "").trimStart();
|
|
9178
|
-
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd))
|
|
9179
|
-
continue;
|
|
9180
|
-
const inputFilePath = typeof input.file_path === "string" ? input.file_path : "";
|
|
9181
|
-
const inputFileExt = inputFilePath ? path21.extname(inputFilePath).toLowerCase() : "";
|
|
9182
|
-
if (CODE_EXTENSIONS.has(inputFileExt)) continue;
|
|
9183
|
-
const dlpMatch = scanArgs(input);
|
|
9239
|
+
if (entry.timestamp) {
|
|
9240
|
+
if (!result.firstDate || entry.timestamp < result.firstDate)
|
|
9241
|
+
result.firstDate = entry.timestamp;
|
|
9242
|
+
if (!result.lastDate || entry.timestamp > result.lastDate) result.lastDate = entry.timestamp;
|
|
9243
|
+
}
|
|
9244
|
+
if (entry.type === "user") {
|
|
9245
|
+
const content2 = entry.message?.content;
|
|
9246
|
+
if (Array.isArray(content2)) {
|
|
9247
|
+
const text = content2.filter((b) => b.type === "text").map((b) => b["text"] ?? "").join("\n");
|
|
9248
|
+
if (text) {
|
|
9249
|
+
const dlpMatch = scanArgs({ text });
|
|
9184
9250
|
if (dlpMatch) {
|
|
9185
|
-
|
|
9186
|
-
|
|
9187
|
-
(
|
|
9188
|
-
);
|
|
9189
|
-
if (!isDupe) {
|
|
9251
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9252
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9253
|
+
dedup.dlpKeys.add(k);
|
|
9190
9254
|
result.dlpFindings.push({
|
|
9191
9255
|
patternName: dlpMatch.patternName,
|
|
9192
9256
|
redactedSample: dlpMatch.redactedSample,
|
|
9193
|
-
toolName,
|
|
9257
|
+
toolName: "user-prompt",
|
|
9194
9258
|
timestamp: entry.timestamp ?? "",
|
|
9195
9259
|
project: projLabel,
|
|
9196
9260
|
sessionId,
|
|
@@ -9198,102 +9262,252 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
|
|
|
9198
9262
|
});
|
|
9199
9263
|
}
|
|
9200
9264
|
}
|
|
9201
|
-
|
|
9202
|
-
|
|
9203
|
-
if (
|
|
9204
|
-
|
|
9205
|
-
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
entry.timestamp ?? "",
|
|
9209
|
-
projLabel,
|
|
9210
|
-
sessionId,
|
|
9211
|
-
"claude",
|
|
9212
|
-
result
|
|
9213
|
-
);
|
|
9265
|
+
}
|
|
9266
|
+
for (const block of content2) {
|
|
9267
|
+
if (block.type !== "tool_result") continue;
|
|
9268
|
+
const filePath = block.tool_use_id ? toolUseFilePaths.get(block.tool_use_id) : void 0;
|
|
9269
|
+
if (filePath) {
|
|
9270
|
+
const ext = path21.extname(filePath).toLowerCase();
|
|
9271
|
+
if (CODE_EXTENSIONS.has(ext)) continue;
|
|
9214
9272
|
}
|
|
9215
|
-
|
|
9216
|
-
|
|
9217
|
-
|
|
9218
|
-
|
|
9219
|
-
|
|
9220
|
-
if (
|
|
9221
|
-
if (
|
|
9222
|
-
const
|
|
9223
|
-
|
|
9224
|
-
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
|
|
9229
|
-
toolName,
|
|
9230
|
-
input,
|
|
9273
|
+
const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
|
|
9274
|
+
if (!resultText) continue;
|
|
9275
|
+
if (isNode9SelfOutput(resultText)) continue;
|
|
9276
|
+
const dlpMatch = scanArgs({ text: resultText });
|
|
9277
|
+
if (dlpMatch) {
|
|
9278
|
+
if (looksLikeFixtureToken(dlpMatch.redactedSample)) continue;
|
|
9279
|
+
if (firstDlpTs === null) firstDlpTs = entry.timestamp ?? null;
|
|
9280
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9281
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9282
|
+
dedup.dlpKeys.add(k);
|
|
9283
|
+
result.dlpFindings.push({
|
|
9284
|
+
patternName: dlpMatch.patternName,
|
|
9285
|
+
redactedSample: dlpMatch.redactedSample,
|
|
9286
|
+
toolName: "tool-result",
|
|
9231
9287
|
timestamp: entry.timestamp ?? "",
|
|
9232
9288
|
project: projLabel,
|
|
9233
9289
|
sessionId,
|
|
9234
9290
|
agent: "claude"
|
|
9235
9291
|
});
|
|
9236
9292
|
}
|
|
9237
|
-
ruleMatched = true;
|
|
9238
|
-
break;
|
|
9239
|
-
}
|
|
9240
|
-
if (!ruleMatched && (toolNameLower === "bash" || toolNameLower === "execute_bash")) {
|
|
9241
|
-
const shellVerdict = detectDangerousShellExec(String(input.command ?? ""));
|
|
9242
|
-
if (shellVerdict) {
|
|
9243
|
-
const astRule = {
|
|
9244
|
-
name: `ast:bash-safe:${shellVerdict}-shell-exec-remote`,
|
|
9245
|
-
tool: "bash",
|
|
9246
|
-
conditions: [],
|
|
9247
|
-
verdict: shellVerdict,
|
|
9248
|
-
reason: `Shell execution of remote download detected by AST analysis (bash-safe)`
|
|
9249
|
-
};
|
|
9250
|
-
const inputPreview = preview(input, 120);
|
|
9251
|
-
const isDupe = result.findings.some(
|
|
9252
|
-
(f) => f.source.rule.name === astRule.name && preview(f.input, 120) === inputPreview && f.project === projLabel
|
|
9253
|
-
);
|
|
9254
|
-
if (!isDupe) {
|
|
9255
|
-
result.findings.push({
|
|
9256
|
-
source: {
|
|
9257
|
-
shieldName: "bash-safe",
|
|
9258
|
-
shieldLabel: "bash-safe (AST)",
|
|
9259
|
-
sourceType: "shield",
|
|
9260
|
-
rule: astRule
|
|
9261
|
-
},
|
|
9262
|
-
toolName,
|
|
9263
|
-
input,
|
|
9264
|
-
timestamp: entry.timestamp ?? "",
|
|
9265
|
-
project: projLabel,
|
|
9266
|
-
sessionId,
|
|
9267
|
-
agent: "claude"
|
|
9268
|
-
});
|
|
9269
|
-
}
|
|
9270
|
-
}
|
|
9271
9293
|
}
|
|
9272
9294
|
}
|
|
9273
9295
|
}
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9296
|
+
continue;
|
|
9297
|
+
}
|
|
9298
|
+
const usage = entry.message?.usage;
|
|
9299
|
+
const model = entry.message?.model;
|
|
9300
|
+
if (usage && model) {
|
|
9301
|
+
const p = claudeModelPrice(model);
|
|
9302
|
+
if (p) {
|
|
9303
|
+
result.totalCostUSD += (usage.input_tokens ?? 0) * p.i + (usage.output_tokens ?? 0) * p.o + (usage.cache_creation_input_tokens ?? 0) * p.cw + (usage.cache_read_input_tokens ?? 0) * p.cr;
|
|
9277
9304
|
}
|
|
9278
9305
|
}
|
|
9279
|
-
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
|
|
9283
|
-
|
|
9284
|
-
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
|
|
9306
|
+
const content = entry.message?.content;
|
|
9307
|
+
if (!Array.isArray(content)) continue;
|
|
9308
|
+
for (const block of content) {
|
|
9309
|
+
if (block.type !== "tool_use") continue;
|
|
9310
|
+
result.totalToolCalls++;
|
|
9311
|
+
const toolName = block.name ?? "";
|
|
9312
|
+
const toolNameLower = toolName.toLowerCase();
|
|
9313
|
+
const input = block.input ?? {};
|
|
9314
|
+
if (block.id && typeof input.file_path === "string") {
|
|
9315
|
+
toolUseFilePaths.set(block.id, input.file_path);
|
|
9316
|
+
}
|
|
9317
|
+
sessionCalls.push({ toolName, input, timestamp: entry.timestamp ?? "" });
|
|
9318
|
+
if (toolNameLower === "bash" || toolNameLower === "execute_bash") {
|
|
9319
|
+
result.bashCalls++;
|
|
9320
|
+
}
|
|
9321
|
+
if (firstEditTs === null && (toolNameLower === "edit" || toolNameLower === "write" || toolNameLower === "write_file" || toolNameLower === "edit_file" || toolNameLower === "multiedit")) {
|
|
9322
|
+
firstEditTs = entry.timestamp ?? null;
|
|
9323
|
+
}
|
|
9324
|
+
const rawCmd = String(input.command ?? "").trimStart();
|
|
9325
|
+
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd)) continue;
|
|
9326
|
+
const inputFilePath = typeof input.file_path === "string" ? input.file_path : "";
|
|
9327
|
+
const inputFileExt = inputFilePath ? path21.extname(inputFilePath).toLowerCase() : "";
|
|
9328
|
+
if (CODE_EXTENSIONS.has(inputFileExt)) continue;
|
|
9329
|
+
const dlpMatch = scanArgs(input);
|
|
9330
|
+
if (dlpMatch) {
|
|
9331
|
+
if (firstDlpTs === null) firstDlpTs = entry.timestamp ?? null;
|
|
9332
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9333
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9334
|
+
dedup.dlpKeys.add(k);
|
|
9335
|
+
result.dlpFindings.push({
|
|
9336
|
+
patternName: dlpMatch.patternName,
|
|
9337
|
+
redactedSample: dlpMatch.redactedSample,
|
|
9338
|
+
toolName,
|
|
9339
|
+
timestamp: entry.timestamp ?? "",
|
|
9340
|
+
project: projLabel,
|
|
9341
|
+
sessionId,
|
|
9342
|
+
agent: "claude"
|
|
9343
|
+
});
|
|
9344
|
+
}
|
|
9345
|
+
}
|
|
9346
|
+
let astFsMatched = false;
|
|
9347
|
+
const astRanForBash = toolNameLower === "bash" || toolNameLower === "execute_bash";
|
|
9348
|
+
if (astRanForBash) {
|
|
9349
|
+
astFsMatched = pushFsOpAstFinding(
|
|
9350
|
+
String(input.command ?? ""),
|
|
9351
|
+
toolName,
|
|
9352
|
+
input,
|
|
9353
|
+
entry.timestamp ?? "",
|
|
9354
|
+
projLabel,
|
|
9355
|
+
sessionId,
|
|
9356
|
+
"claude",
|
|
9357
|
+
result,
|
|
9358
|
+
dedup
|
|
9359
|
+
);
|
|
9360
|
+
}
|
|
9361
|
+
let ruleMatched = astFsMatched;
|
|
9362
|
+
for (const source of ruleSources) {
|
|
9363
|
+
const { rule } = source;
|
|
9364
|
+
if (rule.verdict === "allow") continue;
|
|
9365
|
+
if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
|
|
9366
|
+
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
9367
|
+
if (!evaluateSmartConditions(input, rule)) continue;
|
|
9368
|
+
const inputPreview = preview(input, 120);
|
|
9369
|
+
const k = findingKey(rule.name, inputPreview, projLabel);
|
|
9370
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9371
|
+
dedup.findingsKeys.add(k);
|
|
9372
|
+
result.findings.push({
|
|
9373
|
+
source,
|
|
9374
|
+
toolName,
|
|
9375
|
+
input,
|
|
9376
|
+
timestamp: entry.timestamp ?? "",
|
|
9377
|
+
project: projLabel,
|
|
9378
|
+
sessionId,
|
|
9379
|
+
agent: "claude"
|
|
9380
|
+
});
|
|
9381
|
+
}
|
|
9382
|
+
ruleMatched = true;
|
|
9383
|
+
break;
|
|
9384
|
+
}
|
|
9385
|
+
if (!ruleMatched && (toolNameLower === "bash" || toolNameLower === "execute_bash")) {
|
|
9386
|
+
const shellVerdict = detectDangerousShellExec(String(input.command ?? ""));
|
|
9387
|
+
if (shellVerdict) {
|
|
9388
|
+
const astRule = {
|
|
9389
|
+
name: `ast:bash-safe:${shellVerdict}-shell-exec-remote`,
|
|
9390
|
+
tool: "bash",
|
|
9391
|
+
conditions: [],
|
|
9392
|
+
verdict: shellVerdict,
|
|
9393
|
+
reason: `Shell execution of remote download detected by AST analysis (bash-safe)`
|
|
9394
|
+
};
|
|
9395
|
+
const inputPreview = preview(input, 120);
|
|
9396
|
+
const k = findingKey(astRule.name, inputPreview, projLabel);
|
|
9397
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9398
|
+
dedup.findingsKeys.add(k);
|
|
9399
|
+
result.findings.push({
|
|
9400
|
+
source: {
|
|
9401
|
+
shieldName: "bash-safe",
|
|
9402
|
+
shieldLabel: "bash-safe (AST)",
|
|
9403
|
+
sourceType: "shield",
|
|
9404
|
+
rule: astRule
|
|
9405
|
+
},
|
|
9406
|
+
toolName,
|
|
9407
|
+
input,
|
|
9408
|
+
timestamp: entry.timestamp ?? "",
|
|
9409
|
+
project: projLabel,
|
|
9410
|
+
sessionId,
|
|
9411
|
+
agent: "claude"
|
|
9412
|
+
});
|
|
9413
|
+
}
|
|
9414
|
+
}
|
|
9415
|
+
}
|
|
9416
|
+
}
|
|
9417
|
+
}
|
|
9418
|
+
result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "claude"));
|
|
9419
|
+
if (firstDlpTs !== null && (firstEditTs === null || firstDlpTs < firstEditTs)) {
|
|
9420
|
+
result.sessionsWithEarlySecrets++;
|
|
9421
|
+
}
|
|
9422
|
+
}
|
|
9423
|
+
function processClaudeProject(proj, projectsDir, ruleSources, startDate, result, dedup, onProgress, onLine) {
|
|
9424
|
+
const projPath = path21.join(projectsDir, proj);
|
|
9425
|
+
try {
|
|
9426
|
+
if (!fs19.statSync(projPath).isDirectory()) return;
|
|
9427
|
+
} catch {
|
|
9428
|
+
return;
|
|
9429
|
+
}
|
|
9430
|
+
const projLabel = stripTerminalEscapes(decodeURIComponent(proj).replace(os18.homedir(), "~")).slice(
|
|
9431
|
+
0,
|
|
9432
|
+
40
|
|
9433
|
+
);
|
|
9434
|
+
let files;
|
|
9435
|
+
try {
|
|
9436
|
+
files = fs19.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
9437
|
+
} catch {
|
|
9438
|
+
return;
|
|
9439
|
+
}
|
|
9440
|
+
for (const file of files) {
|
|
9441
|
+
processClaudeFile(
|
|
9442
|
+
file,
|
|
9443
|
+
projPath,
|
|
9444
|
+
projLabel,
|
|
9445
|
+
ruleSources,
|
|
9446
|
+
startDate,
|
|
9447
|
+
result,
|
|
9448
|
+
dedup,
|
|
9449
|
+
onProgress,
|
|
9450
|
+
onLine
|
|
9451
|
+
);
|
|
9452
|
+
}
|
|
9453
|
+
}
|
|
9454
|
+
function emptyClaudeScan() {
|
|
9455
|
+
return {
|
|
9456
|
+
filesScanned: 0,
|
|
9457
|
+
sessions: 0,
|
|
9458
|
+
totalToolCalls: 0,
|
|
9459
|
+
bashCalls: 0,
|
|
9460
|
+
findings: [],
|
|
9461
|
+
dlpFindings: [],
|
|
9462
|
+
loopFindings: [],
|
|
9463
|
+
totalCostUSD: 0,
|
|
9464
|
+
firstDate: null,
|
|
9465
|
+
lastDate: null,
|
|
9466
|
+
sessionsWithEarlySecrets: 0
|
|
9467
|
+
};
|
|
9468
|
+
}
|
|
9469
|
+
function scanClaudeHistory(startDate, onProgress, onLine) {
|
|
9470
|
+
const projectsDir = path21.join(os18.homedir(), ".claude", "projects");
|
|
9471
|
+
const result = emptyClaudeScan();
|
|
9472
|
+
if (!fs19.existsSync(projectsDir)) return result;
|
|
9473
|
+
let projDirs;
|
|
9474
|
+
try {
|
|
9475
|
+
projDirs = fs19.readdirSync(projectsDir);
|
|
9476
|
+
} catch {
|
|
9477
|
+
return result;
|
|
9478
|
+
}
|
|
9479
|
+
const ruleSources = buildRuleSources();
|
|
9480
|
+
const dedup = emptyScanDedup();
|
|
9481
|
+
for (const proj of projDirs) {
|
|
9482
|
+
processClaudeProject(
|
|
9483
|
+
proj,
|
|
9484
|
+
projectsDir,
|
|
9485
|
+
ruleSources,
|
|
9486
|
+
startDate,
|
|
9487
|
+
result,
|
|
9488
|
+
dedup,
|
|
9489
|
+
onProgress,
|
|
9490
|
+
onLine
|
|
9491
|
+
);
|
|
9492
|
+
}
|
|
9493
|
+
return result;
|
|
9494
|
+
}
|
|
9495
|
+
function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
9496
|
+
const tmpDir = path21.join(os18.homedir(), ".gemini", "tmp");
|
|
9497
|
+
const result = {
|
|
9498
|
+
filesScanned: 0,
|
|
9499
|
+
sessions: 0,
|
|
9500
|
+
totalToolCalls: 0,
|
|
9501
|
+
bashCalls: 0,
|
|
9502
|
+
findings: [],
|
|
9503
|
+
dlpFindings: [],
|
|
9291
9504
|
loopFindings: [],
|
|
9292
9505
|
totalCostUSD: 0,
|
|
9293
9506
|
firstDate: null,
|
|
9294
9507
|
lastDate: null,
|
|
9295
9508
|
sessionsWithEarlySecrets: 0
|
|
9296
9509
|
};
|
|
9510
|
+
const dedup = emptyScanDedup();
|
|
9297
9511
|
if (!fs19.existsSync(tmpDir)) return result;
|
|
9298
9512
|
let slugDirs;
|
|
9299
9513
|
try {
|
|
@@ -9350,10 +9564,9 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9350
9564
|
if (text) {
|
|
9351
9565
|
const dlpMatch = scanArgs({ text });
|
|
9352
9566
|
if (dlpMatch) {
|
|
9353
|
-
const
|
|
9354
|
-
|
|
9355
|
-
|
|
9356
|
-
if (!isDupe) {
|
|
9567
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9568
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9569
|
+
dedup.dlpKeys.add(k);
|
|
9357
9570
|
result.dlpFindings.push({
|
|
9358
9571
|
patternName: dlpMatch.patternName,
|
|
9359
9572
|
redactedSample: dlpMatch.redactedSample,
|
|
@@ -9398,10 +9611,9 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9398
9611
|
continue;
|
|
9399
9612
|
const dlpMatch = scanArgs(input);
|
|
9400
9613
|
if (dlpMatch) {
|
|
9401
|
-
const
|
|
9402
|
-
|
|
9403
|
-
|
|
9404
|
-
if (!isDupe) {
|
|
9614
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9615
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9616
|
+
dedup.dlpKeys.add(k);
|
|
9405
9617
|
result.dlpFindings.push({
|
|
9406
9618
|
patternName: dlpMatch.patternName,
|
|
9407
9619
|
redactedSample: dlpMatch.redactedSample,
|
|
@@ -9424,7 +9636,8 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9424
9636
|
projLabel,
|
|
9425
9637
|
sessionId,
|
|
9426
9638
|
"gemini",
|
|
9427
|
-
result
|
|
9639
|
+
result,
|
|
9640
|
+
dedup
|
|
9428
9641
|
);
|
|
9429
9642
|
}
|
|
9430
9643
|
let ruleMatched = astFsMatched;
|
|
@@ -9435,10 +9648,9 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9435
9648
|
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
9436
9649
|
if (!evaluateSmartConditions(input, rule)) continue;
|
|
9437
9650
|
const inputPreview = preview(input, 120);
|
|
9438
|
-
const
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
if (!isDupe) {
|
|
9651
|
+
const k = findingKey(rule.name, inputPreview, projLabel);
|
|
9652
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9653
|
+
dedup.findingsKeys.add(k);
|
|
9442
9654
|
result.findings.push({
|
|
9443
9655
|
source,
|
|
9444
9656
|
toolName,
|
|
@@ -9466,10 +9678,9 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9466
9678
|
reason: `Shell execution of remote download detected by AST analysis (bash-safe)`
|
|
9467
9679
|
};
|
|
9468
9680
|
const inputPreview = preview(input, 120);
|
|
9469
|
-
const
|
|
9470
|
-
|
|
9471
|
-
|
|
9472
|
-
if (!isDupe) {
|
|
9681
|
+
const k = findingKey(astRule.name, inputPreview, projLabel);
|
|
9682
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9683
|
+
dedup.findingsKeys.add(k);
|
|
9473
9684
|
result.findings.push({
|
|
9474
9685
|
source: {
|
|
9475
9686
|
shieldName: "bash-safe",
|
|
@@ -9509,6 +9720,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9509
9720
|
lastDate: null,
|
|
9510
9721
|
sessionsWithEarlySecrets: 0
|
|
9511
9722
|
};
|
|
9723
|
+
const dedup = emptyScanDedup();
|
|
9512
9724
|
if (!fs19.existsSync(sessionsBase)) return result;
|
|
9513
9725
|
const jsonlFiles = [];
|
|
9514
9726
|
try {
|
|
@@ -9590,10 +9802,9 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9590
9802
|
if (text) {
|
|
9591
9803
|
const dlpMatch2 = scanArgs({ text });
|
|
9592
9804
|
if (dlpMatch2) {
|
|
9593
|
-
const
|
|
9594
|
-
|
|
9595
|
-
|
|
9596
|
-
if (!isDupe) {
|
|
9805
|
+
const k = dlpKey(dlpMatch2.patternName, dlpMatch2.redactedSample, projLabel);
|
|
9806
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9807
|
+
dedup.dlpKeys.add(k);
|
|
9597
9808
|
result.dlpFindings.push({
|
|
9598
9809
|
patternName: dlpMatch2.patternName,
|
|
9599
9810
|
redactedSample: dlpMatch2.redactedSample,
|
|
@@ -9635,10 +9846,9 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9635
9846
|
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd)) continue;
|
|
9636
9847
|
const dlpMatch = scanArgs(input);
|
|
9637
9848
|
if (dlpMatch) {
|
|
9638
|
-
const
|
|
9639
|
-
|
|
9640
|
-
|
|
9641
|
-
if (!isDupe) {
|
|
9849
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9850
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9851
|
+
dedup.dlpKeys.add(k);
|
|
9642
9852
|
result.dlpFindings.push({
|
|
9643
9853
|
patternName: dlpMatch.patternName,
|
|
9644
9854
|
redactedSample: dlpMatch.redactedSample,
|
|
@@ -9661,7 +9871,8 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9661
9871
|
projLabel,
|
|
9662
9872
|
sessionId,
|
|
9663
9873
|
"codex",
|
|
9664
|
-
result
|
|
9874
|
+
result,
|
|
9875
|
+
dedup
|
|
9665
9876
|
);
|
|
9666
9877
|
}
|
|
9667
9878
|
let ruleMatched = astFsMatched;
|
|
@@ -9673,10 +9884,9 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9673
9884
|
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
9674
9885
|
if (!evaluateSmartConditions(input, rule)) continue;
|
|
9675
9886
|
const inputPreview = preview(input, 120);
|
|
9676
|
-
const
|
|
9677
|
-
|
|
9678
|
-
|
|
9679
|
-
if (!isDupe) {
|
|
9887
|
+
const k = findingKey(rule.name, inputPreview, projLabel);
|
|
9888
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9889
|
+
dedup.findingsKeys.add(k);
|
|
9680
9890
|
result.findings.push({
|
|
9681
9891
|
source,
|
|
9682
9892
|
toolName,
|
|
@@ -9701,10 +9911,9 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9701
9911
|
reason: `Shell execution of remote download detected by AST analysis (bash-safe)`
|
|
9702
9912
|
};
|
|
9703
9913
|
const inputPreview = preview(input, 120);
|
|
9704
|
-
const
|
|
9705
|
-
|
|
9706
|
-
|
|
9707
|
-
if (!isDupe) {
|
|
9914
|
+
const k = findingKey(astRule.name, inputPreview, projLabel);
|
|
9915
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9916
|
+
dedup.findingsKeys.add(k);
|
|
9708
9917
|
result.findings.push({
|
|
9709
9918
|
source: {
|
|
9710
9919
|
shieldName: "bash-safe",
|
|
@@ -9735,6 +9944,7 @@ function scanShellConfig() {
|
|
|
9735
9944
|
(f) => path21.join(home, f)
|
|
9736
9945
|
);
|
|
9737
9946
|
const findings = [];
|
|
9947
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9738
9948
|
for (const filePath of configFiles) {
|
|
9739
9949
|
if (!fs19.existsSync(filePath)) continue;
|
|
9740
9950
|
let lines;
|
|
@@ -9749,10 +9959,9 @@ function scanShellConfig() {
|
|
|
9749
9959
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
9750
9960
|
const dlpMatch = scanArgs({ text: trimmed });
|
|
9751
9961
|
if (!dlpMatch) continue;
|
|
9752
|
-
const
|
|
9753
|
-
|
|
9754
|
-
|
|
9755
|
-
if (!isDupe) {
|
|
9962
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, shortPath);
|
|
9963
|
+
if (!seen.has(k)) {
|
|
9964
|
+
seen.add(k);
|
|
9756
9965
|
findings.push({
|
|
9757
9966
|
patternName: dlpMatch.patternName,
|
|
9758
9967
|
redactedSample: dlpMatch.redactedSample,
|
|
@@ -10011,6 +10220,263 @@ function renderNarrativeScorecard(input) {
|
|
|
10011
10220
|
console.log(chalk5.dim("\u2192 github.com/node9-ai/node9-proxy"));
|
|
10012
10221
|
console.log("");
|
|
10013
10222
|
}
|
|
10223
|
+
function mkLine(...parts) {
|
|
10224
|
+
let rendered = "";
|
|
10225
|
+
let width = 0;
|
|
10226
|
+
for (const [text, fmt] of parts) {
|
|
10227
|
+
rendered += fmt ? fmt(text) : text;
|
|
10228
|
+
width += text.length;
|
|
10229
|
+
}
|
|
10230
|
+
return { rendered, width };
|
|
10231
|
+
}
|
|
10232
|
+
function shortRule(name, width) {
|
|
10233
|
+
const stripped = name.replace(/^shield:[^:]+:/, "");
|
|
10234
|
+
if (stripped.length <= width) return stripped.padEnd(width);
|
|
10235
|
+
return stripped.slice(0, width - 1) + "\u2026";
|
|
10236
|
+
}
|
|
10237
|
+
function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
|
|
10238
|
+
const { scan, summary, blast, blastExposures, blockedCount, reviewCount } = input;
|
|
10239
|
+
const topLines = [];
|
|
10240
|
+
if (scan.dlpFindings.length > 0) {
|
|
10241
|
+
const latest = scan.dlpFindings[0];
|
|
10242
|
+
const rel = relativeDate(latest.timestamp, now);
|
|
10243
|
+
const noun = `credential leak${scan.dlpFindings.length !== 1 ? "s" : ""}`;
|
|
10244
|
+
topLines.push(
|
|
10245
|
+
mkLine(
|
|
10246
|
+
["\u{1F6A8} ", chalk5.red],
|
|
10247
|
+
[`${scan.dlpFindings.length} ${noun} in tool input `, chalk5.bold],
|
|
10248
|
+
[`(latest: ${rel} ago, ${latest.patternName})`, chalk5.dim]
|
|
10249
|
+
)
|
|
10250
|
+
);
|
|
10251
|
+
}
|
|
10252
|
+
if (blockedCount > 0) {
|
|
10253
|
+
const topBlocked = topRulesByVerdict(summary.sections, "block", 2).map(
|
|
10254
|
+
(r) => r.count > 1 ? `${shortRule(r.name, 20).trimEnd()} \xD7${r.count}` : shortRule(r.name, 20).trimEnd()
|
|
10255
|
+
).join(", ");
|
|
10256
|
+
topLines.push(
|
|
10257
|
+
mkLine(
|
|
10258
|
+
["\u{1F6D1} ", chalk5.red],
|
|
10259
|
+
[`${blockedCount} ops node9 would have blocked `, chalk5.bold],
|
|
10260
|
+
[`(${topBlocked})`, chalk5.dim]
|
|
10261
|
+
)
|
|
10262
|
+
);
|
|
10263
|
+
}
|
|
10264
|
+
if (scan.loopFindings.length > 0) {
|
|
10265
|
+
const { wastePct } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
|
|
10266
|
+
const byTool = /* @__PURE__ */ new Map();
|
|
10267
|
+
for (const f of scan.loopFindings) {
|
|
10268
|
+
byTool.set(f.toolName, (byTool.get(f.toolName) ?? 0) + Math.max(0, f.count - 1));
|
|
10269
|
+
}
|
|
10270
|
+
const top = [...byTool.entries()].sort((a, b) => b[1] - a[1])[0];
|
|
10271
|
+
const wasteSuffix = wastePct > 0 ? `, ${wastePct}% wasted` : "";
|
|
10272
|
+
const detail = top ? `(${top[0]} dominates${wasteSuffix})` : "";
|
|
10273
|
+
topLines.push(
|
|
10274
|
+
mkLine(
|
|
10275
|
+
["\u{1F501} ", chalk5.yellow],
|
|
10276
|
+
[`${scan.loopFindings.length} agent loops detected `, chalk5.bold],
|
|
10277
|
+
[detail, chalk5.dim]
|
|
10278
|
+
)
|
|
10279
|
+
);
|
|
10280
|
+
}
|
|
10281
|
+
if (blastExposures > 0) {
|
|
10282
|
+
const exposed2 = Math.max(0, 100 - blast.score);
|
|
10283
|
+
const pjDiscount = PROTECTIVE_SHIELD_DISCOUNTS["project-jail"] ?? 0;
|
|
10284
|
+
const pjBonus = Math.round(exposed2 * pjDiscount);
|
|
10285
|
+
const cta = pjBonus > 0 ? ` \u2192 enable project-jail (+${pjBonus} pts)` : "";
|
|
10286
|
+
topLines.push(
|
|
10287
|
+
mkLine(
|
|
10288
|
+
["\u{1F52D} ", chalk5.red],
|
|
10289
|
+
[`${blastExposures} secrets reachable on disk`, chalk5.bold],
|
|
10290
|
+
[cta, chalk5.dim]
|
|
10291
|
+
)
|
|
10292
|
+
);
|
|
10293
|
+
}
|
|
10294
|
+
if (topLines.length > 0) {
|
|
10295
|
+
for (const ln of boxPanel("TOP FINDINGS", topLines)) console.log(" " + ln);
|
|
10296
|
+
console.log("");
|
|
10297
|
+
}
|
|
10298
|
+
if (summary.leaks.length > 0) {
|
|
10299
|
+
const leakLines = [];
|
|
10300
|
+
for (const leak of summary.leaks.slice(0, 5)) {
|
|
10301
|
+
const rel = relativeDate(leak.timestamp, now);
|
|
10302
|
+
leakLines.push(
|
|
10303
|
+
mkLine(
|
|
10304
|
+
[rel.padStart(4) + " ", chalk5.dim],
|
|
10305
|
+
[leak.patternName.padEnd(14), chalk5.red.bold],
|
|
10306
|
+
[" "],
|
|
10307
|
+
[leak.redactedSample.padEnd(20), chalk5.red],
|
|
10308
|
+
[" "],
|
|
10309
|
+
[`[${leak.toolName}]`.padEnd(15), chalk5.dim],
|
|
10310
|
+
[" "],
|
|
10311
|
+
[leak.agent, chalk5.dim]
|
|
10312
|
+
)
|
|
10313
|
+
);
|
|
10314
|
+
}
|
|
10315
|
+
const remaining = summary.leaks.length - 5;
|
|
10316
|
+
if (remaining > 0) {
|
|
10317
|
+
leakLines.push(mkLine([`\u2026 +${remaining} more`, chalk5.dim]));
|
|
10318
|
+
}
|
|
10319
|
+
const title = `LEAKS \xB7 ${summary.leaks.length} secret${summary.leaks.length !== 1 ? "s" : ""} in plain text`;
|
|
10320
|
+
for (const ln of boxPanel(title, leakLines)) console.log(" " + ln);
|
|
10321
|
+
console.log("");
|
|
10322
|
+
}
|
|
10323
|
+
if (blockedCount > 0) {
|
|
10324
|
+
const blockedLines = [];
|
|
10325
|
+
const ruleEntries = topRulesByVerdict(summary.sections, "block", 12);
|
|
10326
|
+
for (const r of ruleEntries) {
|
|
10327
|
+
const origin = originForRule(r.name, summary.sections);
|
|
10328
|
+
blockedLines.push(
|
|
10329
|
+
mkLine(
|
|
10330
|
+
["\u2717 ", chalk5.red],
|
|
10331
|
+
[shortRule(r.name, 24), chalk5.bold],
|
|
10332
|
+
[" \xD7" + String(r.count).padEnd(4), chalk5.bold],
|
|
10333
|
+
[" "],
|
|
10334
|
+
[origin, chalk5.dim]
|
|
10335
|
+
)
|
|
10336
|
+
);
|
|
10337
|
+
}
|
|
10338
|
+
const title = `BLOCKED \xB7 ${blockedCount} ops node9 would have stopped`;
|
|
10339
|
+
for (const ln of boxPanel(title, blockedLines)) console.log(" " + ln);
|
|
10340
|
+
console.log("");
|
|
10341
|
+
}
|
|
10342
|
+
if (reviewCount > 0) {
|
|
10343
|
+
const reviewLines = [];
|
|
10344
|
+
const ruleEntries = topRulesByVerdict(summary.sections, "review", 12);
|
|
10345
|
+
for (const r of ruleEntries) {
|
|
10346
|
+
const origin = originForRule(r.name, summary.sections);
|
|
10347
|
+
reviewLines.push(
|
|
10348
|
+
mkLine(
|
|
10349
|
+
["\u{1F441} ", chalk5.yellow],
|
|
10350
|
+
[shortRule(r.name, 24), chalk5.bold],
|
|
10351
|
+
[" \xD7" + String(r.count).padEnd(4), chalk5.bold],
|
|
10352
|
+
[" "],
|
|
10353
|
+
[origin, chalk5.dim]
|
|
10354
|
+
)
|
|
10355
|
+
);
|
|
10356
|
+
}
|
|
10357
|
+
const title = `REVIEW QUEUE \xB7 ${reviewCount} ops flagged for approval`;
|
|
10358
|
+
for (const ln of boxPanel(title, reviewLines)) console.log(" " + ln);
|
|
10359
|
+
console.log("");
|
|
10360
|
+
}
|
|
10361
|
+
if (scan.loopFindings.length > 0) {
|
|
10362
|
+
const { wastePct } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
|
|
10363
|
+
const byTool = /* @__PURE__ */ new Map();
|
|
10364
|
+
let totalRepeats = 0;
|
|
10365
|
+
for (const f of scan.loopFindings) {
|
|
10366
|
+
const repeats = Math.max(0, f.count - 1);
|
|
10367
|
+
byTool.set(f.toolName, (byTool.get(f.toolName) ?? 0) + repeats);
|
|
10368
|
+
totalRepeats += repeats;
|
|
10369
|
+
}
|
|
10370
|
+
const toolEntries = [...byTool.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
10371
|
+
const loopLines = [];
|
|
10372
|
+
for (const [tool, repeats] of toolEntries) {
|
|
10373
|
+
const pct = totalRepeats > 0 ? Math.round(repeats / totalRepeats * 100) : 0;
|
|
10374
|
+
loopLines.push(
|
|
10375
|
+
mkLine(
|
|
10376
|
+
[tool.padEnd(10), chalk5.bold],
|
|
10377
|
+
[`\xD7${num(repeats)} repeats`.padEnd(16)],
|
|
10378
|
+
[`(${pct}%)`, chalk5.dim]
|
|
10379
|
+
)
|
|
10380
|
+
);
|
|
10381
|
+
}
|
|
10382
|
+
const topStuck = [...scan.loopFindings].sort((a, b) => b.count - a.count).slice(0, 3);
|
|
10383
|
+
if (topStuck.length > 0) {
|
|
10384
|
+
loopLines.push(mkLine([""]));
|
|
10385
|
+
loopLines.push(mkLine(["Top stuck patterns:", chalk5.dim]));
|
|
10386
|
+
for (const f of topStuck) {
|
|
10387
|
+
const raw = f.commandPreview || f.toolName;
|
|
10388
|
+
const target = raw.length > 60 ? "\u2026" + raw.slice(raw.length - 59) : raw.padEnd(60);
|
|
10389
|
+
loopLines.push(mkLine([`\xD7${num(f.count).padEnd(4)} `, chalk5.bold], [target, chalk5.dim]));
|
|
10390
|
+
}
|
|
10391
|
+
}
|
|
10392
|
+
const wasteSuffix = wastePct > 0 ? ` \xB7 ${wastePct}% wasted` : "";
|
|
10393
|
+
const title = `AGENT LOOPS \xB7 ${scan.loopFindings.length} repeated patterns${wasteSuffix}`;
|
|
10394
|
+
for (const ln of boxPanel(title, loopLines)) console.log(" " + ln);
|
|
10395
|
+
console.log("");
|
|
10396
|
+
}
|
|
10397
|
+
if (blast.reachable.length > 0 || blast.envFindings.length > 0) {
|
|
10398
|
+
const blastLines = [];
|
|
10399
|
+
const DESC_W = 33;
|
|
10400
|
+
for (const r of blast.reachable.slice(0, 8)) {
|
|
10401
|
+
const trimmed = r.description.split(" \u2014 ")[0].split(/—|--/)[0].trim();
|
|
10402
|
+
const desc = trimmed.length > DESC_W ? trimmed.slice(0, DESC_W - 1) + "\u2026" : trimmed;
|
|
10403
|
+
blastLines.push(mkLine(["\u2717 ", chalk5.red], [r.label.padEnd(36)], [desc, chalk5.dim]));
|
|
10404
|
+
}
|
|
10405
|
+
for (const e of blast.envFindings.slice(0, 3)) {
|
|
10406
|
+
blastLines.push(
|
|
10407
|
+
mkLine(["\u26A0 ", chalk5.yellow], [`${e.key} `], [`(${e.patternName})`, chalk5.dim])
|
|
10408
|
+
);
|
|
10409
|
+
}
|
|
10410
|
+
const totalExposed = blast.reachable.length + blast.envFindings.length;
|
|
10411
|
+
if (totalExposed > 8) {
|
|
10412
|
+
blastLines.push(mkLine([`\u2026 +${totalExposed - 8} more`, chalk5.dim]));
|
|
10413
|
+
}
|
|
10414
|
+
const title = `BLAST RADIUS \xB7 ${totalExposed} path${totalExposed !== 1 ? "s" : ""} reachable right now`;
|
|
10415
|
+
for (const ln of boxPanel(title, blastLines)) console.log(" " + ln);
|
|
10416
|
+
console.log("");
|
|
10417
|
+
}
|
|
10418
|
+
const shieldImpacts = rollupByShield(summary.sections);
|
|
10419
|
+
const exposed = Math.max(0, 100 - blast.score);
|
|
10420
|
+
const shieldLines = [];
|
|
10421
|
+
const ranked = [...shieldImpacts].sort((a, b) => {
|
|
10422
|
+
const aDiscount = PROTECTIVE_SHIELD_DISCOUNTS[a.shieldName] ?? 0;
|
|
10423
|
+
const bDiscount = PROTECTIVE_SHIELD_DISCOUNTS[b.shieldName] ?? 0;
|
|
10424
|
+
if (aDiscount !== bDiscount) return bDiscount - aDiscount;
|
|
10425
|
+
return b.totalCatches - a.totalCatches;
|
|
10426
|
+
});
|
|
10427
|
+
for (const impact of ranked) {
|
|
10428
|
+
if (impact.totalCatches === 0) continue;
|
|
10429
|
+
const discount = PROTECTIVE_SHIELD_DISCOUNTS[impact.shieldName] ?? 0;
|
|
10430
|
+
const bonus = Math.round(exposed * discount);
|
|
10431
|
+
const icon = discount > 0 ? "\u{1F6E1} " : "\u2610 ";
|
|
10432
|
+
const wouldCatch = `would catch ${impact.totalCatches} op${impact.totalCatches !== 1 ? "s" : ""}`;
|
|
10433
|
+
const deltaSuffix = bonus > 0 ? ` \u2192 +${bonus} pts (${blast.score} \u2192 ${blast.score + bonus})` : "";
|
|
10434
|
+
shieldLines.push(
|
|
10435
|
+
mkLine(
|
|
10436
|
+
[icon, discount > 0 ? chalk5.cyan : chalk5.dim],
|
|
10437
|
+
[impact.shieldName.padEnd(14), chalk5.bold],
|
|
10438
|
+
[wouldCatch.padEnd(22), chalk5.dim],
|
|
10439
|
+
[deltaSuffix, bonus > 0 ? chalk5.green.bold : chalk5.dim]
|
|
10440
|
+
)
|
|
10441
|
+
);
|
|
10442
|
+
if (impact.topRuleLabels.length > 0) {
|
|
10443
|
+
const rules = impact.topRuleLabels.join(", ");
|
|
10444
|
+
shieldLines.push(mkLine([" ", chalk5.dim], [rules, chalk5.dim]));
|
|
10445
|
+
}
|
|
10446
|
+
}
|
|
10447
|
+
const hitShieldSet = new Set(
|
|
10448
|
+
shieldImpacts.filter((i) => i.totalCatches > 0).map((i) => i.shieldName)
|
|
10449
|
+
);
|
|
10450
|
+
const zeroHitBuiltins = Object.keys(SHIELDS).filter((name) => !hitShieldSet.has(name)).sort();
|
|
10451
|
+
if (zeroHitBuiltins.length > 0) {
|
|
10452
|
+
shieldLines.push(mkLine([""]));
|
|
10453
|
+
shieldLines.push(mkLine([zeroHitBuiltins.join(" \xB7 "), chalk5.dim]));
|
|
10454
|
+
shieldLines.push(mkLine([" no hits in your history \u2014 install proactively", chalk5.dim]));
|
|
10455
|
+
}
|
|
10456
|
+
const topRec = ranked.find(
|
|
10457
|
+
(r) => r.totalCatches > 0 && (PROTECTIVE_SHIELD_DISCOUNTS[r.shieldName] ?? 0) > 0
|
|
10458
|
+
);
|
|
10459
|
+
if (topRec) {
|
|
10460
|
+
const bonus = Math.round(exposed * (PROTECTIVE_SHIELD_DISCOUNTS[topRec.shieldName] ?? 0));
|
|
10461
|
+
const cta = `\u2192 node9 shield enable ${topRec.shieldName} (start here \u2014 +${bonus} pts)`;
|
|
10462
|
+
shieldLines.push(mkLine([""]));
|
|
10463
|
+
shieldLines.push(mkLine([cta, chalk5.cyan]));
|
|
10464
|
+
}
|
|
10465
|
+
if (shieldLines.length > 0) {
|
|
10466
|
+
const title = "SHIELDS \xB7 install node9 + enable these to catch what we found";
|
|
10467
|
+
for (const ln of boxPanel(title, shieldLines)) console.log(" " + ln);
|
|
10468
|
+
console.log("");
|
|
10469
|
+
}
|
|
10470
|
+
}
|
|
10471
|
+
function originForRule(ruleName, sections) {
|
|
10472
|
+
for (const section of sections) {
|
|
10473
|
+
if (section.rules.some((r) => r.name === ruleName)) {
|
|
10474
|
+
if (section.sourceType === "default") return "default";
|
|
10475
|
+
if (section.sourceType === "shield") return `needs shield:${section.shieldKey ?? section.id}`;
|
|
10476
|
+
}
|
|
10477
|
+
}
|
|
10478
|
+
return "";
|
|
10479
|
+
}
|
|
10014
10480
|
function registerScanCommand(program2) {
|
|
10015
10481
|
program2.command("scan").description("Forecast: scan agent history and show what node9 would catch if installed").option("--all", "Scan all history (default: last 90 days)").option("--days <n>", "Scan last N days of history", "90").option("--top <n>", "Max findings to show per rule (default: 5)", "5").option("--drill-down", "Show all findings with full commands and session IDs").option("--compact", "Compact one-screen scorecard \u2014 for screenshots and sharing").option("--narrative", "Severity-grouped report \u2014 for video / dramatic sharing").option(
|
|
10016
10482
|
"--json",
|
|
@@ -10242,7 +10708,7 @@ function registerScanCommand(program2) {
|
|
|
10242
10708
|
" " + chalk5.dim("AI spend ") + chalk5.bold(fmtCost(scan.totalCostUSD)) + (summary.loopWastedUSD > 0 ? chalk5.dim(" \xB7 wasted on loops ") + chalk5.yellow("~" + fmtCost(summary.loopWastedUSD)) : "")
|
|
10243
10709
|
);
|
|
10244
10710
|
}
|
|
10245
|
-
if (scan.dlpFindings.length > 0 && scan.sessionsWithEarlySecrets > 0) {
|
|
10711
|
+
if (drillDown && scan.dlpFindings.length > 0 && scan.sessionsWithEarlySecrets > 0) {
|
|
10246
10712
|
console.log(
|
|
10247
10713
|
" " + chalk5.dim(
|
|
10248
10714
|
`${scan.sessionsWithEarlySecrets} session${scan.sessionsWithEarlySecrets !== 1 ? "s" : ""} loaded secrets before first edit`
|
|
@@ -10250,6 +10716,26 @@ function registerScanCommand(program2) {
|
|
|
10250
10716
|
);
|
|
10251
10717
|
}
|
|
10252
10718
|
console.log("");
|
|
10719
|
+
if (!drillDown) {
|
|
10720
|
+
renderPanelScorecard({
|
|
10721
|
+
scan,
|
|
10722
|
+
summary,
|
|
10723
|
+
blast,
|
|
10724
|
+
blastExposures,
|
|
10725
|
+
blockedCount,
|
|
10726
|
+
reviewCount
|
|
10727
|
+
});
|
|
10728
|
+
const cta = isWired ? "\u2705 node9 is active" : "\u2192 install node9 to enable protection";
|
|
10729
|
+
console.log(" " + chalk5.green(cta));
|
|
10730
|
+
console.log(
|
|
10731
|
+
" " + chalk5.dim("\u2192 ") + chalk5.cyan("node9 monitor") + chalk5.dim(" live dashboard")
|
|
10732
|
+
);
|
|
10733
|
+
console.log(
|
|
10734
|
+
" " + chalk5.dim("\u2192 ") + chalk5.cyan("node9 scan --drill-down") + chalk5.dim(" full commands + session IDs")
|
|
10735
|
+
);
|
|
10736
|
+
console.log("");
|
|
10737
|
+
return;
|
|
10738
|
+
}
|
|
10253
10739
|
if (scan.dlpFindings.length > 0) {
|
|
10254
10740
|
console.log(" " + chalk5.dim("\u2500".repeat(70)));
|
|
10255
10741
|
console.log(
|
|
@@ -10438,7 +10924,7 @@ function registerScanCommand(program2) {
|
|
|
10438
10924
|
}
|
|
10439
10925
|
);
|
|
10440
10926
|
}
|
|
10441
|
-
var CLAUDE_PRICING, GEMINI_PRICING, CODE_EXTENSIONS, SELF_OUTPUT_MARKERS, FIXTURE_TOKEN_PATTERNS, TERMINAL_ESCAPE_RE2, LOOP_TOOLS, LOOP_THRESHOLD, LOOP_TIMESPAN_THRESHOLD_MS, STUCK_TOOLS_MIN_WASTE, STUCK_TOOLS_LIMIT, RECURRING_SESSION_THRESHOLD, STALE_AGE_DAYS,
|
|
10927
|
+
var CLAUDE_PRICING, GEMINI_PRICING, CODE_EXTENSIONS, SELF_OUTPUT_MARKERS, FIXTURE_TOKEN_PATTERNS, TERMINAL_ESCAPE_RE2, LOOP_TOOLS, LOOP_THRESHOLD, LOOP_TIMESPAN_THRESHOLD_MS, STUCK_TOOLS_MIN_WASTE, STUCK_TOOLS_LIMIT, RECURRING_SESSION_THRESHOLD, STALE_AGE_DAYS, classifyRuleSeverity2, narrativeRuleLabel2;
|
|
10442
10928
|
var init_scan = __esm({
|
|
10443
10929
|
"src/cli/commands/scan.ts"() {
|
|
10444
10930
|
"use strict";
|
|
@@ -10452,6 +10938,7 @@ var init_scan = __esm({
|
|
|
10452
10938
|
init_setup();
|
|
10453
10939
|
init_blast();
|
|
10454
10940
|
init_scan_derive();
|
|
10941
|
+
init_protection();
|
|
10455
10942
|
init_scan_json();
|
|
10456
10943
|
init_scan_history();
|
|
10457
10944
|
CLAUDE_PRICING = {
|
|
@@ -10534,9 +11021,6 @@ var init_scan = __esm({
|
|
|
10534
11021
|
STUCK_TOOLS_LIMIT = 3;
|
|
10535
11022
|
RECURRING_SESSION_THRESHOLD = 3;
|
|
10536
11023
|
STALE_AGE_DAYS = 30;
|
|
10537
|
-
DEFAULT_RULE_NAMES = new Set(
|
|
10538
|
-
DEFAULT_CONFIG.policy.smartRules.map((r) => r.name).filter(Boolean)
|
|
10539
|
-
);
|
|
10540
11024
|
classifyRuleSeverity2 = classifyRuleSeverity;
|
|
10541
11025
|
narrativeRuleLabel2 = narrativeRuleLabel;
|
|
10542
11026
|
}
|
|
@@ -13107,6 +13591,7 @@ var tail_exports = {};
|
|
|
13107
13591
|
__export(tail_exports, {
|
|
13108
13592
|
agentLabel: () => agentLabel,
|
|
13109
13593
|
sessionTag: () => sessionTag,
|
|
13594
|
+
shortenPathSummary: () => shortenPathSummary,
|
|
13110
13595
|
startTail: () => startTail
|
|
13111
13596
|
});
|
|
13112
13597
|
import http2 from "http";
|
|
@@ -13116,6 +13601,12 @@ import os41 from "os";
|
|
|
13116
13601
|
import path47 from "path";
|
|
13117
13602
|
import readline6 from "readline";
|
|
13118
13603
|
import { spawn as spawn9 } from "child_process";
|
|
13604
|
+
function shortenPathSummary(s) {
|
|
13605
|
+
if (!s || !s.startsWith("/")) return s;
|
|
13606
|
+
const parts = s.split("/").filter(Boolean);
|
|
13607
|
+
if (parts.length <= 2) return s;
|
|
13608
|
+
return `\u2026/${parts.slice(-2).join("/")}`;
|
|
13609
|
+
}
|
|
13119
13610
|
function getIcon(tool) {
|
|
13120
13611
|
const t = tool.toLowerCase();
|
|
13121
13612
|
for (const [k, v] of Object.entries(ICONS)) {
|
|
@@ -13869,7 +14360,8 @@ async function startTail(options = {}) {
|
|
|
13869
14360
|
if (event === "snapshot") {
|
|
13870
14361
|
const time = new Date(data.ts).toLocaleTimeString([], { hour12: false });
|
|
13871
14362
|
const hash = data.hash ?? "";
|
|
13872
|
-
const
|
|
14363
|
+
const rawSummary = data.argsSummary ?? data.tool;
|
|
14364
|
+
const summary = shortenPathSummary(rawSummary);
|
|
13873
14365
|
const fileCount = data.fileCount ?? 0;
|
|
13874
14366
|
const files = fileCount > 0 ? chalk30.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
|
|
13875
14367
|
process.stdout.write(
|
|
@@ -16251,63 +16743,13 @@ function registerAuditCommand(program2) {
|
|
|
16251
16743
|
|
|
16252
16744
|
// src/cli/commands/report.ts
|
|
16253
16745
|
import chalk13 from "chalk";
|
|
16746
|
+
|
|
16747
|
+
// src/cli/aggregate/report-audit.ts
|
|
16748
|
+
init_costSync();
|
|
16749
|
+
init_litellm();
|
|
16254
16750
|
import fs35 from "fs";
|
|
16255
|
-
import path36 from "path";
|
|
16256
16751
|
import os31 from "os";
|
|
16257
|
-
|
|
16258
|
-
// src/cli/render/report-json.ts
|
|
16259
|
-
function buildReportJson(input) {
|
|
16260
|
-
const totalBlocked = input.timedOut + input.hardBlocked + input.dlpBlocked + input.loopHits + input.userDenied;
|
|
16261
|
-
const blockRate = input.total > 0 ? totalBlocked / input.total : 0;
|
|
16262
|
-
const deltaPct = input.priorBlockRate === null ? null : Math.round((blockRate - input.priorBlockRate) * 100);
|
|
16263
|
-
return {
|
|
16264
|
-
schemaVersion: 1,
|
|
16265
|
-
generatedAt: input.generatedAt,
|
|
16266
|
-
period: input.period,
|
|
16267
|
-
range: { start: input.start.toISOString(), end: input.end.toISOString() },
|
|
16268
|
-
excludedTests: input.excludedTests,
|
|
16269
|
-
totals: {
|
|
16270
|
-
events: input.total,
|
|
16271
|
-
blocked: totalBlocked,
|
|
16272
|
-
blockRate,
|
|
16273
|
-
userApproved: input.userApproved,
|
|
16274
|
-
userDenied: input.userDenied,
|
|
16275
|
-
timedOut: input.timedOut,
|
|
16276
|
-
hardBlocked: input.hardBlocked,
|
|
16277
|
-
dlpBlocked: input.dlpBlocked,
|
|
16278
|
-
observeDlp: input.observeDlp,
|
|
16279
|
-
loopHits: input.loopHits,
|
|
16280
|
-
unackedDlp: input.unackedDlp
|
|
16281
|
-
},
|
|
16282
|
-
tests: {
|
|
16283
|
-
passes: input.testPasses,
|
|
16284
|
-
fails: input.testFails
|
|
16285
|
-
},
|
|
16286
|
-
cost: {
|
|
16287
|
-
totalUSD: input.cost.claudeUSD + input.cost.codexUSD,
|
|
16288
|
-
claudeUSD: input.cost.claudeUSD,
|
|
16289
|
-
codexUSD: input.cost.codexUSD,
|
|
16290
|
-
inputTokens: input.cost.inputTokens,
|
|
16291
|
-
outputTokens: input.cost.outputTokens,
|
|
16292
|
-
cacheWriteTokens: input.cost.cacheWriteTokens,
|
|
16293
|
-
cacheReadTokens: input.cost.cacheReadTokens,
|
|
16294
|
-
byDay: [...input.cost.byDay.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([day, usd]) => ({ day, usd })),
|
|
16295
|
-
byModel: [...input.cost.byModel.entries()].sort((a, b) => b[1] - a[1]).map(([model, usd]) => ({ model, usd }))
|
|
16296
|
-
},
|
|
16297
|
-
byTool: [...input.toolMap.entries()].sort((a, b) => b[1].calls - a[1].calls).map(([tool, v]) => ({ tool, calls: v.calls, blocked: v.blocked })),
|
|
16298
|
-
byBlock: [...input.blockMap.entries()].sort((a, b) => b[1] - a[1]).map(([rule, count]) => ({ rule, count })),
|
|
16299
|
-
byAgent: [...input.agentMap.entries()].sort((a, b) => b[1] - a[1]).map(([agent, calls]) => ({ agent, calls })),
|
|
16300
|
-
byMcp: [...input.mcpMap.entries()].sort((a, b) => b[1] - a[1]).map(([server, calls]) => ({ server, calls })),
|
|
16301
|
-
byDay: [...input.dailyMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([day, v]) => ({ day, calls: v.calls, blocked: v.blocked })),
|
|
16302
|
-
byHour: [...input.hourMap.entries()].sort((a, b) => a[0] - b[0]).map(([hour, calls]) => ({ hour, calls })),
|
|
16303
|
-
trend: {
|
|
16304
|
-
priorBlockRate: input.priorBlockRate,
|
|
16305
|
-
deltaPct
|
|
16306
|
-
}
|
|
16307
|
-
};
|
|
16308
|
-
}
|
|
16309
|
-
|
|
16310
|
-
// src/cli/commands/report.ts
|
|
16752
|
+
import path36 from "path";
|
|
16311
16753
|
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;
|
|
16312
16754
|
function buildTestTimestamps(allEntries) {
|
|
16313
16755
|
const testTs = /* @__PURE__ */ new Set();
|
|
@@ -16332,8 +16774,7 @@ function isTestEntry(entry, testTs) {
|
|
|
16332
16774
|
}
|
|
16333
16775
|
return false;
|
|
16334
16776
|
}
|
|
16335
|
-
function getDateRange(period) {
|
|
16336
|
-
const now = /* @__PURE__ */ new Date();
|
|
16777
|
+
function getDateRange(period, now) {
|
|
16337
16778
|
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
16338
16779
|
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
|
|
16339
16780
|
switch (period) {
|
|
@@ -16349,6 +16790,11 @@ function getDateRange(period) {
|
|
|
16349
16790
|
s.setDate(s.getDate() - 29);
|
|
16350
16791
|
return { start: s, end };
|
|
16351
16792
|
}
|
|
16793
|
+
case "90d": {
|
|
16794
|
+
const s = new Date(todayStart);
|
|
16795
|
+
s.setDate(s.getDate() - 89);
|
|
16796
|
+
return { start: s, end };
|
|
16797
|
+
}
|
|
16352
16798
|
case "month":
|
|
16353
16799
|
return { start: new Date(now.getFullYear(), now.getMonth(), 1), end };
|
|
16354
16800
|
}
|
|
@@ -16371,40 +16817,6 @@ function isAllow(decision) {
|
|
|
16371
16817
|
function isDlp(checkedBy) {
|
|
16372
16818
|
return !!checkedBy?.includes("dlp");
|
|
16373
16819
|
}
|
|
16374
|
-
var BLOCK_REASON_LABELS = {
|
|
16375
|
-
timeout: "Popup timeout",
|
|
16376
|
-
"smart-rule-block": "Smart rule",
|
|
16377
|
-
"observe-mode-dlp-would-block": "DLP (observe)",
|
|
16378
|
-
"persistent-deny": "Persistent deny",
|
|
16379
|
-
"local-decision": "User denied",
|
|
16380
|
-
"dlp-block": "DLP block",
|
|
16381
|
-
"loop-detected": "Loop detected"
|
|
16382
|
-
};
|
|
16383
|
-
function humanBlockReason(reason) {
|
|
16384
|
-
return BLOCK_REASON_LABELS[reason] ?? reason;
|
|
16385
|
-
}
|
|
16386
|
-
function barStr(value, max, width) {
|
|
16387
|
-
if (max === 0 || width <= 0) return "\u2591".repeat(width);
|
|
16388
|
-
const filled = Math.max(1, Math.round(value / max * width));
|
|
16389
|
-
return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
16390
|
-
}
|
|
16391
|
-
function colorBar(value, max, width) {
|
|
16392
|
-
const s = barStr(value, max, width);
|
|
16393
|
-
const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
|
|
16394
|
-
return chalk13.cyan(s.slice(0, filled)) + chalk13.dim(s.slice(filled));
|
|
16395
|
-
}
|
|
16396
|
-
function fmtDate(d) {
|
|
16397
|
-
const date = typeof d === "string" ? /* @__PURE__ */ new Date(d + "T12:00:00") : d;
|
|
16398
|
-
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
16399
|
-
}
|
|
16400
|
-
function num2(n) {
|
|
16401
|
-
return n.toLocaleString();
|
|
16402
|
-
}
|
|
16403
|
-
function fmtCost2(usd) {
|
|
16404
|
-
if (usd < 1e-3) return "< $0.001";
|
|
16405
|
-
if (usd < 1) return "$" + usd.toFixed(4);
|
|
16406
|
-
return "$" + usd.toFixed(2);
|
|
16407
|
-
}
|
|
16408
16820
|
var CLAUDE_PRICING2 = {
|
|
16409
16821
|
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
16410
16822
|
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
@@ -16424,90 +16836,160 @@ function claudeModelPrice2(model) {
|
|
|
16424
16836
|
}
|
|
16425
16837
|
return null;
|
|
16426
16838
|
}
|
|
16427
|
-
function
|
|
16428
|
-
|
|
16839
|
+
function emptyClaudeCostAccumulator() {
|
|
16840
|
+
return {
|
|
16429
16841
|
total: 0,
|
|
16430
|
-
byDay: /* @__PURE__ */ new Map(),
|
|
16431
|
-
byModel: /* @__PURE__ */ new Map(),
|
|
16432
16842
|
inputTokens: 0,
|
|
16433
16843
|
outputTokens: 0,
|
|
16434
16844
|
cacheWriteTokens: 0,
|
|
16435
|
-
cacheReadTokens: 0
|
|
16845
|
+
cacheReadTokens: 0,
|
|
16846
|
+
byDay: /* @__PURE__ */ new Map(),
|
|
16847
|
+
byModel: /* @__PURE__ */ new Map(),
|
|
16848
|
+
byProject: /* @__PURE__ */ new Map()
|
|
16436
16849
|
};
|
|
16437
|
-
|
|
16438
|
-
|
|
16850
|
+
}
|
|
16851
|
+
function freezeClaudeCost(acc) {
|
|
16852
|
+
return {
|
|
16853
|
+
total: acc.total,
|
|
16854
|
+
byDay: acc.byDay,
|
|
16855
|
+
byModel: acc.byModel,
|
|
16856
|
+
byProject: acc.byProject,
|
|
16857
|
+
inputTokens: acc.inputTokens,
|
|
16858
|
+
outputTokens: acc.outputTokens,
|
|
16859
|
+
cacheWriteTokens: acc.cacheWriteTokens,
|
|
16860
|
+
cacheReadTokens: acc.cacheReadTokens
|
|
16861
|
+
};
|
|
16862
|
+
}
|
|
16863
|
+
function processClaudeCostProject(proj, projectsDir, start, end, acc) {
|
|
16864
|
+
const projPath = path36.join(projectsDir, proj);
|
|
16865
|
+
let files;
|
|
16866
|
+
try {
|
|
16867
|
+
const stat = fs35.statSync(projPath);
|
|
16868
|
+
if (!stat.isDirectory()) return;
|
|
16869
|
+
files = fs35.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
16870
|
+
} catch {
|
|
16871
|
+
return;
|
|
16872
|
+
}
|
|
16873
|
+
const startMs = start.getTime();
|
|
16874
|
+
for (const file of files) {
|
|
16875
|
+
const filePath = path36.join(projPath, file);
|
|
16876
|
+
try {
|
|
16877
|
+
if (fs35.statSync(filePath).mtimeMs < startMs) continue;
|
|
16878
|
+
} catch {
|
|
16879
|
+
continue;
|
|
16880
|
+
}
|
|
16881
|
+
try {
|
|
16882
|
+
const raw = fs35.readFileSync(filePath, "utf-8");
|
|
16883
|
+
for (const line of raw.split("\n")) {
|
|
16884
|
+
if (!line.trim()) continue;
|
|
16885
|
+
let entry;
|
|
16886
|
+
try {
|
|
16887
|
+
entry = JSON.parse(line);
|
|
16888
|
+
} catch {
|
|
16889
|
+
continue;
|
|
16890
|
+
}
|
|
16891
|
+
if (entry.type !== "assistant") continue;
|
|
16892
|
+
if (!entry.timestamp) continue;
|
|
16893
|
+
const ts = new Date(entry.timestamp);
|
|
16894
|
+
if (ts < start || ts > end) continue;
|
|
16895
|
+
const usage = entry.message?.usage;
|
|
16896
|
+
const model = entry.message?.model;
|
|
16897
|
+
if (!usage || !model) continue;
|
|
16898
|
+
const p = claudeModelPrice2(model);
|
|
16899
|
+
if (!p) continue;
|
|
16900
|
+
const inp = usage.input_tokens ?? 0;
|
|
16901
|
+
const out = usage.output_tokens ?? 0;
|
|
16902
|
+
const cw = usage.cache_creation_input_tokens ?? 0;
|
|
16903
|
+
const cr = usage.cache_read_input_tokens ?? 0;
|
|
16904
|
+
const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
|
|
16905
|
+
acc.total += cost;
|
|
16906
|
+
acc.inputTokens += inp;
|
|
16907
|
+
acc.outputTokens += out;
|
|
16908
|
+
acc.cacheWriteTokens += cw;
|
|
16909
|
+
acc.cacheReadTokens += cr;
|
|
16910
|
+
const dateKey = entry.timestamp.slice(0, 10);
|
|
16911
|
+
acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
|
|
16912
|
+
const normModel = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
|
|
16913
|
+
acc.byModel.set(normModel, (acc.byModel.get(normModel) ?? 0) + cost);
|
|
16914
|
+
const projectKey = decodeProjectDirName(proj);
|
|
16915
|
+
const projectRollup = acc.byProject.get(projectKey) ?? {
|
|
16916
|
+
cost: 0,
|
|
16917
|
+
inputTokens: 0,
|
|
16918
|
+
outputTokens: 0
|
|
16919
|
+
};
|
|
16920
|
+
projectRollup.cost += cost;
|
|
16921
|
+
projectRollup.inputTokens += inp;
|
|
16922
|
+
projectRollup.outputTokens += out;
|
|
16923
|
+
acc.byProject.set(projectKey, projectRollup);
|
|
16924
|
+
}
|
|
16925
|
+
} catch {
|
|
16926
|
+
continue;
|
|
16927
|
+
}
|
|
16928
|
+
}
|
|
16929
|
+
}
|
|
16930
|
+
function loadClaudeCost(start, end, projectsDir) {
|
|
16931
|
+
const acc = emptyClaudeCostAccumulator();
|
|
16932
|
+
if (!fs35.existsSync(projectsDir)) return freezeClaudeCost(acc);
|
|
16439
16933
|
let dirs;
|
|
16440
16934
|
try {
|
|
16441
16935
|
dirs = fs35.readdirSync(projectsDir);
|
|
16442
16936
|
} catch {
|
|
16443
|
-
return
|
|
16937
|
+
return freezeClaudeCost(acc);
|
|
16444
16938
|
}
|
|
16445
|
-
let total = 0;
|
|
16446
|
-
let inputTokens = 0;
|
|
16447
|
-
let outputTokens = 0;
|
|
16448
|
-
let cacheWriteTokens = 0;
|
|
16449
|
-
let cacheReadTokens = 0;
|
|
16450
|
-
const byDay = /* @__PURE__ */ new Map();
|
|
16451
|
-
const byModel = /* @__PURE__ */ new Map();
|
|
16452
16939
|
for (const proj of dirs) {
|
|
16453
|
-
|
|
16454
|
-
|
|
16940
|
+
processClaudeCostProject(proj, projectsDir, start, end, acc);
|
|
16941
|
+
}
|
|
16942
|
+
return freezeClaudeCost(acc);
|
|
16943
|
+
}
|
|
16944
|
+
function processCodexCostFile(filePath, start, end, acc) {
|
|
16945
|
+
let lines;
|
|
16946
|
+
try {
|
|
16947
|
+
lines = fs35.readFileSync(filePath, "utf-8").split("\n");
|
|
16948
|
+
} catch {
|
|
16949
|
+
return;
|
|
16950
|
+
}
|
|
16951
|
+
let sessionStart2 = "";
|
|
16952
|
+
let lastTotalInput = 0;
|
|
16953
|
+
let lastTotalCached = 0;
|
|
16954
|
+
let lastTotalOutput = 0;
|
|
16955
|
+
let sessionToolCalls = 0;
|
|
16956
|
+
for (const line of lines) {
|
|
16957
|
+
if (!line.trim()) continue;
|
|
16958
|
+
let entry;
|
|
16455
16959
|
try {
|
|
16456
|
-
|
|
16457
|
-
if (!stat.isDirectory()) continue;
|
|
16458
|
-
files = fs35.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
16960
|
+
entry = JSON.parse(line);
|
|
16459
16961
|
} catch {
|
|
16460
16962
|
continue;
|
|
16461
16963
|
}
|
|
16462
|
-
|
|
16463
|
-
|
|
16464
|
-
|
|
16465
|
-
|
|
16466
|
-
|
|
16467
|
-
|
|
16468
|
-
|
|
16469
|
-
|
|
16470
|
-
|
|
16471
|
-
|
|
16472
|
-
|
|
16473
|
-
|
|
16474
|
-
|
|
16475
|
-
|
|
16476
|
-
if (ts < start || ts > end) continue;
|
|
16477
|
-
const usage = entry.message?.usage;
|
|
16478
|
-
const model = entry.message?.model;
|
|
16479
|
-
if (!usage || !model) continue;
|
|
16480
|
-
const p = claudeModelPrice2(model);
|
|
16481
|
-
if (!p) continue;
|
|
16482
|
-
const inp = usage.input_tokens ?? 0;
|
|
16483
|
-
const out = usage.output_tokens ?? 0;
|
|
16484
|
-
const cw = usage.cache_creation_input_tokens ?? 0;
|
|
16485
|
-
const cr = usage.cache_read_input_tokens ?? 0;
|
|
16486
|
-
const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
|
|
16487
|
-
total += cost;
|
|
16488
|
-
inputTokens += inp;
|
|
16489
|
-
outputTokens += out;
|
|
16490
|
-
cacheWriteTokens += cw;
|
|
16491
|
-
cacheReadTokens += cr;
|
|
16492
|
-
const dateKey = entry.timestamp.slice(0, 10);
|
|
16493
|
-
byDay.set(dateKey, (byDay.get(dateKey) ?? 0) + cost);
|
|
16494
|
-
const normModel = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
|
|
16495
|
-
byModel.set(normModel, (byModel.get(normModel) ?? 0) + cost);
|
|
16496
|
-
}
|
|
16497
|
-
} catch {
|
|
16498
|
-
continue;
|
|
16499
|
-
}
|
|
16964
|
+
const p = entry.payload ?? {};
|
|
16965
|
+
if (entry.type === "session_meta") {
|
|
16966
|
+
sessionStart2 = String(p["timestamp"] ?? "");
|
|
16967
|
+
continue;
|
|
16968
|
+
}
|
|
16969
|
+
if (entry.type === "event_msg" && p["type"] === "token_count") {
|
|
16970
|
+
const info = p["info"] ?? {};
|
|
16971
|
+
const usage = info["total_token_usage"] ?? {};
|
|
16972
|
+
lastTotalInput = usage["input_tokens"] ?? lastTotalInput;
|
|
16973
|
+
lastTotalCached = usage["cached_input_tokens"] ?? lastTotalCached;
|
|
16974
|
+
lastTotalOutput = usage["output_tokens"] ?? lastTotalOutput;
|
|
16975
|
+
}
|
|
16976
|
+
if (entry.type === "response_item" && p["type"] === "function_call") {
|
|
16977
|
+
sessionToolCalls++;
|
|
16500
16978
|
}
|
|
16501
16979
|
}
|
|
16502
|
-
|
|
16980
|
+
if (!sessionStart2) return;
|
|
16981
|
+
const ts = new Date(sessionStart2);
|
|
16982
|
+
if (ts < start || ts > end) return;
|
|
16983
|
+
const nonCached = Math.max(0, lastTotalInput - lastTotalCached);
|
|
16984
|
+
const cost = nonCached * 5e-6 + lastTotalCached * 25e-7 + lastTotalOutput * 15e-6;
|
|
16985
|
+
acc.total += cost;
|
|
16986
|
+
acc.toolCalls += sessionToolCalls;
|
|
16987
|
+
const dateKey = sessionStart2.slice(0, 10);
|
|
16988
|
+
acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
|
|
16503
16989
|
}
|
|
16504
|
-
function
|
|
16505
|
-
const sessionsBase = path36.join(os31.homedir(), ".codex", "sessions");
|
|
16506
|
-
const byDay = /* @__PURE__ */ new Map();
|
|
16507
|
-
let total = 0;
|
|
16508
|
-
let toolCalls = 0;
|
|
16509
|
-
if (!fs35.existsSync(sessionsBase)) return { total, byDay, toolCalls };
|
|
16990
|
+
function listCodexSessionFiles(sessionsBase) {
|
|
16510
16991
|
const jsonlFiles = [];
|
|
16992
|
+
if (!fs35.existsSync(sessionsBase)) return jsonlFiles;
|
|
16511
16993
|
try {
|
|
16512
16994
|
for (const year of fs35.readdirSync(sessionsBase)) {
|
|
16513
16995
|
const yearPath = path36.join(sessionsBase, year);
|
|
@@ -16537,495 +17019,742 @@ function loadCodexCost(start, end) {
|
|
|
16537
17019
|
}
|
|
16538
17020
|
}
|
|
16539
17021
|
} catch {
|
|
16540
|
-
return
|
|
17022
|
+
return [];
|
|
16541
17023
|
}
|
|
16542
|
-
|
|
16543
|
-
|
|
17024
|
+
return jsonlFiles;
|
|
17025
|
+
}
|
|
17026
|
+
function loadCodexCost(start, end, sessionsBase) {
|
|
17027
|
+
const acc = { total: 0, toolCalls: 0, byDay: /* @__PURE__ */ new Map() };
|
|
17028
|
+
const files = listCodexSessionFiles(sessionsBase);
|
|
17029
|
+
for (const filePath of files) {
|
|
17030
|
+
processCodexCostFile(filePath, start, end, acc);
|
|
17031
|
+
}
|
|
17032
|
+
return { total: acc.total, byDay: acc.byDay, toolCalls: acc.toolCalls };
|
|
17033
|
+
}
|
|
17034
|
+
var GEMINI_FALLBACK_MODELS = ["gemini-2.5-flash", "gemini-2.0-flash"];
|
|
17035
|
+
function geminiPriceFor(model) {
|
|
17036
|
+
let tuple = pricingFor(model);
|
|
17037
|
+
if (!tuple && /^gemini-/i.test(model)) {
|
|
17038
|
+
for (const proxy of GEMINI_FALLBACK_MODELS) {
|
|
17039
|
+
tuple = pricingFor(proxy);
|
|
17040
|
+
if (tuple) break;
|
|
17041
|
+
}
|
|
17042
|
+
}
|
|
17043
|
+
if (!tuple) return null;
|
|
17044
|
+
return { input: tuple[0], output: tuple[1], cacheRead: tuple[3] || tuple[0] };
|
|
17045
|
+
}
|
|
17046
|
+
function emptyGeminiAccumulator() {
|
|
17047
|
+
return {
|
|
17048
|
+
total: 0,
|
|
17049
|
+
inputTokens: 0,
|
|
17050
|
+
outputTokens: 0,
|
|
17051
|
+
cacheReadTokens: 0,
|
|
17052
|
+
byDay: /* @__PURE__ */ new Map(),
|
|
17053
|
+
byProject: /* @__PURE__ */ new Map()
|
|
17054
|
+
};
|
|
17055
|
+
}
|
|
17056
|
+
function freezeGeminiCost(acc) {
|
|
17057
|
+
return {
|
|
17058
|
+
total: acc.total,
|
|
17059
|
+
byDay: acc.byDay,
|
|
17060
|
+
byProject: acc.byProject,
|
|
17061
|
+
inputTokens: acc.inputTokens,
|
|
17062
|
+
outputTokens: acc.outputTokens,
|
|
17063
|
+
cacheReadTokens: acc.cacheReadTokens
|
|
17064
|
+
};
|
|
17065
|
+
}
|
|
17066
|
+
function processGeminiCostFile(filePath, projectKey, start, end, acc) {
|
|
17067
|
+
const startMs = start.getTime();
|
|
17068
|
+
try {
|
|
17069
|
+
if (fs35.statSync(filePath).mtimeMs < startMs) return;
|
|
17070
|
+
} catch {
|
|
17071
|
+
return;
|
|
17072
|
+
}
|
|
17073
|
+
let raw;
|
|
17074
|
+
try {
|
|
17075
|
+
raw = fs35.readFileSync(filePath, "utf-8");
|
|
17076
|
+
} catch {
|
|
17077
|
+
return;
|
|
17078
|
+
}
|
|
17079
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
17080
|
+
for (const line of raw.split("\n")) {
|
|
17081
|
+
if (!line.trim()) continue;
|
|
17082
|
+
let entry;
|
|
16544
17083
|
try {
|
|
16545
|
-
|
|
17084
|
+
entry = JSON.parse(line);
|
|
16546
17085
|
} catch {
|
|
16547
17086
|
continue;
|
|
16548
17087
|
}
|
|
16549
|
-
|
|
16550
|
-
|
|
16551
|
-
|
|
16552
|
-
|
|
16553
|
-
|
|
16554
|
-
for (const line of lines) {
|
|
16555
|
-
if (!line.trim()) continue;
|
|
16556
|
-
let entry;
|
|
16557
|
-
try {
|
|
16558
|
-
entry = JSON.parse(line);
|
|
16559
|
-
} catch {
|
|
16560
|
-
continue;
|
|
16561
|
-
}
|
|
16562
|
-
const p = entry.payload ?? {};
|
|
16563
|
-
if (entry.type === "session_meta") {
|
|
16564
|
-
sessionStart2 = String(p["timestamp"] ?? "");
|
|
16565
|
-
continue;
|
|
16566
|
-
}
|
|
16567
|
-
if (entry.type === "event_msg" && p["type"] === "token_count") {
|
|
16568
|
-
const info = p["info"] ?? {};
|
|
16569
|
-
const usage = info["total_token_usage"] ?? {};
|
|
16570
|
-
lastTotalInput = usage["input_tokens"] ?? lastTotalInput;
|
|
16571
|
-
lastTotalCached = usage["cached_input_tokens"] ?? lastTotalCached;
|
|
16572
|
-
lastTotalOutput = usage["output_tokens"] ?? lastTotalOutput;
|
|
16573
|
-
}
|
|
16574
|
-
if (entry.type === "response_item" && p["type"] === "function_call") {
|
|
16575
|
-
sessionToolCalls++;
|
|
16576
|
-
}
|
|
17088
|
+
if (entry.type !== "gemini") continue;
|
|
17089
|
+
if (!entry.tokens || !entry.model || !entry.timestamp) continue;
|
|
17090
|
+
if (entry.id) {
|
|
17091
|
+
if (seenIds.has(entry.id)) continue;
|
|
17092
|
+
seenIds.add(entry.id);
|
|
16577
17093
|
}
|
|
16578
|
-
|
|
16579
|
-
const ts = new Date(sessionStart2);
|
|
17094
|
+
const ts = new Date(entry.timestamp);
|
|
16580
17095
|
if (ts < start || ts > end) continue;
|
|
16581
|
-
const
|
|
16582
|
-
|
|
16583
|
-
|
|
16584
|
-
|
|
16585
|
-
const
|
|
16586
|
-
|
|
17096
|
+
const price = geminiPriceFor(entry.model);
|
|
17097
|
+
if (!price) continue;
|
|
17098
|
+
const inp = entry.tokens.input ?? 0;
|
|
17099
|
+
const out = entry.tokens.output ?? 0;
|
|
17100
|
+
const cached = Math.min(entry.tokens.cached ?? 0, inp);
|
|
17101
|
+
const fresh = Math.max(0, inp - cached);
|
|
17102
|
+
const cost = fresh * price.input + cached * price.cacheRead + out * price.output;
|
|
17103
|
+
acc.total += cost;
|
|
17104
|
+
acc.inputTokens += inp;
|
|
17105
|
+
acc.outputTokens += out;
|
|
17106
|
+
acc.cacheReadTokens += cached;
|
|
17107
|
+
const dateKey = entry.timestamp.slice(0, 10);
|
|
17108
|
+
acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
|
|
17109
|
+
const rollup = acc.byProject.get(projectKey) ?? {
|
|
17110
|
+
cost: 0,
|
|
17111
|
+
inputTokens: 0,
|
|
17112
|
+
outputTokens: 0
|
|
17113
|
+
};
|
|
17114
|
+
rollup.cost += cost;
|
|
17115
|
+
rollup.inputTokens += inp;
|
|
17116
|
+
rollup.outputTokens += out;
|
|
17117
|
+
acc.byProject.set(projectKey, rollup);
|
|
17118
|
+
}
|
|
17119
|
+
}
|
|
17120
|
+
function listGeminiSessionFiles(geminiTmpDir) {
|
|
17121
|
+
const out = [];
|
|
17122
|
+
let dirs;
|
|
17123
|
+
try {
|
|
17124
|
+
if (!fs35.statSync(geminiTmpDir).isDirectory()) return out;
|
|
17125
|
+
dirs = fs35.readdirSync(geminiTmpDir);
|
|
17126
|
+
} catch {
|
|
17127
|
+
return out;
|
|
17128
|
+
}
|
|
17129
|
+
for (const proj of dirs) {
|
|
17130
|
+
const chatsDir = path36.join(geminiTmpDir, proj, "chats");
|
|
17131
|
+
let files;
|
|
17132
|
+
try {
|
|
17133
|
+
if (!fs35.statSync(chatsDir).isDirectory()) continue;
|
|
17134
|
+
files = fs35.readdirSync(chatsDir);
|
|
17135
|
+
} catch {
|
|
17136
|
+
continue;
|
|
17137
|
+
}
|
|
17138
|
+
for (const f of files) {
|
|
17139
|
+
if (!f.endsWith(".jsonl")) continue;
|
|
17140
|
+
out.push({ projectKey: proj, file: path36.join(chatsDir, f) });
|
|
17141
|
+
}
|
|
16587
17142
|
}
|
|
16588
|
-
return
|
|
17143
|
+
return out;
|
|
17144
|
+
}
|
|
17145
|
+
function loadGeminiCost(start, end, geminiTmpDir) {
|
|
17146
|
+
const acc = emptyGeminiAccumulator();
|
|
17147
|
+
if (!fs35.existsSync(geminiTmpDir)) return freezeGeminiCost(acc);
|
|
17148
|
+
for (const { projectKey, file } of listGeminiSessionFiles(geminiTmpDir)) {
|
|
17149
|
+
processGeminiCostFile(file, projectKey, start, end, acc);
|
|
17150
|
+
}
|
|
17151
|
+
return freezeGeminiCost(acc);
|
|
17152
|
+
}
|
|
17153
|
+
function aggregateReportFromAudit(period, opts = {}) {
|
|
17154
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
17155
|
+
const auditLogPath = opts.auditLogPath ?? path36.join(os31.homedir(), ".node9", "audit.log");
|
|
17156
|
+
const claudeProjectsDir = opts.claudeProjectsDir ?? path36.join(os31.homedir(), ".claude", "projects");
|
|
17157
|
+
const codexSessionsDir = opts.codexSessionsDir ?? path36.join(os31.homedir(), ".codex", "sessions");
|
|
17158
|
+
const geminiTmpDir = opts.geminiTmpDir ?? path36.join(os31.homedir(), ".gemini", "tmp");
|
|
17159
|
+
const hasAuditFile = fs35.existsSync(auditLogPath);
|
|
17160
|
+
const allEntries = opts.preloadedAuditEntries ?? parseAuditLog(auditLogPath);
|
|
17161
|
+
const unackedDlp = allEntries.filter((e) => e.source === "response-dlp");
|
|
17162
|
+
const { start, end } = getDateRange(period, now);
|
|
17163
|
+
const responseDlpEntries = allEntries.filter((e) => {
|
|
17164
|
+
if (e.source !== "response-dlp") return false;
|
|
17165
|
+
const ts = new Date(e.ts);
|
|
17166
|
+
return ts >= start && ts <= end;
|
|
17167
|
+
}).map((e) => {
|
|
17168
|
+
const raw = e;
|
|
17169
|
+
return {
|
|
17170
|
+
ts: e.ts,
|
|
17171
|
+
dlpPattern: typeof raw.dlpPattern === "string" ? raw.dlpPattern : void 0,
|
|
17172
|
+
dlpSample: typeof raw.dlpSample === "string" ? raw.dlpSample : void 0
|
|
17173
|
+
};
|
|
17174
|
+
});
|
|
17175
|
+
const claudeCost = opts.preloadedClaudeCost ?? loadClaudeCost(start, end, claudeProjectsDir);
|
|
17176
|
+
const codexCost = opts.preloadedCodexCost ?? loadCodexCost(start, end, codexSessionsDir);
|
|
17177
|
+
const geminiCost = opts.preloadedGeminiCost ?? loadGeminiCost(start, end, geminiTmpDir);
|
|
17178
|
+
for (const [day, c] of codexCost.byDay) {
|
|
17179
|
+
claudeCost.byDay.set(day, (claudeCost.byDay.get(day) ?? 0) + c);
|
|
17180
|
+
}
|
|
17181
|
+
for (const [day, c] of geminiCost.byDay) {
|
|
17182
|
+
claudeCost.byDay.set(day, (claudeCost.byDay.get(day) ?? 0) + c);
|
|
17183
|
+
}
|
|
17184
|
+
for (const [geminiKey, gRollup] of geminiCost.byProject) {
|
|
17185
|
+
let mergedInto = null;
|
|
17186
|
+
for (const claudeKey of claudeCost.byProject.keys()) {
|
|
17187
|
+
const claudeBase = claudeKey.match(/[^/\\]+$/)?.[0] ?? claudeKey;
|
|
17188
|
+
if (claudeBase === geminiKey) {
|
|
17189
|
+
mergedInto = claudeKey;
|
|
17190
|
+
break;
|
|
17191
|
+
}
|
|
17192
|
+
}
|
|
17193
|
+
const targetKey = mergedInto ?? geminiKey;
|
|
17194
|
+
const existing = claudeCost.byProject.get(targetKey) ?? {
|
|
17195
|
+
cost: 0,
|
|
17196
|
+
inputTokens: 0,
|
|
17197
|
+
outputTokens: 0
|
|
17198
|
+
};
|
|
17199
|
+
existing.cost += gRollup.cost;
|
|
17200
|
+
existing.inputTokens += gRollup.inputTokens;
|
|
17201
|
+
existing.outputTokens += gRollup.outputTokens;
|
|
17202
|
+
claudeCost.byProject.set(targetKey, existing);
|
|
17203
|
+
}
|
|
17204
|
+
const periodMs = end.getTime() - start.getTime();
|
|
17205
|
+
const priorEnd = new Date(start.getTime() - 1);
|
|
17206
|
+
const priorStart = new Date(start.getTime() - periodMs);
|
|
17207
|
+
const priorEntries = allEntries.filter((e) => {
|
|
17208
|
+
if (e.source === "post-hook") return false;
|
|
17209
|
+
const ts = new Date(e.ts);
|
|
17210
|
+
return ts >= priorStart && ts <= priorEnd;
|
|
17211
|
+
});
|
|
17212
|
+
const priorBlocked = priorEntries.filter((e) => !isAllow(e.decision)).length;
|
|
17213
|
+
const priorBlockRate = priorEntries.length > 0 ? priorBlocked / priorEntries.length : null;
|
|
17214
|
+
const excludeTests = opts.excludeTests === true;
|
|
17215
|
+
const testTs = excludeTests ? buildTestTimestamps(allEntries) : /* @__PURE__ */ new Set();
|
|
17216
|
+
let excludedTests = 0;
|
|
17217
|
+
const entries = allEntries.filter((e) => {
|
|
17218
|
+
if (e.source === "post-hook") return false;
|
|
17219
|
+
if (e.source === "response-dlp") return false;
|
|
17220
|
+
const ts = new Date(e.ts);
|
|
17221
|
+
if (ts < start || ts > end) return false;
|
|
17222
|
+
if (excludeTests && isTestEntry(e, testTs)) {
|
|
17223
|
+
excludedTests++;
|
|
17224
|
+
return false;
|
|
17225
|
+
}
|
|
17226
|
+
return true;
|
|
17227
|
+
});
|
|
17228
|
+
let userApproved = 0;
|
|
17229
|
+
let userDenied = 0;
|
|
17230
|
+
let timedOut = 0;
|
|
17231
|
+
let hardBlocked = 0;
|
|
17232
|
+
let dlpBlocked = 0;
|
|
17233
|
+
let observeDlp = 0;
|
|
17234
|
+
let loopHits = 0;
|
|
17235
|
+
let testPasses = 0;
|
|
17236
|
+
let testFails = 0;
|
|
17237
|
+
const toolMap = /* @__PURE__ */ new Map();
|
|
17238
|
+
const blockMap = /* @__PURE__ */ new Map();
|
|
17239
|
+
const ruleMap = /* @__PURE__ */ new Map();
|
|
17240
|
+
const agentMap = /* @__PURE__ */ new Map();
|
|
17241
|
+
const mcpMap = /* @__PURE__ */ new Map();
|
|
17242
|
+
const dailyMap = /* @__PURE__ */ new Map();
|
|
17243
|
+
const hourMap = /* @__PURE__ */ new Map();
|
|
17244
|
+
for (const e of entries) {
|
|
17245
|
+
const allow = isAllow(e.decision);
|
|
17246
|
+
const dateKey = e.ts.slice(0, 10);
|
|
17247
|
+
const userInteracted = e.source === "daemon";
|
|
17248
|
+
if (userInteracted) {
|
|
17249
|
+
if (allow) userApproved++;
|
|
17250
|
+
else userDenied++;
|
|
17251
|
+
} else if (!allow) {
|
|
17252
|
+
if (e.checkedBy === "timeout") timedOut++;
|
|
17253
|
+
else if (e.checkedBy === "observe-mode-dlp-would-block") observeDlp++;
|
|
17254
|
+
else if (isDlp(e.checkedBy)) dlpBlocked++;
|
|
17255
|
+
else if (e.checkedBy !== "loop-detected") hardBlocked++;
|
|
17256
|
+
}
|
|
17257
|
+
if (e.checkedBy === "loop-detected") loopHits++;
|
|
17258
|
+
const t = toolMap.get(e.tool) ?? { calls: 0, blocked: 0 };
|
|
17259
|
+
t.calls++;
|
|
17260
|
+
if (!allow) t.blocked++;
|
|
17261
|
+
toolMap.set(e.tool, t);
|
|
17262
|
+
if (!allow && e.checkedBy) {
|
|
17263
|
+
blockMap.set(e.checkedBy, (blockMap.get(e.checkedBy) ?? 0) + 1);
|
|
17264
|
+
}
|
|
17265
|
+
if (!allow && e.ruleName) {
|
|
17266
|
+
ruleMap.set(e.ruleName, (ruleMap.get(e.ruleName) ?? 0) + 1);
|
|
17267
|
+
}
|
|
17268
|
+
if (e.agent) agentMap.set(e.agent, (agentMap.get(e.agent) ?? 0) + 1);
|
|
17269
|
+
if (e.mcpServer) mcpMap.set(e.mcpServer, (mcpMap.get(e.mcpServer) ?? 0) + 1);
|
|
17270
|
+
const hour = new Date(e.ts).getHours();
|
|
17271
|
+
hourMap.set(hour, (hourMap.get(hour) ?? 0) + 1);
|
|
17272
|
+
const d = dailyMap.get(dateKey) ?? { calls: 0, blocked: 0 };
|
|
17273
|
+
d.calls++;
|
|
17274
|
+
if (!allow) d.blocked++;
|
|
17275
|
+
dailyMap.set(dateKey, d);
|
|
17276
|
+
}
|
|
17277
|
+
for (const e of allEntries) {
|
|
17278
|
+
if (e.source !== "test-result") continue;
|
|
17279
|
+
const ts = new Date(e.ts);
|
|
17280
|
+
if (ts < start || ts > end) continue;
|
|
17281
|
+
if (e.testResult === "pass") testPasses++;
|
|
17282
|
+
else if (e.testResult === "fail") testFails++;
|
|
17283
|
+
}
|
|
17284
|
+
if (codexCost.toolCalls > 0) {
|
|
17285
|
+
agentMap.set("Codex", (agentMap.get("Codex") ?? 0) + codexCost.toolCalls);
|
|
17286
|
+
}
|
|
17287
|
+
const data = {
|
|
17288
|
+
period,
|
|
17289
|
+
start,
|
|
17290
|
+
end,
|
|
17291
|
+
excludedTests,
|
|
17292
|
+
total: entries.length,
|
|
17293
|
+
userApproved,
|
|
17294
|
+
userDenied,
|
|
17295
|
+
timedOut,
|
|
17296
|
+
hardBlocked,
|
|
17297
|
+
dlpBlocked,
|
|
17298
|
+
observeDlp,
|
|
17299
|
+
loopHits,
|
|
17300
|
+
testPasses,
|
|
17301
|
+
testFails,
|
|
17302
|
+
unackedDlp: unackedDlp.length,
|
|
17303
|
+
priorBlockRate,
|
|
17304
|
+
cost: {
|
|
17305
|
+
claudeUSD: claudeCost.total,
|
|
17306
|
+
codexUSD: codexCost.total,
|
|
17307
|
+
geminiUSD: geminiCost.total,
|
|
17308
|
+
inputTokens: claudeCost.inputTokens + geminiCost.inputTokens,
|
|
17309
|
+
outputTokens: claudeCost.outputTokens + geminiCost.outputTokens,
|
|
17310
|
+
cacheWriteTokens: claudeCost.cacheWriteTokens,
|
|
17311
|
+
cacheReadTokens: claudeCost.cacheReadTokens + geminiCost.cacheReadTokens,
|
|
17312
|
+
byDay: claudeCost.byDay,
|
|
17313
|
+
byModel: claudeCost.byModel,
|
|
17314
|
+
byProject: claudeCost.byProject
|
|
17315
|
+
},
|
|
17316
|
+
toolMap,
|
|
17317
|
+
blockMap,
|
|
17318
|
+
ruleMap,
|
|
17319
|
+
agentMap,
|
|
17320
|
+
mcpMap,
|
|
17321
|
+
dailyMap,
|
|
17322
|
+
hourMap,
|
|
17323
|
+
generatedAt: now.toISOString()
|
|
17324
|
+
};
|
|
17325
|
+
return { data, hasAuditFile, responseDlpEntries };
|
|
17326
|
+
}
|
|
17327
|
+
|
|
17328
|
+
// src/cli/render/report-json.ts
|
|
17329
|
+
function buildReportJson(input) {
|
|
17330
|
+
const totalBlocked = input.timedOut + input.hardBlocked + input.dlpBlocked + input.loopHits + input.userDenied;
|
|
17331
|
+
const blockRate = input.total > 0 ? totalBlocked / input.total : 0;
|
|
17332
|
+
const deltaPct = input.priorBlockRate === null ? null : Math.round((blockRate - input.priorBlockRate) * 100);
|
|
17333
|
+
return {
|
|
17334
|
+
schemaVersion: 1,
|
|
17335
|
+
generatedAt: input.generatedAt,
|
|
17336
|
+
period: input.period,
|
|
17337
|
+
range: { start: input.start.toISOString(), end: input.end.toISOString() },
|
|
17338
|
+
excludedTests: input.excludedTests,
|
|
17339
|
+
totals: {
|
|
17340
|
+
events: input.total,
|
|
17341
|
+
blocked: totalBlocked,
|
|
17342
|
+
blockRate,
|
|
17343
|
+
userApproved: input.userApproved,
|
|
17344
|
+
userDenied: input.userDenied,
|
|
17345
|
+
timedOut: input.timedOut,
|
|
17346
|
+
hardBlocked: input.hardBlocked,
|
|
17347
|
+
dlpBlocked: input.dlpBlocked,
|
|
17348
|
+
observeDlp: input.observeDlp,
|
|
17349
|
+
loopHits: input.loopHits,
|
|
17350
|
+
unackedDlp: input.unackedDlp
|
|
17351
|
+
},
|
|
17352
|
+
tests: {
|
|
17353
|
+
passes: input.testPasses,
|
|
17354
|
+
fails: input.testFails
|
|
17355
|
+
},
|
|
17356
|
+
cost: {
|
|
17357
|
+
totalUSD: input.cost.claudeUSD + input.cost.codexUSD + input.cost.geminiUSD,
|
|
17358
|
+
claudeUSD: input.cost.claudeUSD,
|
|
17359
|
+
codexUSD: input.cost.codexUSD,
|
|
17360
|
+
geminiUSD: input.cost.geminiUSD,
|
|
17361
|
+
inputTokens: input.cost.inputTokens,
|
|
17362
|
+
outputTokens: input.cost.outputTokens,
|
|
17363
|
+
cacheWriteTokens: input.cost.cacheWriteTokens,
|
|
17364
|
+
cacheReadTokens: input.cost.cacheReadTokens,
|
|
17365
|
+
byDay: [...input.cost.byDay.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([day, usd]) => ({ day, usd })),
|
|
17366
|
+
byModel: [...input.cost.byModel.entries()].sort((a, b) => b[1] - a[1]).map(([model, usd]) => ({ model, usd }))
|
|
17367
|
+
},
|
|
17368
|
+
byTool: [...input.toolMap.entries()].sort((a, b) => b[1].calls - a[1].calls).map(([tool, v]) => ({ tool, calls: v.calls, blocked: v.blocked })),
|
|
17369
|
+
byBlock: [...input.blockMap.entries()].sort((a, b) => b[1] - a[1]).map(([rule, count]) => ({ rule, count })),
|
|
17370
|
+
byAgent: [...input.agentMap.entries()].sort((a, b) => b[1] - a[1]).map(([agent, calls]) => ({ agent, calls })),
|
|
17371
|
+
byMcp: [...input.mcpMap.entries()].sort((a, b) => b[1] - a[1]).map(([server, calls]) => ({ server, calls })),
|
|
17372
|
+
byDay: [...input.dailyMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([day, v]) => ({ day, calls: v.calls, blocked: v.blocked })),
|
|
17373
|
+
byHour: [...input.hourMap.entries()].sort((a, b) => a[0] - b[0]).map(([hour, calls]) => ({ hour, calls })),
|
|
17374
|
+
trend: {
|
|
17375
|
+
priorBlockRate: input.priorBlockRate,
|
|
17376
|
+
deltaPct
|
|
17377
|
+
}
|
|
17378
|
+
};
|
|
17379
|
+
}
|
|
17380
|
+
|
|
17381
|
+
// src/cli/commands/report.ts
|
|
17382
|
+
var BLOCK_REASON_LABELS = {
|
|
17383
|
+
timeout: "Approval timeout",
|
|
17384
|
+
"smart-rule-block": "Smart rule",
|
|
17385
|
+
"observe-mode-dlp-would-block": "DLP (observe)",
|
|
17386
|
+
"persistent-deny": "Persistent deny",
|
|
17387
|
+
"local-decision": "User denied",
|
|
17388
|
+
"dlp-block": "DLP block",
|
|
17389
|
+
"loop-detected": "Loop detected"
|
|
17390
|
+
};
|
|
17391
|
+
function humanBlockReason(reason) {
|
|
17392
|
+
return BLOCK_REASON_LABELS[reason] ?? reason;
|
|
17393
|
+
}
|
|
17394
|
+
function barStr(value, max, width) {
|
|
17395
|
+
if (max === 0 || width <= 0) return "\u2591".repeat(width);
|
|
17396
|
+
const filled = Math.max(1, Math.round(value / max * width));
|
|
17397
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
17398
|
+
}
|
|
17399
|
+
function colorBar(value, max, width) {
|
|
17400
|
+
const s = barStr(value, max, width);
|
|
17401
|
+
const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
|
|
17402
|
+
return chalk13.cyan(s.slice(0, filled)) + chalk13.dim(s.slice(filled));
|
|
17403
|
+
}
|
|
17404
|
+
function fmtDate(d) {
|
|
17405
|
+
const date = typeof d === "string" ? /* @__PURE__ */ new Date(d + "T12:00:00") : d;
|
|
17406
|
+
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
17407
|
+
}
|
|
17408
|
+
function num2(n) {
|
|
17409
|
+
return n.toLocaleString();
|
|
17410
|
+
}
|
|
17411
|
+
function fmtCost2(usd) {
|
|
17412
|
+
if (usd < 1e-3) return "< $0.001";
|
|
17413
|
+
if (usd < 1) return "$" + usd.toFixed(4);
|
|
17414
|
+
return "$" + usd.toFixed(2);
|
|
16589
17415
|
}
|
|
16590
17416
|
function registerReportCommand(program2) {
|
|
16591
17417
|
program2.command("report").description("Activity and security report \u2014 what Claude did, what was blocked").option("--period <period>", "today | 7d | 30d | month", "7d").option("--no-tests", "exclude test runner calls (npm test, vitest, pytest\u2026) from stats").option("--json", "Emit machine-readable JSON to stdout (suppresses renderer)").action((options) => {
|
|
16592
|
-
const period = ["today", "7d", "30d", "month"].includes(
|
|
17418
|
+
const period = ["today", "7d", "30d", "90d", "month"].includes(
|
|
16593
17419
|
options.period
|
|
16594
17420
|
) ? options.period : "7d";
|
|
16595
|
-
const
|
|
16596
|
-
const
|
|
16597
|
-
|
|
16598
|
-
|
|
17421
|
+
const excludeTests = options.tests === false;
|
|
17422
|
+
const { data, hasAuditFile, responseDlpEntries } = aggregateReportFromAudit(period, {
|
|
17423
|
+
excludeTests
|
|
17424
|
+
});
|
|
17425
|
+
if (data.unackedDlp > 0 && !options.json) {
|
|
16599
17426
|
console.log("");
|
|
16600
17427
|
console.log(
|
|
16601
17428
|
chalk13.bgRed.white.bold(
|
|
16602
|
-
` \u26A0\uFE0F DLP ALERT: ${unackedDlp
|
|
17429
|
+
` \u26A0\uFE0F DLP ALERT: ${data.unackedDlp} secret${data.unackedDlp !== 1 ? "s" : ""} found in Claude response text `
|
|
16603
17430
|
) + " " + chalk13.yellow("\u2192 run: node9 dlp")
|
|
16604
17431
|
);
|
|
16605
17432
|
}
|
|
16606
|
-
if (
|
|
17433
|
+
if (!hasAuditFile && !options.json) {
|
|
16607
17434
|
console.log(
|
|
16608
17435
|
chalk13.yellow("\n No audit data found. Run node9 with Claude Code to generate entries.\n")
|
|
16609
17436
|
);
|
|
16610
17437
|
return;
|
|
16611
17438
|
}
|
|
16612
|
-
|
|
16613
|
-
|
|
16614
|
-
|
|
16615
|
-
|
|
16616
|
-
|
|
16617
|
-
|
|
16618
|
-
outputTokens: costOutputTokens,
|
|
16619
|
-
cacheWriteTokens: costCacheWrite,
|
|
16620
|
-
cacheReadTokens: costCacheRead
|
|
16621
|
-
} = loadClaudeCost(start, end);
|
|
16622
|
-
const {
|
|
16623
|
-
total: codexCostUSD,
|
|
16624
|
-
byDay: codexCostByDay,
|
|
16625
|
-
toolCalls: codexToolCalls
|
|
16626
|
-
} = loadCodexCost(start, end);
|
|
16627
|
-
const costUSD = claudeCostUSD + codexCostUSD;
|
|
16628
|
-
for (const [day, c] of codexCostByDay) {
|
|
16629
|
-
costByDay.set(day, (costByDay.get(day) ?? 0) + c);
|
|
16630
|
-
}
|
|
16631
|
-
const periodMs = end.getTime() - start.getTime();
|
|
16632
|
-
const priorEnd = new Date(start.getTime() - 1);
|
|
16633
|
-
const priorStart = new Date(start.getTime() - periodMs);
|
|
16634
|
-
const priorEntries = allEntries.filter((e) => {
|
|
16635
|
-
if (e.source === "post-hook") return false;
|
|
16636
|
-
const ts = new Date(e.ts);
|
|
16637
|
-
return ts >= priorStart && ts <= priorEnd;
|
|
16638
|
-
});
|
|
16639
|
-
const priorBlocked = priorEntries.filter((e) => !isAllow(e.decision)).length;
|
|
16640
|
-
const priorBlockRate = priorEntries.length > 0 ? priorBlocked / priorEntries.length : null;
|
|
16641
|
-
const excludeTests = options.tests === false;
|
|
16642
|
-
const testTs = excludeTests ? buildTestTimestamps(allEntries) : /* @__PURE__ */ new Set();
|
|
16643
|
-
let filteredTestCount = 0;
|
|
16644
|
-
const entries = allEntries.filter((e) => {
|
|
16645
|
-
if (e.source === "post-hook") return false;
|
|
16646
|
-
if (e.source === "response-dlp") return false;
|
|
16647
|
-
const ts = new Date(e.ts);
|
|
16648
|
-
if (ts < start || ts > end) return false;
|
|
16649
|
-
if (excludeTests && isTestEntry(e, testTs)) {
|
|
16650
|
-
filteredTestCount++;
|
|
16651
|
-
return false;
|
|
16652
|
-
}
|
|
16653
|
-
return true;
|
|
16654
|
-
});
|
|
16655
|
-
if (entries.length === 0 && !options.json) {
|
|
17439
|
+
if (options.json) {
|
|
17440
|
+
const envelope = buildReportJson(data);
|
|
17441
|
+
process.stdout.write(JSON.stringify(envelope, null, 2) + "\n");
|
|
17442
|
+
return;
|
|
17443
|
+
}
|
|
17444
|
+
if (data.total === 0) {
|
|
16656
17445
|
console.log(chalk13.yellow(`
|
|
16657
17446
|
No activity for period "${period}".
|
|
16658
17447
|
`));
|
|
16659
17448
|
return;
|
|
16660
17449
|
}
|
|
16661
|
-
|
|
16662
|
-
|
|
16663
|
-
|
|
16664
|
-
|
|
16665
|
-
|
|
16666
|
-
|
|
16667
|
-
|
|
16668
|
-
|
|
16669
|
-
|
|
16670
|
-
|
|
16671
|
-
|
|
16672
|
-
|
|
16673
|
-
|
|
16674
|
-
|
|
16675
|
-
|
|
16676
|
-
|
|
16677
|
-
|
|
16678
|
-
|
|
16679
|
-
|
|
16680
|
-
|
|
16681
|
-
|
|
16682
|
-
|
|
16683
|
-
|
|
16684
|
-
|
|
16685
|
-
|
|
16686
|
-
|
|
16687
|
-
|
|
16688
|
-
|
|
16689
|
-
|
|
16690
|
-
|
|
16691
|
-
|
|
16692
|
-
|
|
16693
|
-
|
|
16694
|
-
|
|
16695
|
-
|
|
16696
|
-
|
|
16697
|
-
|
|
16698
|
-
|
|
16699
|
-
|
|
16700
|
-
|
|
16701
|
-
|
|
16702
|
-
|
|
16703
|
-
|
|
16704
|
-
|
|
16705
|
-
|
|
16706
|
-
|
|
16707
|
-
|
|
16708
|
-
|
|
16709
|
-
|
|
16710
|
-
|
|
16711
|
-
|
|
16712
|
-
|
|
16713
|
-
|
|
16714
|
-
|
|
16715
|
-
|
|
16716
|
-
|
|
16717
|
-
|
|
16718
|
-
|
|
16719
|
-
|
|
16720
|
-
|
|
16721
|
-
|
|
16722
|
-
|
|
16723
|
-
|
|
16724
|
-
|
|
16725
|
-
|
|
16726
|
-
|
|
16727
|
-
|
|
16728
|
-
|
|
16729
|
-
|
|
16730
|
-
|
|
16731
|
-
|
|
16732
|
-
|
|
16733
|
-
|
|
16734
|
-
|
|
16735
|
-
|
|
16736
|
-
|
|
16737
|
-
|
|
16738
|
-
|
|
16739
|
-
|
|
16740
|
-
|
|
16741
|
-
|
|
16742
|
-
|
|
16743
|
-
|
|
16744
|
-
|
|
16745
|
-
|
|
16746
|
-
|
|
16747
|
-
|
|
16748
|
-
|
|
16749
|
-
|
|
16750
|
-
|
|
16751
|
-
|
|
16752
|
-
|
|
16753
|
-
|
|
16754
|
-
|
|
16755
|
-
|
|
16756
|
-
|
|
16757
|
-
|
|
16758
|
-
|
|
16759
|
-
|
|
16760
|
-
|
|
16761
|
-
|
|
16762
|
-
|
|
16763
|
-
|
|
16764
|
-
|
|
16765
|
-
|
|
16766
|
-
|
|
16767
|
-
|
|
16768
|
-
|
|
16769
|
-
|
|
16770
|
-
|
|
16771
|
-
|
|
16772
|
-
|
|
16773
|
-
}
|
|
17450
|
+
renderTerminalReport(data, responseDlpEntries, excludeTests);
|
|
17451
|
+
});
|
|
17452
|
+
}
|
|
17453
|
+
function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
17454
|
+
const {
|
|
17455
|
+
period,
|
|
17456
|
+
start,
|
|
17457
|
+
end,
|
|
17458
|
+
total,
|
|
17459
|
+
excludedTests,
|
|
17460
|
+
userApproved,
|
|
17461
|
+
userDenied,
|
|
17462
|
+
timedOut,
|
|
17463
|
+
hardBlocked,
|
|
17464
|
+
dlpBlocked,
|
|
17465
|
+
observeDlp,
|
|
17466
|
+
loopHits,
|
|
17467
|
+
testPasses,
|
|
17468
|
+
testFails,
|
|
17469
|
+
priorBlockRate,
|
|
17470
|
+
cost: {
|
|
17471
|
+
claudeUSD,
|
|
17472
|
+
codexUSD,
|
|
17473
|
+
geminiUSD,
|
|
17474
|
+
inputTokens: costInputTokens,
|
|
17475
|
+
outputTokens: costOutputTokens,
|
|
17476
|
+
cacheWriteTokens: costCacheWrite,
|
|
17477
|
+
cacheReadTokens: costCacheRead,
|
|
17478
|
+
byDay: costByDay,
|
|
17479
|
+
byModel: costByModel
|
|
17480
|
+
},
|
|
17481
|
+
toolMap,
|
|
17482
|
+
blockMap,
|
|
17483
|
+
agentMap,
|
|
17484
|
+
mcpMap,
|
|
17485
|
+
dailyMap,
|
|
17486
|
+
hourMap
|
|
17487
|
+
} = data;
|
|
17488
|
+
const costUSD = claudeUSD + codexUSD + geminiUSD;
|
|
17489
|
+
const topTools = [...toolMap.entries()].sort((a, b) => b[1].calls - a[1].calls).slice(0, 8);
|
|
17490
|
+
const topBlocks = [...blockMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
|
|
17491
|
+
const dailyList = [...dailyMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).slice(-14);
|
|
17492
|
+
const maxTool = Math.max(...topTools.map(([, v]) => v.calls), 1);
|
|
17493
|
+
const maxBlock = Math.max(...topBlocks.map(([, v]) => v), 1);
|
|
17494
|
+
const maxDaily = Math.max(...dailyList.map(([, v]) => v.calls), 1);
|
|
17495
|
+
const W = Math.min(process.stdout.columns || 80, 100);
|
|
17496
|
+
const INNER = W - 4;
|
|
17497
|
+
const COL = Math.floor(INNER / 2) - 1;
|
|
17498
|
+
const LABEL = 24;
|
|
17499
|
+
const BAR = Math.max(6, Math.min(14, COL - LABEL - 8));
|
|
17500
|
+
const TOOL_COUNT_W = Math.max(...topTools.map(([, v]) => num2(v.calls).length), 1);
|
|
17501
|
+
const BLOCK_COUNT_W = Math.max(...topBlocks.map(([, v]) => num2(v).length), 1);
|
|
17502
|
+
const line = chalk13.dim("\u2500".repeat(W - 2));
|
|
17503
|
+
const periodLabel = {
|
|
17504
|
+
today: "Today",
|
|
17505
|
+
"7d": "Last 7 Days",
|
|
17506
|
+
"30d": "Last 30 Days",
|
|
17507
|
+
"90d": "Last 90 Days",
|
|
17508
|
+
month: "This Month"
|
|
17509
|
+
};
|
|
17510
|
+
console.log("");
|
|
17511
|
+
console.log(
|
|
17512
|
+
" " + chalk13.bold.cyan("\u{1F6E1} node9 Report") + chalk13.dim(" \xB7 ") + chalk13.white(periodLabel[period]) + chalk13.dim(` ${fmtDate(start)} \u2013 ${fmtDate(end)}`) + chalk13.dim(` ${num2(total)} events`) + (excludeTests ? chalk13.dim(` \u2013tests (\u2013${excludedTests})`) : "")
|
|
17513
|
+
);
|
|
17514
|
+
console.log(" " + line);
|
|
17515
|
+
const totalBlocked = timedOut + hardBlocked + dlpBlocked + loopHits + userDenied;
|
|
17516
|
+
const currentRate = total > 0 ? totalBlocked / total : 0;
|
|
17517
|
+
const trendLabel = (() => {
|
|
17518
|
+
if (priorBlockRate === null) return "";
|
|
17519
|
+
const delta = Math.round((currentRate - priorBlockRate) * 100);
|
|
17520
|
+
if (delta === 0) return "";
|
|
17521
|
+
return " " + (delta > 0 ? chalk13.red(`\u25B2${delta}%`) : chalk13.green(`\u25BC${Math.abs(delta)}%`)) + chalk13.dim(" vs prior");
|
|
17522
|
+
})();
|
|
17523
|
+
const reads = toolMap.get("Read")?.calls ?? 0;
|
|
17524
|
+
const edits = (toolMap.get("Edit")?.calls ?? 0) + (toolMap.get("Write")?.calls ?? 0);
|
|
17525
|
+
const ratioLabel = reads > 0 ? chalk13.dim(`edit/read ${(edits / reads).toFixed(1)}`) : chalk13.dim("edit/read \u2013");
|
|
17526
|
+
const testLabel = testPasses + testFails > 0 ? chalk13.dim("tests ") + chalk13.green(`${testPasses}\u2713`) + (testFails > 0 ? " " + chalk13.red(`${testFails}\u2717`) : "") : chalk13.dim("tests \u2013");
|
|
17527
|
+
console.log("");
|
|
17528
|
+
console.log(" " + chalk13.bold("Protection Summary"));
|
|
17529
|
+
console.log(" " + chalk13.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
17530
|
+
console.log(
|
|
17531
|
+
" " + chalk13.dim("Intercepted") + " " + chalk13.white(num2(total)) + chalk13.dim(" tool calls")
|
|
17532
|
+
);
|
|
17533
|
+
console.log("");
|
|
17534
|
+
const COL1 = 18;
|
|
17535
|
+
const summaryRow = (icon, label, count, note, colorFn = (s) => s) => {
|
|
17536
|
+
const countStr = colorFn(num2(count));
|
|
17537
|
+
const noteStr = note ? chalk13.dim(" " + note) : "";
|
|
17538
|
+
console.log(" " + icon + " " + chalk13.white(label.padEnd(COL1)) + countStr + noteStr);
|
|
17539
|
+
};
|
|
17540
|
+
summaryRow(
|
|
17541
|
+
userApproved > 0 ? chalk13.green("\u2705") : chalk13.dim("\u2705"),
|
|
17542
|
+
"User approved",
|
|
17543
|
+
userApproved,
|
|
17544
|
+
userApproved === 0 ? "no popups this period" : void 0,
|
|
17545
|
+
userApproved > 0 ? (s) => chalk13.green(s) : (s) => chalk13.dim(s)
|
|
17546
|
+
);
|
|
17547
|
+
summaryRow(
|
|
17548
|
+
userDenied > 0 ? chalk13.red("\u{1F6AB}") : chalk13.dim("\u{1F6AB}"),
|
|
17549
|
+
"User denied",
|
|
17550
|
+
userDenied,
|
|
17551
|
+
void 0,
|
|
17552
|
+
userDenied > 0 ? (s) => chalk13.red(s) : (s) => chalk13.dim(s)
|
|
17553
|
+
);
|
|
17554
|
+
summaryRow(
|
|
17555
|
+
timedOut > 0 ? chalk13.yellow("\u23F1") : chalk13.dim("\u23F1"),
|
|
17556
|
+
"Timed out",
|
|
17557
|
+
timedOut,
|
|
17558
|
+
timedOut > 0 ? "no approval response" : void 0,
|
|
17559
|
+
timedOut > 0 ? (s) => chalk13.yellow(s) : (s) => chalk13.dim(s)
|
|
17560
|
+
);
|
|
17561
|
+
summaryRow(
|
|
17562
|
+
hardBlocked > 0 ? chalk13.red("\u{1F6D1}") : chalk13.dim("\u{1F6D1}"),
|
|
17563
|
+
"Auto-blocked",
|
|
17564
|
+
hardBlocked,
|
|
17565
|
+
void 0,
|
|
17566
|
+
hardBlocked > 0 ? (s) => chalk13.red(s) : (s) => chalk13.dim(s)
|
|
17567
|
+
);
|
|
17568
|
+
summaryRow(
|
|
17569
|
+
dlpBlocked > 0 ? chalk13.yellow("\u{1F6A8}") : chalk13.dim("\u{1F6A8}"),
|
|
17570
|
+
"DLP blocked",
|
|
17571
|
+
dlpBlocked,
|
|
17572
|
+
void 0,
|
|
17573
|
+
dlpBlocked > 0 ? (s) => chalk13.yellow(s) : (s) => chalk13.dim(s)
|
|
17574
|
+
);
|
|
17575
|
+
summaryRow(
|
|
17576
|
+
observeDlp > 0 ? chalk13.blue("\u{1F441}") : chalk13.dim("\u{1F441}"),
|
|
17577
|
+
"DLP (observe)",
|
|
17578
|
+
observeDlp,
|
|
17579
|
+
observeDlp > 0 ? "would-block in strict mode" : void 0,
|
|
17580
|
+
observeDlp > 0 ? (s) => chalk13.blue(s) : (s) => chalk13.dim(s)
|
|
17581
|
+
);
|
|
17582
|
+
summaryRow(
|
|
17583
|
+
loopHits > 0 ? chalk13.yellow("\u{1F504}") : chalk13.dim("\u{1F504}"),
|
|
17584
|
+
"Loops detected",
|
|
17585
|
+
loopHits,
|
|
17586
|
+
void 0,
|
|
17587
|
+
loopHits > 0 ? (s) => chalk13.yellow(s) : (s) => chalk13.dim(s)
|
|
17588
|
+
);
|
|
17589
|
+
if (trendLabel || ratioLabel || testPasses + testFails > 0) {
|
|
16774
17590
|
console.log("");
|
|
16775
|
-
console.log(
|
|
16776
|
-
|
|
16777
|
-
|
|
16778
|
-
|
|
16779
|
-
|
|
16780
|
-
|
|
16781
|
-
|
|
16782
|
-
|
|
16783
|
-
|
|
16784
|
-
|
|
16785
|
-
|
|
16786
|
-
|
|
16787
|
-
|
|
16788
|
-
|
|
16789
|
-
|
|
16790
|
-
|
|
17591
|
+
console.log(" " + ratioLabel + " " + testLabel + trendLabel);
|
|
17592
|
+
}
|
|
17593
|
+
console.log("");
|
|
17594
|
+
const toolHeaderRaw = "Top Tools";
|
|
17595
|
+
const blockHeaderRaw = "Top Blocks";
|
|
17596
|
+
console.log(
|
|
17597
|
+
" " + chalk13.bold(toolHeaderRaw) + " ".repeat(COL - toolHeaderRaw.length) + " " + chalk13.bold(blockHeaderRaw)
|
|
17598
|
+
);
|
|
17599
|
+
console.log(" " + chalk13.dim("\u2500".repeat(COL)) + " " + chalk13.dim("\u2500".repeat(COL)));
|
|
17600
|
+
const rows = Math.max(topTools.length, topBlocks.length, 1);
|
|
17601
|
+
for (let i = 0; i < rows; i++) {
|
|
17602
|
+
let leftStyled = " ".repeat(COL);
|
|
17603
|
+
if (i < topTools.length) {
|
|
17604
|
+
const [tool, { calls }] = topTools[i];
|
|
17605
|
+
const label = tool.length > LABEL - 1 ? tool.slice(0, LABEL - 2) + "\u2026" : tool;
|
|
17606
|
+
const countStr = num2(calls).padStart(TOOL_COUNT_W);
|
|
17607
|
+
const b = colorBar(calls, maxTool, BAR);
|
|
17608
|
+
const rawLen = LABEL + BAR + 1 + TOOL_COUNT_W;
|
|
17609
|
+
const pad = Math.max(0, COL - rawLen);
|
|
17610
|
+
leftStyled = chalk13.white(label.padEnd(LABEL)) + b + " " + chalk13.white(countStr) + " ".repeat(pad);
|
|
17611
|
+
}
|
|
17612
|
+
let rightStyled = "";
|
|
17613
|
+
if (i < topBlocks.length) {
|
|
17614
|
+
const [reason, count] = topBlocks[i];
|
|
17615
|
+
const readable = humanBlockReason(reason);
|
|
17616
|
+
const label = readable.length > LABEL - 1 ? readable.slice(0, LABEL - 2) + "\u2026" : readable;
|
|
17617
|
+
const countStr = num2(count).padStart(BLOCK_COUNT_W);
|
|
17618
|
+
const b = colorBar(count, maxBlock, BAR);
|
|
17619
|
+
rightStyled = chalk13.white(label.padEnd(LABEL)) + b + " " + chalk13.red(countStr);
|
|
17620
|
+
}
|
|
17621
|
+
console.log(" " + leftStyled + " " + rightStyled);
|
|
17622
|
+
}
|
|
17623
|
+
if (topBlocks.length === 0) {
|
|
17624
|
+
console.log(" " + " ".repeat(COL) + " " + chalk13.dim("nothing blocked \u2713"));
|
|
17625
|
+
}
|
|
17626
|
+
if (agentMap.size >= 1) {
|
|
16791
17627
|
console.log("");
|
|
16792
|
-
console.log(" " + chalk13.bold("
|
|
17628
|
+
console.log(" " + chalk13.bold("Agents"));
|
|
16793
17629
|
console.log(" " + chalk13.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
16794
|
-
|
|
16795
|
-
|
|
16796
|
-
|
|
16797
|
-
|
|
16798
|
-
|
|
16799
|
-
const summaryRow = (icon, label, count, note, colorFn = (s) => s) => {
|
|
16800
|
-
const countStr = colorFn(num2(count));
|
|
16801
|
-
const noteStr = note ? chalk13.dim(" " + note) : "";
|
|
16802
|
-
console.log(" " + icon + " " + chalk13.white(label.padEnd(COL1)) + countStr + noteStr);
|
|
16803
|
-
};
|
|
16804
|
-
summaryRow(
|
|
16805
|
-
userApproved > 0 ? chalk13.green("\u2705") : chalk13.dim("\u2705"),
|
|
16806
|
-
"User approved",
|
|
16807
|
-
userApproved,
|
|
16808
|
-
userApproved === 0 ? "no popups this period" : void 0,
|
|
16809
|
-
userApproved > 0 ? (s) => chalk13.green(s) : (s) => chalk13.dim(s)
|
|
16810
|
-
);
|
|
16811
|
-
summaryRow(
|
|
16812
|
-
userDenied > 0 ? chalk13.red("\u{1F6AB}") : chalk13.dim("\u{1F6AB}"),
|
|
16813
|
-
"User denied",
|
|
16814
|
-
userDenied,
|
|
16815
|
-
void 0,
|
|
16816
|
-
userDenied > 0 ? (s) => chalk13.red(s) : (s) => chalk13.dim(s)
|
|
16817
|
-
);
|
|
16818
|
-
summaryRow(
|
|
16819
|
-
timedOut > 0 ? chalk13.yellow("\u23F1") : chalk13.dim("\u23F1"),
|
|
16820
|
-
"Timed out",
|
|
16821
|
-
timedOut,
|
|
16822
|
-
timedOut > 0 ? "no approval response" : void 0,
|
|
16823
|
-
timedOut > 0 ? (s) => chalk13.yellow(s) : (s) => chalk13.dim(s)
|
|
16824
|
-
);
|
|
16825
|
-
summaryRow(
|
|
16826
|
-
hardBlocked > 0 ? chalk13.red("\u{1F6D1}") : chalk13.dim("\u{1F6D1}"),
|
|
16827
|
-
"Auto-blocked",
|
|
16828
|
-
hardBlocked,
|
|
16829
|
-
void 0,
|
|
16830
|
-
hardBlocked > 0 ? (s) => chalk13.red(s) : (s) => chalk13.dim(s)
|
|
16831
|
-
);
|
|
16832
|
-
summaryRow(
|
|
16833
|
-
dlpBlocked > 0 ? chalk13.yellow("\u{1F6A8}") : chalk13.dim("\u{1F6A8}"),
|
|
16834
|
-
"DLP blocked",
|
|
16835
|
-
dlpBlocked,
|
|
16836
|
-
void 0,
|
|
16837
|
-
dlpBlocked > 0 ? (s) => chalk13.yellow(s) : (s) => chalk13.dim(s)
|
|
16838
|
-
);
|
|
16839
|
-
summaryRow(
|
|
16840
|
-
observeDlp > 0 ? chalk13.blue("\u{1F441}") : chalk13.dim("\u{1F441}"),
|
|
16841
|
-
"DLP (observe)",
|
|
16842
|
-
observeDlp,
|
|
16843
|
-
observeDlp > 0 ? "would-block in strict mode" : void 0,
|
|
16844
|
-
observeDlp > 0 ? (s) => chalk13.blue(s) : (s) => chalk13.dim(s)
|
|
16845
|
-
);
|
|
16846
|
-
summaryRow(
|
|
16847
|
-
loopHits > 0 ? chalk13.yellow("\u{1F504}") : chalk13.dim("\u{1F504}"),
|
|
16848
|
-
"Loops detected",
|
|
16849
|
-
loopHits,
|
|
16850
|
-
void 0,
|
|
16851
|
-
loopHits > 0 ? (s) => chalk13.yellow(s) : (s) => chalk13.dim(s)
|
|
16852
|
-
);
|
|
16853
|
-
if (trendLabel || ratioLabel || testPasses + testFails > 0) {
|
|
16854
|
-
console.log("");
|
|
16855
|
-
console.log(" " + ratioLabel + " " + testLabel + trendLabel);
|
|
17630
|
+
const maxAgent = Math.max(...agentMap.values(), 1);
|
|
17631
|
+
for (const [agent, count] of [...agentMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
17632
|
+
const label = agent.slice(0, LABEL - 1);
|
|
17633
|
+
const b = colorBar(count, maxAgent, BAR);
|
|
17634
|
+
console.log(" " + chalk13.white(label.padEnd(LABEL)) + b + " " + chalk13.white(num2(count)));
|
|
16856
17635
|
}
|
|
17636
|
+
}
|
|
17637
|
+
if (mcpMap.size > 0) {
|
|
16857
17638
|
console.log("");
|
|
16858
|
-
|
|
16859
|
-
|
|
16860
|
-
|
|
16861
|
-
|
|
16862
|
-
|
|
16863
|
-
|
|
16864
|
-
|
|
16865
|
-
|
|
16866
|
-
|
|
16867
|
-
|
|
16868
|
-
|
|
16869
|
-
|
|
16870
|
-
|
|
16871
|
-
|
|
16872
|
-
|
|
16873
|
-
|
|
16874
|
-
|
|
16875
|
-
|
|
16876
|
-
|
|
16877
|
-
|
|
16878
|
-
|
|
16879
|
-
|
|
16880
|
-
|
|
16881
|
-
|
|
16882
|
-
|
|
16883
|
-
|
|
16884
|
-
|
|
16885
|
-
|
|
16886
|
-
|
|
16887
|
-
|
|
16888
|
-
|
|
16889
|
-
|
|
16890
|
-
|
|
16891
|
-
|
|
16892
|
-
|
|
16893
|
-
console.log(" " + chalk13.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
16894
|
-
const maxAgent = Math.max(...agentMap.values(), 1);
|
|
16895
|
-
for (const [agent, count] of [...agentMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
16896
|
-
const label = agent.slice(0, LABEL - 1);
|
|
16897
|
-
const b = colorBar(count, maxAgent, BAR);
|
|
16898
|
-
console.log(" " + chalk13.white(label.padEnd(LABEL)) + b + " " + chalk13.white(num2(count)));
|
|
16899
|
-
}
|
|
16900
|
-
}
|
|
16901
|
-
if (mcpMap.size > 0) {
|
|
16902
|
-
console.log("");
|
|
16903
|
-
console.log(" " + chalk13.bold("MCP Servers"));
|
|
16904
|
-
console.log(" " + chalk13.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
16905
|
-
const maxMcp = Math.max(...mcpMap.values(), 1);
|
|
16906
|
-
for (const [server, count] of [...mcpMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
16907
|
-
const label = server.slice(0, LABEL - 1).padEnd(LABEL);
|
|
16908
|
-
const b = colorBar(count, maxMcp, BAR);
|
|
16909
|
-
console.log(" " + chalk13.white(label) + b + " " + chalk13.white(num2(count)));
|
|
16910
|
-
}
|
|
16911
|
-
}
|
|
16912
|
-
if (hourMap.size > 0) {
|
|
16913
|
-
const BLOCKS = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
16914
|
-
const maxHour = Math.max(...hourMap.values(), 1);
|
|
16915
|
-
const bar = Array.from({ length: 24 }, (_, h) => {
|
|
16916
|
-
const v = hourMap.get(h) ?? 0;
|
|
16917
|
-
return BLOCKS[Math.round(v / maxHour * 8)];
|
|
16918
|
-
}).join("");
|
|
16919
|
-
console.log("");
|
|
16920
|
-
console.log(" " + chalk13.bold("Hour of Day") + chalk13.dim(" (local, 0h \u2013 23h)"));
|
|
16921
|
-
console.log(" " + chalk13.cyan(bar));
|
|
16922
|
-
console.log(" " + chalk13.dim("0h" + " ".repeat(10) + "12h" + " ".repeat(7) + "23h"));
|
|
16923
|
-
}
|
|
16924
|
-
if (dailyList.length > 1) {
|
|
16925
|
-
console.log("");
|
|
16926
|
-
console.log(" " + chalk13.bold("Daily Activity"));
|
|
16927
|
-
console.log(" " + chalk13.dim("\u2500".repeat(W - 2)));
|
|
16928
|
-
const DAY_BAR = Math.max(8, Math.min(30, W - 36));
|
|
16929
|
-
for (const [dateKey, { calls, blocked: db }] of dailyList) {
|
|
16930
|
-
const label = fmtDate(dateKey).padEnd(10);
|
|
16931
|
-
const b = colorBar(calls, maxDaily, DAY_BAR);
|
|
16932
|
-
const dayCost = costByDay.get(dateKey);
|
|
16933
|
-
const costNote = dayCost ? chalk13.magenta(` ${fmtCost2(dayCost)}`) : "";
|
|
16934
|
-
const blockNote = db > 0 ? chalk13.red(` ${db} blocked`) : "";
|
|
16935
|
-
console.log(
|
|
16936
|
-
" " + chalk13.dim(label) + " " + b + " " + chalk13.white(num2(calls)) + blockNote + costNote
|
|
16937
|
-
);
|
|
16938
|
-
}
|
|
16939
|
-
}
|
|
16940
|
-
const totalTokens = costInputTokens + costOutputTokens + costCacheWrite + costCacheRead;
|
|
16941
|
-
if (totalTokens > 0) {
|
|
16942
|
-
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
16943
|
-
console.log("");
|
|
16944
|
-
console.log(" " + chalk13.bold("Tokens") + " " + chalk13.dim(`${num2(totalTokens)} total`));
|
|
16945
|
-
console.log(" " + chalk13.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
16946
|
-
const TOK_BAR = Math.max(6, Math.min(20, W - 30));
|
|
16947
|
-
const TOK_LABEL = 14;
|
|
16948
|
-
const maxNonCache = Math.max(costInputTokens, costOutputTokens, costCacheWrite, 1);
|
|
16949
|
-
const nonCacheRows = [
|
|
16950
|
-
["Input", costInputTokens, chalk13.cyan(num2(costInputTokens))],
|
|
16951
|
-
["Output", costOutputTokens, chalk13.white(num2(costOutputTokens))],
|
|
16952
|
-
["Cache write", costCacheWrite, chalk13.yellow(num2(costCacheWrite))]
|
|
16953
|
-
];
|
|
16954
|
-
for (const [label, count, colored] of nonCacheRows) {
|
|
16955
|
-
if (count === 0) continue;
|
|
16956
|
-
const b = colorBar(count, maxNonCache, TOK_BAR);
|
|
16957
|
-
console.log(" " + chalk13.white(label.padEnd(TOK_LABEL)) + b + " " + colored);
|
|
16958
|
-
}
|
|
16959
|
-
if (costCacheRead > 0) {
|
|
16960
|
-
const cacheBar = colorBar(costCacheRead, costCacheRead, TOK_BAR);
|
|
16961
|
-
const pct = cacheHitPct > 0 ? chalk13.dim(` ${cacheHitPct}% hit rate`) : "";
|
|
16962
|
-
console.log(
|
|
16963
|
-
" " + chalk13.white("Cache read".padEnd(TOK_LABEL)) + cacheBar + " " + chalk13.green(num2(costCacheRead)) + pct
|
|
16964
|
-
);
|
|
16965
|
-
}
|
|
16966
|
-
}
|
|
16967
|
-
if (costUSD > 0) {
|
|
16968
|
-
const periodDays = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 864e5));
|
|
16969
|
-
const avgPerDay = costUSD / periodDays;
|
|
16970
|
-
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
16971
|
-
const costHeaderRight = [
|
|
16972
|
-
chalk13.yellow(fmtCost2(costUSD)),
|
|
16973
|
-
chalk13.dim(`avg ${fmtCost2(avgPerDay)}/day`),
|
|
16974
|
-
cacheHitPct > 0 ? chalk13.dim(`${cacheHitPct}% cache hit`) : null
|
|
16975
|
-
].filter(Boolean).join(chalk13.dim(" \xB7 "));
|
|
16976
|
-
console.log("");
|
|
16977
|
-
console.log(" " + chalk13.bold("Cost") + " " + costHeaderRight);
|
|
16978
|
-
console.log(" " + chalk13.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
16979
|
-
if (codexCostUSD > 0)
|
|
16980
|
-
costByModel.set(
|
|
16981
|
-
"codex (openai)",
|
|
16982
|
-
(costByModel.get("codex (openai)") ?? 0) + codexCostUSD
|
|
16983
|
-
);
|
|
16984
|
-
const modelList = [...costByModel.entries()].sort((a, b) => b[1] - a[1]);
|
|
16985
|
-
const maxModelCost = Math.max(...modelList.map(([, v]) => v), 1e-9);
|
|
16986
|
-
const MODEL_LABEL = 22;
|
|
16987
|
-
const MODEL_BAR = Math.max(6, Math.min(20, W - MODEL_LABEL - 12));
|
|
16988
|
-
for (const [model, cost] of modelList) {
|
|
16989
|
-
const label = model.length > MODEL_LABEL - 1 ? model.slice(0, MODEL_LABEL - 2) + "\u2026" : model;
|
|
16990
|
-
const b = colorBar(cost, maxModelCost, MODEL_BAR);
|
|
16991
|
-
console.log(
|
|
16992
|
-
" " + chalk13.white(label.padEnd(MODEL_LABEL)) + b + " " + chalk13.yellow(fmtCost2(cost))
|
|
16993
|
-
);
|
|
16994
|
-
}
|
|
17639
|
+
console.log(" " + chalk13.bold("MCP Servers"));
|
|
17640
|
+
console.log(" " + chalk13.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
17641
|
+
const maxMcp = Math.max(...mcpMap.values(), 1);
|
|
17642
|
+
for (const [server, count] of [...mcpMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
17643
|
+
const label = server.slice(0, LABEL - 1).padEnd(LABEL);
|
|
17644
|
+
const b = colorBar(count, maxMcp, BAR);
|
|
17645
|
+
console.log(" " + chalk13.white(label) + b + " " + chalk13.white(num2(count)));
|
|
17646
|
+
}
|
|
17647
|
+
}
|
|
17648
|
+
if (hourMap.size > 0) {
|
|
17649
|
+
const BLOCKS = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
17650
|
+
const maxHour = Math.max(...hourMap.values(), 1);
|
|
17651
|
+
const bar = Array.from({ length: 24 }, (_, h) => {
|
|
17652
|
+
const v = hourMap.get(h) ?? 0;
|
|
17653
|
+
return BLOCKS[Math.round(v / maxHour * 8)];
|
|
17654
|
+
}).join("");
|
|
17655
|
+
console.log("");
|
|
17656
|
+
console.log(" " + chalk13.bold("Hour of Day") + chalk13.dim(" (local, 0h \u2013 23h)"));
|
|
17657
|
+
console.log(" " + chalk13.cyan(bar));
|
|
17658
|
+
console.log(" " + chalk13.dim("0h" + " ".repeat(10) + "12h" + " ".repeat(7) + "23h"));
|
|
17659
|
+
}
|
|
17660
|
+
if (dailyList.length > 1) {
|
|
17661
|
+
console.log("");
|
|
17662
|
+
console.log(" " + chalk13.bold("Daily Activity"));
|
|
17663
|
+
console.log(" " + chalk13.dim("\u2500".repeat(W - 2)));
|
|
17664
|
+
const DAY_BAR = Math.max(8, Math.min(30, W - 36));
|
|
17665
|
+
for (const [dateKey, { calls, blocked: db }] of dailyList) {
|
|
17666
|
+
const label = fmtDate(dateKey).padEnd(10);
|
|
17667
|
+
const b = colorBar(calls, maxDaily, DAY_BAR);
|
|
17668
|
+
const dayCost = costByDay.get(dateKey);
|
|
17669
|
+
const costNote = dayCost ? chalk13.magenta(` ${fmtCost2(dayCost)}`) : "";
|
|
17670
|
+
const blockNote = db > 0 ? chalk13.red(` ${db} blocked`) : "";
|
|
17671
|
+
console.log(
|
|
17672
|
+
" " + chalk13.dim(label) + " " + b + " " + chalk13.white(num2(calls)) + blockNote + costNote
|
|
17673
|
+
);
|
|
16995
17674
|
}
|
|
16996
|
-
|
|
16997
|
-
|
|
16998
|
-
|
|
16999
|
-
|
|
17000
|
-
|
|
17001
|
-
|
|
17002
|
-
|
|
17675
|
+
}
|
|
17676
|
+
const totalTokens = costInputTokens + costOutputTokens + costCacheWrite + costCacheRead;
|
|
17677
|
+
if (totalTokens > 0) {
|
|
17678
|
+
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
17679
|
+
console.log("");
|
|
17680
|
+
console.log(" " + chalk13.bold("Tokens") + " " + chalk13.dim(`${num2(totalTokens)} total`));
|
|
17681
|
+
console.log(" " + chalk13.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
17682
|
+
const TOK_BAR = Math.max(6, Math.min(20, W - 30));
|
|
17683
|
+
const TOK_LABEL = 14;
|
|
17684
|
+
const maxNonCache = Math.max(costInputTokens, costOutputTokens, costCacheWrite, 1);
|
|
17685
|
+
const nonCacheRows = [
|
|
17686
|
+
["Input", costInputTokens, chalk13.cyan(num2(costInputTokens))],
|
|
17687
|
+
["Output", costOutputTokens, chalk13.white(num2(costOutputTokens))],
|
|
17688
|
+
["Cache write", costCacheWrite, chalk13.yellow(num2(costCacheWrite))]
|
|
17689
|
+
];
|
|
17690
|
+
for (const [label, count, colored] of nonCacheRows) {
|
|
17691
|
+
if (count === 0) continue;
|
|
17692
|
+
const b = colorBar(count, maxNonCache, TOK_BAR);
|
|
17693
|
+
console.log(" " + chalk13.white(label.padEnd(TOK_LABEL)) + b + " " + colored);
|
|
17694
|
+
}
|
|
17695
|
+
if (costCacheRead > 0) {
|
|
17696
|
+
const cacheBar = colorBar(costCacheRead, costCacheRead, TOK_BAR);
|
|
17697
|
+
const pct = cacheHitPct > 0 ? chalk13.dim(` ${cacheHitPct}% hit rate`) : "";
|
|
17003
17698
|
console.log(
|
|
17004
|
-
" " + chalk13.
|
|
17005
|
-
`${responseDlpEntries.length} secret${responseDlpEntries.length !== 1 ? "s" : ""} found in Claude response text`
|
|
17006
|
-
)
|
|
17699
|
+
" " + chalk13.white("Cache read".padEnd(TOK_LABEL)) + cacheBar + " " + chalk13.green(num2(costCacheRead)) + pct
|
|
17007
17700
|
);
|
|
17008
|
-
|
|
17701
|
+
}
|
|
17702
|
+
}
|
|
17703
|
+
if (costUSD > 0) {
|
|
17704
|
+
const periodDays = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 864e5));
|
|
17705
|
+
const avgPerDay = costUSD / periodDays;
|
|
17706
|
+
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
17707
|
+
const costHeaderRight = [
|
|
17708
|
+
chalk13.yellow(fmtCost2(costUSD)),
|
|
17709
|
+
chalk13.dim(`avg ${fmtCost2(avgPerDay)}/day`),
|
|
17710
|
+
cacheHitPct > 0 ? chalk13.dim(`${cacheHitPct}% cache hit`) : null
|
|
17711
|
+
].filter(Boolean).join(chalk13.dim(" \xB7 "));
|
|
17712
|
+
console.log("");
|
|
17713
|
+
console.log(" " + chalk13.bold("Cost") + " " + costHeaderRight);
|
|
17714
|
+
console.log(" " + chalk13.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
17715
|
+
if (codexUSD > 0)
|
|
17716
|
+
costByModel.set("codex (openai)", (costByModel.get("codex (openai)") ?? 0) + codexUSD);
|
|
17717
|
+
if (geminiUSD > 0)
|
|
17718
|
+
costByModel.set("gemini (google)", (costByModel.get("gemini (google)") ?? 0) + geminiUSD);
|
|
17719
|
+
const modelList = [...costByModel.entries()].sort((a, b) => b[1] - a[1]);
|
|
17720
|
+
const maxModelCost = Math.max(...modelList.map(([, v]) => v), 1e-9);
|
|
17721
|
+
const MODEL_LABEL = 22;
|
|
17722
|
+
const MODEL_BAR = Math.max(6, Math.min(20, W - MODEL_LABEL - 12));
|
|
17723
|
+
for (const [model, cost] of modelList) {
|
|
17724
|
+
const label = model.length > MODEL_LABEL - 1 ? model.slice(0, MODEL_LABEL - 2) + "\u2026" : model;
|
|
17725
|
+
const b = colorBar(cost, maxModelCost, MODEL_BAR);
|
|
17009
17726
|
console.log(
|
|
17010
|
-
" " + chalk13.
|
|
17727
|
+
" " + chalk13.white(label.padEnd(MODEL_LABEL)) + b + " " + chalk13.yellow(fmtCost2(cost))
|
|
17011
17728
|
);
|
|
17012
|
-
console.log(" " + chalk13.yellow("Rotate affected keys immediately."));
|
|
17013
|
-
for (const e of responseDlpEntries.slice(0, 5)) {
|
|
17014
|
-
const ts = chalk13.dim(fmtDate(e.ts) + " ");
|
|
17015
|
-
const pattern = chalk13.red(e.dlpPattern ?? "DLP");
|
|
17016
|
-
const sample = chalk13.gray(e.dlpSample ?? "");
|
|
17017
|
-
console.log(` ${ts}${pattern} ${sample}`);
|
|
17018
|
-
}
|
|
17019
|
-
if (responseDlpEntries.length > 5) {
|
|
17020
|
-
console.log(chalk13.dim(` \u2026 and ${responseDlpEntries.length - 5} more`));
|
|
17021
|
-
}
|
|
17022
17729
|
}
|
|
17730
|
+
}
|
|
17731
|
+
if (responseDlpEntries.length > 0) {
|
|
17023
17732
|
console.log("");
|
|
17024
17733
|
console.log(
|
|
17025
|
-
" " + chalk13.
|
|
17734
|
+
" " + chalk13.red.bold("\u26A0\uFE0F Response DLP") + chalk13.dim(" \xB7 ") + chalk13.red(
|
|
17735
|
+
`${responseDlpEntries.length} secret${responseDlpEntries.length !== 1 ? "s" : ""} found in Claude response text`
|
|
17736
|
+
)
|
|
17026
17737
|
);
|
|
17027
|
-
console.log("");
|
|
17028
|
-
|
|
17738
|
+
console.log(" " + chalk13.dim("\u2500".repeat(Math.min(60, W - 4))));
|
|
17739
|
+
console.log(
|
|
17740
|
+
" " + chalk13.yellow("These were NOT blocked \u2014 Claude included them in response prose.")
|
|
17741
|
+
);
|
|
17742
|
+
console.log(" " + chalk13.yellow("Rotate affected keys immediately."));
|
|
17743
|
+
for (const e of responseDlpEntries.slice(0, 5)) {
|
|
17744
|
+
const ts = chalk13.dim(fmtDate(e.ts) + " ");
|
|
17745
|
+
const pattern = chalk13.red(e.dlpPattern ?? "DLP");
|
|
17746
|
+
const sample = chalk13.gray(e.dlpSample ?? "");
|
|
17747
|
+
console.log(` ${ts}${pattern} ${sample}`);
|
|
17748
|
+
}
|
|
17749
|
+
if (responseDlpEntries.length > 5) {
|
|
17750
|
+
console.log(chalk13.dim(` \u2026 and ${responseDlpEntries.length - 5} more`));
|
|
17751
|
+
}
|
|
17752
|
+
}
|
|
17753
|
+
console.log("");
|
|
17754
|
+
console.log(
|
|
17755
|
+
" " + chalk13.dim("node9 audit --deny") + chalk13.dim(" \xB7 ") + chalk13.dim("node9 report --period today|7d|30d|month --no-tests")
|
|
17756
|
+
);
|
|
17757
|
+
console.log("");
|
|
17029
17758
|
}
|
|
17030
17759
|
|
|
17031
17760
|
// src/cli/commands/daemon-cmd.ts
|