@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.js
CHANGED
|
@@ -118,12 +118,14 @@ function appendHookDebug(toolName, args, meta, auditHashArgsEnabled) {
|
|
|
118
118
|
function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashArgsEnabled) {
|
|
119
119
|
const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
|
|
120
120
|
const testRun = isTestCall(toolName, args) || process.env.NODE9_TESTING === "1" ? { testRun: true } : {};
|
|
121
|
+
const ruleNameField = meta?.ruleName ? { ruleName: meta.ruleName } : {};
|
|
121
122
|
appendToLog(LOCAL_AUDIT_LOG, {
|
|
122
123
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
123
124
|
tool: toolName,
|
|
124
125
|
...argsField,
|
|
125
126
|
decision,
|
|
126
127
|
checkedBy,
|
|
128
|
+
...ruleNameField,
|
|
127
129
|
...testRun,
|
|
128
130
|
agent: meta?.agent,
|
|
129
131
|
mcpServer: meta?.mcpServer,
|
|
@@ -707,7 +709,12 @@ function analyzeFsOperationImpl(command) {
|
|
|
707
709
|
for (const p of paths) {
|
|
708
710
|
for (const sp of SENSITIVE_PATH_RULES) {
|
|
709
711
|
if (sp.match(p)) {
|
|
710
|
-
result = {
|
|
712
|
+
result = {
|
|
713
|
+
ruleName: sp.rule,
|
|
714
|
+
verdict: sp.verdict ?? "block",
|
|
715
|
+
reason: sp.reason,
|
|
716
|
+
path: p
|
|
717
|
+
};
|
|
711
718
|
return false;
|
|
712
719
|
}
|
|
713
720
|
}
|
|
@@ -2367,15 +2374,50 @@ var init_dist = __esm({
|
|
|
2367
2374
|
match: (p) => /(^|[\\/])\.aws[\\/]/i.test(p)
|
|
2368
2375
|
},
|
|
2369
2376
|
{
|
|
2377
|
+
// Mirrors the JSON shield's `.env` pattern (project-jail.json's
|
|
2378
|
+
// review-read-env-any-tool) so the AST FS-op path catches the
|
|
2379
|
+
// same set the regex shield does — including Next.js / Vite's
|
|
2380
|
+
// `.env.<env>.local` double-suffix overrides which are commonly
|
|
2381
|
+
// gitignored AND commonly contain real secrets.
|
|
2382
|
+
//
|
|
2383
|
+
// Intentional non-matches (dev fixtures): .env.example, .env.sample,
|
|
2384
|
+
// .env.template, .env.test, .envrc. See shields.test.ts:983-995
|
|
2385
|
+
// for the canonical test-asserted contract.
|
|
2370
2386
|
rule: "shield:project-jail:block-read-env",
|
|
2371
2387
|
reason: "Reading .env files is blocked by project-jail shield",
|
|
2372
|
-
match: (p) => /(?:^|[\\/])\.env(?:\.local
|
|
2388
|
+
match: (p) => /(?:^|[\\/])\.env(?:\.(?:local|production|staging|development|production\.local|staging\.local|development\.local))?$/i.test(
|
|
2389
|
+
p
|
|
2390
|
+
)
|
|
2373
2391
|
},
|
|
2374
2392
|
{
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2393
|
+
// verdict: 'review' (not 'block') is a deliberate design choice
|
|
2394
|
+
// documented in commit 29327a8. SSH keys and AWS credentials are
|
|
2395
|
+
// cryptographic material with no legitimate read use-case for
|
|
2396
|
+
// an AI agent → hard `block`. But .netrc / .npmrc / .docker /
|
|
2397
|
+
// .kube / gcloud are CONFIG files that hold tokens AND have
|
|
2398
|
+
// legitimate diagnostic reads ("which registry am I configured
|
|
2399
|
+
// for", "what cluster am I on"). Hard-blocking those creates
|
|
2400
|
+
// friction without much safety win because the review gate
|
|
2401
|
+
// still catches genuine exfiltration attempts.
|
|
2402
|
+
//
|
|
2403
|
+
// The review gate FAILS CLOSED on timeout (daemon.approvalTimeoutMs
|
|
2404
|
+
// returns a deny verdict via the orchestrator's timeout branch),
|
|
2405
|
+
// so a stuck or unattended approval does NOT silently grant
|
|
2406
|
+
// credential access. If the threat model demands strict block,
|
|
2407
|
+
// a future per-shield strict-mode toggle is the right fix —
|
|
2408
|
+
// not a regex-level upgrade here.
|
|
2409
|
+
rule: "shield:project-jail:review-read-credentials",
|
|
2410
|
+
reason: "Reading credential files requires approval (project-jail shield)",
|
|
2411
|
+
verdict: "review",
|
|
2412
|
+
match: (p) => (
|
|
2413
|
+
// .kube/config holds Kubernetes cluster credentials and was
|
|
2414
|
+
// flagged as missing by the node9-pr-agent review (the comment
|
|
2415
|
+
// above mentioned .kube but the regex didn't include it — a
|
|
2416
|
+
// textbook code-comment vs code drift). The JSON shield's
|
|
2417
|
+
// review-read-credentials-any-tool already had it. Now aligned.
|
|
2418
|
+
/(?:credentials\.json|\.netrc|\.npmrc|\.docker[\\/]config\.json|gcloud[\\/]credentials|\.kube[\\/]config)$/i.test(
|
|
2419
|
+
p
|
|
2420
|
+
)
|
|
2379
2421
|
)
|
|
2380
2422
|
}
|
|
2381
2423
|
];
|
|
@@ -2391,7 +2433,7 @@ var init_dist = __esm({
|
|
|
2391
2433
|
"shield:project-jail:block-read-ssh",
|
|
2392
2434
|
"shield:project-jail:block-read-aws",
|
|
2393
2435
|
"shield:project-jail:block-read-env",
|
|
2394
|
-
"shield:project-jail:
|
|
2436
|
+
"shield:project-jail:review-read-credentials"
|
|
2395
2437
|
]);
|
|
2396
2438
|
FS_OP_CACHE_MAX = 5e3;
|
|
2397
2439
|
fsOpCache = /* @__PURE__ */ new Map();
|
|
@@ -3079,7 +3121,7 @@ var init_dist = __esm({
|
|
|
3079
3121
|
{
|
|
3080
3122
|
field: "command",
|
|
3081
3123
|
op: "matches",
|
|
3082
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s
|
|
3124
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.ssh[\\/\\\\]",
|
|
3083
3125
|
flags: "i"
|
|
3084
3126
|
}
|
|
3085
3127
|
],
|
|
@@ -3093,7 +3135,7 @@ var init_dist = __esm({
|
|
|
3093
3135
|
{
|
|
3094
3136
|
field: "command",
|
|
3095
3137
|
op: "matches",
|
|
3096
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s
|
|
3138
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.aws[\\/\\\\]",
|
|
3097
3139
|
flags: "i"
|
|
3098
3140
|
}
|
|
3099
3141
|
],
|
|
@@ -3115,7 +3157,7 @@ var init_dist = __esm({
|
|
|
3115
3157
|
reason: "Reading .env files is blocked by project-jail shield"
|
|
3116
3158
|
},
|
|
3117
3159
|
{
|
|
3118
|
-
name: "shield:project-jail:
|
|
3160
|
+
name: "shield:project-jail:review-read-credentials",
|
|
3119
3161
|
tool: "bash",
|
|
3120
3162
|
conditions: [
|
|
3121
3163
|
{
|
|
@@ -3125,8 +3167,64 @@ var init_dist = __esm({
|
|
|
3125
3167
|
flags: "i"
|
|
3126
3168
|
}
|
|
3127
3169
|
],
|
|
3170
|
+
verdict: "review",
|
|
3171
|
+
reason: "Reading credential files requires approval (project-jail shield)"
|
|
3172
|
+
},
|
|
3173
|
+
{
|
|
3174
|
+
name: "shield:project-jail:block-read-ssh-any-tool",
|
|
3175
|
+
tool: "*",
|
|
3176
|
+
conditions: [
|
|
3177
|
+
{
|
|
3178
|
+
field: "file_path",
|
|
3179
|
+
op: "matches",
|
|
3180
|
+
value: "(^|[\\/\\\\])\\.ssh[\\/\\\\]",
|
|
3181
|
+
flags: "i"
|
|
3182
|
+
}
|
|
3183
|
+
],
|
|
3184
|
+
verdict: "block",
|
|
3185
|
+
reason: "Reading SSH private keys is blocked by project-jail shield"
|
|
3186
|
+
},
|
|
3187
|
+
{
|
|
3188
|
+
name: "shield:project-jail:block-read-aws-any-tool",
|
|
3189
|
+
tool: "*",
|
|
3190
|
+
conditions: [
|
|
3191
|
+
{
|
|
3192
|
+
field: "file_path",
|
|
3193
|
+
op: "matches",
|
|
3194
|
+
value: "(^|[\\/\\\\])\\.aws[\\/\\\\]",
|
|
3195
|
+
flags: "i"
|
|
3196
|
+
}
|
|
3197
|
+
],
|
|
3128
3198
|
verdict: "block",
|
|
3129
|
-
reason: "Reading
|
|
3199
|
+
reason: "Reading AWS credentials is blocked by project-jail shield"
|
|
3200
|
+
},
|
|
3201
|
+
{
|
|
3202
|
+
name: "shield:project-jail:review-read-env-any-tool",
|
|
3203
|
+
tool: "*",
|
|
3204
|
+
conditions: [
|
|
3205
|
+
{
|
|
3206
|
+
field: "file_path",
|
|
3207
|
+
op: "matches",
|
|
3208
|
+
value: "(^|[\\/\\\\])\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?$",
|
|
3209
|
+
flags: "i"
|
|
3210
|
+
}
|
|
3211
|
+
],
|
|
3212
|
+
verdict: "review",
|
|
3213
|
+
reason: "Reading .env files requires approval (project-jail shield)"
|
|
3214
|
+
},
|
|
3215
|
+
{
|
|
3216
|
+
name: "shield:project-jail:review-read-credentials-any-tool",
|
|
3217
|
+
tool: "*",
|
|
3218
|
+
conditions: [
|
|
3219
|
+
{
|
|
3220
|
+
field: "file_path",
|
|
3221
|
+
op: "matches",
|
|
3222
|
+
value: ".*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials|\\.kube[\\/\\\\]config)",
|
|
3223
|
+
flags: "i"
|
|
3224
|
+
}
|
|
3225
|
+
],
|
|
3226
|
+
verdict: "review",
|
|
3227
|
+
reason: "Reading credential files requires approval (project-jail shield)"
|
|
3130
3228
|
}
|
|
3131
3229
|
],
|
|
3132
3230
|
dangerousWords: []
|
|
@@ -5793,7 +5891,10 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
5793
5891
|
args,
|
|
5794
5892
|
"deny",
|
|
5795
5893
|
"smart-rule-block-override",
|
|
5796
|
-
|
|
5894
|
+
// Same rationale as the smart-rule-block path above —
|
|
5895
|
+
// pass the specific rule name so [2] SHIELDS can
|
|
5896
|
+
// attribute this override-block to its owning shield.
|
|
5897
|
+
{ ...meta, ruleName: policyResult.ruleName },
|
|
5797
5898
|
hashAuditArgs
|
|
5798
5899
|
);
|
|
5799
5900
|
if (approvers.cloud && creds?.apiKey)
|
|
@@ -5823,7 +5924,20 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
5823
5924
|
}
|
|
5824
5925
|
} else {
|
|
5825
5926
|
if (!isManual)
|
|
5826
|
-
appendLocalAudit(
|
|
5927
|
+
appendLocalAudit(
|
|
5928
|
+
toolName,
|
|
5929
|
+
args,
|
|
5930
|
+
"deny",
|
|
5931
|
+
"smart-rule-block",
|
|
5932
|
+
// Include policyResult.ruleName so the [2] Report SHIELDS
|
|
5933
|
+
// panel can attribute this block to its specific shield
|
|
5934
|
+
// (e.g. `shield:project-jail:block-read-ssh`) via the
|
|
5935
|
+
// rule→shield map. checkedBy stays as the generic
|
|
5936
|
+
// `smart-rule-block` for backward compat with existing
|
|
5937
|
+
// log readers.
|
|
5938
|
+
{ ...meta, ruleName: policyResult.ruleName },
|
|
5939
|
+
hashAuditArgs
|
|
5940
|
+
);
|
|
5827
5941
|
if (approvers.cloud && creds?.apiKey)
|
|
5828
5942
|
auditLocalAllow(toolName, args, "smart-rule-block", creds, meta, void 0, false, {
|
|
5829
5943
|
ruleName: policyResult.ruleName,
|
|
@@ -7645,11 +7759,62 @@ function computeLoopWaste(loops, totalToolCalls) {
|
|
|
7645
7759
|
const wastePct = totalToolCalls > 0 ? Math.round(wastedCalls / totalToolCalls * 100) : 0;
|
|
7646
7760
|
return { wastedCalls, wastePct };
|
|
7647
7761
|
}
|
|
7648
|
-
|
|
7762
|
+
function rollupByShield(sections, topRulesPerShield = 3) {
|
|
7763
|
+
const out = [];
|
|
7764
|
+
for (const section of sections) {
|
|
7765
|
+
if (section.sourceType !== "shield") continue;
|
|
7766
|
+
if (!section.shieldKey) continue;
|
|
7767
|
+
const totalCatches = section.blockedCount + section.reviewCount;
|
|
7768
|
+
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);
|
|
7769
|
+
out.push({
|
|
7770
|
+
shieldName: section.shieldKey,
|
|
7771
|
+
totalCatches,
|
|
7772
|
+
blockCatches: section.blockedCount,
|
|
7773
|
+
reviewCatches: section.reviewCount,
|
|
7774
|
+
topRuleLabels
|
|
7775
|
+
});
|
|
7776
|
+
}
|
|
7777
|
+
return out.sort((a, b) => b.totalCatches - a.totalCatches);
|
|
7778
|
+
}
|
|
7779
|
+
function boxPanel(title, bodyLines, width = PANEL_WIDTH) {
|
|
7780
|
+
const inner = width - 4;
|
|
7781
|
+
const out = [];
|
|
7782
|
+
const titlePad = ` ${title} `;
|
|
7783
|
+
const titleSegment = titlePad.length <= inner ? titlePad : titlePad.slice(0, inner);
|
|
7784
|
+
const dashFill = "\u2500".repeat(Math.max(0, inner - titleSegment.length));
|
|
7785
|
+
out.push(import_chalk3.default.dim("\u256D\u2500") + import_chalk3.default.bold(titleSegment) + import_chalk3.default.dim(`${dashFill}\u2500\u256E`));
|
|
7786
|
+
for (const line of bodyLines) {
|
|
7787
|
+
const padding = " ".repeat(Math.max(0, inner - line.width));
|
|
7788
|
+
out.push(import_chalk3.default.dim("\u2502 ") + line.rendered + padding + import_chalk3.default.dim(" \u2502"));
|
|
7789
|
+
}
|
|
7790
|
+
out.push(import_chalk3.default.dim("\u2570" + "\u2500".repeat(inner + 2) + "\u256F"));
|
|
7791
|
+
return out;
|
|
7792
|
+
}
|
|
7793
|
+
function relativeDate(timestamp, now = /* @__PURE__ */ new Date()) {
|
|
7794
|
+
const t = new Date(timestamp).getTime();
|
|
7795
|
+
if (Number.isNaN(t)) return "?";
|
|
7796
|
+
const days = Math.floor((now.getTime() - t) / 864e5);
|
|
7797
|
+
if (days < 1) return "today";
|
|
7798
|
+
if (days > 90) return "90d+";
|
|
7799
|
+
return `${days}d`;
|
|
7800
|
+
}
|
|
7801
|
+
var import_chalk3, PANEL_WIDTH;
|
|
7649
7802
|
var init_scan_derive = __esm({
|
|
7650
7803
|
"src/cli/render/scan-derive.ts"() {
|
|
7651
7804
|
"use strict";
|
|
7652
7805
|
import_chalk3 = __toESM(require("chalk"));
|
|
7806
|
+
PANEL_WIDTH = 76;
|
|
7807
|
+
}
|
|
7808
|
+
});
|
|
7809
|
+
|
|
7810
|
+
// src/protection.ts
|
|
7811
|
+
var PROTECTIVE_SHIELD_DISCOUNTS;
|
|
7812
|
+
var init_protection = __esm({
|
|
7813
|
+
"src/protection.ts"() {
|
|
7814
|
+
"use strict";
|
|
7815
|
+
PROTECTIVE_SHIELD_DISCOUNTS = {
|
|
7816
|
+
"project-jail": 0.7
|
|
7817
|
+
};
|
|
7653
7818
|
}
|
|
7654
7819
|
});
|
|
7655
7820
|
|
|
@@ -7836,6 +8001,7 @@ async function ensurePricingLoaded() {
|
|
|
7836
8001
|
if (fromDisk && Object.keys(fromDisk).length > 0) {
|
|
7837
8002
|
memCache = fromDisk;
|
|
7838
8003
|
memCacheAt = Date.now();
|
|
8004
|
+
lookupCache.clear();
|
|
7839
8005
|
return;
|
|
7840
8006
|
}
|
|
7841
8007
|
const fetched = await fetchLiteLLMPricing();
|
|
@@ -7843,30 +8009,42 @@ async function ensurePricingLoaded() {
|
|
|
7843
8009
|
memCache = fetched;
|
|
7844
8010
|
memCacheAt = Date.now();
|
|
7845
8011
|
writeCache(fetched);
|
|
8012
|
+
lookupCache.clear();
|
|
7846
8013
|
return;
|
|
7847
8014
|
}
|
|
7848
8015
|
memCache = { ...BUNDLED_PRICING };
|
|
7849
8016
|
memCacheAt = Date.now();
|
|
8017
|
+
lookupCache.clear();
|
|
7850
8018
|
}
|
|
7851
8019
|
function pricingFor(model) {
|
|
7852
8020
|
const norm = normalizeModel(model);
|
|
8021
|
+
const cached = lookupCache.get(norm);
|
|
8022
|
+
if (cached !== void 0) return cached;
|
|
7853
8023
|
const sources = [];
|
|
7854
8024
|
if (memCache) sources.push(memCache);
|
|
7855
8025
|
sources.push(BUNDLED_PRICING);
|
|
8026
|
+
let resolved = null;
|
|
7856
8027
|
for (const source of sources) {
|
|
7857
8028
|
const exact = source[norm];
|
|
7858
|
-
if (exact)
|
|
8029
|
+
if (exact) {
|
|
8030
|
+
resolved = exact;
|
|
8031
|
+
break;
|
|
8032
|
+
}
|
|
7859
8033
|
let best = null;
|
|
7860
8034
|
for (const key of Object.keys(source)) {
|
|
7861
8035
|
if (norm.startsWith(key.toLowerCase()) && (best === null || key.length > best.length)) {
|
|
7862
8036
|
best = key;
|
|
7863
8037
|
}
|
|
7864
8038
|
}
|
|
7865
|
-
if (best)
|
|
8039
|
+
if (best) {
|
|
8040
|
+
resolved = source[best];
|
|
8041
|
+
break;
|
|
8042
|
+
}
|
|
7866
8043
|
}
|
|
7867
|
-
|
|
8044
|
+
lookupCache.set(norm, resolved);
|
|
8045
|
+
return resolved;
|
|
7868
8046
|
}
|
|
7869
|
-
var import_fs15, import_path17, import_os14, LITELLM_URL, BUNDLED_PRICING, CACHE_FILE, TTL_MS, memCache, memCacheAt;
|
|
8047
|
+
var import_fs15, import_path17, import_os14, LITELLM_URL, BUNDLED_PRICING, CACHE_FILE, TTL_MS, memCache, memCacheAt, lookupCache;
|
|
7870
8048
|
var init_litellm = __esm({
|
|
7871
8049
|
"src/pricing/litellm.ts"() {
|
|
7872
8050
|
"use strict";
|
|
@@ -7903,6 +8081,7 @@ var init_litellm = __esm({
|
|
|
7903
8081
|
TTL_MS = 24 * 60 * 60 * 1e3;
|
|
7904
8082
|
memCache = null;
|
|
7905
8083
|
memCacheAt = 0;
|
|
8084
|
+
lookupCache = /* @__PURE__ */ new Map();
|
|
7906
8085
|
}
|
|
7907
8086
|
});
|
|
7908
8087
|
|
|
@@ -7969,7 +8148,7 @@ function parseJSONLFile(filePath, fallbackWorkingDir) {
|
|
|
7969
8148
|
}
|
|
7970
8149
|
return daily;
|
|
7971
8150
|
}
|
|
7972
|
-
function collectEntries() {
|
|
8151
|
+
function collectEntries(sinceMs) {
|
|
7973
8152
|
const projectsDir = import_path18.default.join(import_os15.default.homedir(), ".claude", "projects");
|
|
7974
8153
|
if (!import_fs16.default.existsSync(projectsDir)) return [];
|
|
7975
8154
|
const combined = /* @__PURE__ */ new Map();
|
|
@@ -7994,7 +8173,15 @@ function collectEntries() {
|
|
|
7994
8173
|
}
|
|
7995
8174
|
const fallbackWorkingDir = decodeProjectDirName(dir);
|
|
7996
8175
|
for (const file of files) {
|
|
7997
|
-
const
|
|
8176
|
+
const filePath = import_path18.default.join(dirPath, file);
|
|
8177
|
+
if (sinceMs !== void 0) {
|
|
8178
|
+
try {
|
|
8179
|
+
if (import_fs16.default.statSync(filePath).mtimeMs < sinceMs) continue;
|
|
8180
|
+
} catch {
|
|
8181
|
+
continue;
|
|
8182
|
+
}
|
|
8183
|
+
}
|
|
8184
|
+
const entries = parseJSONLFile(filePath, fallbackWorkingDir);
|
|
7998
8185
|
for (const [key, e] of entries) {
|
|
7999
8186
|
const prev = combined.get(key);
|
|
8000
8187
|
if (prev) {
|
|
@@ -8829,7 +9016,16 @@ function buildRecurringPatternSet(findings) {
|
|
|
8829
9016
|
}
|
|
8830
9017
|
return recurring;
|
|
8831
9018
|
}
|
|
8832
|
-
function
|
|
9019
|
+
function emptyScanDedup() {
|
|
9020
|
+
return { findingsKeys: /* @__PURE__ */ new Set(), dlpKeys: /* @__PURE__ */ new Set() };
|
|
9021
|
+
}
|
|
9022
|
+
function findingKey(ruleName, inputPreview, projLabel) {
|
|
9023
|
+
return `${ruleName ?? "<unnamed>"}|${inputPreview}|${projLabel}`;
|
|
9024
|
+
}
|
|
9025
|
+
function dlpKey(patternName, redactedSample, projLabel) {
|
|
9026
|
+
return `${patternName}|${redactedSample}|${projLabel}`;
|
|
9027
|
+
}
|
|
9028
|
+
function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sessionId, agent, result, dedup) {
|
|
8833
9029
|
const fsVerdict = analyzeFsOperation(command);
|
|
8834
9030
|
if (!fsVerdict) return false;
|
|
8835
9031
|
const synthRule = {
|
|
@@ -8852,10 +9048,9 @@ function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sess
|
|
|
8852
9048
|
rule: synthRule
|
|
8853
9049
|
};
|
|
8854
9050
|
const inputPreview = preview(input, 120);
|
|
8855
|
-
const
|
|
8856
|
-
|
|
8857
|
-
|
|
8858
|
-
if (!isDupe) {
|
|
9051
|
+
const k = findingKey(synthRule.name, inputPreview, projLabel);
|
|
9052
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9053
|
+
dedup.findingsKeys.add(k);
|
|
8859
9054
|
result.findings.push({
|
|
8860
9055
|
source: synthSource,
|
|
8861
9056
|
toolName,
|
|
@@ -8940,22 +9135,15 @@ function buildRuleSources() {
|
|
|
8940
9135
|
sources.push({ shieldName, shieldLabel: shieldName, sourceType: "shield", rule });
|
|
8941
9136
|
}
|
|
8942
9137
|
}
|
|
8943
|
-
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
shieldName: isCloud ? "cloud" : isDefault ? "default" : "custom",
|
|
8953
|
-
shieldLabel: isCloud ? "Cloud Policy" : isDefault ? "Default Rules" : "Your Rules",
|
|
8954
|
-
sourceType,
|
|
8955
|
-
rule
|
|
8956
|
-
});
|
|
8957
|
-
}
|
|
8958
|
-
} catch {
|
|
9138
|
+
for (const rule of DEFAULT_CONFIG.policy.smartRules) {
|
|
9139
|
+
if (!rule.name) continue;
|
|
9140
|
+
if (rule.name.startsWith("shield:")) continue;
|
|
9141
|
+
sources.push({
|
|
9142
|
+
shieldName: "default",
|
|
9143
|
+
shieldLabel: "Default Rules",
|
|
9144
|
+
sourceType: "default",
|
|
9145
|
+
rule
|
|
9146
|
+
});
|
|
8959
9147
|
}
|
|
8960
9148
|
return sources;
|
|
8961
9149
|
}
|
|
@@ -9041,178 +9229,53 @@ function renderProgressBar(done, total, lines) {
|
|
|
9041
9229
|
`\r ${import_chalk5.default.cyan("Scanning")} [${import_chalk5.default.cyan(bar)}] ${import_chalk5.default.dim(fileLabel)}${lineLabel} `
|
|
9042
9230
|
);
|
|
9043
9231
|
}
|
|
9044
|
-
function
|
|
9045
|
-
|
|
9046
|
-
|
|
9047
|
-
|
|
9048
|
-
|
|
9049
|
-
|
|
9050
|
-
bashCalls: 0,
|
|
9051
|
-
findings: [],
|
|
9052
|
-
dlpFindings: [],
|
|
9053
|
-
loopFindings: [],
|
|
9054
|
-
totalCostUSD: 0,
|
|
9055
|
-
firstDate: null,
|
|
9056
|
-
lastDate: null,
|
|
9057
|
-
sessionsWithEarlySecrets: 0
|
|
9058
|
-
};
|
|
9059
|
-
if (!import_fs19.default.existsSync(projectsDir)) return result;
|
|
9060
|
-
let projDirs;
|
|
9232
|
+
function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, result, dedup, onProgress, onLine) {
|
|
9233
|
+
result.filesScanned++;
|
|
9234
|
+
result.sessions++;
|
|
9235
|
+
onProgress?.(result.filesScanned);
|
|
9236
|
+
const sessionId = file.replace(/\.jsonl$/, "");
|
|
9237
|
+
let raw;
|
|
9061
9238
|
try {
|
|
9062
|
-
|
|
9239
|
+
raw = import_fs19.default.readFileSync(import_path21.default.join(projPath, file), "utf-8");
|
|
9063
9240
|
} catch {
|
|
9064
|
-
return
|
|
9241
|
+
return;
|
|
9065
9242
|
}
|
|
9066
|
-
const
|
|
9067
|
-
|
|
9068
|
-
|
|
9243
|
+
const sessionCalls = [];
|
|
9244
|
+
const toolUseFilePaths = /* @__PURE__ */ new Map();
|
|
9245
|
+
let firstDlpTs = null;
|
|
9246
|
+
let firstEditTs = null;
|
|
9247
|
+
for (const line of raw.split("\n")) {
|
|
9248
|
+
if (!line.trim()) continue;
|
|
9249
|
+
onLine?.();
|
|
9250
|
+
let entry;
|
|
9069
9251
|
try {
|
|
9070
|
-
|
|
9252
|
+
entry = JSON.parse(line);
|
|
9071
9253
|
} catch {
|
|
9072
9254
|
continue;
|
|
9073
9255
|
}
|
|
9074
|
-
|
|
9075
|
-
|
|
9076
|
-
|
|
9077
|
-
let files;
|
|
9078
|
-
try {
|
|
9079
|
-
files = import_fs19.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
9080
|
-
} catch {
|
|
9081
|
-
continue;
|
|
9256
|
+
if (entry.type !== "assistant" && entry.type !== "user") continue;
|
|
9257
|
+
if (startDate && entry.timestamp) {
|
|
9258
|
+
if (new Date(entry.timestamp) < startDate) continue;
|
|
9082
9259
|
}
|
|
9083
|
-
|
|
9084
|
-
result.
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
9088
|
-
|
|
9089
|
-
|
|
9090
|
-
|
|
9091
|
-
|
|
9092
|
-
|
|
9093
|
-
|
|
9094
|
-
const sessionCalls = [];
|
|
9095
|
-
const toolUseFilePaths = /* @__PURE__ */ new Map();
|
|
9096
|
-
let firstDlpTs = null;
|
|
9097
|
-
let firstEditTs = null;
|
|
9098
|
-
for (const line of raw.split("\n")) {
|
|
9099
|
-
if (!line.trim()) continue;
|
|
9100
|
-
onLine?.();
|
|
9101
|
-
let entry;
|
|
9102
|
-
try {
|
|
9103
|
-
entry = JSON.parse(line);
|
|
9104
|
-
} catch {
|
|
9105
|
-
continue;
|
|
9106
|
-
}
|
|
9107
|
-
if (entry.type !== "assistant" && entry.type !== "user") continue;
|
|
9108
|
-
if (startDate && entry.timestamp) {
|
|
9109
|
-
if (new Date(entry.timestamp) < startDate) continue;
|
|
9110
|
-
}
|
|
9111
|
-
if (entry.timestamp) {
|
|
9112
|
-
if (!result.firstDate || entry.timestamp < result.firstDate)
|
|
9113
|
-
result.firstDate = entry.timestamp;
|
|
9114
|
-
if (!result.lastDate || entry.timestamp > result.lastDate)
|
|
9115
|
-
result.lastDate = entry.timestamp;
|
|
9116
|
-
}
|
|
9117
|
-
if (entry.type === "user") {
|
|
9118
|
-
const content2 = entry.message?.content;
|
|
9119
|
-
if (Array.isArray(content2)) {
|
|
9120
|
-
const text = content2.filter((b) => b.type === "text").map((b) => b["text"] ?? "").join("\n");
|
|
9121
|
-
if (text) {
|
|
9122
|
-
const dlpMatch = scanArgs({ text });
|
|
9123
|
-
if (dlpMatch) {
|
|
9124
|
-
const isDupe = result.dlpFindings.some(
|
|
9125
|
-
(f) => f.patternName === dlpMatch.patternName && f.redactedSample === dlpMatch.redactedSample && f.project === projLabel
|
|
9126
|
-
);
|
|
9127
|
-
if (!isDupe) {
|
|
9128
|
-
result.dlpFindings.push({
|
|
9129
|
-
patternName: dlpMatch.patternName,
|
|
9130
|
-
redactedSample: dlpMatch.redactedSample,
|
|
9131
|
-
toolName: "user-prompt",
|
|
9132
|
-
timestamp: entry.timestamp ?? "",
|
|
9133
|
-
project: projLabel,
|
|
9134
|
-
sessionId,
|
|
9135
|
-
agent: "claude"
|
|
9136
|
-
});
|
|
9137
|
-
}
|
|
9138
|
-
}
|
|
9139
|
-
}
|
|
9140
|
-
for (const block of content2) {
|
|
9141
|
-
if (block.type !== "tool_result") continue;
|
|
9142
|
-
const filePath = block.tool_use_id ? toolUseFilePaths.get(block.tool_use_id) : void 0;
|
|
9143
|
-
if (filePath) {
|
|
9144
|
-
const ext = import_path21.default.extname(filePath).toLowerCase();
|
|
9145
|
-
if (CODE_EXTENSIONS.has(ext)) continue;
|
|
9146
|
-
}
|
|
9147
|
-
const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
|
|
9148
|
-
if (!resultText) continue;
|
|
9149
|
-
if (isNode9SelfOutput(resultText)) continue;
|
|
9150
|
-
const dlpMatch = scanArgs({ text: resultText });
|
|
9151
|
-
if (dlpMatch) {
|
|
9152
|
-
if (looksLikeFixtureToken(dlpMatch.redactedSample)) continue;
|
|
9153
|
-
if (firstDlpTs === null) firstDlpTs = entry.timestamp ?? null;
|
|
9154
|
-
const isDupe = result.dlpFindings.some(
|
|
9155
|
-
(f) => f.patternName === dlpMatch.patternName && f.redactedSample === dlpMatch.redactedSample && f.project === projLabel
|
|
9156
|
-
);
|
|
9157
|
-
if (!isDupe) {
|
|
9158
|
-
result.dlpFindings.push({
|
|
9159
|
-
patternName: dlpMatch.patternName,
|
|
9160
|
-
redactedSample: dlpMatch.redactedSample,
|
|
9161
|
-
toolName: "tool-result",
|
|
9162
|
-
timestamp: entry.timestamp ?? "",
|
|
9163
|
-
project: projLabel,
|
|
9164
|
-
sessionId,
|
|
9165
|
-
agent: "claude"
|
|
9166
|
-
});
|
|
9167
|
-
}
|
|
9168
|
-
}
|
|
9169
|
-
}
|
|
9170
|
-
}
|
|
9171
|
-
continue;
|
|
9172
|
-
}
|
|
9173
|
-
const usage = entry.message?.usage;
|
|
9174
|
-
const model = entry.message?.model;
|
|
9175
|
-
if (usage && model) {
|
|
9176
|
-
const p = claudeModelPrice(model);
|
|
9177
|
-
if (p) {
|
|
9178
|
-
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;
|
|
9179
|
-
}
|
|
9180
|
-
}
|
|
9181
|
-
const content = entry.message?.content;
|
|
9182
|
-
if (!Array.isArray(content)) continue;
|
|
9183
|
-
for (const block of content) {
|
|
9184
|
-
if (block.type !== "tool_use") continue;
|
|
9185
|
-
result.totalToolCalls++;
|
|
9186
|
-
const toolName = block.name ?? "";
|
|
9187
|
-
const toolNameLower = toolName.toLowerCase();
|
|
9188
|
-
const input = block.input ?? {};
|
|
9189
|
-
if (block.id && typeof input.file_path === "string") {
|
|
9190
|
-
toolUseFilePaths.set(block.id, input.file_path);
|
|
9191
|
-
}
|
|
9192
|
-
sessionCalls.push({ toolName, input, timestamp: entry.timestamp ?? "" });
|
|
9193
|
-
if (toolNameLower === "bash" || toolNameLower === "execute_bash") {
|
|
9194
|
-
result.bashCalls++;
|
|
9195
|
-
}
|
|
9196
|
-
if (firstEditTs === null && (toolNameLower === "edit" || toolNameLower === "write" || toolNameLower === "write_file" || toolNameLower === "edit_file" || toolNameLower === "multiedit")) {
|
|
9197
|
-
firstEditTs = entry.timestamp ?? null;
|
|
9198
|
-
}
|
|
9199
|
-
const rawCmd = String(input.command ?? "").trimStart();
|
|
9200
|
-
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd))
|
|
9201
|
-
continue;
|
|
9202
|
-
const inputFilePath = typeof input.file_path === "string" ? input.file_path : "";
|
|
9203
|
-
const inputFileExt = inputFilePath ? import_path21.default.extname(inputFilePath).toLowerCase() : "";
|
|
9204
|
-
if (CODE_EXTENSIONS.has(inputFileExt)) continue;
|
|
9205
|
-
const dlpMatch = scanArgs(input);
|
|
9260
|
+
if (entry.timestamp) {
|
|
9261
|
+
if (!result.firstDate || entry.timestamp < result.firstDate)
|
|
9262
|
+
result.firstDate = entry.timestamp;
|
|
9263
|
+
if (!result.lastDate || entry.timestamp > result.lastDate) result.lastDate = entry.timestamp;
|
|
9264
|
+
}
|
|
9265
|
+
if (entry.type === "user") {
|
|
9266
|
+
const content2 = entry.message?.content;
|
|
9267
|
+
if (Array.isArray(content2)) {
|
|
9268
|
+
const text = content2.filter((b) => b.type === "text").map((b) => b["text"] ?? "").join("\n");
|
|
9269
|
+
if (text) {
|
|
9270
|
+
const dlpMatch = scanArgs({ text });
|
|
9206
9271
|
if (dlpMatch) {
|
|
9207
|
-
|
|
9208
|
-
|
|
9209
|
-
(
|
|
9210
|
-
);
|
|
9211
|
-
if (!isDupe) {
|
|
9272
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9273
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9274
|
+
dedup.dlpKeys.add(k);
|
|
9212
9275
|
result.dlpFindings.push({
|
|
9213
9276
|
patternName: dlpMatch.patternName,
|
|
9214
9277
|
redactedSample: dlpMatch.redactedSample,
|
|
9215
|
-
toolName,
|
|
9278
|
+
toolName: "user-prompt",
|
|
9216
9279
|
timestamp: entry.timestamp ?? "",
|
|
9217
9280
|
project: projLabel,
|
|
9218
9281
|
sessionId,
|
|
@@ -9220,102 +9283,252 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
|
|
|
9220
9283
|
});
|
|
9221
9284
|
}
|
|
9222
9285
|
}
|
|
9223
|
-
|
|
9224
|
-
|
|
9225
|
-
if (
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
|
|
9229
|
-
|
|
9230
|
-
entry.timestamp ?? "",
|
|
9231
|
-
projLabel,
|
|
9232
|
-
sessionId,
|
|
9233
|
-
"claude",
|
|
9234
|
-
result
|
|
9235
|
-
);
|
|
9286
|
+
}
|
|
9287
|
+
for (const block of content2) {
|
|
9288
|
+
if (block.type !== "tool_result") continue;
|
|
9289
|
+
const filePath = block.tool_use_id ? toolUseFilePaths.get(block.tool_use_id) : void 0;
|
|
9290
|
+
if (filePath) {
|
|
9291
|
+
const ext = import_path21.default.extname(filePath).toLowerCase();
|
|
9292
|
+
if (CODE_EXTENSIONS.has(ext)) continue;
|
|
9236
9293
|
}
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
if (
|
|
9243
|
-
if (
|
|
9244
|
-
const
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
|
|
9251
|
-
toolName,
|
|
9252
|
-
input,
|
|
9294
|
+
const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
|
|
9295
|
+
if (!resultText) continue;
|
|
9296
|
+
if (isNode9SelfOutput(resultText)) continue;
|
|
9297
|
+
const dlpMatch = scanArgs({ text: resultText });
|
|
9298
|
+
if (dlpMatch) {
|
|
9299
|
+
if (looksLikeFixtureToken(dlpMatch.redactedSample)) continue;
|
|
9300
|
+
if (firstDlpTs === null) firstDlpTs = entry.timestamp ?? null;
|
|
9301
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9302
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9303
|
+
dedup.dlpKeys.add(k);
|
|
9304
|
+
result.dlpFindings.push({
|
|
9305
|
+
patternName: dlpMatch.patternName,
|
|
9306
|
+
redactedSample: dlpMatch.redactedSample,
|
|
9307
|
+
toolName: "tool-result",
|
|
9253
9308
|
timestamp: entry.timestamp ?? "",
|
|
9254
9309
|
project: projLabel,
|
|
9255
9310
|
sessionId,
|
|
9256
9311
|
agent: "claude"
|
|
9257
9312
|
});
|
|
9258
9313
|
}
|
|
9259
|
-
ruleMatched = true;
|
|
9260
|
-
break;
|
|
9261
|
-
}
|
|
9262
|
-
if (!ruleMatched && (toolNameLower === "bash" || toolNameLower === "execute_bash")) {
|
|
9263
|
-
const shellVerdict = detectDangerousShellExec(String(input.command ?? ""));
|
|
9264
|
-
if (shellVerdict) {
|
|
9265
|
-
const astRule = {
|
|
9266
|
-
name: `ast:bash-safe:${shellVerdict}-shell-exec-remote`,
|
|
9267
|
-
tool: "bash",
|
|
9268
|
-
conditions: [],
|
|
9269
|
-
verdict: shellVerdict,
|
|
9270
|
-
reason: `Shell execution of remote download detected by AST analysis (bash-safe)`
|
|
9271
|
-
};
|
|
9272
|
-
const inputPreview = preview(input, 120);
|
|
9273
|
-
const isDupe = result.findings.some(
|
|
9274
|
-
(f) => f.source.rule.name === astRule.name && preview(f.input, 120) === inputPreview && f.project === projLabel
|
|
9275
|
-
);
|
|
9276
|
-
if (!isDupe) {
|
|
9277
|
-
result.findings.push({
|
|
9278
|
-
source: {
|
|
9279
|
-
shieldName: "bash-safe",
|
|
9280
|
-
shieldLabel: "bash-safe (AST)",
|
|
9281
|
-
sourceType: "shield",
|
|
9282
|
-
rule: astRule
|
|
9283
|
-
},
|
|
9284
|
-
toolName,
|
|
9285
|
-
input,
|
|
9286
|
-
timestamp: entry.timestamp ?? "",
|
|
9287
|
-
project: projLabel,
|
|
9288
|
-
sessionId,
|
|
9289
|
-
agent: "claude"
|
|
9290
|
-
});
|
|
9291
|
-
}
|
|
9292
|
-
}
|
|
9293
9314
|
}
|
|
9294
9315
|
}
|
|
9295
9316
|
}
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9317
|
+
continue;
|
|
9318
|
+
}
|
|
9319
|
+
const usage = entry.message?.usage;
|
|
9320
|
+
const model = entry.message?.model;
|
|
9321
|
+
if (usage && model) {
|
|
9322
|
+
const p = claudeModelPrice(model);
|
|
9323
|
+
if (p) {
|
|
9324
|
+
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;
|
|
9299
9325
|
}
|
|
9300
9326
|
}
|
|
9301
|
-
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
|
|
9311
|
-
|
|
9312
|
-
|
|
9327
|
+
const content = entry.message?.content;
|
|
9328
|
+
if (!Array.isArray(content)) continue;
|
|
9329
|
+
for (const block of content) {
|
|
9330
|
+
if (block.type !== "tool_use") continue;
|
|
9331
|
+
result.totalToolCalls++;
|
|
9332
|
+
const toolName = block.name ?? "";
|
|
9333
|
+
const toolNameLower = toolName.toLowerCase();
|
|
9334
|
+
const input = block.input ?? {};
|
|
9335
|
+
if (block.id && typeof input.file_path === "string") {
|
|
9336
|
+
toolUseFilePaths.set(block.id, input.file_path);
|
|
9337
|
+
}
|
|
9338
|
+
sessionCalls.push({ toolName, input, timestamp: entry.timestamp ?? "" });
|
|
9339
|
+
if (toolNameLower === "bash" || toolNameLower === "execute_bash") {
|
|
9340
|
+
result.bashCalls++;
|
|
9341
|
+
}
|
|
9342
|
+
if (firstEditTs === null && (toolNameLower === "edit" || toolNameLower === "write" || toolNameLower === "write_file" || toolNameLower === "edit_file" || toolNameLower === "multiedit")) {
|
|
9343
|
+
firstEditTs = entry.timestamp ?? null;
|
|
9344
|
+
}
|
|
9345
|
+
const rawCmd = String(input.command ?? "").trimStart();
|
|
9346
|
+
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd)) continue;
|
|
9347
|
+
const inputFilePath = typeof input.file_path === "string" ? input.file_path : "";
|
|
9348
|
+
const inputFileExt = inputFilePath ? import_path21.default.extname(inputFilePath).toLowerCase() : "";
|
|
9349
|
+
if (CODE_EXTENSIONS.has(inputFileExt)) continue;
|
|
9350
|
+
const dlpMatch = scanArgs(input);
|
|
9351
|
+
if (dlpMatch) {
|
|
9352
|
+
if (firstDlpTs === null) firstDlpTs = entry.timestamp ?? null;
|
|
9353
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9354
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9355
|
+
dedup.dlpKeys.add(k);
|
|
9356
|
+
result.dlpFindings.push({
|
|
9357
|
+
patternName: dlpMatch.patternName,
|
|
9358
|
+
redactedSample: dlpMatch.redactedSample,
|
|
9359
|
+
toolName,
|
|
9360
|
+
timestamp: entry.timestamp ?? "",
|
|
9361
|
+
project: projLabel,
|
|
9362
|
+
sessionId,
|
|
9363
|
+
agent: "claude"
|
|
9364
|
+
});
|
|
9365
|
+
}
|
|
9366
|
+
}
|
|
9367
|
+
let astFsMatched = false;
|
|
9368
|
+
const astRanForBash = toolNameLower === "bash" || toolNameLower === "execute_bash";
|
|
9369
|
+
if (astRanForBash) {
|
|
9370
|
+
astFsMatched = pushFsOpAstFinding(
|
|
9371
|
+
String(input.command ?? ""),
|
|
9372
|
+
toolName,
|
|
9373
|
+
input,
|
|
9374
|
+
entry.timestamp ?? "",
|
|
9375
|
+
projLabel,
|
|
9376
|
+
sessionId,
|
|
9377
|
+
"claude",
|
|
9378
|
+
result,
|
|
9379
|
+
dedup
|
|
9380
|
+
);
|
|
9381
|
+
}
|
|
9382
|
+
let ruleMatched = astFsMatched;
|
|
9383
|
+
for (const source of ruleSources) {
|
|
9384
|
+
const { rule } = source;
|
|
9385
|
+
if (rule.verdict === "allow") continue;
|
|
9386
|
+
if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
|
|
9387
|
+
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
9388
|
+
if (!evaluateSmartConditions(input, rule)) continue;
|
|
9389
|
+
const inputPreview = preview(input, 120);
|
|
9390
|
+
const k = findingKey(rule.name, inputPreview, projLabel);
|
|
9391
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9392
|
+
dedup.findingsKeys.add(k);
|
|
9393
|
+
result.findings.push({
|
|
9394
|
+
source,
|
|
9395
|
+
toolName,
|
|
9396
|
+
input,
|
|
9397
|
+
timestamp: entry.timestamp ?? "",
|
|
9398
|
+
project: projLabel,
|
|
9399
|
+
sessionId,
|
|
9400
|
+
agent: "claude"
|
|
9401
|
+
});
|
|
9402
|
+
}
|
|
9403
|
+
ruleMatched = true;
|
|
9404
|
+
break;
|
|
9405
|
+
}
|
|
9406
|
+
if (!ruleMatched && (toolNameLower === "bash" || toolNameLower === "execute_bash")) {
|
|
9407
|
+
const shellVerdict = detectDangerousShellExec(String(input.command ?? ""));
|
|
9408
|
+
if (shellVerdict) {
|
|
9409
|
+
const astRule = {
|
|
9410
|
+
name: `ast:bash-safe:${shellVerdict}-shell-exec-remote`,
|
|
9411
|
+
tool: "bash",
|
|
9412
|
+
conditions: [],
|
|
9413
|
+
verdict: shellVerdict,
|
|
9414
|
+
reason: `Shell execution of remote download detected by AST analysis (bash-safe)`
|
|
9415
|
+
};
|
|
9416
|
+
const inputPreview = preview(input, 120);
|
|
9417
|
+
const k = findingKey(astRule.name, inputPreview, projLabel);
|
|
9418
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9419
|
+
dedup.findingsKeys.add(k);
|
|
9420
|
+
result.findings.push({
|
|
9421
|
+
source: {
|
|
9422
|
+
shieldName: "bash-safe",
|
|
9423
|
+
shieldLabel: "bash-safe (AST)",
|
|
9424
|
+
sourceType: "shield",
|
|
9425
|
+
rule: astRule
|
|
9426
|
+
},
|
|
9427
|
+
toolName,
|
|
9428
|
+
input,
|
|
9429
|
+
timestamp: entry.timestamp ?? "",
|
|
9430
|
+
project: projLabel,
|
|
9431
|
+
sessionId,
|
|
9432
|
+
agent: "claude"
|
|
9433
|
+
});
|
|
9434
|
+
}
|
|
9435
|
+
}
|
|
9436
|
+
}
|
|
9437
|
+
}
|
|
9438
|
+
}
|
|
9439
|
+
result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "claude"));
|
|
9440
|
+
if (firstDlpTs !== null && (firstEditTs === null || firstDlpTs < firstEditTs)) {
|
|
9441
|
+
result.sessionsWithEarlySecrets++;
|
|
9442
|
+
}
|
|
9443
|
+
}
|
|
9444
|
+
function processClaudeProject(proj, projectsDir, ruleSources, startDate, result, dedup, onProgress, onLine) {
|
|
9445
|
+
const projPath = import_path21.default.join(projectsDir, proj);
|
|
9446
|
+
try {
|
|
9447
|
+
if (!import_fs19.default.statSync(projPath).isDirectory()) return;
|
|
9448
|
+
} catch {
|
|
9449
|
+
return;
|
|
9450
|
+
}
|
|
9451
|
+
const projLabel = stripTerminalEscapes(decodeURIComponent(proj).replace(import_os18.default.homedir(), "~")).slice(
|
|
9452
|
+
0,
|
|
9453
|
+
40
|
|
9454
|
+
);
|
|
9455
|
+
let files;
|
|
9456
|
+
try {
|
|
9457
|
+
files = import_fs19.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
9458
|
+
} catch {
|
|
9459
|
+
return;
|
|
9460
|
+
}
|
|
9461
|
+
for (const file of files) {
|
|
9462
|
+
processClaudeFile(
|
|
9463
|
+
file,
|
|
9464
|
+
projPath,
|
|
9465
|
+
projLabel,
|
|
9466
|
+
ruleSources,
|
|
9467
|
+
startDate,
|
|
9468
|
+
result,
|
|
9469
|
+
dedup,
|
|
9470
|
+
onProgress,
|
|
9471
|
+
onLine
|
|
9472
|
+
);
|
|
9473
|
+
}
|
|
9474
|
+
}
|
|
9475
|
+
function emptyClaudeScan() {
|
|
9476
|
+
return {
|
|
9477
|
+
filesScanned: 0,
|
|
9478
|
+
sessions: 0,
|
|
9479
|
+
totalToolCalls: 0,
|
|
9480
|
+
bashCalls: 0,
|
|
9481
|
+
findings: [],
|
|
9482
|
+
dlpFindings: [],
|
|
9483
|
+
loopFindings: [],
|
|
9484
|
+
totalCostUSD: 0,
|
|
9485
|
+
firstDate: null,
|
|
9486
|
+
lastDate: null,
|
|
9487
|
+
sessionsWithEarlySecrets: 0
|
|
9488
|
+
};
|
|
9489
|
+
}
|
|
9490
|
+
function scanClaudeHistory(startDate, onProgress, onLine) {
|
|
9491
|
+
const projectsDir = import_path21.default.join(import_os18.default.homedir(), ".claude", "projects");
|
|
9492
|
+
const result = emptyClaudeScan();
|
|
9493
|
+
if (!import_fs19.default.existsSync(projectsDir)) return result;
|
|
9494
|
+
let projDirs;
|
|
9495
|
+
try {
|
|
9496
|
+
projDirs = import_fs19.default.readdirSync(projectsDir);
|
|
9497
|
+
} catch {
|
|
9498
|
+
return result;
|
|
9499
|
+
}
|
|
9500
|
+
const ruleSources = buildRuleSources();
|
|
9501
|
+
const dedup = emptyScanDedup();
|
|
9502
|
+
for (const proj of projDirs) {
|
|
9503
|
+
processClaudeProject(
|
|
9504
|
+
proj,
|
|
9505
|
+
projectsDir,
|
|
9506
|
+
ruleSources,
|
|
9507
|
+
startDate,
|
|
9508
|
+
result,
|
|
9509
|
+
dedup,
|
|
9510
|
+
onProgress,
|
|
9511
|
+
onLine
|
|
9512
|
+
);
|
|
9513
|
+
}
|
|
9514
|
+
return result;
|
|
9515
|
+
}
|
|
9516
|
+
function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
9517
|
+
const tmpDir = import_path21.default.join(import_os18.default.homedir(), ".gemini", "tmp");
|
|
9518
|
+
const result = {
|
|
9519
|
+
filesScanned: 0,
|
|
9520
|
+
sessions: 0,
|
|
9521
|
+
totalToolCalls: 0,
|
|
9522
|
+
bashCalls: 0,
|
|
9523
|
+
findings: [],
|
|
9524
|
+
dlpFindings: [],
|
|
9313
9525
|
loopFindings: [],
|
|
9314
9526
|
totalCostUSD: 0,
|
|
9315
9527
|
firstDate: null,
|
|
9316
9528
|
lastDate: null,
|
|
9317
9529
|
sessionsWithEarlySecrets: 0
|
|
9318
9530
|
};
|
|
9531
|
+
const dedup = emptyScanDedup();
|
|
9319
9532
|
if (!import_fs19.default.existsSync(tmpDir)) return result;
|
|
9320
9533
|
let slugDirs;
|
|
9321
9534
|
try {
|
|
@@ -9372,10 +9585,9 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9372
9585
|
if (text) {
|
|
9373
9586
|
const dlpMatch = scanArgs({ text });
|
|
9374
9587
|
if (dlpMatch) {
|
|
9375
|
-
const
|
|
9376
|
-
|
|
9377
|
-
|
|
9378
|
-
if (!isDupe) {
|
|
9588
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9589
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9590
|
+
dedup.dlpKeys.add(k);
|
|
9379
9591
|
result.dlpFindings.push({
|
|
9380
9592
|
patternName: dlpMatch.patternName,
|
|
9381
9593
|
redactedSample: dlpMatch.redactedSample,
|
|
@@ -9420,10 +9632,9 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9420
9632
|
continue;
|
|
9421
9633
|
const dlpMatch = scanArgs(input);
|
|
9422
9634
|
if (dlpMatch) {
|
|
9423
|
-
const
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
if (!isDupe) {
|
|
9635
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9636
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9637
|
+
dedup.dlpKeys.add(k);
|
|
9427
9638
|
result.dlpFindings.push({
|
|
9428
9639
|
patternName: dlpMatch.patternName,
|
|
9429
9640
|
redactedSample: dlpMatch.redactedSample,
|
|
@@ -9446,7 +9657,8 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9446
9657
|
projLabel,
|
|
9447
9658
|
sessionId,
|
|
9448
9659
|
"gemini",
|
|
9449
|
-
result
|
|
9660
|
+
result,
|
|
9661
|
+
dedup
|
|
9450
9662
|
);
|
|
9451
9663
|
}
|
|
9452
9664
|
let ruleMatched = astFsMatched;
|
|
@@ -9457,10 +9669,9 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9457
9669
|
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
9458
9670
|
if (!evaluateSmartConditions(input, rule)) continue;
|
|
9459
9671
|
const inputPreview = preview(input, 120);
|
|
9460
|
-
const
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
if (!isDupe) {
|
|
9672
|
+
const k = findingKey(rule.name, inputPreview, projLabel);
|
|
9673
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9674
|
+
dedup.findingsKeys.add(k);
|
|
9464
9675
|
result.findings.push({
|
|
9465
9676
|
source,
|
|
9466
9677
|
toolName,
|
|
@@ -9488,10 +9699,9 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9488
9699
|
reason: `Shell execution of remote download detected by AST analysis (bash-safe)`
|
|
9489
9700
|
};
|
|
9490
9701
|
const inputPreview = preview(input, 120);
|
|
9491
|
-
const
|
|
9492
|
-
|
|
9493
|
-
|
|
9494
|
-
if (!isDupe) {
|
|
9702
|
+
const k = findingKey(astRule.name, inputPreview, projLabel);
|
|
9703
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9704
|
+
dedup.findingsKeys.add(k);
|
|
9495
9705
|
result.findings.push({
|
|
9496
9706
|
source: {
|
|
9497
9707
|
shieldName: "bash-safe",
|
|
@@ -9531,6 +9741,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9531
9741
|
lastDate: null,
|
|
9532
9742
|
sessionsWithEarlySecrets: 0
|
|
9533
9743
|
};
|
|
9744
|
+
const dedup = emptyScanDedup();
|
|
9534
9745
|
if (!import_fs19.default.existsSync(sessionsBase)) return result;
|
|
9535
9746
|
const jsonlFiles = [];
|
|
9536
9747
|
try {
|
|
@@ -9612,10 +9823,9 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9612
9823
|
if (text) {
|
|
9613
9824
|
const dlpMatch2 = scanArgs({ text });
|
|
9614
9825
|
if (dlpMatch2) {
|
|
9615
|
-
const
|
|
9616
|
-
|
|
9617
|
-
|
|
9618
|
-
if (!isDupe) {
|
|
9826
|
+
const k = dlpKey(dlpMatch2.patternName, dlpMatch2.redactedSample, projLabel);
|
|
9827
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9828
|
+
dedup.dlpKeys.add(k);
|
|
9619
9829
|
result.dlpFindings.push({
|
|
9620
9830
|
patternName: dlpMatch2.patternName,
|
|
9621
9831
|
redactedSample: dlpMatch2.redactedSample,
|
|
@@ -9657,10 +9867,9 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9657
9867
|
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd)) continue;
|
|
9658
9868
|
const dlpMatch = scanArgs(input);
|
|
9659
9869
|
if (dlpMatch) {
|
|
9660
|
-
const
|
|
9661
|
-
|
|
9662
|
-
|
|
9663
|
-
if (!isDupe) {
|
|
9870
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9871
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9872
|
+
dedup.dlpKeys.add(k);
|
|
9664
9873
|
result.dlpFindings.push({
|
|
9665
9874
|
patternName: dlpMatch.patternName,
|
|
9666
9875
|
redactedSample: dlpMatch.redactedSample,
|
|
@@ -9683,7 +9892,8 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9683
9892
|
projLabel,
|
|
9684
9893
|
sessionId,
|
|
9685
9894
|
"codex",
|
|
9686
|
-
result
|
|
9895
|
+
result,
|
|
9896
|
+
dedup
|
|
9687
9897
|
);
|
|
9688
9898
|
}
|
|
9689
9899
|
let ruleMatched = astFsMatched;
|
|
@@ -9695,10 +9905,9 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9695
9905
|
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
9696
9906
|
if (!evaluateSmartConditions(input, rule)) continue;
|
|
9697
9907
|
const inputPreview = preview(input, 120);
|
|
9698
|
-
const
|
|
9699
|
-
|
|
9700
|
-
|
|
9701
|
-
if (!isDupe) {
|
|
9908
|
+
const k = findingKey(rule.name, inputPreview, projLabel);
|
|
9909
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9910
|
+
dedup.findingsKeys.add(k);
|
|
9702
9911
|
result.findings.push({
|
|
9703
9912
|
source,
|
|
9704
9913
|
toolName,
|
|
@@ -9723,10 +9932,9 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9723
9932
|
reason: `Shell execution of remote download detected by AST analysis (bash-safe)`
|
|
9724
9933
|
};
|
|
9725
9934
|
const inputPreview = preview(input, 120);
|
|
9726
|
-
const
|
|
9727
|
-
|
|
9728
|
-
|
|
9729
|
-
if (!isDupe) {
|
|
9935
|
+
const k = findingKey(astRule.name, inputPreview, projLabel);
|
|
9936
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9937
|
+
dedup.findingsKeys.add(k);
|
|
9730
9938
|
result.findings.push({
|
|
9731
9939
|
source: {
|
|
9732
9940
|
shieldName: "bash-safe",
|
|
@@ -9757,6 +9965,7 @@ function scanShellConfig() {
|
|
|
9757
9965
|
(f) => import_path21.default.join(home, f)
|
|
9758
9966
|
);
|
|
9759
9967
|
const findings = [];
|
|
9968
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9760
9969
|
for (const filePath of configFiles) {
|
|
9761
9970
|
if (!import_fs19.default.existsSync(filePath)) continue;
|
|
9762
9971
|
let lines;
|
|
@@ -9771,10 +9980,9 @@ function scanShellConfig() {
|
|
|
9771
9980
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
9772
9981
|
const dlpMatch = scanArgs({ text: trimmed });
|
|
9773
9982
|
if (!dlpMatch) continue;
|
|
9774
|
-
const
|
|
9775
|
-
|
|
9776
|
-
|
|
9777
|
-
if (!isDupe) {
|
|
9983
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, shortPath);
|
|
9984
|
+
if (!seen.has(k)) {
|
|
9985
|
+
seen.add(k);
|
|
9778
9986
|
findings.push({
|
|
9779
9987
|
patternName: dlpMatch.patternName,
|
|
9780
9988
|
redactedSample: dlpMatch.redactedSample,
|
|
@@ -10033,6 +10241,263 @@ function renderNarrativeScorecard(input) {
|
|
|
10033
10241
|
console.log(import_chalk5.default.dim("\u2192 github.com/node9-ai/node9-proxy"));
|
|
10034
10242
|
console.log("");
|
|
10035
10243
|
}
|
|
10244
|
+
function mkLine(...parts) {
|
|
10245
|
+
let rendered = "";
|
|
10246
|
+
let width = 0;
|
|
10247
|
+
for (const [text, fmt] of parts) {
|
|
10248
|
+
rendered += fmt ? fmt(text) : text;
|
|
10249
|
+
width += text.length;
|
|
10250
|
+
}
|
|
10251
|
+
return { rendered, width };
|
|
10252
|
+
}
|
|
10253
|
+
function shortRule(name, width) {
|
|
10254
|
+
const stripped = name.replace(/^shield:[^:]+:/, "");
|
|
10255
|
+
if (stripped.length <= width) return stripped.padEnd(width);
|
|
10256
|
+
return stripped.slice(0, width - 1) + "\u2026";
|
|
10257
|
+
}
|
|
10258
|
+
function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
|
|
10259
|
+
const { scan, summary, blast, blastExposures, blockedCount, reviewCount } = input;
|
|
10260
|
+
const topLines = [];
|
|
10261
|
+
if (scan.dlpFindings.length > 0) {
|
|
10262
|
+
const latest = scan.dlpFindings[0];
|
|
10263
|
+
const rel = relativeDate(latest.timestamp, now);
|
|
10264
|
+
const noun = `credential leak${scan.dlpFindings.length !== 1 ? "s" : ""}`;
|
|
10265
|
+
topLines.push(
|
|
10266
|
+
mkLine(
|
|
10267
|
+
["\u{1F6A8} ", import_chalk5.default.red],
|
|
10268
|
+
[`${scan.dlpFindings.length} ${noun} in tool input `, import_chalk5.default.bold],
|
|
10269
|
+
[`(latest: ${rel} ago, ${latest.patternName})`, import_chalk5.default.dim]
|
|
10270
|
+
)
|
|
10271
|
+
);
|
|
10272
|
+
}
|
|
10273
|
+
if (blockedCount > 0) {
|
|
10274
|
+
const topBlocked = topRulesByVerdict(summary.sections, "block", 2).map(
|
|
10275
|
+
(r) => r.count > 1 ? `${shortRule(r.name, 20).trimEnd()} \xD7${r.count}` : shortRule(r.name, 20).trimEnd()
|
|
10276
|
+
).join(", ");
|
|
10277
|
+
topLines.push(
|
|
10278
|
+
mkLine(
|
|
10279
|
+
["\u{1F6D1} ", import_chalk5.default.red],
|
|
10280
|
+
[`${blockedCount} ops node9 would have blocked `, import_chalk5.default.bold],
|
|
10281
|
+
[`(${topBlocked})`, import_chalk5.default.dim]
|
|
10282
|
+
)
|
|
10283
|
+
);
|
|
10284
|
+
}
|
|
10285
|
+
if (scan.loopFindings.length > 0) {
|
|
10286
|
+
const { wastePct } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
|
|
10287
|
+
const byTool = /* @__PURE__ */ new Map();
|
|
10288
|
+
for (const f of scan.loopFindings) {
|
|
10289
|
+
byTool.set(f.toolName, (byTool.get(f.toolName) ?? 0) + Math.max(0, f.count - 1));
|
|
10290
|
+
}
|
|
10291
|
+
const top = [...byTool.entries()].sort((a, b) => b[1] - a[1])[0];
|
|
10292
|
+
const wasteSuffix = wastePct > 0 ? `, ${wastePct}% wasted` : "";
|
|
10293
|
+
const detail = top ? `(${top[0]} dominates${wasteSuffix})` : "";
|
|
10294
|
+
topLines.push(
|
|
10295
|
+
mkLine(
|
|
10296
|
+
["\u{1F501} ", import_chalk5.default.yellow],
|
|
10297
|
+
[`${scan.loopFindings.length} agent loops detected `, import_chalk5.default.bold],
|
|
10298
|
+
[detail, import_chalk5.default.dim]
|
|
10299
|
+
)
|
|
10300
|
+
);
|
|
10301
|
+
}
|
|
10302
|
+
if (blastExposures > 0) {
|
|
10303
|
+
const exposed2 = Math.max(0, 100 - blast.score);
|
|
10304
|
+
const pjDiscount = PROTECTIVE_SHIELD_DISCOUNTS["project-jail"] ?? 0;
|
|
10305
|
+
const pjBonus = Math.round(exposed2 * pjDiscount);
|
|
10306
|
+
const cta = pjBonus > 0 ? ` \u2192 enable project-jail (+${pjBonus} pts)` : "";
|
|
10307
|
+
topLines.push(
|
|
10308
|
+
mkLine(
|
|
10309
|
+
["\u{1F52D} ", import_chalk5.default.red],
|
|
10310
|
+
[`${blastExposures} secrets reachable on disk`, import_chalk5.default.bold],
|
|
10311
|
+
[cta, import_chalk5.default.dim]
|
|
10312
|
+
)
|
|
10313
|
+
);
|
|
10314
|
+
}
|
|
10315
|
+
if (topLines.length > 0) {
|
|
10316
|
+
for (const ln of boxPanel("TOP FINDINGS", topLines)) console.log(" " + ln);
|
|
10317
|
+
console.log("");
|
|
10318
|
+
}
|
|
10319
|
+
if (summary.leaks.length > 0) {
|
|
10320
|
+
const leakLines = [];
|
|
10321
|
+
for (const leak of summary.leaks.slice(0, 5)) {
|
|
10322
|
+
const rel = relativeDate(leak.timestamp, now);
|
|
10323
|
+
leakLines.push(
|
|
10324
|
+
mkLine(
|
|
10325
|
+
[rel.padStart(4) + " ", import_chalk5.default.dim],
|
|
10326
|
+
[leak.patternName.padEnd(14), import_chalk5.default.red.bold],
|
|
10327
|
+
[" "],
|
|
10328
|
+
[leak.redactedSample.padEnd(20), import_chalk5.default.red],
|
|
10329
|
+
[" "],
|
|
10330
|
+
[`[${leak.toolName}]`.padEnd(15), import_chalk5.default.dim],
|
|
10331
|
+
[" "],
|
|
10332
|
+
[leak.agent, import_chalk5.default.dim]
|
|
10333
|
+
)
|
|
10334
|
+
);
|
|
10335
|
+
}
|
|
10336
|
+
const remaining = summary.leaks.length - 5;
|
|
10337
|
+
if (remaining > 0) {
|
|
10338
|
+
leakLines.push(mkLine([`\u2026 +${remaining} more`, import_chalk5.default.dim]));
|
|
10339
|
+
}
|
|
10340
|
+
const title = `LEAKS \xB7 ${summary.leaks.length} secret${summary.leaks.length !== 1 ? "s" : ""} in plain text`;
|
|
10341
|
+
for (const ln of boxPanel(title, leakLines)) console.log(" " + ln);
|
|
10342
|
+
console.log("");
|
|
10343
|
+
}
|
|
10344
|
+
if (blockedCount > 0) {
|
|
10345
|
+
const blockedLines = [];
|
|
10346
|
+
const ruleEntries = topRulesByVerdict(summary.sections, "block", 12);
|
|
10347
|
+
for (const r of ruleEntries) {
|
|
10348
|
+
const origin = originForRule(r.name, summary.sections);
|
|
10349
|
+
blockedLines.push(
|
|
10350
|
+
mkLine(
|
|
10351
|
+
["\u2717 ", import_chalk5.default.red],
|
|
10352
|
+
[shortRule(r.name, 24), import_chalk5.default.bold],
|
|
10353
|
+
[" \xD7" + String(r.count).padEnd(4), import_chalk5.default.bold],
|
|
10354
|
+
[" "],
|
|
10355
|
+
[origin, import_chalk5.default.dim]
|
|
10356
|
+
)
|
|
10357
|
+
);
|
|
10358
|
+
}
|
|
10359
|
+
const title = `BLOCKED \xB7 ${blockedCount} ops node9 would have stopped`;
|
|
10360
|
+
for (const ln of boxPanel(title, blockedLines)) console.log(" " + ln);
|
|
10361
|
+
console.log("");
|
|
10362
|
+
}
|
|
10363
|
+
if (reviewCount > 0) {
|
|
10364
|
+
const reviewLines = [];
|
|
10365
|
+
const ruleEntries = topRulesByVerdict(summary.sections, "review", 12);
|
|
10366
|
+
for (const r of ruleEntries) {
|
|
10367
|
+
const origin = originForRule(r.name, summary.sections);
|
|
10368
|
+
reviewLines.push(
|
|
10369
|
+
mkLine(
|
|
10370
|
+
["\u{1F441} ", import_chalk5.default.yellow],
|
|
10371
|
+
[shortRule(r.name, 24), import_chalk5.default.bold],
|
|
10372
|
+
[" \xD7" + String(r.count).padEnd(4), import_chalk5.default.bold],
|
|
10373
|
+
[" "],
|
|
10374
|
+
[origin, import_chalk5.default.dim]
|
|
10375
|
+
)
|
|
10376
|
+
);
|
|
10377
|
+
}
|
|
10378
|
+
const title = `REVIEW QUEUE \xB7 ${reviewCount} ops flagged for approval`;
|
|
10379
|
+
for (const ln of boxPanel(title, reviewLines)) console.log(" " + ln);
|
|
10380
|
+
console.log("");
|
|
10381
|
+
}
|
|
10382
|
+
if (scan.loopFindings.length > 0) {
|
|
10383
|
+
const { wastePct } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
|
|
10384
|
+
const byTool = /* @__PURE__ */ new Map();
|
|
10385
|
+
let totalRepeats = 0;
|
|
10386
|
+
for (const f of scan.loopFindings) {
|
|
10387
|
+
const repeats = Math.max(0, f.count - 1);
|
|
10388
|
+
byTool.set(f.toolName, (byTool.get(f.toolName) ?? 0) + repeats);
|
|
10389
|
+
totalRepeats += repeats;
|
|
10390
|
+
}
|
|
10391
|
+
const toolEntries = [...byTool.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
10392
|
+
const loopLines = [];
|
|
10393
|
+
for (const [tool, repeats] of toolEntries) {
|
|
10394
|
+
const pct = totalRepeats > 0 ? Math.round(repeats / totalRepeats * 100) : 0;
|
|
10395
|
+
loopLines.push(
|
|
10396
|
+
mkLine(
|
|
10397
|
+
[tool.padEnd(10), import_chalk5.default.bold],
|
|
10398
|
+
[`\xD7${num(repeats)} repeats`.padEnd(16)],
|
|
10399
|
+
[`(${pct}%)`, import_chalk5.default.dim]
|
|
10400
|
+
)
|
|
10401
|
+
);
|
|
10402
|
+
}
|
|
10403
|
+
const topStuck = [...scan.loopFindings].sort((a, b) => b.count - a.count).slice(0, 3);
|
|
10404
|
+
if (topStuck.length > 0) {
|
|
10405
|
+
loopLines.push(mkLine([""]));
|
|
10406
|
+
loopLines.push(mkLine(["Top stuck patterns:", import_chalk5.default.dim]));
|
|
10407
|
+
for (const f of topStuck) {
|
|
10408
|
+
const raw = f.commandPreview || f.toolName;
|
|
10409
|
+
const target = raw.length > 60 ? "\u2026" + raw.slice(raw.length - 59) : raw.padEnd(60);
|
|
10410
|
+
loopLines.push(mkLine([`\xD7${num(f.count).padEnd(4)} `, import_chalk5.default.bold], [target, import_chalk5.default.dim]));
|
|
10411
|
+
}
|
|
10412
|
+
}
|
|
10413
|
+
const wasteSuffix = wastePct > 0 ? ` \xB7 ${wastePct}% wasted` : "";
|
|
10414
|
+
const title = `AGENT LOOPS \xB7 ${scan.loopFindings.length} repeated patterns${wasteSuffix}`;
|
|
10415
|
+
for (const ln of boxPanel(title, loopLines)) console.log(" " + ln);
|
|
10416
|
+
console.log("");
|
|
10417
|
+
}
|
|
10418
|
+
if (blast.reachable.length > 0 || blast.envFindings.length > 0) {
|
|
10419
|
+
const blastLines = [];
|
|
10420
|
+
const DESC_W = 33;
|
|
10421
|
+
for (const r of blast.reachable.slice(0, 8)) {
|
|
10422
|
+
const trimmed = r.description.split(" \u2014 ")[0].split(/—|--/)[0].trim();
|
|
10423
|
+
const desc = trimmed.length > DESC_W ? trimmed.slice(0, DESC_W - 1) + "\u2026" : trimmed;
|
|
10424
|
+
blastLines.push(mkLine(["\u2717 ", import_chalk5.default.red], [r.label.padEnd(36)], [desc, import_chalk5.default.dim]));
|
|
10425
|
+
}
|
|
10426
|
+
for (const e of blast.envFindings.slice(0, 3)) {
|
|
10427
|
+
blastLines.push(
|
|
10428
|
+
mkLine(["\u26A0 ", import_chalk5.default.yellow], [`${e.key} `], [`(${e.patternName})`, import_chalk5.default.dim])
|
|
10429
|
+
);
|
|
10430
|
+
}
|
|
10431
|
+
const totalExposed = blast.reachable.length + blast.envFindings.length;
|
|
10432
|
+
if (totalExposed > 8) {
|
|
10433
|
+
blastLines.push(mkLine([`\u2026 +${totalExposed - 8} more`, import_chalk5.default.dim]));
|
|
10434
|
+
}
|
|
10435
|
+
const title = `BLAST RADIUS \xB7 ${totalExposed} path${totalExposed !== 1 ? "s" : ""} reachable right now`;
|
|
10436
|
+
for (const ln of boxPanel(title, blastLines)) console.log(" " + ln);
|
|
10437
|
+
console.log("");
|
|
10438
|
+
}
|
|
10439
|
+
const shieldImpacts = rollupByShield(summary.sections);
|
|
10440
|
+
const exposed = Math.max(0, 100 - blast.score);
|
|
10441
|
+
const shieldLines = [];
|
|
10442
|
+
const ranked = [...shieldImpacts].sort((a, b) => {
|
|
10443
|
+
const aDiscount = PROTECTIVE_SHIELD_DISCOUNTS[a.shieldName] ?? 0;
|
|
10444
|
+
const bDiscount = PROTECTIVE_SHIELD_DISCOUNTS[b.shieldName] ?? 0;
|
|
10445
|
+
if (aDiscount !== bDiscount) return bDiscount - aDiscount;
|
|
10446
|
+
return b.totalCatches - a.totalCatches;
|
|
10447
|
+
});
|
|
10448
|
+
for (const impact of ranked) {
|
|
10449
|
+
if (impact.totalCatches === 0) continue;
|
|
10450
|
+
const discount = PROTECTIVE_SHIELD_DISCOUNTS[impact.shieldName] ?? 0;
|
|
10451
|
+
const bonus = Math.round(exposed * discount);
|
|
10452
|
+
const icon = discount > 0 ? "\u{1F6E1} " : "\u2610 ";
|
|
10453
|
+
const wouldCatch = `would catch ${impact.totalCatches} op${impact.totalCatches !== 1 ? "s" : ""}`;
|
|
10454
|
+
const deltaSuffix = bonus > 0 ? ` \u2192 +${bonus} pts (${blast.score} \u2192 ${blast.score + bonus})` : "";
|
|
10455
|
+
shieldLines.push(
|
|
10456
|
+
mkLine(
|
|
10457
|
+
[icon, discount > 0 ? import_chalk5.default.cyan : import_chalk5.default.dim],
|
|
10458
|
+
[impact.shieldName.padEnd(14), import_chalk5.default.bold],
|
|
10459
|
+
[wouldCatch.padEnd(22), import_chalk5.default.dim],
|
|
10460
|
+
[deltaSuffix, bonus > 0 ? import_chalk5.default.green.bold : import_chalk5.default.dim]
|
|
10461
|
+
)
|
|
10462
|
+
);
|
|
10463
|
+
if (impact.topRuleLabels.length > 0) {
|
|
10464
|
+
const rules = impact.topRuleLabels.join(", ");
|
|
10465
|
+
shieldLines.push(mkLine([" ", import_chalk5.default.dim], [rules, import_chalk5.default.dim]));
|
|
10466
|
+
}
|
|
10467
|
+
}
|
|
10468
|
+
const hitShieldSet = new Set(
|
|
10469
|
+
shieldImpacts.filter((i) => i.totalCatches > 0).map((i) => i.shieldName)
|
|
10470
|
+
);
|
|
10471
|
+
const zeroHitBuiltins = Object.keys(SHIELDS).filter((name) => !hitShieldSet.has(name)).sort();
|
|
10472
|
+
if (zeroHitBuiltins.length > 0) {
|
|
10473
|
+
shieldLines.push(mkLine([""]));
|
|
10474
|
+
shieldLines.push(mkLine([zeroHitBuiltins.join(" \xB7 "), import_chalk5.default.dim]));
|
|
10475
|
+
shieldLines.push(mkLine([" no hits in your history \u2014 install proactively", import_chalk5.default.dim]));
|
|
10476
|
+
}
|
|
10477
|
+
const topRec = ranked.find(
|
|
10478
|
+
(r) => r.totalCatches > 0 && (PROTECTIVE_SHIELD_DISCOUNTS[r.shieldName] ?? 0) > 0
|
|
10479
|
+
);
|
|
10480
|
+
if (topRec) {
|
|
10481
|
+
const bonus = Math.round(exposed * (PROTECTIVE_SHIELD_DISCOUNTS[topRec.shieldName] ?? 0));
|
|
10482
|
+
const cta = `\u2192 node9 shield enable ${topRec.shieldName} (start here \u2014 +${bonus} pts)`;
|
|
10483
|
+
shieldLines.push(mkLine([""]));
|
|
10484
|
+
shieldLines.push(mkLine([cta, import_chalk5.default.cyan]));
|
|
10485
|
+
}
|
|
10486
|
+
if (shieldLines.length > 0) {
|
|
10487
|
+
const title = "SHIELDS \xB7 install node9 + enable these to catch what we found";
|
|
10488
|
+
for (const ln of boxPanel(title, shieldLines)) console.log(" " + ln);
|
|
10489
|
+
console.log("");
|
|
10490
|
+
}
|
|
10491
|
+
}
|
|
10492
|
+
function originForRule(ruleName, sections) {
|
|
10493
|
+
for (const section of sections) {
|
|
10494
|
+
if (section.rules.some((r) => r.name === ruleName)) {
|
|
10495
|
+
if (section.sourceType === "default") return "default";
|
|
10496
|
+
if (section.sourceType === "shield") return `needs shield:${section.shieldKey ?? section.id}`;
|
|
10497
|
+
}
|
|
10498
|
+
}
|
|
10499
|
+
return "";
|
|
10500
|
+
}
|
|
10036
10501
|
function registerScanCommand(program2) {
|
|
10037
10502
|
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(
|
|
10038
10503
|
"--json",
|
|
@@ -10264,7 +10729,7 @@ function registerScanCommand(program2) {
|
|
|
10264
10729
|
" " + import_chalk5.default.dim("AI spend ") + import_chalk5.default.bold(fmtCost(scan.totalCostUSD)) + (summary.loopWastedUSD > 0 ? import_chalk5.default.dim(" \xB7 wasted on loops ") + import_chalk5.default.yellow("~" + fmtCost(summary.loopWastedUSD)) : "")
|
|
10265
10730
|
);
|
|
10266
10731
|
}
|
|
10267
|
-
if (scan.dlpFindings.length > 0 && scan.sessionsWithEarlySecrets > 0) {
|
|
10732
|
+
if (drillDown && scan.dlpFindings.length > 0 && scan.sessionsWithEarlySecrets > 0) {
|
|
10268
10733
|
console.log(
|
|
10269
10734
|
" " + import_chalk5.default.dim(
|
|
10270
10735
|
`${scan.sessionsWithEarlySecrets} session${scan.sessionsWithEarlySecrets !== 1 ? "s" : ""} loaded secrets before first edit`
|
|
@@ -10272,6 +10737,26 @@ function registerScanCommand(program2) {
|
|
|
10272
10737
|
);
|
|
10273
10738
|
}
|
|
10274
10739
|
console.log("");
|
|
10740
|
+
if (!drillDown) {
|
|
10741
|
+
renderPanelScorecard({
|
|
10742
|
+
scan,
|
|
10743
|
+
summary,
|
|
10744
|
+
blast,
|
|
10745
|
+
blastExposures,
|
|
10746
|
+
blockedCount,
|
|
10747
|
+
reviewCount
|
|
10748
|
+
});
|
|
10749
|
+
const cta = isWired ? "\u2705 node9 is active" : "\u2192 install node9 to enable protection";
|
|
10750
|
+
console.log(" " + import_chalk5.default.green(cta));
|
|
10751
|
+
console.log(
|
|
10752
|
+
" " + import_chalk5.default.dim("\u2192 ") + import_chalk5.default.cyan("node9 monitor") + import_chalk5.default.dim(" live dashboard")
|
|
10753
|
+
);
|
|
10754
|
+
console.log(
|
|
10755
|
+
" " + import_chalk5.default.dim("\u2192 ") + import_chalk5.default.cyan("node9 scan --drill-down") + import_chalk5.default.dim(" full commands + session IDs")
|
|
10756
|
+
);
|
|
10757
|
+
console.log("");
|
|
10758
|
+
return;
|
|
10759
|
+
}
|
|
10275
10760
|
if (scan.dlpFindings.length > 0) {
|
|
10276
10761
|
console.log(" " + import_chalk5.default.dim("\u2500".repeat(70)));
|
|
10277
10762
|
console.log(
|
|
@@ -10460,7 +10945,7 @@ function registerScanCommand(program2) {
|
|
|
10460
10945
|
}
|
|
10461
10946
|
);
|
|
10462
10947
|
}
|
|
10463
|
-
var import_chalk5, import_fs19, import_path21, import_os18, 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,
|
|
10948
|
+
var import_chalk5, import_fs19, import_path21, import_os18, 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;
|
|
10464
10949
|
var init_scan = __esm({
|
|
10465
10950
|
"src/cli/commands/scan.ts"() {
|
|
10466
10951
|
"use strict";
|
|
@@ -10478,6 +10963,7 @@ var init_scan = __esm({
|
|
|
10478
10963
|
init_setup();
|
|
10479
10964
|
init_blast();
|
|
10480
10965
|
init_scan_derive();
|
|
10966
|
+
init_protection();
|
|
10481
10967
|
init_scan_json();
|
|
10482
10968
|
init_scan_history();
|
|
10483
10969
|
CLAUDE_PRICING = {
|
|
@@ -10560,9 +11046,6 @@ var init_scan = __esm({
|
|
|
10560
11046
|
STUCK_TOOLS_LIMIT = 3;
|
|
10561
11047
|
RECURRING_SESSION_THRESHOLD = 3;
|
|
10562
11048
|
STALE_AGE_DAYS = 30;
|
|
10563
|
-
DEFAULT_RULE_NAMES = new Set(
|
|
10564
|
-
DEFAULT_CONFIG.policy.smartRules.map((r) => r.name).filter(Boolean)
|
|
10565
|
-
);
|
|
10566
11049
|
classifyRuleSeverity2 = classifyRuleSeverity;
|
|
10567
11050
|
narrativeRuleLabel2 = narrativeRuleLabel;
|
|
10568
11051
|
}
|
|
@@ -13135,8 +13618,15 @@ var tail_exports = {};
|
|
|
13135
13618
|
__export(tail_exports, {
|
|
13136
13619
|
agentLabel: () => agentLabel,
|
|
13137
13620
|
sessionTag: () => sessionTag,
|
|
13621
|
+
shortenPathSummary: () => shortenPathSummary,
|
|
13138
13622
|
startTail: () => startTail
|
|
13139
13623
|
});
|
|
13624
|
+
function shortenPathSummary(s) {
|
|
13625
|
+
if (!s || !s.startsWith("/")) return s;
|
|
13626
|
+
const parts = s.split("/").filter(Boolean);
|
|
13627
|
+
if (parts.length <= 2) return s;
|
|
13628
|
+
return `\u2026/${parts.slice(-2).join("/")}`;
|
|
13629
|
+
}
|
|
13140
13630
|
function getIcon(tool) {
|
|
13141
13631
|
const t = tool.toLowerCase();
|
|
13142
13632
|
for (const [k, v] of Object.entries(ICONS)) {
|
|
@@ -13890,7 +14380,8 @@ async function startTail(options = {}) {
|
|
|
13890
14380
|
if (event === "snapshot") {
|
|
13891
14381
|
const time = new Date(data.ts).toLocaleTimeString([], { hour12: false });
|
|
13892
14382
|
const hash = data.hash ?? "";
|
|
13893
|
-
const
|
|
14383
|
+
const rawSummary = data.argsSummary ?? data.tool;
|
|
14384
|
+
const summary = shortenPathSummary(rawSummary);
|
|
13894
14385
|
const fileCount = data.fileCount ?? 0;
|
|
13895
14386
|
const files = fileCount > 0 ? import_chalk30.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
|
|
13896
14387
|
process.stdout.write(
|
|
@@ -16279,63 +16770,13 @@ function registerAuditCommand(program2) {
|
|
|
16279
16770
|
|
|
16280
16771
|
// src/cli/commands/report.ts
|
|
16281
16772
|
var import_chalk13 = __toESM(require("chalk"));
|
|
16773
|
+
|
|
16774
|
+
// src/cli/aggregate/report-audit.ts
|
|
16282
16775
|
var import_fs35 = __toESM(require("fs"));
|
|
16283
|
-
var import_path36 = __toESM(require("path"));
|
|
16284
16776
|
var import_os31 = __toESM(require("os"));
|
|
16285
|
-
|
|
16286
|
-
|
|
16287
|
-
|
|
16288
|
-
const totalBlocked = input.timedOut + input.hardBlocked + input.dlpBlocked + input.loopHits + input.userDenied;
|
|
16289
|
-
const blockRate = input.total > 0 ? totalBlocked / input.total : 0;
|
|
16290
|
-
const deltaPct = input.priorBlockRate === null ? null : Math.round((blockRate - input.priorBlockRate) * 100);
|
|
16291
|
-
return {
|
|
16292
|
-
schemaVersion: 1,
|
|
16293
|
-
generatedAt: input.generatedAt,
|
|
16294
|
-
period: input.period,
|
|
16295
|
-
range: { start: input.start.toISOString(), end: input.end.toISOString() },
|
|
16296
|
-
excludedTests: input.excludedTests,
|
|
16297
|
-
totals: {
|
|
16298
|
-
events: input.total,
|
|
16299
|
-
blocked: totalBlocked,
|
|
16300
|
-
blockRate,
|
|
16301
|
-
userApproved: input.userApproved,
|
|
16302
|
-
userDenied: input.userDenied,
|
|
16303
|
-
timedOut: input.timedOut,
|
|
16304
|
-
hardBlocked: input.hardBlocked,
|
|
16305
|
-
dlpBlocked: input.dlpBlocked,
|
|
16306
|
-
observeDlp: input.observeDlp,
|
|
16307
|
-
loopHits: input.loopHits,
|
|
16308
|
-
unackedDlp: input.unackedDlp
|
|
16309
|
-
},
|
|
16310
|
-
tests: {
|
|
16311
|
-
passes: input.testPasses,
|
|
16312
|
-
fails: input.testFails
|
|
16313
|
-
},
|
|
16314
|
-
cost: {
|
|
16315
|
-
totalUSD: input.cost.claudeUSD + input.cost.codexUSD,
|
|
16316
|
-
claudeUSD: input.cost.claudeUSD,
|
|
16317
|
-
codexUSD: input.cost.codexUSD,
|
|
16318
|
-
inputTokens: input.cost.inputTokens,
|
|
16319
|
-
outputTokens: input.cost.outputTokens,
|
|
16320
|
-
cacheWriteTokens: input.cost.cacheWriteTokens,
|
|
16321
|
-
cacheReadTokens: input.cost.cacheReadTokens,
|
|
16322
|
-
byDay: [...input.cost.byDay.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([day, usd]) => ({ day, usd })),
|
|
16323
|
-
byModel: [...input.cost.byModel.entries()].sort((a, b) => b[1] - a[1]).map(([model, usd]) => ({ model, usd }))
|
|
16324
|
-
},
|
|
16325
|
-
byTool: [...input.toolMap.entries()].sort((a, b) => b[1].calls - a[1].calls).map(([tool, v]) => ({ tool, calls: v.calls, blocked: v.blocked })),
|
|
16326
|
-
byBlock: [...input.blockMap.entries()].sort((a, b) => b[1] - a[1]).map(([rule, count]) => ({ rule, count })),
|
|
16327
|
-
byAgent: [...input.agentMap.entries()].sort((a, b) => b[1] - a[1]).map(([agent, calls]) => ({ agent, calls })),
|
|
16328
|
-
byMcp: [...input.mcpMap.entries()].sort((a, b) => b[1] - a[1]).map(([server, calls]) => ({ server, calls })),
|
|
16329
|
-
byDay: [...input.dailyMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([day, v]) => ({ day, calls: v.calls, blocked: v.blocked })),
|
|
16330
|
-
byHour: [...input.hourMap.entries()].sort((a, b) => a[0] - b[0]).map(([hour, calls]) => ({ hour, calls })),
|
|
16331
|
-
trend: {
|
|
16332
|
-
priorBlockRate: input.priorBlockRate,
|
|
16333
|
-
deltaPct
|
|
16334
|
-
}
|
|
16335
|
-
};
|
|
16336
|
-
}
|
|
16337
|
-
|
|
16338
|
-
// src/cli/commands/report.ts
|
|
16777
|
+
var import_path36 = __toESM(require("path"));
|
|
16778
|
+
init_costSync();
|
|
16779
|
+
init_litellm();
|
|
16339
16780
|
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;
|
|
16340
16781
|
function buildTestTimestamps(allEntries) {
|
|
16341
16782
|
const testTs = /* @__PURE__ */ new Set();
|
|
@@ -16360,8 +16801,7 @@ function isTestEntry(entry, testTs) {
|
|
|
16360
16801
|
}
|
|
16361
16802
|
return false;
|
|
16362
16803
|
}
|
|
16363
|
-
function getDateRange(period) {
|
|
16364
|
-
const now = /* @__PURE__ */ new Date();
|
|
16804
|
+
function getDateRange(period, now) {
|
|
16365
16805
|
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
16366
16806
|
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
|
|
16367
16807
|
switch (period) {
|
|
@@ -16377,6 +16817,11 @@ function getDateRange(period) {
|
|
|
16377
16817
|
s.setDate(s.getDate() - 29);
|
|
16378
16818
|
return { start: s, end };
|
|
16379
16819
|
}
|
|
16820
|
+
case "90d": {
|
|
16821
|
+
const s = new Date(todayStart);
|
|
16822
|
+
s.setDate(s.getDate() - 89);
|
|
16823
|
+
return { start: s, end };
|
|
16824
|
+
}
|
|
16380
16825
|
case "month":
|
|
16381
16826
|
return { start: new Date(now.getFullYear(), now.getMonth(), 1), end };
|
|
16382
16827
|
}
|
|
@@ -16399,40 +16844,6 @@ function isAllow(decision) {
|
|
|
16399
16844
|
function isDlp(checkedBy) {
|
|
16400
16845
|
return !!checkedBy?.includes("dlp");
|
|
16401
16846
|
}
|
|
16402
|
-
var BLOCK_REASON_LABELS = {
|
|
16403
|
-
timeout: "Popup timeout",
|
|
16404
|
-
"smart-rule-block": "Smart rule",
|
|
16405
|
-
"observe-mode-dlp-would-block": "DLP (observe)",
|
|
16406
|
-
"persistent-deny": "Persistent deny",
|
|
16407
|
-
"local-decision": "User denied",
|
|
16408
|
-
"dlp-block": "DLP block",
|
|
16409
|
-
"loop-detected": "Loop detected"
|
|
16410
|
-
};
|
|
16411
|
-
function humanBlockReason(reason) {
|
|
16412
|
-
return BLOCK_REASON_LABELS[reason] ?? reason;
|
|
16413
|
-
}
|
|
16414
|
-
function barStr(value, max, width) {
|
|
16415
|
-
if (max === 0 || width <= 0) return "\u2591".repeat(width);
|
|
16416
|
-
const filled = Math.max(1, Math.round(value / max * width));
|
|
16417
|
-
return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
16418
|
-
}
|
|
16419
|
-
function colorBar(value, max, width) {
|
|
16420
|
-
const s = barStr(value, max, width);
|
|
16421
|
-
const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
|
|
16422
|
-
return import_chalk13.default.cyan(s.slice(0, filled)) + import_chalk13.default.dim(s.slice(filled));
|
|
16423
|
-
}
|
|
16424
|
-
function fmtDate(d) {
|
|
16425
|
-
const date = typeof d === "string" ? /* @__PURE__ */ new Date(d + "T12:00:00") : d;
|
|
16426
|
-
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
16427
|
-
}
|
|
16428
|
-
function num2(n) {
|
|
16429
|
-
return n.toLocaleString();
|
|
16430
|
-
}
|
|
16431
|
-
function fmtCost2(usd) {
|
|
16432
|
-
if (usd < 1e-3) return "< $0.001";
|
|
16433
|
-
if (usd < 1) return "$" + usd.toFixed(4);
|
|
16434
|
-
return "$" + usd.toFixed(2);
|
|
16435
|
-
}
|
|
16436
16847
|
var CLAUDE_PRICING2 = {
|
|
16437
16848
|
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
16438
16849
|
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
@@ -16452,90 +16863,160 @@ function claudeModelPrice2(model) {
|
|
|
16452
16863
|
}
|
|
16453
16864
|
return null;
|
|
16454
16865
|
}
|
|
16455
|
-
function
|
|
16456
|
-
|
|
16866
|
+
function emptyClaudeCostAccumulator() {
|
|
16867
|
+
return {
|
|
16457
16868
|
total: 0,
|
|
16458
|
-
byDay: /* @__PURE__ */ new Map(),
|
|
16459
|
-
byModel: /* @__PURE__ */ new Map(),
|
|
16460
16869
|
inputTokens: 0,
|
|
16461
16870
|
outputTokens: 0,
|
|
16462
16871
|
cacheWriteTokens: 0,
|
|
16463
|
-
cacheReadTokens: 0
|
|
16872
|
+
cacheReadTokens: 0,
|
|
16873
|
+
byDay: /* @__PURE__ */ new Map(),
|
|
16874
|
+
byModel: /* @__PURE__ */ new Map(),
|
|
16875
|
+
byProject: /* @__PURE__ */ new Map()
|
|
16464
16876
|
};
|
|
16465
|
-
|
|
16466
|
-
|
|
16877
|
+
}
|
|
16878
|
+
function freezeClaudeCost(acc) {
|
|
16879
|
+
return {
|
|
16880
|
+
total: acc.total,
|
|
16881
|
+
byDay: acc.byDay,
|
|
16882
|
+
byModel: acc.byModel,
|
|
16883
|
+
byProject: acc.byProject,
|
|
16884
|
+
inputTokens: acc.inputTokens,
|
|
16885
|
+
outputTokens: acc.outputTokens,
|
|
16886
|
+
cacheWriteTokens: acc.cacheWriteTokens,
|
|
16887
|
+
cacheReadTokens: acc.cacheReadTokens
|
|
16888
|
+
};
|
|
16889
|
+
}
|
|
16890
|
+
function processClaudeCostProject(proj, projectsDir, start, end, acc) {
|
|
16891
|
+
const projPath = import_path36.default.join(projectsDir, proj);
|
|
16892
|
+
let files;
|
|
16893
|
+
try {
|
|
16894
|
+
const stat = import_fs35.default.statSync(projPath);
|
|
16895
|
+
if (!stat.isDirectory()) return;
|
|
16896
|
+
files = import_fs35.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
16897
|
+
} catch {
|
|
16898
|
+
return;
|
|
16899
|
+
}
|
|
16900
|
+
const startMs = start.getTime();
|
|
16901
|
+
for (const file of files) {
|
|
16902
|
+
const filePath = import_path36.default.join(projPath, file);
|
|
16903
|
+
try {
|
|
16904
|
+
if (import_fs35.default.statSync(filePath).mtimeMs < startMs) continue;
|
|
16905
|
+
} catch {
|
|
16906
|
+
continue;
|
|
16907
|
+
}
|
|
16908
|
+
try {
|
|
16909
|
+
const raw = import_fs35.default.readFileSync(filePath, "utf-8");
|
|
16910
|
+
for (const line of raw.split("\n")) {
|
|
16911
|
+
if (!line.trim()) continue;
|
|
16912
|
+
let entry;
|
|
16913
|
+
try {
|
|
16914
|
+
entry = JSON.parse(line);
|
|
16915
|
+
} catch {
|
|
16916
|
+
continue;
|
|
16917
|
+
}
|
|
16918
|
+
if (entry.type !== "assistant") continue;
|
|
16919
|
+
if (!entry.timestamp) continue;
|
|
16920
|
+
const ts = new Date(entry.timestamp);
|
|
16921
|
+
if (ts < start || ts > end) continue;
|
|
16922
|
+
const usage = entry.message?.usage;
|
|
16923
|
+
const model = entry.message?.model;
|
|
16924
|
+
if (!usage || !model) continue;
|
|
16925
|
+
const p = claudeModelPrice2(model);
|
|
16926
|
+
if (!p) continue;
|
|
16927
|
+
const inp = usage.input_tokens ?? 0;
|
|
16928
|
+
const out = usage.output_tokens ?? 0;
|
|
16929
|
+
const cw = usage.cache_creation_input_tokens ?? 0;
|
|
16930
|
+
const cr = usage.cache_read_input_tokens ?? 0;
|
|
16931
|
+
const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
|
|
16932
|
+
acc.total += cost;
|
|
16933
|
+
acc.inputTokens += inp;
|
|
16934
|
+
acc.outputTokens += out;
|
|
16935
|
+
acc.cacheWriteTokens += cw;
|
|
16936
|
+
acc.cacheReadTokens += cr;
|
|
16937
|
+
const dateKey = entry.timestamp.slice(0, 10);
|
|
16938
|
+
acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
|
|
16939
|
+
const normModel = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
|
|
16940
|
+
acc.byModel.set(normModel, (acc.byModel.get(normModel) ?? 0) + cost);
|
|
16941
|
+
const projectKey = decodeProjectDirName(proj);
|
|
16942
|
+
const projectRollup = acc.byProject.get(projectKey) ?? {
|
|
16943
|
+
cost: 0,
|
|
16944
|
+
inputTokens: 0,
|
|
16945
|
+
outputTokens: 0
|
|
16946
|
+
};
|
|
16947
|
+
projectRollup.cost += cost;
|
|
16948
|
+
projectRollup.inputTokens += inp;
|
|
16949
|
+
projectRollup.outputTokens += out;
|
|
16950
|
+
acc.byProject.set(projectKey, projectRollup);
|
|
16951
|
+
}
|
|
16952
|
+
} catch {
|
|
16953
|
+
continue;
|
|
16954
|
+
}
|
|
16955
|
+
}
|
|
16956
|
+
}
|
|
16957
|
+
function loadClaudeCost(start, end, projectsDir) {
|
|
16958
|
+
const acc = emptyClaudeCostAccumulator();
|
|
16959
|
+
if (!import_fs35.default.existsSync(projectsDir)) return freezeClaudeCost(acc);
|
|
16467
16960
|
let dirs;
|
|
16468
16961
|
try {
|
|
16469
16962
|
dirs = import_fs35.default.readdirSync(projectsDir);
|
|
16470
16963
|
} catch {
|
|
16471
|
-
return
|
|
16964
|
+
return freezeClaudeCost(acc);
|
|
16472
16965
|
}
|
|
16473
|
-
let total = 0;
|
|
16474
|
-
let inputTokens = 0;
|
|
16475
|
-
let outputTokens = 0;
|
|
16476
|
-
let cacheWriteTokens = 0;
|
|
16477
|
-
let cacheReadTokens = 0;
|
|
16478
|
-
const byDay = /* @__PURE__ */ new Map();
|
|
16479
|
-
const byModel = /* @__PURE__ */ new Map();
|
|
16480
16966
|
for (const proj of dirs) {
|
|
16481
|
-
|
|
16482
|
-
|
|
16967
|
+
processClaudeCostProject(proj, projectsDir, start, end, acc);
|
|
16968
|
+
}
|
|
16969
|
+
return freezeClaudeCost(acc);
|
|
16970
|
+
}
|
|
16971
|
+
function processCodexCostFile(filePath, start, end, acc) {
|
|
16972
|
+
let lines;
|
|
16973
|
+
try {
|
|
16974
|
+
lines = import_fs35.default.readFileSync(filePath, "utf-8").split("\n");
|
|
16975
|
+
} catch {
|
|
16976
|
+
return;
|
|
16977
|
+
}
|
|
16978
|
+
let sessionStart2 = "";
|
|
16979
|
+
let lastTotalInput = 0;
|
|
16980
|
+
let lastTotalCached = 0;
|
|
16981
|
+
let lastTotalOutput = 0;
|
|
16982
|
+
let sessionToolCalls = 0;
|
|
16983
|
+
for (const line of lines) {
|
|
16984
|
+
if (!line.trim()) continue;
|
|
16985
|
+
let entry;
|
|
16483
16986
|
try {
|
|
16484
|
-
|
|
16485
|
-
if (!stat.isDirectory()) continue;
|
|
16486
|
-
files = import_fs35.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
16987
|
+
entry = JSON.parse(line);
|
|
16487
16988
|
} catch {
|
|
16488
16989
|
continue;
|
|
16489
16990
|
}
|
|
16490
|
-
|
|
16491
|
-
|
|
16492
|
-
|
|
16493
|
-
|
|
16494
|
-
|
|
16495
|
-
|
|
16496
|
-
|
|
16497
|
-
|
|
16498
|
-
|
|
16499
|
-
|
|
16500
|
-
|
|
16501
|
-
|
|
16502
|
-
|
|
16503
|
-
|
|
16504
|
-
if (ts < start || ts > end) continue;
|
|
16505
|
-
const usage = entry.message?.usage;
|
|
16506
|
-
const model = entry.message?.model;
|
|
16507
|
-
if (!usage || !model) continue;
|
|
16508
|
-
const p = claudeModelPrice2(model);
|
|
16509
|
-
if (!p) continue;
|
|
16510
|
-
const inp = usage.input_tokens ?? 0;
|
|
16511
|
-
const out = usage.output_tokens ?? 0;
|
|
16512
|
-
const cw = usage.cache_creation_input_tokens ?? 0;
|
|
16513
|
-
const cr = usage.cache_read_input_tokens ?? 0;
|
|
16514
|
-
const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
|
|
16515
|
-
total += cost;
|
|
16516
|
-
inputTokens += inp;
|
|
16517
|
-
outputTokens += out;
|
|
16518
|
-
cacheWriteTokens += cw;
|
|
16519
|
-
cacheReadTokens += cr;
|
|
16520
|
-
const dateKey = entry.timestamp.slice(0, 10);
|
|
16521
|
-
byDay.set(dateKey, (byDay.get(dateKey) ?? 0) + cost);
|
|
16522
|
-
const normModel = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
|
|
16523
|
-
byModel.set(normModel, (byModel.get(normModel) ?? 0) + cost);
|
|
16524
|
-
}
|
|
16525
|
-
} catch {
|
|
16526
|
-
continue;
|
|
16527
|
-
}
|
|
16991
|
+
const p = entry.payload ?? {};
|
|
16992
|
+
if (entry.type === "session_meta") {
|
|
16993
|
+
sessionStart2 = String(p["timestamp"] ?? "");
|
|
16994
|
+
continue;
|
|
16995
|
+
}
|
|
16996
|
+
if (entry.type === "event_msg" && p["type"] === "token_count") {
|
|
16997
|
+
const info = p["info"] ?? {};
|
|
16998
|
+
const usage = info["total_token_usage"] ?? {};
|
|
16999
|
+
lastTotalInput = usage["input_tokens"] ?? lastTotalInput;
|
|
17000
|
+
lastTotalCached = usage["cached_input_tokens"] ?? lastTotalCached;
|
|
17001
|
+
lastTotalOutput = usage["output_tokens"] ?? lastTotalOutput;
|
|
17002
|
+
}
|
|
17003
|
+
if (entry.type === "response_item" && p["type"] === "function_call") {
|
|
17004
|
+
sessionToolCalls++;
|
|
16528
17005
|
}
|
|
16529
17006
|
}
|
|
16530
|
-
|
|
17007
|
+
if (!sessionStart2) return;
|
|
17008
|
+
const ts = new Date(sessionStart2);
|
|
17009
|
+
if (ts < start || ts > end) return;
|
|
17010
|
+
const nonCached = Math.max(0, lastTotalInput - lastTotalCached);
|
|
17011
|
+
const cost = nonCached * 5e-6 + lastTotalCached * 25e-7 + lastTotalOutput * 15e-6;
|
|
17012
|
+
acc.total += cost;
|
|
17013
|
+
acc.toolCalls += sessionToolCalls;
|
|
17014
|
+
const dateKey = sessionStart2.slice(0, 10);
|
|
17015
|
+
acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
|
|
16531
17016
|
}
|
|
16532
|
-
function
|
|
16533
|
-
const sessionsBase = import_path36.default.join(import_os31.default.homedir(), ".codex", "sessions");
|
|
16534
|
-
const byDay = /* @__PURE__ */ new Map();
|
|
16535
|
-
let total = 0;
|
|
16536
|
-
let toolCalls = 0;
|
|
16537
|
-
if (!import_fs35.default.existsSync(sessionsBase)) return { total, byDay, toolCalls };
|
|
17017
|
+
function listCodexSessionFiles(sessionsBase) {
|
|
16538
17018
|
const jsonlFiles = [];
|
|
17019
|
+
if (!import_fs35.default.existsSync(sessionsBase)) return jsonlFiles;
|
|
16539
17020
|
try {
|
|
16540
17021
|
for (const year of import_fs35.default.readdirSync(sessionsBase)) {
|
|
16541
17022
|
const yearPath = import_path36.default.join(sessionsBase, year);
|
|
@@ -16565,495 +17046,742 @@ function loadCodexCost(start, end) {
|
|
|
16565
17046
|
}
|
|
16566
17047
|
}
|
|
16567
17048
|
} catch {
|
|
16568
|
-
return
|
|
17049
|
+
return [];
|
|
16569
17050
|
}
|
|
16570
|
-
|
|
16571
|
-
|
|
17051
|
+
return jsonlFiles;
|
|
17052
|
+
}
|
|
17053
|
+
function loadCodexCost(start, end, sessionsBase) {
|
|
17054
|
+
const acc = { total: 0, toolCalls: 0, byDay: /* @__PURE__ */ new Map() };
|
|
17055
|
+
const files = listCodexSessionFiles(sessionsBase);
|
|
17056
|
+
for (const filePath of files) {
|
|
17057
|
+
processCodexCostFile(filePath, start, end, acc);
|
|
17058
|
+
}
|
|
17059
|
+
return { total: acc.total, byDay: acc.byDay, toolCalls: acc.toolCalls };
|
|
17060
|
+
}
|
|
17061
|
+
var GEMINI_FALLBACK_MODELS = ["gemini-2.5-flash", "gemini-2.0-flash"];
|
|
17062
|
+
function geminiPriceFor(model) {
|
|
17063
|
+
let tuple = pricingFor(model);
|
|
17064
|
+
if (!tuple && /^gemini-/i.test(model)) {
|
|
17065
|
+
for (const proxy of GEMINI_FALLBACK_MODELS) {
|
|
17066
|
+
tuple = pricingFor(proxy);
|
|
17067
|
+
if (tuple) break;
|
|
17068
|
+
}
|
|
17069
|
+
}
|
|
17070
|
+
if (!tuple) return null;
|
|
17071
|
+
return { input: tuple[0], output: tuple[1], cacheRead: tuple[3] || tuple[0] };
|
|
17072
|
+
}
|
|
17073
|
+
function emptyGeminiAccumulator() {
|
|
17074
|
+
return {
|
|
17075
|
+
total: 0,
|
|
17076
|
+
inputTokens: 0,
|
|
17077
|
+
outputTokens: 0,
|
|
17078
|
+
cacheReadTokens: 0,
|
|
17079
|
+
byDay: /* @__PURE__ */ new Map(),
|
|
17080
|
+
byProject: /* @__PURE__ */ new Map()
|
|
17081
|
+
};
|
|
17082
|
+
}
|
|
17083
|
+
function freezeGeminiCost(acc) {
|
|
17084
|
+
return {
|
|
17085
|
+
total: acc.total,
|
|
17086
|
+
byDay: acc.byDay,
|
|
17087
|
+
byProject: acc.byProject,
|
|
17088
|
+
inputTokens: acc.inputTokens,
|
|
17089
|
+
outputTokens: acc.outputTokens,
|
|
17090
|
+
cacheReadTokens: acc.cacheReadTokens
|
|
17091
|
+
};
|
|
17092
|
+
}
|
|
17093
|
+
function processGeminiCostFile(filePath, projectKey, start, end, acc) {
|
|
17094
|
+
const startMs = start.getTime();
|
|
17095
|
+
try {
|
|
17096
|
+
if (import_fs35.default.statSync(filePath).mtimeMs < startMs) return;
|
|
17097
|
+
} catch {
|
|
17098
|
+
return;
|
|
17099
|
+
}
|
|
17100
|
+
let raw;
|
|
17101
|
+
try {
|
|
17102
|
+
raw = import_fs35.default.readFileSync(filePath, "utf-8");
|
|
17103
|
+
} catch {
|
|
17104
|
+
return;
|
|
17105
|
+
}
|
|
17106
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
17107
|
+
for (const line of raw.split("\n")) {
|
|
17108
|
+
if (!line.trim()) continue;
|
|
17109
|
+
let entry;
|
|
16572
17110
|
try {
|
|
16573
|
-
|
|
17111
|
+
entry = JSON.parse(line);
|
|
16574
17112
|
} catch {
|
|
16575
17113
|
continue;
|
|
16576
17114
|
}
|
|
16577
|
-
|
|
16578
|
-
|
|
16579
|
-
|
|
16580
|
-
|
|
16581
|
-
|
|
16582
|
-
for (const line of lines) {
|
|
16583
|
-
if (!line.trim()) continue;
|
|
16584
|
-
let entry;
|
|
16585
|
-
try {
|
|
16586
|
-
entry = JSON.parse(line);
|
|
16587
|
-
} catch {
|
|
16588
|
-
continue;
|
|
16589
|
-
}
|
|
16590
|
-
const p = entry.payload ?? {};
|
|
16591
|
-
if (entry.type === "session_meta") {
|
|
16592
|
-
sessionStart2 = String(p["timestamp"] ?? "");
|
|
16593
|
-
continue;
|
|
16594
|
-
}
|
|
16595
|
-
if (entry.type === "event_msg" && p["type"] === "token_count") {
|
|
16596
|
-
const info = p["info"] ?? {};
|
|
16597
|
-
const usage = info["total_token_usage"] ?? {};
|
|
16598
|
-
lastTotalInput = usage["input_tokens"] ?? lastTotalInput;
|
|
16599
|
-
lastTotalCached = usage["cached_input_tokens"] ?? lastTotalCached;
|
|
16600
|
-
lastTotalOutput = usage["output_tokens"] ?? lastTotalOutput;
|
|
16601
|
-
}
|
|
16602
|
-
if (entry.type === "response_item" && p["type"] === "function_call") {
|
|
16603
|
-
sessionToolCalls++;
|
|
16604
|
-
}
|
|
17115
|
+
if (entry.type !== "gemini") continue;
|
|
17116
|
+
if (!entry.tokens || !entry.model || !entry.timestamp) continue;
|
|
17117
|
+
if (entry.id) {
|
|
17118
|
+
if (seenIds.has(entry.id)) continue;
|
|
17119
|
+
seenIds.add(entry.id);
|
|
16605
17120
|
}
|
|
16606
|
-
|
|
16607
|
-
const ts = new Date(sessionStart2);
|
|
17121
|
+
const ts = new Date(entry.timestamp);
|
|
16608
17122
|
if (ts < start || ts > end) continue;
|
|
16609
|
-
const
|
|
16610
|
-
|
|
16611
|
-
|
|
16612
|
-
|
|
16613
|
-
const
|
|
16614
|
-
|
|
17123
|
+
const price = geminiPriceFor(entry.model);
|
|
17124
|
+
if (!price) continue;
|
|
17125
|
+
const inp = entry.tokens.input ?? 0;
|
|
17126
|
+
const out = entry.tokens.output ?? 0;
|
|
17127
|
+
const cached = Math.min(entry.tokens.cached ?? 0, inp);
|
|
17128
|
+
const fresh = Math.max(0, inp - cached);
|
|
17129
|
+
const cost = fresh * price.input + cached * price.cacheRead + out * price.output;
|
|
17130
|
+
acc.total += cost;
|
|
17131
|
+
acc.inputTokens += inp;
|
|
17132
|
+
acc.outputTokens += out;
|
|
17133
|
+
acc.cacheReadTokens += cached;
|
|
17134
|
+
const dateKey = entry.timestamp.slice(0, 10);
|
|
17135
|
+
acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
|
|
17136
|
+
const rollup = acc.byProject.get(projectKey) ?? {
|
|
17137
|
+
cost: 0,
|
|
17138
|
+
inputTokens: 0,
|
|
17139
|
+
outputTokens: 0
|
|
17140
|
+
};
|
|
17141
|
+
rollup.cost += cost;
|
|
17142
|
+
rollup.inputTokens += inp;
|
|
17143
|
+
rollup.outputTokens += out;
|
|
17144
|
+
acc.byProject.set(projectKey, rollup);
|
|
17145
|
+
}
|
|
17146
|
+
}
|
|
17147
|
+
function listGeminiSessionFiles(geminiTmpDir) {
|
|
17148
|
+
const out = [];
|
|
17149
|
+
let dirs;
|
|
17150
|
+
try {
|
|
17151
|
+
if (!import_fs35.default.statSync(geminiTmpDir).isDirectory()) return out;
|
|
17152
|
+
dirs = import_fs35.default.readdirSync(geminiTmpDir);
|
|
17153
|
+
} catch {
|
|
17154
|
+
return out;
|
|
17155
|
+
}
|
|
17156
|
+
for (const proj of dirs) {
|
|
17157
|
+
const chatsDir = import_path36.default.join(geminiTmpDir, proj, "chats");
|
|
17158
|
+
let files;
|
|
17159
|
+
try {
|
|
17160
|
+
if (!import_fs35.default.statSync(chatsDir).isDirectory()) continue;
|
|
17161
|
+
files = import_fs35.default.readdirSync(chatsDir);
|
|
17162
|
+
} catch {
|
|
17163
|
+
continue;
|
|
17164
|
+
}
|
|
17165
|
+
for (const f of files) {
|
|
17166
|
+
if (!f.endsWith(".jsonl")) continue;
|
|
17167
|
+
out.push({ projectKey: proj, file: import_path36.default.join(chatsDir, f) });
|
|
17168
|
+
}
|
|
16615
17169
|
}
|
|
16616
|
-
return
|
|
17170
|
+
return out;
|
|
17171
|
+
}
|
|
17172
|
+
function loadGeminiCost(start, end, geminiTmpDir) {
|
|
17173
|
+
const acc = emptyGeminiAccumulator();
|
|
17174
|
+
if (!import_fs35.default.existsSync(geminiTmpDir)) return freezeGeminiCost(acc);
|
|
17175
|
+
for (const { projectKey, file } of listGeminiSessionFiles(geminiTmpDir)) {
|
|
17176
|
+
processGeminiCostFile(file, projectKey, start, end, acc);
|
|
17177
|
+
}
|
|
17178
|
+
return freezeGeminiCost(acc);
|
|
17179
|
+
}
|
|
17180
|
+
function aggregateReportFromAudit(period, opts = {}) {
|
|
17181
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
17182
|
+
const auditLogPath = opts.auditLogPath ?? import_path36.default.join(import_os31.default.homedir(), ".node9", "audit.log");
|
|
17183
|
+
const claudeProjectsDir = opts.claudeProjectsDir ?? import_path36.default.join(import_os31.default.homedir(), ".claude", "projects");
|
|
17184
|
+
const codexSessionsDir = opts.codexSessionsDir ?? import_path36.default.join(import_os31.default.homedir(), ".codex", "sessions");
|
|
17185
|
+
const geminiTmpDir = opts.geminiTmpDir ?? import_path36.default.join(import_os31.default.homedir(), ".gemini", "tmp");
|
|
17186
|
+
const hasAuditFile = import_fs35.default.existsSync(auditLogPath);
|
|
17187
|
+
const allEntries = opts.preloadedAuditEntries ?? parseAuditLog(auditLogPath);
|
|
17188
|
+
const unackedDlp = allEntries.filter((e) => e.source === "response-dlp");
|
|
17189
|
+
const { start, end } = getDateRange(period, now);
|
|
17190
|
+
const responseDlpEntries = allEntries.filter((e) => {
|
|
17191
|
+
if (e.source !== "response-dlp") return false;
|
|
17192
|
+
const ts = new Date(e.ts);
|
|
17193
|
+
return ts >= start && ts <= end;
|
|
17194
|
+
}).map((e) => {
|
|
17195
|
+
const raw = e;
|
|
17196
|
+
return {
|
|
17197
|
+
ts: e.ts,
|
|
17198
|
+
dlpPattern: typeof raw.dlpPattern === "string" ? raw.dlpPattern : void 0,
|
|
17199
|
+
dlpSample: typeof raw.dlpSample === "string" ? raw.dlpSample : void 0
|
|
17200
|
+
};
|
|
17201
|
+
});
|
|
17202
|
+
const claudeCost = opts.preloadedClaudeCost ?? loadClaudeCost(start, end, claudeProjectsDir);
|
|
17203
|
+
const codexCost = opts.preloadedCodexCost ?? loadCodexCost(start, end, codexSessionsDir);
|
|
17204
|
+
const geminiCost = opts.preloadedGeminiCost ?? loadGeminiCost(start, end, geminiTmpDir);
|
|
17205
|
+
for (const [day, c] of codexCost.byDay) {
|
|
17206
|
+
claudeCost.byDay.set(day, (claudeCost.byDay.get(day) ?? 0) + c);
|
|
17207
|
+
}
|
|
17208
|
+
for (const [day, c] of geminiCost.byDay) {
|
|
17209
|
+
claudeCost.byDay.set(day, (claudeCost.byDay.get(day) ?? 0) + c);
|
|
17210
|
+
}
|
|
17211
|
+
for (const [geminiKey, gRollup] of geminiCost.byProject) {
|
|
17212
|
+
let mergedInto = null;
|
|
17213
|
+
for (const claudeKey of claudeCost.byProject.keys()) {
|
|
17214
|
+
const claudeBase = claudeKey.match(/[^/\\]+$/)?.[0] ?? claudeKey;
|
|
17215
|
+
if (claudeBase === geminiKey) {
|
|
17216
|
+
mergedInto = claudeKey;
|
|
17217
|
+
break;
|
|
17218
|
+
}
|
|
17219
|
+
}
|
|
17220
|
+
const targetKey = mergedInto ?? geminiKey;
|
|
17221
|
+
const existing = claudeCost.byProject.get(targetKey) ?? {
|
|
17222
|
+
cost: 0,
|
|
17223
|
+
inputTokens: 0,
|
|
17224
|
+
outputTokens: 0
|
|
17225
|
+
};
|
|
17226
|
+
existing.cost += gRollup.cost;
|
|
17227
|
+
existing.inputTokens += gRollup.inputTokens;
|
|
17228
|
+
existing.outputTokens += gRollup.outputTokens;
|
|
17229
|
+
claudeCost.byProject.set(targetKey, existing);
|
|
17230
|
+
}
|
|
17231
|
+
const periodMs = end.getTime() - start.getTime();
|
|
17232
|
+
const priorEnd = new Date(start.getTime() - 1);
|
|
17233
|
+
const priorStart = new Date(start.getTime() - periodMs);
|
|
17234
|
+
const priorEntries = allEntries.filter((e) => {
|
|
17235
|
+
if (e.source === "post-hook") return false;
|
|
17236
|
+
const ts = new Date(e.ts);
|
|
17237
|
+
return ts >= priorStart && ts <= priorEnd;
|
|
17238
|
+
});
|
|
17239
|
+
const priorBlocked = priorEntries.filter((e) => !isAllow(e.decision)).length;
|
|
17240
|
+
const priorBlockRate = priorEntries.length > 0 ? priorBlocked / priorEntries.length : null;
|
|
17241
|
+
const excludeTests = opts.excludeTests === true;
|
|
17242
|
+
const testTs = excludeTests ? buildTestTimestamps(allEntries) : /* @__PURE__ */ new Set();
|
|
17243
|
+
let excludedTests = 0;
|
|
17244
|
+
const entries = allEntries.filter((e) => {
|
|
17245
|
+
if (e.source === "post-hook") return false;
|
|
17246
|
+
if (e.source === "response-dlp") return false;
|
|
17247
|
+
const ts = new Date(e.ts);
|
|
17248
|
+
if (ts < start || ts > end) return false;
|
|
17249
|
+
if (excludeTests && isTestEntry(e, testTs)) {
|
|
17250
|
+
excludedTests++;
|
|
17251
|
+
return false;
|
|
17252
|
+
}
|
|
17253
|
+
return true;
|
|
17254
|
+
});
|
|
17255
|
+
let userApproved = 0;
|
|
17256
|
+
let userDenied = 0;
|
|
17257
|
+
let timedOut = 0;
|
|
17258
|
+
let hardBlocked = 0;
|
|
17259
|
+
let dlpBlocked = 0;
|
|
17260
|
+
let observeDlp = 0;
|
|
17261
|
+
let loopHits = 0;
|
|
17262
|
+
let testPasses = 0;
|
|
17263
|
+
let testFails = 0;
|
|
17264
|
+
const toolMap = /* @__PURE__ */ new Map();
|
|
17265
|
+
const blockMap = /* @__PURE__ */ new Map();
|
|
17266
|
+
const ruleMap = /* @__PURE__ */ new Map();
|
|
17267
|
+
const agentMap = /* @__PURE__ */ new Map();
|
|
17268
|
+
const mcpMap = /* @__PURE__ */ new Map();
|
|
17269
|
+
const dailyMap = /* @__PURE__ */ new Map();
|
|
17270
|
+
const hourMap = /* @__PURE__ */ new Map();
|
|
17271
|
+
for (const e of entries) {
|
|
17272
|
+
const allow = isAllow(e.decision);
|
|
17273
|
+
const dateKey = e.ts.slice(0, 10);
|
|
17274
|
+
const userInteracted = e.source === "daemon";
|
|
17275
|
+
if (userInteracted) {
|
|
17276
|
+
if (allow) userApproved++;
|
|
17277
|
+
else userDenied++;
|
|
17278
|
+
} else if (!allow) {
|
|
17279
|
+
if (e.checkedBy === "timeout") timedOut++;
|
|
17280
|
+
else if (e.checkedBy === "observe-mode-dlp-would-block") observeDlp++;
|
|
17281
|
+
else if (isDlp(e.checkedBy)) dlpBlocked++;
|
|
17282
|
+
else if (e.checkedBy !== "loop-detected") hardBlocked++;
|
|
17283
|
+
}
|
|
17284
|
+
if (e.checkedBy === "loop-detected") loopHits++;
|
|
17285
|
+
const t = toolMap.get(e.tool) ?? { calls: 0, blocked: 0 };
|
|
17286
|
+
t.calls++;
|
|
17287
|
+
if (!allow) t.blocked++;
|
|
17288
|
+
toolMap.set(e.tool, t);
|
|
17289
|
+
if (!allow && e.checkedBy) {
|
|
17290
|
+
blockMap.set(e.checkedBy, (blockMap.get(e.checkedBy) ?? 0) + 1);
|
|
17291
|
+
}
|
|
17292
|
+
if (!allow && e.ruleName) {
|
|
17293
|
+
ruleMap.set(e.ruleName, (ruleMap.get(e.ruleName) ?? 0) + 1);
|
|
17294
|
+
}
|
|
17295
|
+
if (e.agent) agentMap.set(e.agent, (agentMap.get(e.agent) ?? 0) + 1);
|
|
17296
|
+
if (e.mcpServer) mcpMap.set(e.mcpServer, (mcpMap.get(e.mcpServer) ?? 0) + 1);
|
|
17297
|
+
const hour = new Date(e.ts).getHours();
|
|
17298
|
+
hourMap.set(hour, (hourMap.get(hour) ?? 0) + 1);
|
|
17299
|
+
const d = dailyMap.get(dateKey) ?? { calls: 0, blocked: 0 };
|
|
17300
|
+
d.calls++;
|
|
17301
|
+
if (!allow) d.blocked++;
|
|
17302
|
+
dailyMap.set(dateKey, d);
|
|
17303
|
+
}
|
|
17304
|
+
for (const e of allEntries) {
|
|
17305
|
+
if (e.source !== "test-result") continue;
|
|
17306
|
+
const ts = new Date(e.ts);
|
|
17307
|
+
if (ts < start || ts > end) continue;
|
|
17308
|
+
if (e.testResult === "pass") testPasses++;
|
|
17309
|
+
else if (e.testResult === "fail") testFails++;
|
|
17310
|
+
}
|
|
17311
|
+
if (codexCost.toolCalls > 0) {
|
|
17312
|
+
agentMap.set("Codex", (agentMap.get("Codex") ?? 0) + codexCost.toolCalls);
|
|
17313
|
+
}
|
|
17314
|
+
const data = {
|
|
17315
|
+
period,
|
|
17316
|
+
start,
|
|
17317
|
+
end,
|
|
17318
|
+
excludedTests,
|
|
17319
|
+
total: entries.length,
|
|
17320
|
+
userApproved,
|
|
17321
|
+
userDenied,
|
|
17322
|
+
timedOut,
|
|
17323
|
+
hardBlocked,
|
|
17324
|
+
dlpBlocked,
|
|
17325
|
+
observeDlp,
|
|
17326
|
+
loopHits,
|
|
17327
|
+
testPasses,
|
|
17328
|
+
testFails,
|
|
17329
|
+
unackedDlp: unackedDlp.length,
|
|
17330
|
+
priorBlockRate,
|
|
17331
|
+
cost: {
|
|
17332
|
+
claudeUSD: claudeCost.total,
|
|
17333
|
+
codexUSD: codexCost.total,
|
|
17334
|
+
geminiUSD: geminiCost.total,
|
|
17335
|
+
inputTokens: claudeCost.inputTokens + geminiCost.inputTokens,
|
|
17336
|
+
outputTokens: claudeCost.outputTokens + geminiCost.outputTokens,
|
|
17337
|
+
cacheWriteTokens: claudeCost.cacheWriteTokens,
|
|
17338
|
+
cacheReadTokens: claudeCost.cacheReadTokens + geminiCost.cacheReadTokens,
|
|
17339
|
+
byDay: claudeCost.byDay,
|
|
17340
|
+
byModel: claudeCost.byModel,
|
|
17341
|
+
byProject: claudeCost.byProject
|
|
17342
|
+
},
|
|
17343
|
+
toolMap,
|
|
17344
|
+
blockMap,
|
|
17345
|
+
ruleMap,
|
|
17346
|
+
agentMap,
|
|
17347
|
+
mcpMap,
|
|
17348
|
+
dailyMap,
|
|
17349
|
+
hourMap,
|
|
17350
|
+
generatedAt: now.toISOString()
|
|
17351
|
+
};
|
|
17352
|
+
return { data, hasAuditFile, responseDlpEntries };
|
|
17353
|
+
}
|
|
17354
|
+
|
|
17355
|
+
// src/cli/render/report-json.ts
|
|
17356
|
+
function buildReportJson(input) {
|
|
17357
|
+
const totalBlocked = input.timedOut + input.hardBlocked + input.dlpBlocked + input.loopHits + input.userDenied;
|
|
17358
|
+
const blockRate = input.total > 0 ? totalBlocked / input.total : 0;
|
|
17359
|
+
const deltaPct = input.priorBlockRate === null ? null : Math.round((blockRate - input.priorBlockRate) * 100);
|
|
17360
|
+
return {
|
|
17361
|
+
schemaVersion: 1,
|
|
17362
|
+
generatedAt: input.generatedAt,
|
|
17363
|
+
period: input.period,
|
|
17364
|
+
range: { start: input.start.toISOString(), end: input.end.toISOString() },
|
|
17365
|
+
excludedTests: input.excludedTests,
|
|
17366
|
+
totals: {
|
|
17367
|
+
events: input.total,
|
|
17368
|
+
blocked: totalBlocked,
|
|
17369
|
+
blockRate,
|
|
17370
|
+
userApproved: input.userApproved,
|
|
17371
|
+
userDenied: input.userDenied,
|
|
17372
|
+
timedOut: input.timedOut,
|
|
17373
|
+
hardBlocked: input.hardBlocked,
|
|
17374
|
+
dlpBlocked: input.dlpBlocked,
|
|
17375
|
+
observeDlp: input.observeDlp,
|
|
17376
|
+
loopHits: input.loopHits,
|
|
17377
|
+
unackedDlp: input.unackedDlp
|
|
17378
|
+
},
|
|
17379
|
+
tests: {
|
|
17380
|
+
passes: input.testPasses,
|
|
17381
|
+
fails: input.testFails
|
|
17382
|
+
},
|
|
17383
|
+
cost: {
|
|
17384
|
+
totalUSD: input.cost.claudeUSD + input.cost.codexUSD + input.cost.geminiUSD,
|
|
17385
|
+
claudeUSD: input.cost.claudeUSD,
|
|
17386
|
+
codexUSD: input.cost.codexUSD,
|
|
17387
|
+
geminiUSD: input.cost.geminiUSD,
|
|
17388
|
+
inputTokens: input.cost.inputTokens,
|
|
17389
|
+
outputTokens: input.cost.outputTokens,
|
|
17390
|
+
cacheWriteTokens: input.cost.cacheWriteTokens,
|
|
17391
|
+
cacheReadTokens: input.cost.cacheReadTokens,
|
|
17392
|
+
byDay: [...input.cost.byDay.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([day, usd]) => ({ day, usd })),
|
|
17393
|
+
byModel: [...input.cost.byModel.entries()].sort((a, b) => b[1] - a[1]).map(([model, usd]) => ({ model, usd }))
|
|
17394
|
+
},
|
|
17395
|
+
byTool: [...input.toolMap.entries()].sort((a, b) => b[1].calls - a[1].calls).map(([tool, v]) => ({ tool, calls: v.calls, blocked: v.blocked })),
|
|
17396
|
+
byBlock: [...input.blockMap.entries()].sort((a, b) => b[1] - a[1]).map(([rule, count]) => ({ rule, count })),
|
|
17397
|
+
byAgent: [...input.agentMap.entries()].sort((a, b) => b[1] - a[1]).map(([agent, calls]) => ({ agent, calls })),
|
|
17398
|
+
byMcp: [...input.mcpMap.entries()].sort((a, b) => b[1] - a[1]).map(([server, calls]) => ({ server, calls })),
|
|
17399
|
+
byDay: [...input.dailyMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([day, v]) => ({ day, calls: v.calls, blocked: v.blocked })),
|
|
17400
|
+
byHour: [...input.hourMap.entries()].sort((a, b) => a[0] - b[0]).map(([hour, calls]) => ({ hour, calls })),
|
|
17401
|
+
trend: {
|
|
17402
|
+
priorBlockRate: input.priorBlockRate,
|
|
17403
|
+
deltaPct
|
|
17404
|
+
}
|
|
17405
|
+
};
|
|
17406
|
+
}
|
|
17407
|
+
|
|
17408
|
+
// src/cli/commands/report.ts
|
|
17409
|
+
var BLOCK_REASON_LABELS = {
|
|
17410
|
+
timeout: "Approval timeout",
|
|
17411
|
+
"smart-rule-block": "Smart rule",
|
|
17412
|
+
"observe-mode-dlp-would-block": "DLP (observe)",
|
|
17413
|
+
"persistent-deny": "Persistent deny",
|
|
17414
|
+
"local-decision": "User denied",
|
|
17415
|
+
"dlp-block": "DLP block",
|
|
17416
|
+
"loop-detected": "Loop detected"
|
|
17417
|
+
};
|
|
17418
|
+
function humanBlockReason(reason) {
|
|
17419
|
+
return BLOCK_REASON_LABELS[reason] ?? reason;
|
|
17420
|
+
}
|
|
17421
|
+
function barStr(value, max, width) {
|
|
17422
|
+
if (max === 0 || width <= 0) return "\u2591".repeat(width);
|
|
17423
|
+
const filled = Math.max(1, Math.round(value / max * width));
|
|
17424
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
17425
|
+
}
|
|
17426
|
+
function colorBar(value, max, width) {
|
|
17427
|
+
const s = barStr(value, max, width);
|
|
17428
|
+
const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
|
|
17429
|
+
return import_chalk13.default.cyan(s.slice(0, filled)) + import_chalk13.default.dim(s.slice(filled));
|
|
17430
|
+
}
|
|
17431
|
+
function fmtDate(d) {
|
|
17432
|
+
const date = typeof d === "string" ? /* @__PURE__ */ new Date(d + "T12:00:00") : d;
|
|
17433
|
+
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
17434
|
+
}
|
|
17435
|
+
function num2(n) {
|
|
17436
|
+
return n.toLocaleString();
|
|
17437
|
+
}
|
|
17438
|
+
function fmtCost2(usd) {
|
|
17439
|
+
if (usd < 1e-3) return "< $0.001";
|
|
17440
|
+
if (usd < 1) return "$" + usd.toFixed(4);
|
|
17441
|
+
return "$" + usd.toFixed(2);
|
|
16617
17442
|
}
|
|
16618
17443
|
function registerReportCommand(program2) {
|
|
16619
17444
|
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) => {
|
|
16620
|
-
const period = ["today", "7d", "30d", "month"].includes(
|
|
17445
|
+
const period = ["today", "7d", "30d", "90d", "month"].includes(
|
|
16621
17446
|
options.period
|
|
16622
17447
|
) ? options.period : "7d";
|
|
16623
|
-
const
|
|
16624
|
-
const
|
|
16625
|
-
|
|
16626
|
-
|
|
17448
|
+
const excludeTests = options.tests === false;
|
|
17449
|
+
const { data, hasAuditFile, responseDlpEntries } = aggregateReportFromAudit(period, {
|
|
17450
|
+
excludeTests
|
|
17451
|
+
});
|
|
17452
|
+
if (data.unackedDlp > 0 && !options.json) {
|
|
16627
17453
|
console.log("");
|
|
16628
17454
|
console.log(
|
|
16629
17455
|
import_chalk13.default.bgRed.white.bold(
|
|
16630
|
-
` \u26A0\uFE0F DLP ALERT: ${unackedDlp
|
|
17456
|
+
` \u26A0\uFE0F DLP ALERT: ${data.unackedDlp} secret${data.unackedDlp !== 1 ? "s" : ""} found in Claude response text `
|
|
16631
17457
|
) + " " + import_chalk13.default.yellow("\u2192 run: node9 dlp")
|
|
16632
17458
|
);
|
|
16633
17459
|
}
|
|
16634
|
-
if (
|
|
17460
|
+
if (!hasAuditFile && !options.json) {
|
|
16635
17461
|
console.log(
|
|
16636
17462
|
import_chalk13.default.yellow("\n No audit data found. Run node9 with Claude Code to generate entries.\n")
|
|
16637
17463
|
);
|
|
16638
17464
|
return;
|
|
16639
17465
|
}
|
|
16640
|
-
|
|
16641
|
-
|
|
16642
|
-
|
|
16643
|
-
|
|
16644
|
-
|
|
16645
|
-
|
|
16646
|
-
outputTokens: costOutputTokens,
|
|
16647
|
-
cacheWriteTokens: costCacheWrite,
|
|
16648
|
-
cacheReadTokens: costCacheRead
|
|
16649
|
-
} = loadClaudeCost(start, end);
|
|
16650
|
-
const {
|
|
16651
|
-
total: codexCostUSD,
|
|
16652
|
-
byDay: codexCostByDay,
|
|
16653
|
-
toolCalls: codexToolCalls
|
|
16654
|
-
} = loadCodexCost(start, end);
|
|
16655
|
-
const costUSD = claudeCostUSD + codexCostUSD;
|
|
16656
|
-
for (const [day, c] of codexCostByDay) {
|
|
16657
|
-
costByDay.set(day, (costByDay.get(day) ?? 0) + c);
|
|
16658
|
-
}
|
|
16659
|
-
const periodMs = end.getTime() - start.getTime();
|
|
16660
|
-
const priorEnd = new Date(start.getTime() - 1);
|
|
16661
|
-
const priorStart = new Date(start.getTime() - periodMs);
|
|
16662
|
-
const priorEntries = allEntries.filter((e) => {
|
|
16663
|
-
if (e.source === "post-hook") return false;
|
|
16664
|
-
const ts = new Date(e.ts);
|
|
16665
|
-
return ts >= priorStart && ts <= priorEnd;
|
|
16666
|
-
});
|
|
16667
|
-
const priorBlocked = priorEntries.filter((e) => !isAllow(e.decision)).length;
|
|
16668
|
-
const priorBlockRate = priorEntries.length > 0 ? priorBlocked / priorEntries.length : null;
|
|
16669
|
-
const excludeTests = options.tests === false;
|
|
16670
|
-
const testTs = excludeTests ? buildTestTimestamps(allEntries) : /* @__PURE__ */ new Set();
|
|
16671
|
-
let filteredTestCount = 0;
|
|
16672
|
-
const entries = allEntries.filter((e) => {
|
|
16673
|
-
if (e.source === "post-hook") return false;
|
|
16674
|
-
if (e.source === "response-dlp") return false;
|
|
16675
|
-
const ts = new Date(e.ts);
|
|
16676
|
-
if (ts < start || ts > end) return false;
|
|
16677
|
-
if (excludeTests && isTestEntry(e, testTs)) {
|
|
16678
|
-
filteredTestCount++;
|
|
16679
|
-
return false;
|
|
16680
|
-
}
|
|
16681
|
-
return true;
|
|
16682
|
-
});
|
|
16683
|
-
if (entries.length === 0 && !options.json) {
|
|
17466
|
+
if (options.json) {
|
|
17467
|
+
const envelope = buildReportJson(data);
|
|
17468
|
+
process.stdout.write(JSON.stringify(envelope, null, 2) + "\n");
|
|
17469
|
+
return;
|
|
17470
|
+
}
|
|
17471
|
+
if (data.total === 0) {
|
|
16684
17472
|
console.log(import_chalk13.default.yellow(`
|
|
16685
17473
|
No activity for period "${period}".
|
|
16686
17474
|
`));
|
|
16687
17475
|
return;
|
|
16688
17476
|
}
|
|
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
|
-
|
|
16774
|
-
|
|
16775
|
-
|
|
16776
|
-
|
|
16777
|
-
|
|
16778
|
-
|
|
16779
|
-
|
|
16780
|
-
|
|
16781
|
-
|
|
16782
|
-
|
|
16783
|
-
|
|
16784
|
-
|
|
16785
|
-
|
|
16786
|
-
|
|
16787
|
-
|
|
16788
|
-
|
|
16789
|
-
|
|
16790
|
-
|
|
16791
|
-
|
|
16792
|
-
|
|
16793
|
-
|
|
16794
|
-
|
|
16795
|
-
|
|
16796
|
-
|
|
16797
|
-
|
|
16798
|
-
|
|
16799
|
-
|
|
16800
|
-
|
|
16801
|
-
}
|
|
17477
|
+
renderTerminalReport(data, responseDlpEntries, excludeTests);
|
|
17478
|
+
});
|
|
17479
|
+
}
|
|
17480
|
+
function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
17481
|
+
const {
|
|
17482
|
+
period,
|
|
17483
|
+
start,
|
|
17484
|
+
end,
|
|
17485
|
+
total,
|
|
17486
|
+
excludedTests,
|
|
17487
|
+
userApproved,
|
|
17488
|
+
userDenied,
|
|
17489
|
+
timedOut,
|
|
17490
|
+
hardBlocked,
|
|
17491
|
+
dlpBlocked,
|
|
17492
|
+
observeDlp,
|
|
17493
|
+
loopHits,
|
|
17494
|
+
testPasses,
|
|
17495
|
+
testFails,
|
|
17496
|
+
priorBlockRate,
|
|
17497
|
+
cost: {
|
|
17498
|
+
claudeUSD,
|
|
17499
|
+
codexUSD,
|
|
17500
|
+
geminiUSD,
|
|
17501
|
+
inputTokens: costInputTokens,
|
|
17502
|
+
outputTokens: costOutputTokens,
|
|
17503
|
+
cacheWriteTokens: costCacheWrite,
|
|
17504
|
+
cacheReadTokens: costCacheRead,
|
|
17505
|
+
byDay: costByDay,
|
|
17506
|
+
byModel: costByModel
|
|
17507
|
+
},
|
|
17508
|
+
toolMap,
|
|
17509
|
+
blockMap,
|
|
17510
|
+
agentMap,
|
|
17511
|
+
mcpMap,
|
|
17512
|
+
dailyMap,
|
|
17513
|
+
hourMap
|
|
17514
|
+
} = data;
|
|
17515
|
+
const costUSD = claudeUSD + codexUSD + geminiUSD;
|
|
17516
|
+
const topTools = [...toolMap.entries()].sort((a, b) => b[1].calls - a[1].calls).slice(0, 8);
|
|
17517
|
+
const topBlocks = [...blockMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
|
|
17518
|
+
const dailyList = [...dailyMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).slice(-14);
|
|
17519
|
+
const maxTool = Math.max(...topTools.map(([, v]) => v.calls), 1);
|
|
17520
|
+
const maxBlock = Math.max(...topBlocks.map(([, v]) => v), 1);
|
|
17521
|
+
const maxDaily = Math.max(...dailyList.map(([, v]) => v.calls), 1);
|
|
17522
|
+
const W = Math.min(process.stdout.columns || 80, 100);
|
|
17523
|
+
const INNER = W - 4;
|
|
17524
|
+
const COL = Math.floor(INNER / 2) - 1;
|
|
17525
|
+
const LABEL = 24;
|
|
17526
|
+
const BAR = Math.max(6, Math.min(14, COL - LABEL - 8));
|
|
17527
|
+
const TOOL_COUNT_W = Math.max(...topTools.map(([, v]) => num2(v.calls).length), 1);
|
|
17528
|
+
const BLOCK_COUNT_W = Math.max(...topBlocks.map(([, v]) => num2(v).length), 1);
|
|
17529
|
+
const line = import_chalk13.default.dim("\u2500".repeat(W - 2));
|
|
17530
|
+
const periodLabel = {
|
|
17531
|
+
today: "Today",
|
|
17532
|
+
"7d": "Last 7 Days",
|
|
17533
|
+
"30d": "Last 30 Days",
|
|
17534
|
+
"90d": "Last 90 Days",
|
|
17535
|
+
month: "This Month"
|
|
17536
|
+
};
|
|
17537
|
+
console.log("");
|
|
17538
|
+
console.log(
|
|
17539
|
+
" " + import_chalk13.default.bold.cyan("\u{1F6E1} node9 Report") + import_chalk13.default.dim(" \xB7 ") + import_chalk13.default.white(periodLabel[period]) + import_chalk13.default.dim(` ${fmtDate(start)} \u2013 ${fmtDate(end)}`) + import_chalk13.default.dim(` ${num2(total)} events`) + (excludeTests ? import_chalk13.default.dim(` \u2013tests (\u2013${excludedTests})`) : "")
|
|
17540
|
+
);
|
|
17541
|
+
console.log(" " + line);
|
|
17542
|
+
const totalBlocked = timedOut + hardBlocked + dlpBlocked + loopHits + userDenied;
|
|
17543
|
+
const currentRate = total > 0 ? totalBlocked / total : 0;
|
|
17544
|
+
const trendLabel = (() => {
|
|
17545
|
+
if (priorBlockRate === null) return "";
|
|
17546
|
+
const delta = Math.round((currentRate - priorBlockRate) * 100);
|
|
17547
|
+
if (delta === 0) return "";
|
|
17548
|
+
return " " + (delta > 0 ? import_chalk13.default.red(`\u25B2${delta}%`) : import_chalk13.default.green(`\u25BC${Math.abs(delta)}%`)) + import_chalk13.default.dim(" vs prior");
|
|
17549
|
+
})();
|
|
17550
|
+
const reads = toolMap.get("Read")?.calls ?? 0;
|
|
17551
|
+
const edits = (toolMap.get("Edit")?.calls ?? 0) + (toolMap.get("Write")?.calls ?? 0);
|
|
17552
|
+
const ratioLabel = reads > 0 ? import_chalk13.default.dim(`edit/read ${(edits / reads).toFixed(1)}`) : import_chalk13.default.dim("edit/read \u2013");
|
|
17553
|
+
const testLabel = testPasses + testFails > 0 ? import_chalk13.default.dim("tests ") + import_chalk13.default.green(`${testPasses}\u2713`) + (testFails > 0 ? " " + import_chalk13.default.red(`${testFails}\u2717`) : "") : import_chalk13.default.dim("tests \u2013");
|
|
17554
|
+
console.log("");
|
|
17555
|
+
console.log(" " + import_chalk13.default.bold("Protection Summary"));
|
|
17556
|
+
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
17557
|
+
console.log(
|
|
17558
|
+
" " + import_chalk13.default.dim("Intercepted") + " " + import_chalk13.default.white(num2(total)) + import_chalk13.default.dim(" tool calls")
|
|
17559
|
+
);
|
|
17560
|
+
console.log("");
|
|
17561
|
+
const COL1 = 18;
|
|
17562
|
+
const summaryRow = (icon, label, count, note, colorFn = (s) => s) => {
|
|
17563
|
+
const countStr = colorFn(num2(count));
|
|
17564
|
+
const noteStr = note ? import_chalk13.default.dim(" " + note) : "";
|
|
17565
|
+
console.log(" " + icon + " " + import_chalk13.default.white(label.padEnd(COL1)) + countStr + noteStr);
|
|
17566
|
+
};
|
|
17567
|
+
summaryRow(
|
|
17568
|
+
userApproved > 0 ? import_chalk13.default.green("\u2705") : import_chalk13.default.dim("\u2705"),
|
|
17569
|
+
"User approved",
|
|
17570
|
+
userApproved,
|
|
17571
|
+
userApproved === 0 ? "no popups this period" : void 0,
|
|
17572
|
+
userApproved > 0 ? (s) => import_chalk13.default.green(s) : (s) => import_chalk13.default.dim(s)
|
|
17573
|
+
);
|
|
17574
|
+
summaryRow(
|
|
17575
|
+
userDenied > 0 ? import_chalk13.default.red("\u{1F6AB}") : import_chalk13.default.dim("\u{1F6AB}"),
|
|
17576
|
+
"User denied",
|
|
17577
|
+
userDenied,
|
|
17578
|
+
void 0,
|
|
17579
|
+
userDenied > 0 ? (s) => import_chalk13.default.red(s) : (s) => import_chalk13.default.dim(s)
|
|
17580
|
+
);
|
|
17581
|
+
summaryRow(
|
|
17582
|
+
timedOut > 0 ? import_chalk13.default.yellow("\u23F1") : import_chalk13.default.dim("\u23F1"),
|
|
17583
|
+
"Timed out",
|
|
17584
|
+
timedOut,
|
|
17585
|
+
timedOut > 0 ? "no approval response" : void 0,
|
|
17586
|
+
timedOut > 0 ? (s) => import_chalk13.default.yellow(s) : (s) => import_chalk13.default.dim(s)
|
|
17587
|
+
);
|
|
17588
|
+
summaryRow(
|
|
17589
|
+
hardBlocked > 0 ? import_chalk13.default.red("\u{1F6D1}") : import_chalk13.default.dim("\u{1F6D1}"),
|
|
17590
|
+
"Auto-blocked",
|
|
17591
|
+
hardBlocked,
|
|
17592
|
+
void 0,
|
|
17593
|
+
hardBlocked > 0 ? (s) => import_chalk13.default.red(s) : (s) => import_chalk13.default.dim(s)
|
|
17594
|
+
);
|
|
17595
|
+
summaryRow(
|
|
17596
|
+
dlpBlocked > 0 ? import_chalk13.default.yellow("\u{1F6A8}") : import_chalk13.default.dim("\u{1F6A8}"),
|
|
17597
|
+
"DLP blocked",
|
|
17598
|
+
dlpBlocked,
|
|
17599
|
+
void 0,
|
|
17600
|
+
dlpBlocked > 0 ? (s) => import_chalk13.default.yellow(s) : (s) => import_chalk13.default.dim(s)
|
|
17601
|
+
);
|
|
17602
|
+
summaryRow(
|
|
17603
|
+
observeDlp > 0 ? import_chalk13.default.blue("\u{1F441}") : import_chalk13.default.dim("\u{1F441}"),
|
|
17604
|
+
"DLP (observe)",
|
|
17605
|
+
observeDlp,
|
|
17606
|
+
observeDlp > 0 ? "would-block in strict mode" : void 0,
|
|
17607
|
+
observeDlp > 0 ? (s) => import_chalk13.default.blue(s) : (s) => import_chalk13.default.dim(s)
|
|
17608
|
+
);
|
|
17609
|
+
summaryRow(
|
|
17610
|
+
loopHits > 0 ? import_chalk13.default.yellow("\u{1F504}") : import_chalk13.default.dim("\u{1F504}"),
|
|
17611
|
+
"Loops detected",
|
|
17612
|
+
loopHits,
|
|
17613
|
+
void 0,
|
|
17614
|
+
loopHits > 0 ? (s) => import_chalk13.default.yellow(s) : (s) => import_chalk13.default.dim(s)
|
|
17615
|
+
);
|
|
17616
|
+
if (trendLabel || ratioLabel || testPasses + testFails > 0) {
|
|
16802
17617
|
console.log("");
|
|
16803
|
-
console.log(
|
|
16804
|
-
|
|
16805
|
-
|
|
16806
|
-
|
|
16807
|
-
|
|
16808
|
-
|
|
16809
|
-
|
|
16810
|
-
|
|
16811
|
-
|
|
16812
|
-
|
|
16813
|
-
|
|
16814
|
-
|
|
16815
|
-
|
|
16816
|
-
|
|
16817
|
-
|
|
16818
|
-
|
|
17618
|
+
console.log(" " + ratioLabel + " " + testLabel + trendLabel);
|
|
17619
|
+
}
|
|
17620
|
+
console.log("");
|
|
17621
|
+
const toolHeaderRaw = "Top Tools";
|
|
17622
|
+
const blockHeaderRaw = "Top Blocks";
|
|
17623
|
+
console.log(
|
|
17624
|
+
" " + import_chalk13.default.bold(toolHeaderRaw) + " ".repeat(COL - toolHeaderRaw.length) + " " + import_chalk13.default.bold(blockHeaderRaw)
|
|
17625
|
+
);
|
|
17626
|
+
console.log(" " + import_chalk13.default.dim("\u2500".repeat(COL)) + " " + import_chalk13.default.dim("\u2500".repeat(COL)));
|
|
17627
|
+
const rows = Math.max(topTools.length, topBlocks.length, 1);
|
|
17628
|
+
for (let i = 0; i < rows; i++) {
|
|
17629
|
+
let leftStyled = " ".repeat(COL);
|
|
17630
|
+
if (i < topTools.length) {
|
|
17631
|
+
const [tool, { calls }] = topTools[i];
|
|
17632
|
+
const label = tool.length > LABEL - 1 ? tool.slice(0, LABEL - 2) + "\u2026" : tool;
|
|
17633
|
+
const countStr = num2(calls).padStart(TOOL_COUNT_W);
|
|
17634
|
+
const b = colorBar(calls, maxTool, BAR);
|
|
17635
|
+
const rawLen = LABEL + BAR + 1 + TOOL_COUNT_W;
|
|
17636
|
+
const pad = Math.max(0, COL - rawLen);
|
|
17637
|
+
leftStyled = import_chalk13.default.white(label.padEnd(LABEL)) + b + " " + import_chalk13.default.white(countStr) + " ".repeat(pad);
|
|
17638
|
+
}
|
|
17639
|
+
let rightStyled = "";
|
|
17640
|
+
if (i < topBlocks.length) {
|
|
17641
|
+
const [reason, count] = topBlocks[i];
|
|
17642
|
+
const readable = humanBlockReason(reason);
|
|
17643
|
+
const label = readable.length > LABEL - 1 ? readable.slice(0, LABEL - 2) + "\u2026" : readable;
|
|
17644
|
+
const countStr = num2(count).padStart(BLOCK_COUNT_W);
|
|
17645
|
+
const b = colorBar(count, maxBlock, BAR);
|
|
17646
|
+
rightStyled = import_chalk13.default.white(label.padEnd(LABEL)) + b + " " + import_chalk13.default.red(countStr);
|
|
17647
|
+
}
|
|
17648
|
+
console.log(" " + leftStyled + " " + rightStyled);
|
|
17649
|
+
}
|
|
17650
|
+
if (topBlocks.length === 0) {
|
|
17651
|
+
console.log(" " + " ".repeat(COL) + " " + import_chalk13.default.dim("nothing blocked \u2713"));
|
|
17652
|
+
}
|
|
17653
|
+
if (agentMap.size >= 1) {
|
|
16819
17654
|
console.log("");
|
|
16820
|
-
console.log(" " + import_chalk13.default.bold("
|
|
17655
|
+
console.log(" " + import_chalk13.default.bold("Agents"));
|
|
16821
17656
|
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
16822
|
-
|
|
16823
|
-
|
|
16824
|
-
|
|
16825
|
-
|
|
16826
|
-
|
|
16827
|
-
const summaryRow = (icon, label, count, note, colorFn = (s) => s) => {
|
|
16828
|
-
const countStr = colorFn(num2(count));
|
|
16829
|
-
const noteStr = note ? import_chalk13.default.dim(" " + note) : "";
|
|
16830
|
-
console.log(" " + icon + " " + import_chalk13.default.white(label.padEnd(COL1)) + countStr + noteStr);
|
|
16831
|
-
};
|
|
16832
|
-
summaryRow(
|
|
16833
|
-
userApproved > 0 ? import_chalk13.default.green("\u2705") : import_chalk13.default.dim("\u2705"),
|
|
16834
|
-
"User approved",
|
|
16835
|
-
userApproved,
|
|
16836
|
-
userApproved === 0 ? "no popups this period" : void 0,
|
|
16837
|
-
userApproved > 0 ? (s) => import_chalk13.default.green(s) : (s) => import_chalk13.default.dim(s)
|
|
16838
|
-
);
|
|
16839
|
-
summaryRow(
|
|
16840
|
-
userDenied > 0 ? import_chalk13.default.red("\u{1F6AB}") : import_chalk13.default.dim("\u{1F6AB}"),
|
|
16841
|
-
"User denied",
|
|
16842
|
-
userDenied,
|
|
16843
|
-
void 0,
|
|
16844
|
-
userDenied > 0 ? (s) => import_chalk13.default.red(s) : (s) => import_chalk13.default.dim(s)
|
|
16845
|
-
);
|
|
16846
|
-
summaryRow(
|
|
16847
|
-
timedOut > 0 ? import_chalk13.default.yellow("\u23F1") : import_chalk13.default.dim("\u23F1"),
|
|
16848
|
-
"Timed out",
|
|
16849
|
-
timedOut,
|
|
16850
|
-
timedOut > 0 ? "no approval response" : void 0,
|
|
16851
|
-
timedOut > 0 ? (s) => import_chalk13.default.yellow(s) : (s) => import_chalk13.default.dim(s)
|
|
16852
|
-
);
|
|
16853
|
-
summaryRow(
|
|
16854
|
-
hardBlocked > 0 ? import_chalk13.default.red("\u{1F6D1}") : import_chalk13.default.dim("\u{1F6D1}"),
|
|
16855
|
-
"Auto-blocked",
|
|
16856
|
-
hardBlocked,
|
|
16857
|
-
void 0,
|
|
16858
|
-
hardBlocked > 0 ? (s) => import_chalk13.default.red(s) : (s) => import_chalk13.default.dim(s)
|
|
16859
|
-
);
|
|
16860
|
-
summaryRow(
|
|
16861
|
-
dlpBlocked > 0 ? import_chalk13.default.yellow("\u{1F6A8}") : import_chalk13.default.dim("\u{1F6A8}"),
|
|
16862
|
-
"DLP blocked",
|
|
16863
|
-
dlpBlocked,
|
|
16864
|
-
void 0,
|
|
16865
|
-
dlpBlocked > 0 ? (s) => import_chalk13.default.yellow(s) : (s) => import_chalk13.default.dim(s)
|
|
16866
|
-
);
|
|
16867
|
-
summaryRow(
|
|
16868
|
-
observeDlp > 0 ? import_chalk13.default.blue("\u{1F441}") : import_chalk13.default.dim("\u{1F441}"),
|
|
16869
|
-
"DLP (observe)",
|
|
16870
|
-
observeDlp,
|
|
16871
|
-
observeDlp > 0 ? "would-block in strict mode" : void 0,
|
|
16872
|
-
observeDlp > 0 ? (s) => import_chalk13.default.blue(s) : (s) => import_chalk13.default.dim(s)
|
|
16873
|
-
);
|
|
16874
|
-
summaryRow(
|
|
16875
|
-
loopHits > 0 ? import_chalk13.default.yellow("\u{1F504}") : import_chalk13.default.dim("\u{1F504}"),
|
|
16876
|
-
"Loops detected",
|
|
16877
|
-
loopHits,
|
|
16878
|
-
void 0,
|
|
16879
|
-
loopHits > 0 ? (s) => import_chalk13.default.yellow(s) : (s) => import_chalk13.default.dim(s)
|
|
16880
|
-
);
|
|
16881
|
-
if (trendLabel || ratioLabel || testPasses + testFails > 0) {
|
|
16882
|
-
console.log("");
|
|
16883
|
-
console.log(" " + ratioLabel + " " + testLabel + trendLabel);
|
|
17657
|
+
const maxAgent = Math.max(...agentMap.values(), 1);
|
|
17658
|
+
for (const [agent, count] of [...agentMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
17659
|
+
const label = agent.slice(0, LABEL - 1);
|
|
17660
|
+
const b = colorBar(count, maxAgent, BAR);
|
|
17661
|
+
console.log(" " + import_chalk13.default.white(label.padEnd(LABEL)) + b + " " + import_chalk13.default.white(num2(count)));
|
|
16884
17662
|
}
|
|
17663
|
+
}
|
|
17664
|
+
if (mcpMap.size > 0) {
|
|
16885
17665
|
console.log("");
|
|
16886
|
-
|
|
16887
|
-
|
|
16888
|
-
|
|
16889
|
-
|
|
16890
|
-
|
|
16891
|
-
|
|
16892
|
-
|
|
16893
|
-
|
|
16894
|
-
|
|
16895
|
-
|
|
16896
|
-
|
|
16897
|
-
|
|
16898
|
-
|
|
16899
|
-
|
|
16900
|
-
|
|
16901
|
-
|
|
16902
|
-
|
|
16903
|
-
|
|
16904
|
-
|
|
16905
|
-
|
|
16906
|
-
|
|
16907
|
-
|
|
16908
|
-
|
|
16909
|
-
|
|
16910
|
-
|
|
16911
|
-
|
|
16912
|
-
|
|
16913
|
-
|
|
16914
|
-
|
|
16915
|
-
|
|
16916
|
-
|
|
16917
|
-
|
|
16918
|
-
|
|
16919
|
-
|
|
16920
|
-
|
|
16921
|
-
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
16922
|
-
const maxAgent = Math.max(...agentMap.values(), 1);
|
|
16923
|
-
for (const [agent, count] of [...agentMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
16924
|
-
const label = agent.slice(0, LABEL - 1);
|
|
16925
|
-
const b = colorBar(count, maxAgent, BAR);
|
|
16926
|
-
console.log(" " + import_chalk13.default.white(label.padEnd(LABEL)) + b + " " + import_chalk13.default.white(num2(count)));
|
|
16927
|
-
}
|
|
16928
|
-
}
|
|
16929
|
-
if (mcpMap.size > 0) {
|
|
16930
|
-
console.log("");
|
|
16931
|
-
console.log(" " + import_chalk13.default.bold("MCP Servers"));
|
|
16932
|
-
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
16933
|
-
const maxMcp = Math.max(...mcpMap.values(), 1);
|
|
16934
|
-
for (const [server, count] of [...mcpMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
16935
|
-
const label = server.slice(0, LABEL - 1).padEnd(LABEL);
|
|
16936
|
-
const b = colorBar(count, maxMcp, BAR);
|
|
16937
|
-
console.log(" " + import_chalk13.default.white(label) + b + " " + import_chalk13.default.white(num2(count)));
|
|
16938
|
-
}
|
|
16939
|
-
}
|
|
16940
|
-
if (hourMap.size > 0) {
|
|
16941
|
-
const BLOCKS = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
16942
|
-
const maxHour = Math.max(...hourMap.values(), 1);
|
|
16943
|
-
const bar = Array.from({ length: 24 }, (_, h) => {
|
|
16944
|
-
const v = hourMap.get(h) ?? 0;
|
|
16945
|
-
return BLOCKS[Math.round(v / maxHour * 8)];
|
|
16946
|
-
}).join("");
|
|
16947
|
-
console.log("");
|
|
16948
|
-
console.log(" " + import_chalk13.default.bold("Hour of Day") + import_chalk13.default.dim(" (local, 0h \u2013 23h)"));
|
|
16949
|
-
console.log(" " + import_chalk13.default.cyan(bar));
|
|
16950
|
-
console.log(" " + import_chalk13.default.dim("0h" + " ".repeat(10) + "12h" + " ".repeat(7) + "23h"));
|
|
16951
|
-
}
|
|
16952
|
-
if (dailyList.length > 1) {
|
|
16953
|
-
console.log("");
|
|
16954
|
-
console.log(" " + import_chalk13.default.bold("Daily Activity"));
|
|
16955
|
-
console.log(" " + import_chalk13.default.dim("\u2500".repeat(W - 2)));
|
|
16956
|
-
const DAY_BAR = Math.max(8, Math.min(30, W - 36));
|
|
16957
|
-
for (const [dateKey, { calls, blocked: db }] of dailyList) {
|
|
16958
|
-
const label = fmtDate(dateKey).padEnd(10);
|
|
16959
|
-
const b = colorBar(calls, maxDaily, DAY_BAR);
|
|
16960
|
-
const dayCost = costByDay.get(dateKey);
|
|
16961
|
-
const costNote = dayCost ? import_chalk13.default.magenta(` ${fmtCost2(dayCost)}`) : "";
|
|
16962
|
-
const blockNote = db > 0 ? import_chalk13.default.red(` ${db} blocked`) : "";
|
|
16963
|
-
console.log(
|
|
16964
|
-
" " + import_chalk13.default.dim(label) + " " + b + " " + import_chalk13.default.white(num2(calls)) + blockNote + costNote
|
|
16965
|
-
);
|
|
16966
|
-
}
|
|
16967
|
-
}
|
|
16968
|
-
const totalTokens = costInputTokens + costOutputTokens + costCacheWrite + costCacheRead;
|
|
16969
|
-
if (totalTokens > 0) {
|
|
16970
|
-
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
16971
|
-
console.log("");
|
|
16972
|
-
console.log(" " + import_chalk13.default.bold("Tokens") + " " + import_chalk13.default.dim(`${num2(totalTokens)} total`));
|
|
16973
|
-
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
16974
|
-
const TOK_BAR = Math.max(6, Math.min(20, W - 30));
|
|
16975
|
-
const TOK_LABEL = 14;
|
|
16976
|
-
const maxNonCache = Math.max(costInputTokens, costOutputTokens, costCacheWrite, 1);
|
|
16977
|
-
const nonCacheRows = [
|
|
16978
|
-
["Input", costInputTokens, import_chalk13.default.cyan(num2(costInputTokens))],
|
|
16979
|
-
["Output", costOutputTokens, import_chalk13.default.white(num2(costOutputTokens))],
|
|
16980
|
-
["Cache write", costCacheWrite, import_chalk13.default.yellow(num2(costCacheWrite))]
|
|
16981
|
-
];
|
|
16982
|
-
for (const [label, count, colored] of nonCacheRows) {
|
|
16983
|
-
if (count === 0) continue;
|
|
16984
|
-
const b = colorBar(count, maxNonCache, TOK_BAR);
|
|
16985
|
-
console.log(" " + import_chalk13.default.white(label.padEnd(TOK_LABEL)) + b + " " + colored);
|
|
16986
|
-
}
|
|
16987
|
-
if (costCacheRead > 0) {
|
|
16988
|
-
const cacheBar = colorBar(costCacheRead, costCacheRead, TOK_BAR);
|
|
16989
|
-
const pct = cacheHitPct > 0 ? import_chalk13.default.dim(` ${cacheHitPct}% hit rate`) : "";
|
|
16990
|
-
console.log(
|
|
16991
|
-
" " + import_chalk13.default.white("Cache read".padEnd(TOK_LABEL)) + cacheBar + " " + import_chalk13.default.green(num2(costCacheRead)) + pct
|
|
16992
|
-
);
|
|
16993
|
-
}
|
|
16994
|
-
}
|
|
16995
|
-
if (costUSD > 0) {
|
|
16996
|
-
const periodDays = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 864e5));
|
|
16997
|
-
const avgPerDay = costUSD / periodDays;
|
|
16998
|
-
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
16999
|
-
const costHeaderRight = [
|
|
17000
|
-
import_chalk13.default.yellow(fmtCost2(costUSD)),
|
|
17001
|
-
import_chalk13.default.dim(`avg ${fmtCost2(avgPerDay)}/day`),
|
|
17002
|
-
cacheHitPct > 0 ? import_chalk13.default.dim(`${cacheHitPct}% cache hit`) : null
|
|
17003
|
-
].filter(Boolean).join(import_chalk13.default.dim(" \xB7 "));
|
|
17004
|
-
console.log("");
|
|
17005
|
-
console.log(" " + import_chalk13.default.bold("Cost") + " " + costHeaderRight);
|
|
17006
|
-
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
17007
|
-
if (codexCostUSD > 0)
|
|
17008
|
-
costByModel.set(
|
|
17009
|
-
"codex (openai)",
|
|
17010
|
-
(costByModel.get("codex (openai)") ?? 0) + codexCostUSD
|
|
17011
|
-
);
|
|
17012
|
-
const modelList = [...costByModel.entries()].sort((a, b) => b[1] - a[1]);
|
|
17013
|
-
const maxModelCost = Math.max(...modelList.map(([, v]) => v), 1e-9);
|
|
17014
|
-
const MODEL_LABEL = 22;
|
|
17015
|
-
const MODEL_BAR = Math.max(6, Math.min(20, W - MODEL_LABEL - 12));
|
|
17016
|
-
for (const [model, cost] of modelList) {
|
|
17017
|
-
const label = model.length > MODEL_LABEL - 1 ? model.slice(0, MODEL_LABEL - 2) + "\u2026" : model;
|
|
17018
|
-
const b = colorBar(cost, maxModelCost, MODEL_BAR);
|
|
17019
|
-
console.log(
|
|
17020
|
-
" " + import_chalk13.default.white(label.padEnd(MODEL_LABEL)) + b + " " + import_chalk13.default.yellow(fmtCost2(cost))
|
|
17021
|
-
);
|
|
17022
|
-
}
|
|
17666
|
+
console.log(" " + import_chalk13.default.bold("MCP Servers"));
|
|
17667
|
+
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
17668
|
+
const maxMcp = Math.max(...mcpMap.values(), 1);
|
|
17669
|
+
for (const [server, count] of [...mcpMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
17670
|
+
const label = server.slice(0, LABEL - 1).padEnd(LABEL);
|
|
17671
|
+
const b = colorBar(count, maxMcp, BAR);
|
|
17672
|
+
console.log(" " + import_chalk13.default.white(label) + b + " " + import_chalk13.default.white(num2(count)));
|
|
17673
|
+
}
|
|
17674
|
+
}
|
|
17675
|
+
if (hourMap.size > 0) {
|
|
17676
|
+
const BLOCKS = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
17677
|
+
const maxHour = Math.max(...hourMap.values(), 1);
|
|
17678
|
+
const bar = Array.from({ length: 24 }, (_, h) => {
|
|
17679
|
+
const v = hourMap.get(h) ?? 0;
|
|
17680
|
+
return BLOCKS[Math.round(v / maxHour * 8)];
|
|
17681
|
+
}).join("");
|
|
17682
|
+
console.log("");
|
|
17683
|
+
console.log(" " + import_chalk13.default.bold("Hour of Day") + import_chalk13.default.dim(" (local, 0h \u2013 23h)"));
|
|
17684
|
+
console.log(" " + import_chalk13.default.cyan(bar));
|
|
17685
|
+
console.log(" " + import_chalk13.default.dim("0h" + " ".repeat(10) + "12h" + " ".repeat(7) + "23h"));
|
|
17686
|
+
}
|
|
17687
|
+
if (dailyList.length > 1) {
|
|
17688
|
+
console.log("");
|
|
17689
|
+
console.log(" " + import_chalk13.default.bold("Daily Activity"));
|
|
17690
|
+
console.log(" " + import_chalk13.default.dim("\u2500".repeat(W - 2)));
|
|
17691
|
+
const DAY_BAR = Math.max(8, Math.min(30, W - 36));
|
|
17692
|
+
for (const [dateKey, { calls, blocked: db }] of dailyList) {
|
|
17693
|
+
const label = fmtDate(dateKey).padEnd(10);
|
|
17694
|
+
const b = colorBar(calls, maxDaily, DAY_BAR);
|
|
17695
|
+
const dayCost = costByDay.get(dateKey);
|
|
17696
|
+
const costNote = dayCost ? import_chalk13.default.magenta(` ${fmtCost2(dayCost)}`) : "";
|
|
17697
|
+
const blockNote = db > 0 ? import_chalk13.default.red(` ${db} blocked`) : "";
|
|
17698
|
+
console.log(
|
|
17699
|
+
" " + import_chalk13.default.dim(label) + " " + b + " " + import_chalk13.default.white(num2(calls)) + blockNote + costNote
|
|
17700
|
+
);
|
|
17023
17701
|
}
|
|
17024
|
-
|
|
17025
|
-
|
|
17026
|
-
|
|
17027
|
-
|
|
17028
|
-
|
|
17029
|
-
|
|
17030
|
-
|
|
17702
|
+
}
|
|
17703
|
+
const totalTokens = costInputTokens + costOutputTokens + costCacheWrite + costCacheRead;
|
|
17704
|
+
if (totalTokens > 0) {
|
|
17705
|
+
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
17706
|
+
console.log("");
|
|
17707
|
+
console.log(" " + import_chalk13.default.bold("Tokens") + " " + import_chalk13.default.dim(`${num2(totalTokens)} total`));
|
|
17708
|
+
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
17709
|
+
const TOK_BAR = Math.max(6, Math.min(20, W - 30));
|
|
17710
|
+
const TOK_LABEL = 14;
|
|
17711
|
+
const maxNonCache = Math.max(costInputTokens, costOutputTokens, costCacheWrite, 1);
|
|
17712
|
+
const nonCacheRows = [
|
|
17713
|
+
["Input", costInputTokens, import_chalk13.default.cyan(num2(costInputTokens))],
|
|
17714
|
+
["Output", costOutputTokens, import_chalk13.default.white(num2(costOutputTokens))],
|
|
17715
|
+
["Cache write", costCacheWrite, import_chalk13.default.yellow(num2(costCacheWrite))]
|
|
17716
|
+
];
|
|
17717
|
+
for (const [label, count, colored] of nonCacheRows) {
|
|
17718
|
+
if (count === 0) continue;
|
|
17719
|
+
const b = colorBar(count, maxNonCache, TOK_BAR);
|
|
17720
|
+
console.log(" " + import_chalk13.default.white(label.padEnd(TOK_LABEL)) + b + " " + colored);
|
|
17721
|
+
}
|
|
17722
|
+
if (costCacheRead > 0) {
|
|
17723
|
+
const cacheBar = colorBar(costCacheRead, costCacheRead, TOK_BAR);
|
|
17724
|
+
const pct = cacheHitPct > 0 ? import_chalk13.default.dim(` ${cacheHitPct}% hit rate`) : "";
|
|
17031
17725
|
console.log(
|
|
17032
|
-
" " + import_chalk13.default.
|
|
17033
|
-
`${responseDlpEntries.length} secret${responseDlpEntries.length !== 1 ? "s" : ""} found in Claude response text`
|
|
17034
|
-
)
|
|
17726
|
+
" " + import_chalk13.default.white("Cache read".padEnd(TOK_LABEL)) + cacheBar + " " + import_chalk13.default.green(num2(costCacheRead)) + pct
|
|
17035
17727
|
);
|
|
17036
|
-
|
|
17728
|
+
}
|
|
17729
|
+
}
|
|
17730
|
+
if (costUSD > 0) {
|
|
17731
|
+
const periodDays = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 864e5));
|
|
17732
|
+
const avgPerDay = costUSD / periodDays;
|
|
17733
|
+
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
17734
|
+
const costHeaderRight = [
|
|
17735
|
+
import_chalk13.default.yellow(fmtCost2(costUSD)),
|
|
17736
|
+
import_chalk13.default.dim(`avg ${fmtCost2(avgPerDay)}/day`),
|
|
17737
|
+
cacheHitPct > 0 ? import_chalk13.default.dim(`${cacheHitPct}% cache hit`) : null
|
|
17738
|
+
].filter(Boolean).join(import_chalk13.default.dim(" \xB7 "));
|
|
17739
|
+
console.log("");
|
|
17740
|
+
console.log(" " + import_chalk13.default.bold("Cost") + " " + costHeaderRight);
|
|
17741
|
+
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
17742
|
+
if (codexUSD > 0)
|
|
17743
|
+
costByModel.set("codex (openai)", (costByModel.get("codex (openai)") ?? 0) + codexUSD);
|
|
17744
|
+
if (geminiUSD > 0)
|
|
17745
|
+
costByModel.set("gemini (google)", (costByModel.get("gemini (google)") ?? 0) + geminiUSD);
|
|
17746
|
+
const modelList = [...costByModel.entries()].sort((a, b) => b[1] - a[1]);
|
|
17747
|
+
const maxModelCost = Math.max(...modelList.map(([, v]) => v), 1e-9);
|
|
17748
|
+
const MODEL_LABEL = 22;
|
|
17749
|
+
const MODEL_BAR = Math.max(6, Math.min(20, W - MODEL_LABEL - 12));
|
|
17750
|
+
for (const [model, cost] of modelList) {
|
|
17751
|
+
const label = model.length > MODEL_LABEL - 1 ? model.slice(0, MODEL_LABEL - 2) + "\u2026" : model;
|
|
17752
|
+
const b = colorBar(cost, maxModelCost, MODEL_BAR);
|
|
17037
17753
|
console.log(
|
|
17038
|
-
" " + import_chalk13.default.
|
|
17754
|
+
" " + import_chalk13.default.white(label.padEnd(MODEL_LABEL)) + b + " " + import_chalk13.default.yellow(fmtCost2(cost))
|
|
17039
17755
|
);
|
|
17040
|
-
console.log(" " + import_chalk13.default.yellow("Rotate affected keys immediately."));
|
|
17041
|
-
for (const e of responseDlpEntries.slice(0, 5)) {
|
|
17042
|
-
const ts = import_chalk13.default.dim(fmtDate(e.ts) + " ");
|
|
17043
|
-
const pattern = import_chalk13.default.red(e.dlpPattern ?? "DLP");
|
|
17044
|
-
const sample = import_chalk13.default.gray(e.dlpSample ?? "");
|
|
17045
|
-
console.log(` ${ts}${pattern} ${sample}`);
|
|
17046
|
-
}
|
|
17047
|
-
if (responseDlpEntries.length > 5) {
|
|
17048
|
-
console.log(import_chalk13.default.dim(` \u2026 and ${responseDlpEntries.length - 5} more`));
|
|
17049
|
-
}
|
|
17050
17756
|
}
|
|
17757
|
+
}
|
|
17758
|
+
if (responseDlpEntries.length > 0) {
|
|
17051
17759
|
console.log("");
|
|
17052
17760
|
console.log(
|
|
17053
|
-
" " + import_chalk13.default.
|
|
17761
|
+
" " + import_chalk13.default.red.bold("\u26A0\uFE0F Response DLP") + import_chalk13.default.dim(" \xB7 ") + import_chalk13.default.red(
|
|
17762
|
+
`${responseDlpEntries.length} secret${responseDlpEntries.length !== 1 ? "s" : ""} found in Claude response text`
|
|
17763
|
+
)
|
|
17054
17764
|
);
|
|
17055
|
-
console.log("");
|
|
17056
|
-
|
|
17765
|
+
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(60, W - 4))));
|
|
17766
|
+
console.log(
|
|
17767
|
+
" " + import_chalk13.default.yellow("These were NOT blocked \u2014 Claude included them in response prose.")
|
|
17768
|
+
);
|
|
17769
|
+
console.log(" " + import_chalk13.default.yellow("Rotate affected keys immediately."));
|
|
17770
|
+
for (const e of responseDlpEntries.slice(0, 5)) {
|
|
17771
|
+
const ts = import_chalk13.default.dim(fmtDate(e.ts) + " ");
|
|
17772
|
+
const pattern = import_chalk13.default.red(e.dlpPattern ?? "DLP");
|
|
17773
|
+
const sample = import_chalk13.default.gray(e.dlpSample ?? "");
|
|
17774
|
+
console.log(` ${ts}${pattern} ${sample}`);
|
|
17775
|
+
}
|
|
17776
|
+
if (responseDlpEntries.length > 5) {
|
|
17777
|
+
console.log(import_chalk13.default.dim(` \u2026 and ${responseDlpEntries.length - 5} more`));
|
|
17778
|
+
}
|
|
17779
|
+
}
|
|
17780
|
+
console.log("");
|
|
17781
|
+
console.log(
|
|
17782
|
+
" " + import_chalk13.default.dim("node9 audit --deny") + import_chalk13.default.dim(" \xB7 ") + import_chalk13.default.dim("node9 report --period today|7d|30d|month --no-tests")
|
|
17783
|
+
);
|
|
17784
|
+
console.log("");
|
|
17057
17785
|
}
|
|
17058
17786
|
|
|
17059
17787
|
// src/cli/commands/daemon-cmd.ts
|