@node9/proxy 1.19.4 → 1.20.1
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 +57 -102
- package/dist/cli.js +1660 -925
- package/dist/cli.mjs +1660 -924
- package/dist/dashboard.mjs +3503 -1343
- package/dist/index.js +127 -13
- package/dist/index.mjs +127 -13
- package/package.json +3 -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,64 @@ 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 titleWidth = (0, import_string_width.default)(titlePad);
|
|
7784
|
+
const titleSegment = titleWidth <= inner ? titlePad : titlePad.slice(0, inner);
|
|
7785
|
+
const dashFill = "\u2500".repeat(Math.max(0, inner - (0, import_string_width.default)(titleSegment)));
|
|
7786
|
+
out.push(import_chalk3.default.dim("\u256D\u2500") + import_chalk3.default.bold(titleSegment) + import_chalk3.default.dim(`${dashFill}\u2500\u256E`));
|
|
7787
|
+
for (const line of bodyLines) {
|
|
7788
|
+
const padding = " ".repeat(Math.max(0, inner - line.width));
|
|
7789
|
+
out.push(import_chalk3.default.dim("\u2502 ") + line.rendered + padding + import_chalk3.default.dim(" \u2502"));
|
|
7790
|
+
}
|
|
7791
|
+
out.push(import_chalk3.default.dim("\u2570" + "\u2500".repeat(inner + 2) + "\u256F"));
|
|
7792
|
+
return out;
|
|
7793
|
+
}
|
|
7794
|
+
function relativeDate(timestamp, now = /* @__PURE__ */ new Date()) {
|
|
7795
|
+
const t = new Date(timestamp).getTime();
|
|
7796
|
+
if (Number.isNaN(t)) return "?";
|
|
7797
|
+
const days = Math.floor((now.getTime() - t) / 864e5);
|
|
7798
|
+
if (days < 1) return "today";
|
|
7799
|
+
if (days > 90) return "90d+";
|
|
7800
|
+
return `${days}d`;
|
|
7801
|
+
}
|
|
7802
|
+
var import_chalk3, import_string_width, PANEL_WIDTH;
|
|
7649
7803
|
var init_scan_derive = __esm({
|
|
7650
7804
|
"src/cli/render/scan-derive.ts"() {
|
|
7651
7805
|
"use strict";
|
|
7652
7806
|
import_chalk3 = __toESM(require("chalk"));
|
|
7807
|
+
import_string_width = __toESM(require("string-width"));
|
|
7808
|
+
PANEL_WIDTH = 76;
|
|
7809
|
+
}
|
|
7810
|
+
});
|
|
7811
|
+
|
|
7812
|
+
// src/protection.ts
|
|
7813
|
+
var PROTECTIVE_SHIELD_DISCOUNTS;
|
|
7814
|
+
var init_protection = __esm({
|
|
7815
|
+
"src/protection.ts"() {
|
|
7816
|
+
"use strict";
|
|
7817
|
+
PROTECTIVE_SHIELD_DISCOUNTS = {
|
|
7818
|
+
"project-jail": 0.7
|
|
7819
|
+
};
|
|
7653
7820
|
}
|
|
7654
7821
|
});
|
|
7655
7822
|
|
|
@@ -7836,6 +8003,7 @@ async function ensurePricingLoaded() {
|
|
|
7836
8003
|
if (fromDisk && Object.keys(fromDisk).length > 0) {
|
|
7837
8004
|
memCache = fromDisk;
|
|
7838
8005
|
memCacheAt = Date.now();
|
|
8006
|
+
lookupCache.clear();
|
|
7839
8007
|
return;
|
|
7840
8008
|
}
|
|
7841
8009
|
const fetched = await fetchLiteLLMPricing();
|
|
@@ -7843,30 +8011,42 @@ async function ensurePricingLoaded() {
|
|
|
7843
8011
|
memCache = fetched;
|
|
7844
8012
|
memCacheAt = Date.now();
|
|
7845
8013
|
writeCache(fetched);
|
|
8014
|
+
lookupCache.clear();
|
|
7846
8015
|
return;
|
|
7847
8016
|
}
|
|
7848
8017
|
memCache = { ...BUNDLED_PRICING };
|
|
7849
8018
|
memCacheAt = Date.now();
|
|
8019
|
+
lookupCache.clear();
|
|
7850
8020
|
}
|
|
7851
8021
|
function pricingFor(model) {
|
|
7852
8022
|
const norm = normalizeModel(model);
|
|
8023
|
+
const cached = lookupCache.get(norm);
|
|
8024
|
+
if (cached !== void 0) return cached;
|
|
7853
8025
|
const sources = [];
|
|
7854
8026
|
if (memCache) sources.push(memCache);
|
|
7855
8027
|
sources.push(BUNDLED_PRICING);
|
|
8028
|
+
let resolved = null;
|
|
7856
8029
|
for (const source of sources) {
|
|
7857
8030
|
const exact = source[norm];
|
|
7858
|
-
if (exact)
|
|
8031
|
+
if (exact) {
|
|
8032
|
+
resolved = exact;
|
|
8033
|
+
break;
|
|
8034
|
+
}
|
|
7859
8035
|
let best = null;
|
|
7860
8036
|
for (const key of Object.keys(source)) {
|
|
7861
8037
|
if (norm.startsWith(key.toLowerCase()) && (best === null || key.length > best.length)) {
|
|
7862
8038
|
best = key;
|
|
7863
8039
|
}
|
|
7864
8040
|
}
|
|
7865
|
-
if (best)
|
|
8041
|
+
if (best) {
|
|
8042
|
+
resolved = source[best];
|
|
8043
|
+
break;
|
|
8044
|
+
}
|
|
7866
8045
|
}
|
|
7867
|
-
|
|
8046
|
+
lookupCache.set(norm, resolved);
|
|
8047
|
+
return resolved;
|
|
7868
8048
|
}
|
|
7869
|
-
var import_fs15, import_path17, import_os14, LITELLM_URL, BUNDLED_PRICING, CACHE_FILE, TTL_MS, memCache, memCacheAt;
|
|
8049
|
+
var import_fs15, import_path17, import_os14, LITELLM_URL, BUNDLED_PRICING, CACHE_FILE, TTL_MS, memCache, memCacheAt, lookupCache;
|
|
7870
8050
|
var init_litellm = __esm({
|
|
7871
8051
|
"src/pricing/litellm.ts"() {
|
|
7872
8052
|
"use strict";
|
|
@@ -7903,6 +8083,7 @@ var init_litellm = __esm({
|
|
|
7903
8083
|
TTL_MS = 24 * 60 * 60 * 1e3;
|
|
7904
8084
|
memCache = null;
|
|
7905
8085
|
memCacheAt = 0;
|
|
8086
|
+
lookupCache = /* @__PURE__ */ new Map();
|
|
7906
8087
|
}
|
|
7907
8088
|
});
|
|
7908
8089
|
|
|
@@ -7969,7 +8150,7 @@ function parseJSONLFile(filePath, fallbackWorkingDir) {
|
|
|
7969
8150
|
}
|
|
7970
8151
|
return daily;
|
|
7971
8152
|
}
|
|
7972
|
-
function collectEntries() {
|
|
8153
|
+
function collectEntries(sinceMs) {
|
|
7973
8154
|
const projectsDir = import_path18.default.join(import_os15.default.homedir(), ".claude", "projects");
|
|
7974
8155
|
if (!import_fs16.default.existsSync(projectsDir)) return [];
|
|
7975
8156
|
const combined = /* @__PURE__ */ new Map();
|
|
@@ -7994,7 +8175,15 @@ function collectEntries() {
|
|
|
7994
8175
|
}
|
|
7995
8176
|
const fallbackWorkingDir = decodeProjectDirName(dir);
|
|
7996
8177
|
for (const file of files) {
|
|
7997
|
-
const
|
|
8178
|
+
const filePath = import_path18.default.join(dirPath, file);
|
|
8179
|
+
if (sinceMs !== void 0) {
|
|
8180
|
+
try {
|
|
8181
|
+
if (import_fs16.default.statSync(filePath).mtimeMs < sinceMs) continue;
|
|
8182
|
+
} catch {
|
|
8183
|
+
continue;
|
|
8184
|
+
}
|
|
8185
|
+
}
|
|
8186
|
+
const entries = parseJSONLFile(filePath, fallbackWorkingDir);
|
|
7998
8187
|
for (const [key, e] of entries) {
|
|
7999
8188
|
const prev = combined.get(key);
|
|
8000
8189
|
if (prev) {
|
|
@@ -8829,7 +9018,16 @@ function buildRecurringPatternSet(findings) {
|
|
|
8829
9018
|
}
|
|
8830
9019
|
return recurring;
|
|
8831
9020
|
}
|
|
8832
|
-
function
|
|
9021
|
+
function emptyScanDedup() {
|
|
9022
|
+
return { findingsKeys: /* @__PURE__ */ new Set(), dlpKeys: /* @__PURE__ */ new Set() };
|
|
9023
|
+
}
|
|
9024
|
+
function findingKey(ruleName, inputPreview, projLabel) {
|
|
9025
|
+
return `${ruleName ?? "<unnamed>"}|${inputPreview}|${projLabel}`;
|
|
9026
|
+
}
|
|
9027
|
+
function dlpKey(patternName, redactedSample, projLabel) {
|
|
9028
|
+
return `${patternName}|${redactedSample}|${projLabel}`;
|
|
9029
|
+
}
|
|
9030
|
+
function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sessionId, agent, result, dedup) {
|
|
8833
9031
|
const fsVerdict = analyzeFsOperation(command);
|
|
8834
9032
|
if (!fsVerdict) return false;
|
|
8835
9033
|
const synthRule = {
|
|
@@ -8852,10 +9050,9 @@ function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sess
|
|
|
8852
9050
|
rule: synthRule
|
|
8853
9051
|
};
|
|
8854
9052
|
const inputPreview = preview(input, 120);
|
|
8855
|
-
const
|
|
8856
|
-
|
|
8857
|
-
|
|
8858
|
-
if (!isDupe) {
|
|
9053
|
+
const k = findingKey(synthRule.name, inputPreview, projLabel);
|
|
9054
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9055
|
+
dedup.findingsKeys.add(k);
|
|
8859
9056
|
result.findings.push({
|
|
8860
9057
|
source: synthSource,
|
|
8861
9058
|
toolName,
|
|
@@ -8940,22 +9137,15 @@ function buildRuleSources() {
|
|
|
8940
9137
|
sources.push({ shieldName, shieldLabel: shieldName, sourceType: "shield", rule });
|
|
8941
9138
|
}
|
|
8942
9139
|
}
|
|
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 {
|
|
9140
|
+
for (const rule of DEFAULT_CONFIG.policy.smartRules) {
|
|
9141
|
+
if (!rule.name) continue;
|
|
9142
|
+
if (rule.name.startsWith("shield:")) continue;
|
|
9143
|
+
sources.push({
|
|
9144
|
+
shieldName: "default",
|
|
9145
|
+
shieldLabel: "Default Rules",
|
|
9146
|
+
sourceType: "default",
|
|
9147
|
+
rule
|
|
9148
|
+
});
|
|
8959
9149
|
}
|
|
8960
9150
|
return sources;
|
|
8961
9151
|
}
|
|
@@ -9041,178 +9231,53 @@ function renderProgressBar(done, total, lines) {
|
|
|
9041
9231
|
`\r ${import_chalk5.default.cyan("Scanning")} [${import_chalk5.default.cyan(bar)}] ${import_chalk5.default.dim(fileLabel)}${lineLabel} `
|
|
9042
9232
|
);
|
|
9043
9233
|
}
|
|
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;
|
|
9234
|
+
function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, result, dedup, onProgress, onLine) {
|
|
9235
|
+
result.filesScanned++;
|
|
9236
|
+
result.sessions++;
|
|
9237
|
+
onProgress?.(result.filesScanned);
|
|
9238
|
+
const sessionId = file.replace(/\.jsonl$/, "");
|
|
9239
|
+
let raw;
|
|
9061
9240
|
try {
|
|
9062
|
-
|
|
9241
|
+
raw = import_fs19.default.readFileSync(import_path21.default.join(projPath, file), "utf-8");
|
|
9063
9242
|
} catch {
|
|
9064
|
-
return
|
|
9243
|
+
return;
|
|
9065
9244
|
}
|
|
9066
|
-
const
|
|
9067
|
-
|
|
9068
|
-
|
|
9245
|
+
const sessionCalls = [];
|
|
9246
|
+
const toolUseFilePaths = /* @__PURE__ */ new Map();
|
|
9247
|
+
let firstDlpTs = null;
|
|
9248
|
+
let firstEditTs = null;
|
|
9249
|
+
for (const line of raw.split("\n")) {
|
|
9250
|
+
if (!line.trim()) continue;
|
|
9251
|
+
onLine?.();
|
|
9252
|
+
let entry;
|
|
9069
9253
|
try {
|
|
9070
|
-
|
|
9254
|
+
entry = JSON.parse(line);
|
|
9071
9255
|
} catch {
|
|
9072
9256
|
continue;
|
|
9073
9257
|
}
|
|
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;
|
|
9258
|
+
if (entry.type !== "assistant" && entry.type !== "user") continue;
|
|
9259
|
+
if (startDate && entry.timestamp) {
|
|
9260
|
+
if (new Date(entry.timestamp) < startDate) continue;
|
|
9082
9261
|
}
|
|
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);
|
|
9262
|
+
if (entry.timestamp) {
|
|
9263
|
+
if (!result.firstDate || entry.timestamp < result.firstDate)
|
|
9264
|
+
result.firstDate = entry.timestamp;
|
|
9265
|
+
if (!result.lastDate || entry.timestamp > result.lastDate) result.lastDate = entry.timestamp;
|
|
9266
|
+
}
|
|
9267
|
+
if (entry.type === "user") {
|
|
9268
|
+
const content2 = entry.message?.content;
|
|
9269
|
+
if (Array.isArray(content2)) {
|
|
9270
|
+
const text = content2.filter((b) => b.type === "text").map((b) => b["text"] ?? "").join("\n");
|
|
9271
|
+
if (text) {
|
|
9272
|
+
const dlpMatch = scanArgs({ text });
|
|
9206
9273
|
if (dlpMatch) {
|
|
9207
|
-
|
|
9208
|
-
|
|
9209
|
-
(
|
|
9210
|
-
);
|
|
9211
|
-
if (!isDupe) {
|
|
9274
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9275
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9276
|
+
dedup.dlpKeys.add(k);
|
|
9212
9277
|
result.dlpFindings.push({
|
|
9213
9278
|
patternName: dlpMatch.patternName,
|
|
9214
9279
|
redactedSample: dlpMatch.redactedSample,
|
|
9215
|
-
toolName,
|
|
9280
|
+
toolName: "user-prompt",
|
|
9216
9281
|
timestamp: entry.timestamp ?? "",
|
|
9217
9282
|
project: projLabel,
|
|
9218
9283
|
sessionId,
|
|
@@ -9220,102 +9285,252 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
|
|
|
9220
9285
|
});
|
|
9221
9286
|
}
|
|
9222
9287
|
}
|
|
9223
|
-
|
|
9224
|
-
|
|
9225
|
-
if (
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
|
|
9229
|
-
|
|
9230
|
-
entry.timestamp ?? "",
|
|
9231
|
-
projLabel,
|
|
9232
|
-
sessionId,
|
|
9233
|
-
"claude",
|
|
9234
|
-
result
|
|
9235
|
-
);
|
|
9288
|
+
}
|
|
9289
|
+
for (const block of content2) {
|
|
9290
|
+
if (block.type !== "tool_result") continue;
|
|
9291
|
+
const filePath = block.tool_use_id ? toolUseFilePaths.get(block.tool_use_id) : void 0;
|
|
9292
|
+
if (filePath) {
|
|
9293
|
+
const ext = import_path21.default.extname(filePath).toLowerCase();
|
|
9294
|
+
if (CODE_EXTENSIONS.has(ext)) continue;
|
|
9236
9295
|
}
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
if (
|
|
9243
|
-
if (
|
|
9244
|
-
const
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
|
|
9251
|
-
toolName,
|
|
9252
|
-
input,
|
|
9296
|
+
const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
|
|
9297
|
+
if (!resultText) continue;
|
|
9298
|
+
if (isNode9SelfOutput(resultText)) continue;
|
|
9299
|
+
const dlpMatch = scanArgs({ text: resultText });
|
|
9300
|
+
if (dlpMatch) {
|
|
9301
|
+
if (looksLikeFixtureToken(dlpMatch.redactedSample)) continue;
|
|
9302
|
+
if (firstDlpTs === null) firstDlpTs = entry.timestamp ?? null;
|
|
9303
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9304
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9305
|
+
dedup.dlpKeys.add(k);
|
|
9306
|
+
result.dlpFindings.push({
|
|
9307
|
+
patternName: dlpMatch.patternName,
|
|
9308
|
+
redactedSample: dlpMatch.redactedSample,
|
|
9309
|
+
toolName: "tool-result",
|
|
9253
9310
|
timestamp: entry.timestamp ?? "",
|
|
9254
9311
|
project: projLabel,
|
|
9255
9312
|
sessionId,
|
|
9256
9313
|
agent: "claude"
|
|
9257
9314
|
});
|
|
9258
9315
|
}
|
|
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
9316
|
}
|
|
9294
9317
|
}
|
|
9295
9318
|
}
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9319
|
+
continue;
|
|
9320
|
+
}
|
|
9321
|
+
const usage = entry.message?.usage;
|
|
9322
|
+
const model = entry.message?.model;
|
|
9323
|
+
if (usage && model) {
|
|
9324
|
+
const p = claudeModelPrice(model);
|
|
9325
|
+
if (p) {
|
|
9326
|
+
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
9327
|
}
|
|
9300
9328
|
}
|
|
9301
|
-
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
|
|
9311
|
-
|
|
9312
|
-
|
|
9329
|
+
const content = entry.message?.content;
|
|
9330
|
+
if (!Array.isArray(content)) continue;
|
|
9331
|
+
for (const block of content) {
|
|
9332
|
+
if (block.type !== "tool_use") continue;
|
|
9333
|
+
result.totalToolCalls++;
|
|
9334
|
+
const toolName = block.name ?? "";
|
|
9335
|
+
const toolNameLower = toolName.toLowerCase();
|
|
9336
|
+
const input = block.input ?? {};
|
|
9337
|
+
if (block.id && typeof input.file_path === "string") {
|
|
9338
|
+
toolUseFilePaths.set(block.id, input.file_path);
|
|
9339
|
+
}
|
|
9340
|
+
sessionCalls.push({ toolName, input, timestamp: entry.timestamp ?? "" });
|
|
9341
|
+
if (toolNameLower === "bash" || toolNameLower === "execute_bash") {
|
|
9342
|
+
result.bashCalls++;
|
|
9343
|
+
}
|
|
9344
|
+
if (firstEditTs === null && (toolNameLower === "edit" || toolNameLower === "write" || toolNameLower === "write_file" || toolNameLower === "edit_file" || toolNameLower === "multiedit")) {
|
|
9345
|
+
firstEditTs = entry.timestamp ?? null;
|
|
9346
|
+
}
|
|
9347
|
+
const rawCmd = String(input.command ?? "").trimStart();
|
|
9348
|
+
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd)) continue;
|
|
9349
|
+
const inputFilePath = typeof input.file_path === "string" ? input.file_path : "";
|
|
9350
|
+
const inputFileExt = inputFilePath ? import_path21.default.extname(inputFilePath).toLowerCase() : "";
|
|
9351
|
+
if (CODE_EXTENSIONS.has(inputFileExt)) continue;
|
|
9352
|
+
const dlpMatch = scanArgs(input);
|
|
9353
|
+
if (dlpMatch) {
|
|
9354
|
+
if (firstDlpTs === null) firstDlpTs = entry.timestamp ?? null;
|
|
9355
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9356
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9357
|
+
dedup.dlpKeys.add(k);
|
|
9358
|
+
result.dlpFindings.push({
|
|
9359
|
+
patternName: dlpMatch.patternName,
|
|
9360
|
+
redactedSample: dlpMatch.redactedSample,
|
|
9361
|
+
toolName,
|
|
9362
|
+
timestamp: entry.timestamp ?? "",
|
|
9363
|
+
project: projLabel,
|
|
9364
|
+
sessionId,
|
|
9365
|
+
agent: "claude"
|
|
9366
|
+
});
|
|
9367
|
+
}
|
|
9368
|
+
}
|
|
9369
|
+
let astFsMatched = false;
|
|
9370
|
+
const astRanForBash = toolNameLower === "bash" || toolNameLower === "execute_bash";
|
|
9371
|
+
if (astRanForBash) {
|
|
9372
|
+
astFsMatched = pushFsOpAstFinding(
|
|
9373
|
+
String(input.command ?? ""),
|
|
9374
|
+
toolName,
|
|
9375
|
+
input,
|
|
9376
|
+
entry.timestamp ?? "",
|
|
9377
|
+
projLabel,
|
|
9378
|
+
sessionId,
|
|
9379
|
+
"claude",
|
|
9380
|
+
result,
|
|
9381
|
+
dedup
|
|
9382
|
+
);
|
|
9383
|
+
}
|
|
9384
|
+
let ruleMatched = astFsMatched;
|
|
9385
|
+
for (const source of ruleSources) {
|
|
9386
|
+
const { rule } = source;
|
|
9387
|
+
if (rule.verdict === "allow") continue;
|
|
9388
|
+
if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
|
|
9389
|
+
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
9390
|
+
if (!evaluateSmartConditions(input, rule)) continue;
|
|
9391
|
+
const inputPreview = preview(input, 120);
|
|
9392
|
+
const k = findingKey(rule.name, inputPreview, projLabel);
|
|
9393
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9394
|
+
dedup.findingsKeys.add(k);
|
|
9395
|
+
result.findings.push({
|
|
9396
|
+
source,
|
|
9397
|
+
toolName,
|
|
9398
|
+
input,
|
|
9399
|
+
timestamp: entry.timestamp ?? "",
|
|
9400
|
+
project: projLabel,
|
|
9401
|
+
sessionId,
|
|
9402
|
+
agent: "claude"
|
|
9403
|
+
});
|
|
9404
|
+
}
|
|
9405
|
+
ruleMatched = true;
|
|
9406
|
+
break;
|
|
9407
|
+
}
|
|
9408
|
+
if (!ruleMatched && (toolNameLower === "bash" || toolNameLower === "execute_bash")) {
|
|
9409
|
+
const shellVerdict = detectDangerousShellExec(String(input.command ?? ""));
|
|
9410
|
+
if (shellVerdict) {
|
|
9411
|
+
const astRule = {
|
|
9412
|
+
name: `ast:bash-safe:${shellVerdict}-shell-exec-remote`,
|
|
9413
|
+
tool: "bash",
|
|
9414
|
+
conditions: [],
|
|
9415
|
+
verdict: shellVerdict,
|
|
9416
|
+
reason: `Shell execution of remote download detected by AST analysis (bash-safe)`
|
|
9417
|
+
};
|
|
9418
|
+
const inputPreview = preview(input, 120);
|
|
9419
|
+
const k = findingKey(astRule.name, inputPreview, projLabel);
|
|
9420
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9421
|
+
dedup.findingsKeys.add(k);
|
|
9422
|
+
result.findings.push({
|
|
9423
|
+
source: {
|
|
9424
|
+
shieldName: "bash-safe",
|
|
9425
|
+
shieldLabel: "bash-safe (AST)",
|
|
9426
|
+
sourceType: "shield",
|
|
9427
|
+
rule: astRule
|
|
9428
|
+
},
|
|
9429
|
+
toolName,
|
|
9430
|
+
input,
|
|
9431
|
+
timestamp: entry.timestamp ?? "",
|
|
9432
|
+
project: projLabel,
|
|
9433
|
+
sessionId,
|
|
9434
|
+
agent: "claude"
|
|
9435
|
+
});
|
|
9436
|
+
}
|
|
9437
|
+
}
|
|
9438
|
+
}
|
|
9439
|
+
}
|
|
9440
|
+
}
|
|
9441
|
+
result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "claude"));
|
|
9442
|
+
if (firstDlpTs !== null && (firstEditTs === null || firstDlpTs < firstEditTs)) {
|
|
9443
|
+
result.sessionsWithEarlySecrets++;
|
|
9444
|
+
}
|
|
9445
|
+
}
|
|
9446
|
+
function processClaudeProject(proj, projectsDir, ruleSources, startDate, result, dedup, onProgress, onLine) {
|
|
9447
|
+
const projPath = import_path21.default.join(projectsDir, proj);
|
|
9448
|
+
try {
|
|
9449
|
+
if (!import_fs19.default.statSync(projPath).isDirectory()) return;
|
|
9450
|
+
} catch {
|
|
9451
|
+
return;
|
|
9452
|
+
}
|
|
9453
|
+
const projLabel = stripTerminalEscapes(decodeURIComponent(proj).replace(import_os18.default.homedir(), "~")).slice(
|
|
9454
|
+
0,
|
|
9455
|
+
40
|
|
9456
|
+
);
|
|
9457
|
+
let files;
|
|
9458
|
+
try {
|
|
9459
|
+
files = import_fs19.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
9460
|
+
} catch {
|
|
9461
|
+
return;
|
|
9462
|
+
}
|
|
9463
|
+
for (const file of files) {
|
|
9464
|
+
processClaudeFile(
|
|
9465
|
+
file,
|
|
9466
|
+
projPath,
|
|
9467
|
+
projLabel,
|
|
9468
|
+
ruleSources,
|
|
9469
|
+
startDate,
|
|
9470
|
+
result,
|
|
9471
|
+
dedup,
|
|
9472
|
+
onProgress,
|
|
9473
|
+
onLine
|
|
9474
|
+
);
|
|
9475
|
+
}
|
|
9476
|
+
}
|
|
9477
|
+
function emptyClaudeScan() {
|
|
9478
|
+
return {
|
|
9479
|
+
filesScanned: 0,
|
|
9480
|
+
sessions: 0,
|
|
9481
|
+
totalToolCalls: 0,
|
|
9482
|
+
bashCalls: 0,
|
|
9483
|
+
findings: [],
|
|
9484
|
+
dlpFindings: [],
|
|
9485
|
+
loopFindings: [],
|
|
9486
|
+
totalCostUSD: 0,
|
|
9487
|
+
firstDate: null,
|
|
9488
|
+
lastDate: null,
|
|
9489
|
+
sessionsWithEarlySecrets: 0
|
|
9490
|
+
};
|
|
9491
|
+
}
|
|
9492
|
+
function scanClaudeHistory(startDate, onProgress, onLine) {
|
|
9493
|
+
const projectsDir = import_path21.default.join(import_os18.default.homedir(), ".claude", "projects");
|
|
9494
|
+
const result = emptyClaudeScan();
|
|
9495
|
+
if (!import_fs19.default.existsSync(projectsDir)) return result;
|
|
9496
|
+
let projDirs;
|
|
9497
|
+
try {
|
|
9498
|
+
projDirs = import_fs19.default.readdirSync(projectsDir);
|
|
9499
|
+
} catch {
|
|
9500
|
+
return result;
|
|
9501
|
+
}
|
|
9502
|
+
const ruleSources = buildRuleSources();
|
|
9503
|
+
const dedup = emptyScanDedup();
|
|
9504
|
+
for (const proj of projDirs) {
|
|
9505
|
+
processClaudeProject(
|
|
9506
|
+
proj,
|
|
9507
|
+
projectsDir,
|
|
9508
|
+
ruleSources,
|
|
9509
|
+
startDate,
|
|
9510
|
+
result,
|
|
9511
|
+
dedup,
|
|
9512
|
+
onProgress,
|
|
9513
|
+
onLine
|
|
9514
|
+
);
|
|
9515
|
+
}
|
|
9516
|
+
return result;
|
|
9517
|
+
}
|
|
9518
|
+
function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
9519
|
+
const tmpDir = import_path21.default.join(import_os18.default.homedir(), ".gemini", "tmp");
|
|
9520
|
+
const result = {
|
|
9521
|
+
filesScanned: 0,
|
|
9522
|
+
sessions: 0,
|
|
9523
|
+
totalToolCalls: 0,
|
|
9524
|
+
bashCalls: 0,
|
|
9525
|
+
findings: [],
|
|
9526
|
+
dlpFindings: [],
|
|
9313
9527
|
loopFindings: [],
|
|
9314
9528
|
totalCostUSD: 0,
|
|
9315
9529
|
firstDate: null,
|
|
9316
9530
|
lastDate: null,
|
|
9317
9531
|
sessionsWithEarlySecrets: 0
|
|
9318
9532
|
};
|
|
9533
|
+
const dedup = emptyScanDedup();
|
|
9319
9534
|
if (!import_fs19.default.existsSync(tmpDir)) return result;
|
|
9320
9535
|
let slugDirs;
|
|
9321
9536
|
try {
|
|
@@ -9372,10 +9587,9 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9372
9587
|
if (text) {
|
|
9373
9588
|
const dlpMatch = scanArgs({ text });
|
|
9374
9589
|
if (dlpMatch) {
|
|
9375
|
-
const
|
|
9376
|
-
|
|
9377
|
-
|
|
9378
|
-
if (!isDupe) {
|
|
9590
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9591
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9592
|
+
dedup.dlpKeys.add(k);
|
|
9379
9593
|
result.dlpFindings.push({
|
|
9380
9594
|
patternName: dlpMatch.patternName,
|
|
9381
9595
|
redactedSample: dlpMatch.redactedSample,
|
|
@@ -9420,10 +9634,9 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9420
9634
|
continue;
|
|
9421
9635
|
const dlpMatch = scanArgs(input);
|
|
9422
9636
|
if (dlpMatch) {
|
|
9423
|
-
const
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
if (!isDupe) {
|
|
9637
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9638
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9639
|
+
dedup.dlpKeys.add(k);
|
|
9427
9640
|
result.dlpFindings.push({
|
|
9428
9641
|
patternName: dlpMatch.patternName,
|
|
9429
9642
|
redactedSample: dlpMatch.redactedSample,
|
|
@@ -9446,7 +9659,8 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9446
9659
|
projLabel,
|
|
9447
9660
|
sessionId,
|
|
9448
9661
|
"gemini",
|
|
9449
|
-
result
|
|
9662
|
+
result,
|
|
9663
|
+
dedup
|
|
9450
9664
|
);
|
|
9451
9665
|
}
|
|
9452
9666
|
let ruleMatched = astFsMatched;
|
|
@@ -9457,10 +9671,9 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9457
9671
|
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
9458
9672
|
if (!evaluateSmartConditions(input, rule)) continue;
|
|
9459
9673
|
const inputPreview = preview(input, 120);
|
|
9460
|
-
const
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
if (!isDupe) {
|
|
9674
|
+
const k = findingKey(rule.name, inputPreview, projLabel);
|
|
9675
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9676
|
+
dedup.findingsKeys.add(k);
|
|
9464
9677
|
result.findings.push({
|
|
9465
9678
|
source,
|
|
9466
9679
|
toolName,
|
|
@@ -9488,10 +9701,9 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9488
9701
|
reason: `Shell execution of remote download detected by AST analysis (bash-safe)`
|
|
9489
9702
|
};
|
|
9490
9703
|
const inputPreview = preview(input, 120);
|
|
9491
|
-
const
|
|
9492
|
-
|
|
9493
|
-
|
|
9494
|
-
if (!isDupe) {
|
|
9704
|
+
const k = findingKey(astRule.name, inputPreview, projLabel);
|
|
9705
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9706
|
+
dedup.findingsKeys.add(k);
|
|
9495
9707
|
result.findings.push({
|
|
9496
9708
|
source: {
|
|
9497
9709
|
shieldName: "bash-safe",
|
|
@@ -9531,6 +9743,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9531
9743
|
lastDate: null,
|
|
9532
9744
|
sessionsWithEarlySecrets: 0
|
|
9533
9745
|
};
|
|
9746
|
+
const dedup = emptyScanDedup();
|
|
9534
9747
|
if (!import_fs19.default.existsSync(sessionsBase)) return result;
|
|
9535
9748
|
const jsonlFiles = [];
|
|
9536
9749
|
try {
|
|
@@ -9612,10 +9825,9 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9612
9825
|
if (text) {
|
|
9613
9826
|
const dlpMatch2 = scanArgs({ text });
|
|
9614
9827
|
if (dlpMatch2) {
|
|
9615
|
-
const
|
|
9616
|
-
|
|
9617
|
-
|
|
9618
|
-
if (!isDupe) {
|
|
9828
|
+
const k = dlpKey(dlpMatch2.patternName, dlpMatch2.redactedSample, projLabel);
|
|
9829
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9830
|
+
dedup.dlpKeys.add(k);
|
|
9619
9831
|
result.dlpFindings.push({
|
|
9620
9832
|
patternName: dlpMatch2.patternName,
|
|
9621
9833
|
redactedSample: dlpMatch2.redactedSample,
|
|
@@ -9657,10 +9869,9 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9657
9869
|
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd)) continue;
|
|
9658
9870
|
const dlpMatch = scanArgs(input);
|
|
9659
9871
|
if (dlpMatch) {
|
|
9660
|
-
const
|
|
9661
|
-
|
|
9662
|
-
|
|
9663
|
-
if (!isDupe) {
|
|
9872
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9873
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9874
|
+
dedup.dlpKeys.add(k);
|
|
9664
9875
|
result.dlpFindings.push({
|
|
9665
9876
|
patternName: dlpMatch.patternName,
|
|
9666
9877
|
redactedSample: dlpMatch.redactedSample,
|
|
@@ -9683,7 +9894,8 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9683
9894
|
projLabel,
|
|
9684
9895
|
sessionId,
|
|
9685
9896
|
"codex",
|
|
9686
|
-
result
|
|
9897
|
+
result,
|
|
9898
|
+
dedup
|
|
9687
9899
|
);
|
|
9688
9900
|
}
|
|
9689
9901
|
let ruleMatched = astFsMatched;
|
|
@@ -9695,10 +9907,9 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9695
9907
|
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
9696
9908
|
if (!evaluateSmartConditions(input, rule)) continue;
|
|
9697
9909
|
const inputPreview = preview(input, 120);
|
|
9698
|
-
const
|
|
9699
|
-
|
|
9700
|
-
|
|
9701
|
-
if (!isDupe) {
|
|
9910
|
+
const k = findingKey(rule.name, inputPreview, projLabel);
|
|
9911
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9912
|
+
dedup.findingsKeys.add(k);
|
|
9702
9913
|
result.findings.push({
|
|
9703
9914
|
source,
|
|
9704
9915
|
toolName,
|
|
@@ -9723,10 +9934,9 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9723
9934
|
reason: `Shell execution of remote download detected by AST analysis (bash-safe)`
|
|
9724
9935
|
};
|
|
9725
9936
|
const inputPreview = preview(input, 120);
|
|
9726
|
-
const
|
|
9727
|
-
|
|
9728
|
-
|
|
9729
|
-
if (!isDupe) {
|
|
9937
|
+
const k = findingKey(astRule.name, inputPreview, projLabel);
|
|
9938
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9939
|
+
dedup.findingsKeys.add(k);
|
|
9730
9940
|
result.findings.push({
|
|
9731
9941
|
source: {
|
|
9732
9942
|
shieldName: "bash-safe",
|
|
@@ -9757,6 +9967,7 @@ function scanShellConfig() {
|
|
|
9757
9967
|
(f) => import_path21.default.join(home, f)
|
|
9758
9968
|
);
|
|
9759
9969
|
const findings = [];
|
|
9970
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9760
9971
|
for (const filePath of configFiles) {
|
|
9761
9972
|
if (!import_fs19.default.existsSync(filePath)) continue;
|
|
9762
9973
|
let lines;
|
|
@@ -9771,10 +9982,9 @@ function scanShellConfig() {
|
|
|
9771
9982
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
9772
9983
|
const dlpMatch = scanArgs({ text: trimmed });
|
|
9773
9984
|
if (!dlpMatch) continue;
|
|
9774
|
-
const
|
|
9775
|
-
|
|
9776
|
-
|
|
9777
|
-
if (!isDupe) {
|
|
9985
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, shortPath);
|
|
9986
|
+
if (!seen.has(k)) {
|
|
9987
|
+
seen.add(k);
|
|
9778
9988
|
findings.push({
|
|
9779
9989
|
patternName: dlpMatch.patternName,
|
|
9780
9990
|
redactedSample: dlpMatch.redactedSample,
|
|
@@ -10033,6 +10243,267 @@ function renderNarrativeScorecard(input) {
|
|
|
10033
10243
|
console.log(import_chalk5.default.dim("\u2192 github.com/node9-ai/node9-proxy"));
|
|
10034
10244
|
console.log("");
|
|
10035
10245
|
}
|
|
10246
|
+
function mkLine(...parts) {
|
|
10247
|
+
let rendered = "";
|
|
10248
|
+
let width = 0;
|
|
10249
|
+
for (const [text, fmt] of parts) {
|
|
10250
|
+
rendered += fmt ? fmt(text) : text;
|
|
10251
|
+
width += (0, import_string_width2.default)(text);
|
|
10252
|
+
}
|
|
10253
|
+
return { rendered, width };
|
|
10254
|
+
}
|
|
10255
|
+
function shortRule(name, width) {
|
|
10256
|
+
const stripped = name.replace(/^shield:[^:]+:/, "");
|
|
10257
|
+
if (stripped.length <= width) return stripped.padEnd(width);
|
|
10258
|
+
return stripped.slice(0, width - 1) + "\u2026";
|
|
10259
|
+
}
|
|
10260
|
+
function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
|
|
10261
|
+
const { scan, summary, blast, blastExposures, blockedCount, reviewCount } = input;
|
|
10262
|
+
const topLines = [];
|
|
10263
|
+
if (scan.dlpFindings.length > 0) {
|
|
10264
|
+
const latest = scan.dlpFindings[0];
|
|
10265
|
+
const rel = relativeDate(latest.timestamp, now);
|
|
10266
|
+
const noun = `credential leak${scan.dlpFindings.length !== 1 ? "s" : ""}`;
|
|
10267
|
+
topLines.push(
|
|
10268
|
+
mkLine(
|
|
10269
|
+
["\u{1F6A8} ", import_chalk5.default.red],
|
|
10270
|
+
[`${scan.dlpFindings.length} ${noun} in tool input `, import_chalk5.default.bold],
|
|
10271
|
+
[`(latest: ${rel} ago, ${latest.patternName})`, import_chalk5.default.dim]
|
|
10272
|
+
)
|
|
10273
|
+
);
|
|
10274
|
+
}
|
|
10275
|
+
if (blockedCount > 0) {
|
|
10276
|
+
const topBlocked = topRulesByVerdict(summary.sections, "block", 2).map(
|
|
10277
|
+
(r) => r.count > 1 ? `${shortRule(r.name, 20).trimEnd()} \xD7${r.count}` : shortRule(r.name, 20).trimEnd()
|
|
10278
|
+
).join(", ");
|
|
10279
|
+
topLines.push(
|
|
10280
|
+
mkLine(
|
|
10281
|
+
["\u{1F6D1} ", import_chalk5.default.red],
|
|
10282
|
+
[`${blockedCount} ops node9 would have blocked `, import_chalk5.default.bold],
|
|
10283
|
+
[`(${topBlocked})`, import_chalk5.default.dim]
|
|
10284
|
+
)
|
|
10285
|
+
);
|
|
10286
|
+
}
|
|
10287
|
+
if (scan.loopFindings.length > 0) {
|
|
10288
|
+
const { wastePct } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
|
|
10289
|
+
const byTool = /* @__PURE__ */ new Map();
|
|
10290
|
+
for (const f of scan.loopFindings) {
|
|
10291
|
+
byTool.set(f.toolName, (byTool.get(f.toolName) ?? 0) + Math.max(0, f.count - 1));
|
|
10292
|
+
}
|
|
10293
|
+
const top = [...byTool.entries()].sort((a, b) => b[1] - a[1])[0];
|
|
10294
|
+
const wasteSuffix = wastePct > 0 ? `, ${wastePct}% wasted` : "";
|
|
10295
|
+
const detail = top ? `(${top[0]} dominates${wasteSuffix})` : "";
|
|
10296
|
+
topLines.push(
|
|
10297
|
+
mkLine(
|
|
10298
|
+
["\u{1F501} ", import_chalk5.default.yellow],
|
|
10299
|
+
[`${scan.loopFindings.length} agent loops detected `, import_chalk5.default.bold],
|
|
10300
|
+
[detail, import_chalk5.default.dim]
|
|
10301
|
+
)
|
|
10302
|
+
);
|
|
10303
|
+
}
|
|
10304
|
+
if (blastExposures > 0) {
|
|
10305
|
+
const exposed2 = Math.max(0, 100 - blast.score);
|
|
10306
|
+
const pjDiscount = PROTECTIVE_SHIELD_DISCOUNTS["project-jail"] ?? 0;
|
|
10307
|
+
const pjBonus = Math.round(exposed2 * pjDiscount);
|
|
10308
|
+
const cta = pjBonus > 0 ? ` \u2192 enable project-jail (+${pjBonus} pts)` : "";
|
|
10309
|
+
topLines.push(
|
|
10310
|
+
mkLine(
|
|
10311
|
+
["\u{1F52D} ", import_chalk5.default.red],
|
|
10312
|
+
[`${blastExposures} secrets reachable on disk`, import_chalk5.default.bold],
|
|
10313
|
+
[cta, import_chalk5.default.dim]
|
|
10314
|
+
)
|
|
10315
|
+
);
|
|
10316
|
+
}
|
|
10317
|
+
if (topLines.length > 0) {
|
|
10318
|
+
for (const ln of boxPanel("TOP FINDINGS", topLines)) console.log(" " + ln);
|
|
10319
|
+
console.log("");
|
|
10320
|
+
}
|
|
10321
|
+
if (summary.leaks.length > 0) {
|
|
10322
|
+
const leakLines = [];
|
|
10323
|
+
for (const leak of summary.leaks.slice(0, 5)) {
|
|
10324
|
+
const rel = relativeDate(leak.timestamp, now);
|
|
10325
|
+
leakLines.push(
|
|
10326
|
+
mkLine(
|
|
10327
|
+
[rel.padStart(4) + " ", import_chalk5.default.dim],
|
|
10328
|
+
[leak.patternName.padEnd(14), import_chalk5.default.red.bold],
|
|
10329
|
+
[" "],
|
|
10330
|
+
[leak.redactedSample.padEnd(20), import_chalk5.default.red],
|
|
10331
|
+
[" "],
|
|
10332
|
+
[`[${leak.toolName}]`.padEnd(15), import_chalk5.default.dim],
|
|
10333
|
+
[" "],
|
|
10334
|
+
[leak.agent, import_chalk5.default.dim]
|
|
10335
|
+
)
|
|
10336
|
+
);
|
|
10337
|
+
}
|
|
10338
|
+
const remaining = summary.leaks.length - 5;
|
|
10339
|
+
if (remaining > 0) {
|
|
10340
|
+
leakLines.push(mkLine([`\u2026 +${remaining} more`, import_chalk5.default.dim]));
|
|
10341
|
+
}
|
|
10342
|
+
const title = `LEAKS \xB7 ${summary.leaks.length} secret${summary.leaks.length !== 1 ? "s" : ""} in plain text`;
|
|
10343
|
+
for (const ln of boxPanel(title, leakLines)) console.log(" " + ln);
|
|
10344
|
+
console.log("");
|
|
10345
|
+
}
|
|
10346
|
+
if (blockedCount > 0) {
|
|
10347
|
+
const blockedLines = [];
|
|
10348
|
+
const ruleEntries = topRulesByVerdict(summary.sections, "block", 12);
|
|
10349
|
+
for (const r of ruleEntries) {
|
|
10350
|
+
const origin = originForRule(r.name, summary.sections);
|
|
10351
|
+
blockedLines.push(
|
|
10352
|
+
mkLine(
|
|
10353
|
+
["\u2717 ", import_chalk5.default.red],
|
|
10354
|
+
[shortRule(r.name, 24), import_chalk5.default.bold],
|
|
10355
|
+
[" \xD7" + String(r.count).padEnd(4), import_chalk5.default.bold],
|
|
10356
|
+
[" "],
|
|
10357
|
+
[origin, import_chalk5.default.dim]
|
|
10358
|
+
)
|
|
10359
|
+
);
|
|
10360
|
+
}
|
|
10361
|
+
const title = `BLOCKED \xB7 ${blockedCount} ops node9 would have stopped`;
|
|
10362
|
+
for (const ln of boxPanel(title, blockedLines)) console.log(" " + ln);
|
|
10363
|
+
console.log("");
|
|
10364
|
+
}
|
|
10365
|
+
if (reviewCount > 0) {
|
|
10366
|
+
const reviewLines = [];
|
|
10367
|
+
const ruleEntries = topRulesByVerdict(summary.sections, "review", 12);
|
|
10368
|
+
for (const r of ruleEntries) {
|
|
10369
|
+
const origin = originForRule(r.name, summary.sections);
|
|
10370
|
+
reviewLines.push(
|
|
10371
|
+
mkLine(
|
|
10372
|
+
// VS-16 (U+FE0F) forces emoji-presentation so string-width
|
|
10373
|
+
// returns 2 cells (matching how modern terminals actually
|
|
10374
|
+
// render it). Without VS-16 string-width says 1 cell — and
|
|
10375
|
+
// the right border drifts off. Same applies to 🛡 / ⚠ below.
|
|
10376
|
+
["\u{1F441}\uFE0F ", import_chalk5.default.yellow],
|
|
10377
|
+
[shortRule(r.name, 24), import_chalk5.default.bold],
|
|
10378
|
+
[" \xD7" + String(r.count).padEnd(4), import_chalk5.default.bold],
|
|
10379
|
+
[" "],
|
|
10380
|
+
[origin, import_chalk5.default.dim]
|
|
10381
|
+
)
|
|
10382
|
+
);
|
|
10383
|
+
}
|
|
10384
|
+
const title = `REVIEW QUEUE \xB7 ${reviewCount} ops flagged for approval`;
|
|
10385
|
+
for (const ln of boxPanel(title, reviewLines)) console.log(" " + ln);
|
|
10386
|
+
console.log("");
|
|
10387
|
+
}
|
|
10388
|
+
if (scan.loopFindings.length > 0) {
|
|
10389
|
+
const { wastePct } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
|
|
10390
|
+
const byTool = /* @__PURE__ */ new Map();
|
|
10391
|
+
let totalRepeats = 0;
|
|
10392
|
+
for (const f of scan.loopFindings) {
|
|
10393
|
+
const repeats = Math.max(0, f.count - 1);
|
|
10394
|
+
byTool.set(f.toolName, (byTool.get(f.toolName) ?? 0) + repeats);
|
|
10395
|
+
totalRepeats += repeats;
|
|
10396
|
+
}
|
|
10397
|
+
const toolEntries = [...byTool.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
10398
|
+
const loopLines = [];
|
|
10399
|
+
for (const [tool, repeats] of toolEntries) {
|
|
10400
|
+
const pct = totalRepeats > 0 ? Math.round(repeats / totalRepeats * 100) : 0;
|
|
10401
|
+
loopLines.push(
|
|
10402
|
+
mkLine(
|
|
10403
|
+
[tool.padEnd(10), import_chalk5.default.bold],
|
|
10404
|
+
[`\xD7${num(repeats)} repeats`.padEnd(16)],
|
|
10405
|
+
[`(${pct}%)`, import_chalk5.default.dim]
|
|
10406
|
+
)
|
|
10407
|
+
);
|
|
10408
|
+
}
|
|
10409
|
+
const topStuck = [...scan.loopFindings].sort((a, b) => b.count - a.count).slice(0, 3);
|
|
10410
|
+
if (topStuck.length > 0) {
|
|
10411
|
+
loopLines.push(mkLine([""]));
|
|
10412
|
+
loopLines.push(mkLine(["Top stuck patterns:", import_chalk5.default.dim]));
|
|
10413
|
+
for (const f of topStuck) {
|
|
10414
|
+
const raw = f.commandPreview || f.toolName;
|
|
10415
|
+
const target = raw.length > 60 ? "\u2026" + raw.slice(raw.length - 59) : raw.padEnd(60);
|
|
10416
|
+
loopLines.push(mkLine([`\xD7${num(f.count).padEnd(4)} `, import_chalk5.default.bold], [target, import_chalk5.default.dim]));
|
|
10417
|
+
}
|
|
10418
|
+
}
|
|
10419
|
+
const wasteSuffix = wastePct > 0 ? ` \xB7 ${wastePct}% wasted` : "";
|
|
10420
|
+
const title = `AGENT LOOPS \xB7 ${scan.loopFindings.length} repeated patterns${wasteSuffix}`;
|
|
10421
|
+
for (const ln of boxPanel(title, loopLines)) console.log(" " + ln);
|
|
10422
|
+
console.log("");
|
|
10423
|
+
}
|
|
10424
|
+
if (blast.reachable.length > 0 || blast.envFindings.length > 0) {
|
|
10425
|
+
const blastLines = [];
|
|
10426
|
+
const DESC_W = 33;
|
|
10427
|
+
for (const r of blast.reachable.slice(0, 8)) {
|
|
10428
|
+
const trimmed = r.description.split(" \u2014 ")[0].split(/—|--/)[0].trim();
|
|
10429
|
+
const desc = trimmed.length > DESC_W ? trimmed.slice(0, DESC_W - 1) + "\u2026" : trimmed;
|
|
10430
|
+
blastLines.push(mkLine(["\u2717 ", import_chalk5.default.red], [r.label.padEnd(36)], [desc, import_chalk5.default.dim]));
|
|
10431
|
+
}
|
|
10432
|
+
for (const e of blast.envFindings.slice(0, 3)) {
|
|
10433
|
+
blastLines.push(
|
|
10434
|
+
mkLine(["\u26A0\uFE0F ", import_chalk5.default.yellow], [`${e.key} `], [`(${e.patternName})`, import_chalk5.default.dim])
|
|
10435
|
+
);
|
|
10436
|
+
}
|
|
10437
|
+
const totalExposed = blast.reachable.length + blast.envFindings.length;
|
|
10438
|
+
if (totalExposed > 8) {
|
|
10439
|
+
blastLines.push(mkLine([`\u2026 +${totalExposed - 8} more`, import_chalk5.default.dim]));
|
|
10440
|
+
}
|
|
10441
|
+
const title = `BLAST RADIUS \xB7 ${totalExposed} path${totalExposed !== 1 ? "s" : ""} reachable right now`;
|
|
10442
|
+
for (const ln of boxPanel(title, blastLines)) console.log(" " + ln);
|
|
10443
|
+
console.log("");
|
|
10444
|
+
}
|
|
10445
|
+
const shieldImpacts = rollupByShield(summary.sections);
|
|
10446
|
+
const exposed = Math.max(0, 100 - blast.score);
|
|
10447
|
+
const shieldLines = [];
|
|
10448
|
+
const ranked = [...shieldImpacts].sort((a, b) => {
|
|
10449
|
+
const aDiscount = PROTECTIVE_SHIELD_DISCOUNTS[a.shieldName] ?? 0;
|
|
10450
|
+
const bDiscount = PROTECTIVE_SHIELD_DISCOUNTS[b.shieldName] ?? 0;
|
|
10451
|
+
if (aDiscount !== bDiscount) return bDiscount - aDiscount;
|
|
10452
|
+
return b.totalCatches - a.totalCatches;
|
|
10453
|
+
});
|
|
10454
|
+
for (const impact of ranked) {
|
|
10455
|
+
if (impact.totalCatches === 0) continue;
|
|
10456
|
+
const discount = PROTECTIVE_SHIELD_DISCOUNTS[impact.shieldName] ?? 0;
|
|
10457
|
+
const bonus = Math.round(exposed * discount);
|
|
10458
|
+
const icon = discount > 0 ? "\u{1F6E1}\uFE0F " : "\u2610 ";
|
|
10459
|
+
const wouldCatch = `would catch ${impact.totalCatches} op${impact.totalCatches !== 1 ? "s" : ""}`;
|
|
10460
|
+
const deltaSuffix = bonus > 0 ? ` \u2192 +${bonus} pts (${blast.score} \u2192 ${blast.score + bonus})` : "";
|
|
10461
|
+
shieldLines.push(
|
|
10462
|
+
mkLine(
|
|
10463
|
+
[icon, discount > 0 ? import_chalk5.default.cyan : import_chalk5.default.dim],
|
|
10464
|
+
[impact.shieldName.padEnd(14), import_chalk5.default.bold],
|
|
10465
|
+
[wouldCatch.padEnd(22), import_chalk5.default.dim],
|
|
10466
|
+
[deltaSuffix, bonus > 0 ? import_chalk5.default.green.bold : import_chalk5.default.dim]
|
|
10467
|
+
)
|
|
10468
|
+
);
|
|
10469
|
+
if (impact.topRuleLabels.length > 0) {
|
|
10470
|
+
const rules = impact.topRuleLabels.join(", ");
|
|
10471
|
+
shieldLines.push(mkLine([" ", import_chalk5.default.dim], [rules, import_chalk5.default.dim]));
|
|
10472
|
+
}
|
|
10473
|
+
}
|
|
10474
|
+
const hitShieldSet = new Set(
|
|
10475
|
+
shieldImpacts.filter((i) => i.totalCatches > 0).map((i) => i.shieldName)
|
|
10476
|
+
);
|
|
10477
|
+
const zeroHitBuiltins = Object.keys(SHIELDS).filter((name) => !hitShieldSet.has(name)).sort();
|
|
10478
|
+
if (zeroHitBuiltins.length > 0) {
|
|
10479
|
+
shieldLines.push(mkLine([""]));
|
|
10480
|
+
shieldLines.push(mkLine([zeroHitBuiltins.join(" \xB7 "), import_chalk5.default.dim]));
|
|
10481
|
+
shieldLines.push(mkLine([" no hits in your history \u2014 install proactively", import_chalk5.default.dim]));
|
|
10482
|
+
}
|
|
10483
|
+
const topRec = ranked.find(
|
|
10484
|
+
(r) => r.totalCatches > 0 && (PROTECTIVE_SHIELD_DISCOUNTS[r.shieldName] ?? 0) > 0
|
|
10485
|
+
);
|
|
10486
|
+
if (topRec) {
|
|
10487
|
+
const bonus = Math.round(exposed * (PROTECTIVE_SHIELD_DISCOUNTS[topRec.shieldName] ?? 0));
|
|
10488
|
+
const cta = `\u2192 node9 shield enable ${topRec.shieldName} (start here \u2014 +${bonus} pts)`;
|
|
10489
|
+
shieldLines.push(mkLine([""]));
|
|
10490
|
+
shieldLines.push(mkLine([cta, import_chalk5.default.cyan]));
|
|
10491
|
+
}
|
|
10492
|
+
if (shieldLines.length > 0) {
|
|
10493
|
+
const title = "SHIELDS \xB7 install node9 + enable these to catch what we found";
|
|
10494
|
+
for (const ln of boxPanel(title, shieldLines)) console.log(" " + ln);
|
|
10495
|
+
console.log("");
|
|
10496
|
+
}
|
|
10497
|
+
}
|
|
10498
|
+
function originForRule(ruleName, sections) {
|
|
10499
|
+
for (const section of sections) {
|
|
10500
|
+
if (section.rules.some((r) => r.name === ruleName)) {
|
|
10501
|
+
if (section.sourceType === "default") return "default";
|
|
10502
|
+
if (section.sourceType === "shield") return `needs shield:${section.shieldKey ?? section.id}`;
|
|
10503
|
+
}
|
|
10504
|
+
}
|
|
10505
|
+
return "";
|
|
10506
|
+
}
|
|
10036
10507
|
function registerScanCommand(program2) {
|
|
10037
10508
|
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
10509
|
"--json",
|
|
@@ -10264,7 +10735,7 @@ function registerScanCommand(program2) {
|
|
|
10264
10735
|
" " + 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
10736
|
);
|
|
10266
10737
|
}
|
|
10267
|
-
if (scan.dlpFindings.length > 0 && scan.sessionsWithEarlySecrets > 0) {
|
|
10738
|
+
if (drillDown && scan.dlpFindings.length > 0 && scan.sessionsWithEarlySecrets > 0) {
|
|
10268
10739
|
console.log(
|
|
10269
10740
|
" " + import_chalk5.default.dim(
|
|
10270
10741
|
`${scan.sessionsWithEarlySecrets} session${scan.sessionsWithEarlySecrets !== 1 ? "s" : ""} loaded secrets before first edit`
|
|
@@ -10272,6 +10743,26 @@ function registerScanCommand(program2) {
|
|
|
10272
10743
|
);
|
|
10273
10744
|
}
|
|
10274
10745
|
console.log("");
|
|
10746
|
+
if (!drillDown) {
|
|
10747
|
+
renderPanelScorecard({
|
|
10748
|
+
scan,
|
|
10749
|
+
summary,
|
|
10750
|
+
blast,
|
|
10751
|
+
blastExposures,
|
|
10752
|
+
blockedCount,
|
|
10753
|
+
reviewCount
|
|
10754
|
+
});
|
|
10755
|
+
const cta = isWired ? "\u2705 node9 is active" : "\u2192 install node9 to enable protection";
|
|
10756
|
+
console.log(" " + import_chalk5.default.green(cta));
|
|
10757
|
+
console.log(
|
|
10758
|
+
" " + import_chalk5.default.dim("\u2192 ") + import_chalk5.default.cyan("node9 monitor") + import_chalk5.default.dim(" live dashboard")
|
|
10759
|
+
);
|
|
10760
|
+
console.log(
|
|
10761
|
+
" " + import_chalk5.default.dim("\u2192 ") + import_chalk5.default.cyan("node9 scan --drill-down") + import_chalk5.default.dim(" full commands + session IDs")
|
|
10762
|
+
);
|
|
10763
|
+
console.log("");
|
|
10764
|
+
return;
|
|
10765
|
+
}
|
|
10275
10766
|
if (scan.dlpFindings.length > 0) {
|
|
10276
10767
|
console.log(" " + import_chalk5.default.dim("\u2500".repeat(70)));
|
|
10277
10768
|
console.log(
|
|
@@ -10460,7 +10951,7 @@ function registerScanCommand(program2) {
|
|
|
10460
10951
|
}
|
|
10461
10952
|
);
|
|
10462
10953
|
}
|
|
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,
|
|
10954
|
+
var import_chalk5, import_fs19, import_path21, import_os18, import_string_width2, 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
10955
|
var init_scan = __esm({
|
|
10465
10956
|
"src/cli/commands/scan.ts"() {
|
|
10466
10957
|
"use strict";
|
|
@@ -10478,6 +10969,8 @@ var init_scan = __esm({
|
|
|
10478
10969
|
init_setup();
|
|
10479
10970
|
init_blast();
|
|
10480
10971
|
init_scan_derive();
|
|
10972
|
+
init_protection();
|
|
10973
|
+
import_string_width2 = __toESM(require("string-width"));
|
|
10481
10974
|
init_scan_json();
|
|
10482
10975
|
init_scan_history();
|
|
10483
10976
|
CLAUDE_PRICING = {
|
|
@@ -10560,9 +11053,6 @@ var init_scan = __esm({
|
|
|
10560
11053
|
STUCK_TOOLS_LIMIT = 3;
|
|
10561
11054
|
RECURRING_SESSION_THRESHOLD = 3;
|
|
10562
11055
|
STALE_AGE_DAYS = 30;
|
|
10563
|
-
DEFAULT_RULE_NAMES = new Set(
|
|
10564
|
-
DEFAULT_CONFIG.policy.smartRules.map((r) => r.name).filter(Boolean)
|
|
10565
|
-
);
|
|
10566
11056
|
classifyRuleSeverity2 = classifyRuleSeverity;
|
|
10567
11057
|
narrativeRuleLabel2 = narrativeRuleLabel;
|
|
10568
11058
|
}
|
|
@@ -13135,8 +13625,15 @@ var tail_exports = {};
|
|
|
13135
13625
|
__export(tail_exports, {
|
|
13136
13626
|
agentLabel: () => agentLabel,
|
|
13137
13627
|
sessionTag: () => sessionTag,
|
|
13628
|
+
shortenPathSummary: () => shortenPathSummary,
|
|
13138
13629
|
startTail: () => startTail
|
|
13139
13630
|
});
|
|
13631
|
+
function shortenPathSummary(s) {
|
|
13632
|
+
if (!s || !s.startsWith("/")) return s;
|
|
13633
|
+
const parts = s.split("/").filter(Boolean);
|
|
13634
|
+
if (parts.length <= 2) return s;
|
|
13635
|
+
return `\u2026/${parts.slice(-2).join("/")}`;
|
|
13636
|
+
}
|
|
13140
13637
|
function getIcon(tool) {
|
|
13141
13638
|
const t = tool.toLowerCase();
|
|
13142
13639
|
for (const [k, v] of Object.entries(ICONS)) {
|
|
@@ -13890,7 +14387,8 @@ async function startTail(options = {}) {
|
|
|
13890
14387
|
if (event === "snapshot") {
|
|
13891
14388
|
const time = new Date(data.ts).toLocaleTimeString([], { hour12: false });
|
|
13892
14389
|
const hash = data.hash ?? "";
|
|
13893
|
-
const
|
|
14390
|
+
const rawSummary = data.argsSummary ?? data.tool;
|
|
14391
|
+
const summary = shortenPathSummary(rawSummary);
|
|
13894
14392
|
const fileCount = data.fileCount ?? 0;
|
|
13895
14393
|
const files = fileCount > 0 ? import_chalk30.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
|
|
13896
14394
|
process.stdout.write(
|
|
@@ -16279,63 +16777,13 @@ function registerAuditCommand(program2) {
|
|
|
16279
16777
|
|
|
16280
16778
|
// src/cli/commands/report.ts
|
|
16281
16779
|
var import_chalk13 = __toESM(require("chalk"));
|
|
16780
|
+
|
|
16781
|
+
// src/cli/aggregate/report-audit.ts
|
|
16282
16782
|
var import_fs35 = __toESM(require("fs"));
|
|
16283
|
-
var import_path36 = __toESM(require("path"));
|
|
16284
16783
|
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
|
|
16784
|
+
var import_path36 = __toESM(require("path"));
|
|
16785
|
+
init_costSync();
|
|
16786
|
+
init_litellm();
|
|
16339
16787
|
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
16788
|
function buildTestTimestamps(allEntries) {
|
|
16341
16789
|
const testTs = /* @__PURE__ */ new Set();
|
|
@@ -16360,8 +16808,7 @@ function isTestEntry(entry, testTs) {
|
|
|
16360
16808
|
}
|
|
16361
16809
|
return false;
|
|
16362
16810
|
}
|
|
16363
|
-
function getDateRange(period) {
|
|
16364
|
-
const now = /* @__PURE__ */ new Date();
|
|
16811
|
+
function getDateRange(period, now) {
|
|
16365
16812
|
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
16366
16813
|
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
|
|
16367
16814
|
switch (period) {
|
|
@@ -16377,6 +16824,11 @@ function getDateRange(period) {
|
|
|
16377
16824
|
s.setDate(s.getDate() - 29);
|
|
16378
16825
|
return { start: s, end };
|
|
16379
16826
|
}
|
|
16827
|
+
case "90d": {
|
|
16828
|
+
const s = new Date(todayStart);
|
|
16829
|
+
s.setDate(s.getDate() - 89);
|
|
16830
|
+
return { start: s, end };
|
|
16831
|
+
}
|
|
16380
16832
|
case "month":
|
|
16381
16833
|
return { start: new Date(now.getFullYear(), now.getMonth(), 1), end };
|
|
16382
16834
|
}
|
|
@@ -16399,40 +16851,6 @@ function isAllow(decision) {
|
|
|
16399
16851
|
function isDlp(checkedBy) {
|
|
16400
16852
|
return !!checkedBy?.includes("dlp");
|
|
16401
16853
|
}
|
|
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
16854
|
var CLAUDE_PRICING2 = {
|
|
16437
16855
|
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
16438
16856
|
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
@@ -16452,90 +16870,160 @@ function claudeModelPrice2(model) {
|
|
|
16452
16870
|
}
|
|
16453
16871
|
return null;
|
|
16454
16872
|
}
|
|
16455
|
-
function
|
|
16456
|
-
|
|
16873
|
+
function emptyClaudeCostAccumulator() {
|
|
16874
|
+
return {
|
|
16457
16875
|
total: 0,
|
|
16458
|
-
byDay: /* @__PURE__ */ new Map(),
|
|
16459
|
-
byModel: /* @__PURE__ */ new Map(),
|
|
16460
16876
|
inputTokens: 0,
|
|
16461
16877
|
outputTokens: 0,
|
|
16462
16878
|
cacheWriteTokens: 0,
|
|
16463
|
-
cacheReadTokens: 0
|
|
16879
|
+
cacheReadTokens: 0,
|
|
16880
|
+
byDay: /* @__PURE__ */ new Map(),
|
|
16881
|
+
byModel: /* @__PURE__ */ new Map(),
|
|
16882
|
+
byProject: /* @__PURE__ */ new Map()
|
|
16464
16883
|
};
|
|
16465
|
-
|
|
16466
|
-
|
|
16884
|
+
}
|
|
16885
|
+
function freezeClaudeCost(acc) {
|
|
16886
|
+
return {
|
|
16887
|
+
total: acc.total,
|
|
16888
|
+
byDay: acc.byDay,
|
|
16889
|
+
byModel: acc.byModel,
|
|
16890
|
+
byProject: acc.byProject,
|
|
16891
|
+
inputTokens: acc.inputTokens,
|
|
16892
|
+
outputTokens: acc.outputTokens,
|
|
16893
|
+
cacheWriteTokens: acc.cacheWriteTokens,
|
|
16894
|
+
cacheReadTokens: acc.cacheReadTokens
|
|
16895
|
+
};
|
|
16896
|
+
}
|
|
16897
|
+
function processClaudeCostProject(proj, projectsDir, start, end, acc) {
|
|
16898
|
+
const projPath = import_path36.default.join(projectsDir, proj);
|
|
16899
|
+
let files;
|
|
16900
|
+
try {
|
|
16901
|
+
const stat = import_fs35.default.statSync(projPath);
|
|
16902
|
+
if (!stat.isDirectory()) return;
|
|
16903
|
+
files = import_fs35.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
16904
|
+
} catch {
|
|
16905
|
+
return;
|
|
16906
|
+
}
|
|
16907
|
+
const startMs = start.getTime();
|
|
16908
|
+
for (const file of files) {
|
|
16909
|
+
const filePath = import_path36.default.join(projPath, file);
|
|
16910
|
+
try {
|
|
16911
|
+
if (import_fs35.default.statSync(filePath).mtimeMs < startMs) continue;
|
|
16912
|
+
} catch {
|
|
16913
|
+
continue;
|
|
16914
|
+
}
|
|
16915
|
+
try {
|
|
16916
|
+
const raw = import_fs35.default.readFileSync(filePath, "utf-8");
|
|
16917
|
+
for (const line of raw.split("\n")) {
|
|
16918
|
+
if (!line.trim()) continue;
|
|
16919
|
+
let entry;
|
|
16920
|
+
try {
|
|
16921
|
+
entry = JSON.parse(line);
|
|
16922
|
+
} catch {
|
|
16923
|
+
continue;
|
|
16924
|
+
}
|
|
16925
|
+
if (entry.type !== "assistant") continue;
|
|
16926
|
+
if (!entry.timestamp) continue;
|
|
16927
|
+
const ts = new Date(entry.timestamp);
|
|
16928
|
+
if (ts < start || ts > end) continue;
|
|
16929
|
+
const usage = entry.message?.usage;
|
|
16930
|
+
const model = entry.message?.model;
|
|
16931
|
+
if (!usage || !model) continue;
|
|
16932
|
+
const p = claudeModelPrice2(model);
|
|
16933
|
+
if (!p) continue;
|
|
16934
|
+
const inp = usage.input_tokens ?? 0;
|
|
16935
|
+
const out = usage.output_tokens ?? 0;
|
|
16936
|
+
const cw = usage.cache_creation_input_tokens ?? 0;
|
|
16937
|
+
const cr = usage.cache_read_input_tokens ?? 0;
|
|
16938
|
+
const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
|
|
16939
|
+
acc.total += cost;
|
|
16940
|
+
acc.inputTokens += inp;
|
|
16941
|
+
acc.outputTokens += out;
|
|
16942
|
+
acc.cacheWriteTokens += cw;
|
|
16943
|
+
acc.cacheReadTokens += cr;
|
|
16944
|
+
const dateKey = entry.timestamp.slice(0, 10);
|
|
16945
|
+
acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
|
|
16946
|
+
const normModel = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
|
|
16947
|
+
acc.byModel.set(normModel, (acc.byModel.get(normModel) ?? 0) + cost);
|
|
16948
|
+
const projectKey = decodeProjectDirName(proj);
|
|
16949
|
+
const projectRollup = acc.byProject.get(projectKey) ?? {
|
|
16950
|
+
cost: 0,
|
|
16951
|
+
inputTokens: 0,
|
|
16952
|
+
outputTokens: 0
|
|
16953
|
+
};
|
|
16954
|
+
projectRollup.cost += cost;
|
|
16955
|
+
projectRollup.inputTokens += inp;
|
|
16956
|
+
projectRollup.outputTokens += out;
|
|
16957
|
+
acc.byProject.set(projectKey, projectRollup);
|
|
16958
|
+
}
|
|
16959
|
+
} catch {
|
|
16960
|
+
continue;
|
|
16961
|
+
}
|
|
16962
|
+
}
|
|
16963
|
+
}
|
|
16964
|
+
function loadClaudeCost(start, end, projectsDir) {
|
|
16965
|
+
const acc = emptyClaudeCostAccumulator();
|
|
16966
|
+
if (!import_fs35.default.existsSync(projectsDir)) return freezeClaudeCost(acc);
|
|
16467
16967
|
let dirs;
|
|
16468
16968
|
try {
|
|
16469
16969
|
dirs = import_fs35.default.readdirSync(projectsDir);
|
|
16470
16970
|
} catch {
|
|
16471
|
-
return
|
|
16971
|
+
return freezeClaudeCost(acc);
|
|
16472
16972
|
}
|
|
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
16973
|
for (const proj of dirs) {
|
|
16481
|
-
|
|
16482
|
-
|
|
16974
|
+
processClaudeCostProject(proj, projectsDir, start, end, acc);
|
|
16975
|
+
}
|
|
16976
|
+
return freezeClaudeCost(acc);
|
|
16977
|
+
}
|
|
16978
|
+
function processCodexCostFile(filePath, start, end, acc) {
|
|
16979
|
+
let lines;
|
|
16980
|
+
try {
|
|
16981
|
+
lines = import_fs35.default.readFileSync(filePath, "utf-8").split("\n");
|
|
16982
|
+
} catch {
|
|
16983
|
+
return;
|
|
16984
|
+
}
|
|
16985
|
+
let sessionStart2 = "";
|
|
16986
|
+
let lastTotalInput = 0;
|
|
16987
|
+
let lastTotalCached = 0;
|
|
16988
|
+
let lastTotalOutput = 0;
|
|
16989
|
+
let sessionToolCalls = 0;
|
|
16990
|
+
for (const line of lines) {
|
|
16991
|
+
if (!line.trim()) continue;
|
|
16992
|
+
let entry;
|
|
16483
16993
|
try {
|
|
16484
|
-
|
|
16485
|
-
if (!stat.isDirectory()) continue;
|
|
16486
|
-
files = import_fs35.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
16994
|
+
entry = JSON.parse(line);
|
|
16487
16995
|
} catch {
|
|
16488
16996
|
continue;
|
|
16489
16997
|
}
|
|
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
|
-
}
|
|
16998
|
+
const p = entry.payload ?? {};
|
|
16999
|
+
if (entry.type === "session_meta") {
|
|
17000
|
+
sessionStart2 = String(p["timestamp"] ?? "");
|
|
17001
|
+
continue;
|
|
17002
|
+
}
|
|
17003
|
+
if (entry.type === "event_msg" && p["type"] === "token_count") {
|
|
17004
|
+
const info = p["info"] ?? {};
|
|
17005
|
+
const usage = info["total_token_usage"] ?? {};
|
|
17006
|
+
lastTotalInput = usage["input_tokens"] ?? lastTotalInput;
|
|
17007
|
+
lastTotalCached = usage["cached_input_tokens"] ?? lastTotalCached;
|
|
17008
|
+
lastTotalOutput = usage["output_tokens"] ?? lastTotalOutput;
|
|
17009
|
+
}
|
|
17010
|
+
if (entry.type === "response_item" && p["type"] === "function_call") {
|
|
17011
|
+
sessionToolCalls++;
|
|
16528
17012
|
}
|
|
16529
17013
|
}
|
|
16530
|
-
|
|
17014
|
+
if (!sessionStart2) return;
|
|
17015
|
+
const ts = new Date(sessionStart2);
|
|
17016
|
+
if (ts < start || ts > end) return;
|
|
17017
|
+
const nonCached = Math.max(0, lastTotalInput - lastTotalCached);
|
|
17018
|
+
const cost = nonCached * 5e-6 + lastTotalCached * 25e-7 + lastTotalOutput * 15e-6;
|
|
17019
|
+
acc.total += cost;
|
|
17020
|
+
acc.toolCalls += sessionToolCalls;
|
|
17021
|
+
const dateKey = sessionStart2.slice(0, 10);
|
|
17022
|
+
acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
|
|
16531
17023
|
}
|
|
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 };
|
|
17024
|
+
function listCodexSessionFiles(sessionsBase) {
|
|
16538
17025
|
const jsonlFiles = [];
|
|
17026
|
+
if (!import_fs35.default.existsSync(sessionsBase)) return jsonlFiles;
|
|
16539
17027
|
try {
|
|
16540
17028
|
for (const year of import_fs35.default.readdirSync(sessionsBase)) {
|
|
16541
17029
|
const yearPath = import_path36.default.join(sessionsBase, year);
|
|
@@ -16565,495 +17053,742 @@ function loadCodexCost(start, end) {
|
|
|
16565
17053
|
}
|
|
16566
17054
|
}
|
|
16567
17055
|
} catch {
|
|
16568
|
-
return
|
|
17056
|
+
return [];
|
|
16569
17057
|
}
|
|
16570
|
-
|
|
16571
|
-
|
|
17058
|
+
return jsonlFiles;
|
|
17059
|
+
}
|
|
17060
|
+
function loadCodexCost(start, end, sessionsBase) {
|
|
17061
|
+
const acc = { total: 0, toolCalls: 0, byDay: /* @__PURE__ */ new Map() };
|
|
17062
|
+
const files = listCodexSessionFiles(sessionsBase);
|
|
17063
|
+
for (const filePath of files) {
|
|
17064
|
+
processCodexCostFile(filePath, start, end, acc);
|
|
17065
|
+
}
|
|
17066
|
+
return { total: acc.total, byDay: acc.byDay, toolCalls: acc.toolCalls };
|
|
17067
|
+
}
|
|
17068
|
+
var GEMINI_FALLBACK_MODELS = ["gemini-2.5-flash", "gemini-2.0-flash"];
|
|
17069
|
+
function geminiPriceFor(model) {
|
|
17070
|
+
let tuple = pricingFor(model);
|
|
17071
|
+
if (!tuple && /^gemini-/i.test(model)) {
|
|
17072
|
+
for (const proxy of GEMINI_FALLBACK_MODELS) {
|
|
17073
|
+
tuple = pricingFor(proxy);
|
|
17074
|
+
if (tuple) break;
|
|
17075
|
+
}
|
|
17076
|
+
}
|
|
17077
|
+
if (!tuple) return null;
|
|
17078
|
+
return { input: tuple[0], output: tuple[1], cacheRead: tuple[3] || tuple[0] };
|
|
17079
|
+
}
|
|
17080
|
+
function emptyGeminiAccumulator() {
|
|
17081
|
+
return {
|
|
17082
|
+
total: 0,
|
|
17083
|
+
inputTokens: 0,
|
|
17084
|
+
outputTokens: 0,
|
|
17085
|
+
cacheReadTokens: 0,
|
|
17086
|
+
byDay: /* @__PURE__ */ new Map(),
|
|
17087
|
+
byProject: /* @__PURE__ */ new Map()
|
|
17088
|
+
};
|
|
17089
|
+
}
|
|
17090
|
+
function freezeGeminiCost(acc) {
|
|
17091
|
+
return {
|
|
17092
|
+
total: acc.total,
|
|
17093
|
+
byDay: acc.byDay,
|
|
17094
|
+
byProject: acc.byProject,
|
|
17095
|
+
inputTokens: acc.inputTokens,
|
|
17096
|
+
outputTokens: acc.outputTokens,
|
|
17097
|
+
cacheReadTokens: acc.cacheReadTokens
|
|
17098
|
+
};
|
|
17099
|
+
}
|
|
17100
|
+
function processGeminiCostFile(filePath, projectKey, start, end, acc) {
|
|
17101
|
+
const startMs = start.getTime();
|
|
17102
|
+
try {
|
|
17103
|
+
if (import_fs35.default.statSync(filePath).mtimeMs < startMs) return;
|
|
17104
|
+
} catch {
|
|
17105
|
+
return;
|
|
17106
|
+
}
|
|
17107
|
+
let raw;
|
|
17108
|
+
try {
|
|
17109
|
+
raw = import_fs35.default.readFileSync(filePath, "utf-8");
|
|
17110
|
+
} catch {
|
|
17111
|
+
return;
|
|
17112
|
+
}
|
|
17113
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
17114
|
+
for (const line of raw.split("\n")) {
|
|
17115
|
+
if (!line.trim()) continue;
|
|
17116
|
+
let entry;
|
|
16572
17117
|
try {
|
|
16573
|
-
|
|
17118
|
+
entry = JSON.parse(line);
|
|
16574
17119
|
} catch {
|
|
16575
17120
|
continue;
|
|
16576
17121
|
}
|
|
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
|
-
}
|
|
17122
|
+
if (entry.type !== "gemini") continue;
|
|
17123
|
+
if (!entry.tokens || !entry.model || !entry.timestamp) continue;
|
|
17124
|
+
if (entry.id) {
|
|
17125
|
+
if (seenIds.has(entry.id)) continue;
|
|
17126
|
+
seenIds.add(entry.id);
|
|
16605
17127
|
}
|
|
16606
|
-
|
|
16607
|
-
const ts = new Date(sessionStart2);
|
|
17128
|
+
const ts = new Date(entry.timestamp);
|
|
16608
17129
|
if (ts < start || ts > end) continue;
|
|
16609
|
-
const
|
|
16610
|
-
|
|
16611
|
-
|
|
16612
|
-
|
|
16613
|
-
const
|
|
16614
|
-
|
|
17130
|
+
const price = geminiPriceFor(entry.model);
|
|
17131
|
+
if (!price) continue;
|
|
17132
|
+
const inp = entry.tokens.input ?? 0;
|
|
17133
|
+
const out = entry.tokens.output ?? 0;
|
|
17134
|
+
const cached = Math.min(entry.tokens.cached ?? 0, inp);
|
|
17135
|
+
const fresh = Math.max(0, inp - cached);
|
|
17136
|
+
const cost = fresh * price.input + cached * price.cacheRead + out * price.output;
|
|
17137
|
+
acc.total += cost;
|
|
17138
|
+
acc.inputTokens += inp;
|
|
17139
|
+
acc.outputTokens += out;
|
|
17140
|
+
acc.cacheReadTokens += cached;
|
|
17141
|
+
const dateKey = entry.timestamp.slice(0, 10);
|
|
17142
|
+
acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
|
|
17143
|
+
const rollup = acc.byProject.get(projectKey) ?? {
|
|
17144
|
+
cost: 0,
|
|
17145
|
+
inputTokens: 0,
|
|
17146
|
+
outputTokens: 0
|
|
17147
|
+
};
|
|
17148
|
+
rollup.cost += cost;
|
|
17149
|
+
rollup.inputTokens += inp;
|
|
17150
|
+
rollup.outputTokens += out;
|
|
17151
|
+
acc.byProject.set(projectKey, rollup);
|
|
17152
|
+
}
|
|
17153
|
+
}
|
|
17154
|
+
function listGeminiSessionFiles(geminiTmpDir) {
|
|
17155
|
+
const out = [];
|
|
17156
|
+
let dirs;
|
|
17157
|
+
try {
|
|
17158
|
+
if (!import_fs35.default.statSync(geminiTmpDir).isDirectory()) return out;
|
|
17159
|
+
dirs = import_fs35.default.readdirSync(geminiTmpDir);
|
|
17160
|
+
} catch {
|
|
17161
|
+
return out;
|
|
17162
|
+
}
|
|
17163
|
+
for (const proj of dirs) {
|
|
17164
|
+
const chatsDir = import_path36.default.join(geminiTmpDir, proj, "chats");
|
|
17165
|
+
let files;
|
|
17166
|
+
try {
|
|
17167
|
+
if (!import_fs35.default.statSync(chatsDir).isDirectory()) continue;
|
|
17168
|
+
files = import_fs35.default.readdirSync(chatsDir);
|
|
17169
|
+
} catch {
|
|
17170
|
+
continue;
|
|
17171
|
+
}
|
|
17172
|
+
for (const f of files) {
|
|
17173
|
+
if (!f.endsWith(".jsonl")) continue;
|
|
17174
|
+
out.push({ projectKey: proj, file: import_path36.default.join(chatsDir, f) });
|
|
17175
|
+
}
|
|
16615
17176
|
}
|
|
16616
|
-
return
|
|
17177
|
+
return out;
|
|
17178
|
+
}
|
|
17179
|
+
function loadGeminiCost(start, end, geminiTmpDir) {
|
|
17180
|
+
const acc = emptyGeminiAccumulator();
|
|
17181
|
+
if (!import_fs35.default.existsSync(geminiTmpDir)) return freezeGeminiCost(acc);
|
|
17182
|
+
for (const { projectKey, file } of listGeminiSessionFiles(geminiTmpDir)) {
|
|
17183
|
+
processGeminiCostFile(file, projectKey, start, end, acc);
|
|
17184
|
+
}
|
|
17185
|
+
return freezeGeminiCost(acc);
|
|
17186
|
+
}
|
|
17187
|
+
function aggregateReportFromAudit(period, opts = {}) {
|
|
17188
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
17189
|
+
const auditLogPath = opts.auditLogPath ?? import_path36.default.join(import_os31.default.homedir(), ".node9", "audit.log");
|
|
17190
|
+
const claudeProjectsDir = opts.claudeProjectsDir ?? import_path36.default.join(import_os31.default.homedir(), ".claude", "projects");
|
|
17191
|
+
const codexSessionsDir = opts.codexSessionsDir ?? import_path36.default.join(import_os31.default.homedir(), ".codex", "sessions");
|
|
17192
|
+
const geminiTmpDir = opts.geminiTmpDir ?? import_path36.default.join(import_os31.default.homedir(), ".gemini", "tmp");
|
|
17193
|
+
const hasAuditFile = import_fs35.default.existsSync(auditLogPath);
|
|
17194
|
+
const allEntries = opts.preloadedAuditEntries ?? parseAuditLog(auditLogPath);
|
|
17195
|
+
const unackedDlp = allEntries.filter((e) => e.source === "response-dlp");
|
|
17196
|
+
const { start, end } = getDateRange(period, now);
|
|
17197
|
+
const responseDlpEntries = allEntries.filter((e) => {
|
|
17198
|
+
if (e.source !== "response-dlp") return false;
|
|
17199
|
+
const ts = new Date(e.ts);
|
|
17200
|
+
return ts >= start && ts <= end;
|
|
17201
|
+
}).map((e) => {
|
|
17202
|
+
const raw = e;
|
|
17203
|
+
return {
|
|
17204
|
+
ts: e.ts,
|
|
17205
|
+
dlpPattern: typeof raw.dlpPattern === "string" ? raw.dlpPattern : void 0,
|
|
17206
|
+
dlpSample: typeof raw.dlpSample === "string" ? raw.dlpSample : void 0
|
|
17207
|
+
};
|
|
17208
|
+
});
|
|
17209
|
+
const claudeCost = opts.preloadedClaudeCost ?? loadClaudeCost(start, end, claudeProjectsDir);
|
|
17210
|
+
const codexCost = opts.preloadedCodexCost ?? loadCodexCost(start, end, codexSessionsDir);
|
|
17211
|
+
const geminiCost = opts.preloadedGeminiCost ?? loadGeminiCost(start, end, geminiTmpDir);
|
|
17212
|
+
for (const [day, c] of codexCost.byDay) {
|
|
17213
|
+
claudeCost.byDay.set(day, (claudeCost.byDay.get(day) ?? 0) + c);
|
|
17214
|
+
}
|
|
17215
|
+
for (const [day, c] of geminiCost.byDay) {
|
|
17216
|
+
claudeCost.byDay.set(day, (claudeCost.byDay.get(day) ?? 0) + c);
|
|
17217
|
+
}
|
|
17218
|
+
for (const [geminiKey, gRollup] of geminiCost.byProject) {
|
|
17219
|
+
let mergedInto = null;
|
|
17220
|
+
for (const claudeKey of claudeCost.byProject.keys()) {
|
|
17221
|
+
const claudeBase = claudeKey.match(/[^/\\]+$/)?.[0] ?? claudeKey;
|
|
17222
|
+
if (claudeBase === geminiKey) {
|
|
17223
|
+
mergedInto = claudeKey;
|
|
17224
|
+
break;
|
|
17225
|
+
}
|
|
17226
|
+
}
|
|
17227
|
+
const targetKey = mergedInto ?? geminiKey;
|
|
17228
|
+
const existing = claudeCost.byProject.get(targetKey) ?? {
|
|
17229
|
+
cost: 0,
|
|
17230
|
+
inputTokens: 0,
|
|
17231
|
+
outputTokens: 0
|
|
17232
|
+
};
|
|
17233
|
+
existing.cost += gRollup.cost;
|
|
17234
|
+
existing.inputTokens += gRollup.inputTokens;
|
|
17235
|
+
existing.outputTokens += gRollup.outputTokens;
|
|
17236
|
+
claudeCost.byProject.set(targetKey, existing);
|
|
17237
|
+
}
|
|
17238
|
+
const periodMs = end.getTime() - start.getTime();
|
|
17239
|
+
const priorEnd = new Date(start.getTime() - 1);
|
|
17240
|
+
const priorStart = new Date(start.getTime() - periodMs);
|
|
17241
|
+
const priorEntries = allEntries.filter((e) => {
|
|
17242
|
+
if (e.source === "post-hook") return false;
|
|
17243
|
+
const ts = new Date(e.ts);
|
|
17244
|
+
return ts >= priorStart && ts <= priorEnd;
|
|
17245
|
+
});
|
|
17246
|
+
const priorBlocked = priorEntries.filter((e) => !isAllow(e.decision)).length;
|
|
17247
|
+
const priorBlockRate = priorEntries.length > 0 ? priorBlocked / priorEntries.length : null;
|
|
17248
|
+
const excludeTests = opts.excludeTests === true;
|
|
17249
|
+
const testTs = excludeTests ? buildTestTimestamps(allEntries) : /* @__PURE__ */ new Set();
|
|
17250
|
+
let excludedTests = 0;
|
|
17251
|
+
const entries = allEntries.filter((e) => {
|
|
17252
|
+
if (e.source === "post-hook") return false;
|
|
17253
|
+
if (e.source === "response-dlp") return false;
|
|
17254
|
+
const ts = new Date(e.ts);
|
|
17255
|
+
if (ts < start || ts > end) return false;
|
|
17256
|
+
if (excludeTests && isTestEntry(e, testTs)) {
|
|
17257
|
+
excludedTests++;
|
|
17258
|
+
return false;
|
|
17259
|
+
}
|
|
17260
|
+
return true;
|
|
17261
|
+
});
|
|
17262
|
+
let userApproved = 0;
|
|
17263
|
+
let userDenied = 0;
|
|
17264
|
+
let timedOut = 0;
|
|
17265
|
+
let hardBlocked = 0;
|
|
17266
|
+
let dlpBlocked = 0;
|
|
17267
|
+
let observeDlp = 0;
|
|
17268
|
+
let loopHits = 0;
|
|
17269
|
+
let testPasses = 0;
|
|
17270
|
+
let testFails = 0;
|
|
17271
|
+
const toolMap = /* @__PURE__ */ new Map();
|
|
17272
|
+
const blockMap = /* @__PURE__ */ new Map();
|
|
17273
|
+
const ruleMap = /* @__PURE__ */ new Map();
|
|
17274
|
+
const agentMap = /* @__PURE__ */ new Map();
|
|
17275
|
+
const mcpMap = /* @__PURE__ */ new Map();
|
|
17276
|
+
const dailyMap = /* @__PURE__ */ new Map();
|
|
17277
|
+
const hourMap = /* @__PURE__ */ new Map();
|
|
17278
|
+
for (const e of entries) {
|
|
17279
|
+
const allow = isAllow(e.decision);
|
|
17280
|
+
const dateKey = e.ts.slice(0, 10);
|
|
17281
|
+
const userInteracted = e.source === "daemon";
|
|
17282
|
+
if (userInteracted) {
|
|
17283
|
+
if (allow) userApproved++;
|
|
17284
|
+
else userDenied++;
|
|
17285
|
+
} else if (!allow) {
|
|
17286
|
+
if (e.checkedBy === "timeout") timedOut++;
|
|
17287
|
+
else if (e.checkedBy === "observe-mode-dlp-would-block") observeDlp++;
|
|
17288
|
+
else if (isDlp(e.checkedBy)) dlpBlocked++;
|
|
17289
|
+
else if (e.checkedBy !== "loop-detected") hardBlocked++;
|
|
17290
|
+
}
|
|
17291
|
+
if (e.checkedBy === "loop-detected") loopHits++;
|
|
17292
|
+
const t = toolMap.get(e.tool) ?? { calls: 0, blocked: 0 };
|
|
17293
|
+
t.calls++;
|
|
17294
|
+
if (!allow) t.blocked++;
|
|
17295
|
+
toolMap.set(e.tool, t);
|
|
17296
|
+
if (!allow && e.checkedBy) {
|
|
17297
|
+
blockMap.set(e.checkedBy, (blockMap.get(e.checkedBy) ?? 0) + 1);
|
|
17298
|
+
}
|
|
17299
|
+
if (!allow && e.ruleName) {
|
|
17300
|
+
ruleMap.set(e.ruleName, (ruleMap.get(e.ruleName) ?? 0) + 1);
|
|
17301
|
+
}
|
|
17302
|
+
if (e.agent) agentMap.set(e.agent, (agentMap.get(e.agent) ?? 0) + 1);
|
|
17303
|
+
if (e.mcpServer) mcpMap.set(e.mcpServer, (mcpMap.get(e.mcpServer) ?? 0) + 1);
|
|
17304
|
+
const hour = new Date(e.ts).getHours();
|
|
17305
|
+
hourMap.set(hour, (hourMap.get(hour) ?? 0) + 1);
|
|
17306
|
+
const d = dailyMap.get(dateKey) ?? { calls: 0, blocked: 0 };
|
|
17307
|
+
d.calls++;
|
|
17308
|
+
if (!allow) d.blocked++;
|
|
17309
|
+
dailyMap.set(dateKey, d);
|
|
17310
|
+
}
|
|
17311
|
+
for (const e of allEntries) {
|
|
17312
|
+
if (e.source !== "test-result") continue;
|
|
17313
|
+
const ts = new Date(e.ts);
|
|
17314
|
+
if (ts < start || ts > end) continue;
|
|
17315
|
+
if (e.testResult === "pass") testPasses++;
|
|
17316
|
+
else if (e.testResult === "fail") testFails++;
|
|
17317
|
+
}
|
|
17318
|
+
if (codexCost.toolCalls > 0) {
|
|
17319
|
+
agentMap.set("Codex", (agentMap.get("Codex") ?? 0) + codexCost.toolCalls);
|
|
17320
|
+
}
|
|
17321
|
+
const data = {
|
|
17322
|
+
period,
|
|
17323
|
+
start,
|
|
17324
|
+
end,
|
|
17325
|
+
excludedTests,
|
|
17326
|
+
total: entries.length,
|
|
17327
|
+
userApproved,
|
|
17328
|
+
userDenied,
|
|
17329
|
+
timedOut,
|
|
17330
|
+
hardBlocked,
|
|
17331
|
+
dlpBlocked,
|
|
17332
|
+
observeDlp,
|
|
17333
|
+
loopHits,
|
|
17334
|
+
testPasses,
|
|
17335
|
+
testFails,
|
|
17336
|
+
unackedDlp: unackedDlp.length,
|
|
17337
|
+
priorBlockRate,
|
|
17338
|
+
cost: {
|
|
17339
|
+
claudeUSD: claudeCost.total,
|
|
17340
|
+
codexUSD: codexCost.total,
|
|
17341
|
+
geminiUSD: geminiCost.total,
|
|
17342
|
+
inputTokens: claudeCost.inputTokens + geminiCost.inputTokens,
|
|
17343
|
+
outputTokens: claudeCost.outputTokens + geminiCost.outputTokens,
|
|
17344
|
+
cacheWriteTokens: claudeCost.cacheWriteTokens,
|
|
17345
|
+
cacheReadTokens: claudeCost.cacheReadTokens + geminiCost.cacheReadTokens,
|
|
17346
|
+
byDay: claudeCost.byDay,
|
|
17347
|
+
byModel: claudeCost.byModel,
|
|
17348
|
+
byProject: claudeCost.byProject
|
|
17349
|
+
},
|
|
17350
|
+
toolMap,
|
|
17351
|
+
blockMap,
|
|
17352
|
+
ruleMap,
|
|
17353
|
+
agentMap,
|
|
17354
|
+
mcpMap,
|
|
17355
|
+
dailyMap,
|
|
17356
|
+
hourMap,
|
|
17357
|
+
generatedAt: now.toISOString()
|
|
17358
|
+
};
|
|
17359
|
+
return { data, hasAuditFile, responseDlpEntries };
|
|
17360
|
+
}
|
|
17361
|
+
|
|
17362
|
+
// src/cli/render/report-json.ts
|
|
17363
|
+
function buildReportJson(input) {
|
|
17364
|
+
const totalBlocked = input.timedOut + input.hardBlocked + input.dlpBlocked + input.loopHits + input.userDenied;
|
|
17365
|
+
const blockRate = input.total > 0 ? totalBlocked / input.total : 0;
|
|
17366
|
+
const deltaPct = input.priorBlockRate === null ? null : Math.round((blockRate - input.priorBlockRate) * 100);
|
|
17367
|
+
return {
|
|
17368
|
+
schemaVersion: 1,
|
|
17369
|
+
generatedAt: input.generatedAt,
|
|
17370
|
+
period: input.period,
|
|
17371
|
+
range: { start: input.start.toISOString(), end: input.end.toISOString() },
|
|
17372
|
+
excludedTests: input.excludedTests,
|
|
17373
|
+
totals: {
|
|
17374
|
+
events: input.total,
|
|
17375
|
+
blocked: totalBlocked,
|
|
17376
|
+
blockRate,
|
|
17377
|
+
userApproved: input.userApproved,
|
|
17378
|
+
userDenied: input.userDenied,
|
|
17379
|
+
timedOut: input.timedOut,
|
|
17380
|
+
hardBlocked: input.hardBlocked,
|
|
17381
|
+
dlpBlocked: input.dlpBlocked,
|
|
17382
|
+
observeDlp: input.observeDlp,
|
|
17383
|
+
loopHits: input.loopHits,
|
|
17384
|
+
unackedDlp: input.unackedDlp
|
|
17385
|
+
},
|
|
17386
|
+
tests: {
|
|
17387
|
+
passes: input.testPasses,
|
|
17388
|
+
fails: input.testFails
|
|
17389
|
+
},
|
|
17390
|
+
cost: {
|
|
17391
|
+
totalUSD: input.cost.claudeUSD + input.cost.codexUSD + input.cost.geminiUSD,
|
|
17392
|
+
claudeUSD: input.cost.claudeUSD,
|
|
17393
|
+
codexUSD: input.cost.codexUSD,
|
|
17394
|
+
geminiUSD: input.cost.geminiUSD,
|
|
17395
|
+
inputTokens: input.cost.inputTokens,
|
|
17396
|
+
outputTokens: input.cost.outputTokens,
|
|
17397
|
+
cacheWriteTokens: input.cost.cacheWriteTokens,
|
|
17398
|
+
cacheReadTokens: input.cost.cacheReadTokens,
|
|
17399
|
+
byDay: [...input.cost.byDay.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([day, usd]) => ({ day, usd })),
|
|
17400
|
+
byModel: [...input.cost.byModel.entries()].sort((a, b) => b[1] - a[1]).map(([model, usd]) => ({ model, usd }))
|
|
17401
|
+
},
|
|
17402
|
+
byTool: [...input.toolMap.entries()].sort((a, b) => b[1].calls - a[1].calls).map(([tool, v]) => ({ tool, calls: v.calls, blocked: v.blocked })),
|
|
17403
|
+
byBlock: [...input.blockMap.entries()].sort((a, b) => b[1] - a[1]).map(([rule, count]) => ({ rule, count })),
|
|
17404
|
+
byAgent: [...input.agentMap.entries()].sort((a, b) => b[1] - a[1]).map(([agent, calls]) => ({ agent, calls })),
|
|
17405
|
+
byMcp: [...input.mcpMap.entries()].sort((a, b) => b[1] - a[1]).map(([server, calls]) => ({ server, calls })),
|
|
17406
|
+
byDay: [...input.dailyMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([day, v]) => ({ day, calls: v.calls, blocked: v.blocked })),
|
|
17407
|
+
byHour: [...input.hourMap.entries()].sort((a, b) => a[0] - b[0]).map(([hour, calls]) => ({ hour, calls })),
|
|
17408
|
+
trend: {
|
|
17409
|
+
priorBlockRate: input.priorBlockRate,
|
|
17410
|
+
deltaPct
|
|
17411
|
+
}
|
|
17412
|
+
};
|
|
17413
|
+
}
|
|
17414
|
+
|
|
17415
|
+
// src/cli/commands/report.ts
|
|
17416
|
+
var BLOCK_REASON_LABELS = {
|
|
17417
|
+
timeout: "Approval timeout",
|
|
17418
|
+
"smart-rule-block": "Smart rule",
|
|
17419
|
+
"observe-mode-dlp-would-block": "DLP (observe)",
|
|
17420
|
+
"persistent-deny": "Persistent deny",
|
|
17421
|
+
"local-decision": "User denied",
|
|
17422
|
+
"dlp-block": "DLP block",
|
|
17423
|
+
"loop-detected": "Loop detected"
|
|
17424
|
+
};
|
|
17425
|
+
function humanBlockReason(reason) {
|
|
17426
|
+
return BLOCK_REASON_LABELS[reason] ?? reason;
|
|
17427
|
+
}
|
|
17428
|
+
function barStr(value, max, width) {
|
|
17429
|
+
if (max === 0 || width <= 0) return "\u2591".repeat(width);
|
|
17430
|
+
const filled = Math.max(1, Math.round(value / max * width));
|
|
17431
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
17432
|
+
}
|
|
17433
|
+
function colorBar(value, max, width) {
|
|
17434
|
+
const s = barStr(value, max, width);
|
|
17435
|
+
const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
|
|
17436
|
+
return import_chalk13.default.cyan(s.slice(0, filled)) + import_chalk13.default.dim(s.slice(filled));
|
|
17437
|
+
}
|
|
17438
|
+
function fmtDate(d) {
|
|
17439
|
+
const date = typeof d === "string" ? /* @__PURE__ */ new Date(d + "T12:00:00") : d;
|
|
17440
|
+
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
17441
|
+
}
|
|
17442
|
+
function num2(n) {
|
|
17443
|
+
return n.toLocaleString();
|
|
17444
|
+
}
|
|
17445
|
+
function fmtCost2(usd) {
|
|
17446
|
+
if (usd < 1e-3) return "< $0.001";
|
|
17447
|
+
if (usd < 1) return "$" + usd.toFixed(4);
|
|
17448
|
+
return "$" + usd.toFixed(2);
|
|
16617
17449
|
}
|
|
16618
17450
|
function registerReportCommand(program2) {
|
|
16619
17451
|
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(
|
|
17452
|
+
const period = ["today", "7d", "30d", "90d", "month"].includes(
|
|
16621
17453
|
options.period
|
|
16622
17454
|
) ? options.period : "7d";
|
|
16623
|
-
const
|
|
16624
|
-
const
|
|
16625
|
-
|
|
16626
|
-
|
|
17455
|
+
const excludeTests = options.tests === false;
|
|
17456
|
+
const { data, hasAuditFile, responseDlpEntries } = aggregateReportFromAudit(period, {
|
|
17457
|
+
excludeTests
|
|
17458
|
+
});
|
|
17459
|
+
if (data.unackedDlp > 0 && !options.json) {
|
|
16627
17460
|
console.log("");
|
|
16628
17461
|
console.log(
|
|
16629
17462
|
import_chalk13.default.bgRed.white.bold(
|
|
16630
|
-
` \u26A0\uFE0F DLP ALERT: ${unackedDlp
|
|
17463
|
+
` \u26A0\uFE0F DLP ALERT: ${data.unackedDlp} secret${data.unackedDlp !== 1 ? "s" : ""} found in Claude response text `
|
|
16631
17464
|
) + " " + import_chalk13.default.yellow("\u2192 run: node9 dlp")
|
|
16632
17465
|
);
|
|
16633
17466
|
}
|
|
16634
|
-
if (
|
|
17467
|
+
if (!hasAuditFile && !options.json) {
|
|
16635
17468
|
console.log(
|
|
16636
17469
|
import_chalk13.default.yellow("\n No audit data found. Run node9 with Claude Code to generate entries.\n")
|
|
16637
17470
|
);
|
|
16638
17471
|
return;
|
|
16639
17472
|
}
|
|
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) {
|
|
17473
|
+
if (options.json) {
|
|
17474
|
+
const envelope = buildReportJson(data);
|
|
17475
|
+
process.stdout.write(JSON.stringify(envelope, null, 2) + "\n");
|
|
17476
|
+
return;
|
|
17477
|
+
}
|
|
17478
|
+
if (data.total === 0) {
|
|
16684
17479
|
console.log(import_chalk13.default.yellow(`
|
|
16685
17480
|
No activity for period "${period}".
|
|
16686
17481
|
`));
|
|
16687
17482
|
return;
|
|
16688
17483
|
}
|
|
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
|
-
}
|
|
17484
|
+
renderTerminalReport(data, responseDlpEntries, excludeTests);
|
|
17485
|
+
});
|
|
17486
|
+
}
|
|
17487
|
+
function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
17488
|
+
const {
|
|
17489
|
+
period,
|
|
17490
|
+
start,
|
|
17491
|
+
end,
|
|
17492
|
+
total,
|
|
17493
|
+
excludedTests,
|
|
17494
|
+
userApproved,
|
|
17495
|
+
userDenied,
|
|
17496
|
+
timedOut,
|
|
17497
|
+
hardBlocked,
|
|
17498
|
+
dlpBlocked,
|
|
17499
|
+
observeDlp,
|
|
17500
|
+
loopHits,
|
|
17501
|
+
testPasses,
|
|
17502
|
+
testFails,
|
|
17503
|
+
priorBlockRate,
|
|
17504
|
+
cost: {
|
|
17505
|
+
claudeUSD,
|
|
17506
|
+
codexUSD,
|
|
17507
|
+
geminiUSD,
|
|
17508
|
+
inputTokens: costInputTokens,
|
|
17509
|
+
outputTokens: costOutputTokens,
|
|
17510
|
+
cacheWriteTokens: costCacheWrite,
|
|
17511
|
+
cacheReadTokens: costCacheRead,
|
|
17512
|
+
byDay: costByDay,
|
|
17513
|
+
byModel: costByModel
|
|
17514
|
+
},
|
|
17515
|
+
toolMap,
|
|
17516
|
+
blockMap,
|
|
17517
|
+
agentMap,
|
|
17518
|
+
mcpMap,
|
|
17519
|
+
dailyMap,
|
|
17520
|
+
hourMap
|
|
17521
|
+
} = data;
|
|
17522
|
+
const costUSD = claudeUSD + codexUSD + geminiUSD;
|
|
17523
|
+
const topTools = [...toolMap.entries()].sort((a, b) => b[1].calls - a[1].calls).slice(0, 8);
|
|
17524
|
+
const topBlocks = [...blockMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
|
|
17525
|
+
const dailyList = [...dailyMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).slice(-14);
|
|
17526
|
+
const maxTool = Math.max(...topTools.map(([, v]) => v.calls), 1);
|
|
17527
|
+
const maxBlock = Math.max(...topBlocks.map(([, v]) => v), 1);
|
|
17528
|
+
const maxDaily = Math.max(...dailyList.map(([, v]) => v.calls), 1);
|
|
17529
|
+
const W = Math.min(process.stdout.columns || 80, 100);
|
|
17530
|
+
const INNER = W - 4;
|
|
17531
|
+
const COL = Math.floor(INNER / 2) - 1;
|
|
17532
|
+
const LABEL = 24;
|
|
17533
|
+
const BAR = Math.max(6, Math.min(14, COL - LABEL - 8));
|
|
17534
|
+
const TOOL_COUNT_W = Math.max(...topTools.map(([, v]) => num2(v.calls).length), 1);
|
|
17535
|
+
const BLOCK_COUNT_W = Math.max(...topBlocks.map(([, v]) => num2(v).length), 1);
|
|
17536
|
+
const line = import_chalk13.default.dim("\u2500".repeat(W - 2));
|
|
17537
|
+
const periodLabel = {
|
|
17538
|
+
today: "Today",
|
|
17539
|
+
"7d": "Last 7 Days",
|
|
17540
|
+
"30d": "Last 30 Days",
|
|
17541
|
+
"90d": "Last 90 Days",
|
|
17542
|
+
month: "This Month"
|
|
17543
|
+
};
|
|
17544
|
+
console.log("");
|
|
17545
|
+
console.log(
|
|
17546
|
+
" " + 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})`) : "")
|
|
17547
|
+
);
|
|
17548
|
+
console.log(" " + line);
|
|
17549
|
+
const totalBlocked = timedOut + hardBlocked + dlpBlocked + loopHits + userDenied;
|
|
17550
|
+
const currentRate = total > 0 ? totalBlocked / total : 0;
|
|
17551
|
+
const trendLabel = (() => {
|
|
17552
|
+
if (priorBlockRate === null) return "";
|
|
17553
|
+
const delta = Math.round((currentRate - priorBlockRate) * 100);
|
|
17554
|
+
if (delta === 0) return "";
|
|
17555
|
+
return " " + (delta > 0 ? import_chalk13.default.red(`\u25B2${delta}%`) : import_chalk13.default.green(`\u25BC${Math.abs(delta)}%`)) + import_chalk13.default.dim(" vs prior");
|
|
17556
|
+
})();
|
|
17557
|
+
const reads = toolMap.get("Read")?.calls ?? 0;
|
|
17558
|
+
const edits = (toolMap.get("Edit")?.calls ?? 0) + (toolMap.get("Write")?.calls ?? 0);
|
|
17559
|
+
const ratioLabel = reads > 0 ? import_chalk13.default.dim(`edit/read ${(edits / reads).toFixed(1)}`) : import_chalk13.default.dim("edit/read \u2013");
|
|
17560
|
+
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");
|
|
17561
|
+
console.log("");
|
|
17562
|
+
console.log(" " + import_chalk13.default.bold("Protection Summary"));
|
|
17563
|
+
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
17564
|
+
console.log(
|
|
17565
|
+
" " + import_chalk13.default.dim("Intercepted") + " " + import_chalk13.default.white(num2(total)) + import_chalk13.default.dim(" tool calls")
|
|
17566
|
+
);
|
|
17567
|
+
console.log("");
|
|
17568
|
+
const COL1 = 18;
|
|
17569
|
+
const summaryRow = (icon, label, count, note, colorFn = (s) => s) => {
|
|
17570
|
+
const countStr = colorFn(num2(count));
|
|
17571
|
+
const noteStr = note ? import_chalk13.default.dim(" " + note) : "";
|
|
17572
|
+
console.log(" " + icon + " " + import_chalk13.default.white(label.padEnd(COL1)) + countStr + noteStr);
|
|
17573
|
+
};
|
|
17574
|
+
summaryRow(
|
|
17575
|
+
userApproved > 0 ? import_chalk13.default.green("\u2705") : import_chalk13.default.dim("\u2705"),
|
|
17576
|
+
"User approved",
|
|
17577
|
+
userApproved,
|
|
17578
|
+
userApproved === 0 ? "no popups this period" : void 0,
|
|
17579
|
+
userApproved > 0 ? (s) => import_chalk13.default.green(s) : (s) => import_chalk13.default.dim(s)
|
|
17580
|
+
);
|
|
17581
|
+
summaryRow(
|
|
17582
|
+
userDenied > 0 ? import_chalk13.default.red("\u{1F6AB}") : import_chalk13.default.dim("\u{1F6AB}"),
|
|
17583
|
+
"User denied",
|
|
17584
|
+
userDenied,
|
|
17585
|
+
void 0,
|
|
17586
|
+
userDenied > 0 ? (s) => import_chalk13.default.red(s) : (s) => import_chalk13.default.dim(s)
|
|
17587
|
+
);
|
|
17588
|
+
summaryRow(
|
|
17589
|
+
timedOut > 0 ? import_chalk13.default.yellow("\u23F1") : import_chalk13.default.dim("\u23F1"),
|
|
17590
|
+
"Timed out",
|
|
17591
|
+
timedOut,
|
|
17592
|
+
timedOut > 0 ? "no approval response" : void 0,
|
|
17593
|
+
timedOut > 0 ? (s) => import_chalk13.default.yellow(s) : (s) => import_chalk13.default.dim(s)
|
|
17594
|
+
);
|
|
17595
|
+
summaryRow(
|
|
17596
|
+
hardBlocked > 0 ? import_chalk13.default.red("\u{1F6D1}") : import_chalk13.default.dim("\u{1F6D1}"),
|
|
17597
|
+
"Auto-blocked",
|
|
17598
|
+
hardBlocked,
|
|
17599
|
+
void 0,
|
|
17600
|
+
hardBlocked > 0 ? (s) => import_chalk13.default.red(s) : (s) => import_chalk13.default.dim(s)
|
|
17601
|
+
);
|
|
17602
|
+
summaryRow(
|
|
17603
|
+
dlpBlocked > 0 ? import_chalk13.default.yellow("\u{1F6A8}") : import_chalk13.default.dim("\u{1F6A8}"),
|
|
17604
|
+
"DLP blocked",
|
|
17605
|
+
dlpBlocked,
|
|
17606
|
+
void 0,
|
|
17607
|
+
dlpBlocked > 0 ? (s) => import_chalk13.default.yellow(s) : (s) => import_chalk13.default.dim(s)
|
|
17608
|
+
);
|
|
17609
|
+
summaryRow(
|
|
17610
|
+
observeDlp > 0 ? import_chalk13.default.blue("\u{1F441}") : import_chalk13.default.dim("\u{1F441}"),
|
|
17611
|
+
"DLP (observe)",
|
|
17612
|
+
observeDlp,
|
|
17613
|
+
observeDlp > 0 ? "would-block in strict mode" : void 0,
|
|
17614
|
+
observeDlp > 0 ? (s) => import_chalk13.default.blue(s) : (s) => import_chalk13.default.dim(s)
|
|
17615
|
+
);
|
|
17616
|
+
summaryRow(
|
|
17617
|
+
loopHits > 0 ? import_chalk13.default.yellow("\u{1F504}") : import_chalk13.default.dim("\u{1F504}"),
|
|
17618
|
+
"Loops detected",
|
|
17619
|
+
loopHits,
|
|
17620
|
+
void 0,
|
|
17621
|
+
loopHits > 0 ? (s) => import_chalk13.default.yellow(s) : (s) => import_chalk13.default.dim(s)
|
|
17622
|
+
);
|
|
17623
|
+
if (trendLabel || ratioLabel || testPasses + testFails > 0) {
|
|
16802
17624
|
console.log("");
|
|
16803
|
-
console.log(
|
|
16804
|
-
|
|
16805
|
-
|
|
16806
|
-
|
|
16807
|
-
|
|
16808
|
-
|
|
16809
|
-
|
|
16810
|
-
|
|
16811
|
-
|
|
16812
|
-
|
|
16813
|
-
|
|
16814
|
-
|
|
16815
|
-
|
|
16816
|
-
|
|
16817
|
-
|
|
16818
|
-
|
|
17625
|
+
console.log(" " + ratioLabel + " " + testLabel + trendLabel);
|
|
17626
|
+
}
|
|
17627
|
+
console.log("");
|
|
17628
|
+
const toolHeaderRaw = "Top Tools";
|
|
17629
|
+
const blockHeaderRaw = "Top Blocks";
|
|
17630
|
+
console.log(
|
|
17631
|
+
" " + import_chalk13.default.bold(toolHeaderRaw) + " ".repeat(COL - toolHeaderRaw.length) + " " + import_chalk13.default.bold(blockHeaderRaw)
|
|
17632
|
+
);
|
|
17633
|
+
console.log(" " + import_chalk13.default.dim("\u2500".repeat(COL)) + " " + import_chalk13.default.dim("\u2500".repeat(COL)));
|
|
17634
|
+
const rows = Math.max(topTools.length, topBlocks.length, 1);
|
|
17635
|
+
for (let i = 0; i < rows; i++) {
|
|
17636
|
+
let leftStyled = " ".repeat(COL);
|
|
17637
|
+
if (i < topTools.length) {
|
|
17638
|
+
const [tool, { calls }] = topTools[i];
|
|
17639
|
+
const label = tool.length > LABEL - 1 ? tool.slice(0, LABEL - 2) + "\u2026" : tool;
|
|
17640
|
+
const countStr = num2(calls).padStart(TOOL_COUNT_W);
|
|
17641
|
+
const b = colorBar(calls, maxTool, BAR);
|
|
17642
|
+
const rawLen = LABEL + BAR + 1 + TOOL_COUNT_W;
|
|
17643
|
+
const pad = Math.max(0, COL - rawLen);
|
|
17644
|
+
leftStyled = import_chalk13.default.white(label.padEnd(LABEL)) + b + " " + import_chalk13.default.white(countStr) + " ".repeat(pad);
|
|
17645
|
+
}
|
|
17646
|
+
let rightStyled = "";
|
|
17647
|
+
if (i < topBlocks.length) {
|
|
17648
|
+
const [reason, count] = topBlocks[i];
|
|
17649
|
+
const readable = humanBlockReason(reason);
|
|
17650
|
+
const label = readable.length > LABEL - 1 ? readable.slice(0, LABEL - 2) + "\u2026" : readable;
|
|
17651
|
+
const countStr = num2(count).padStart(BLOCK_COUNT_W);
|
|
17652
|
+
const b = colorBar(count, maxBlock, BAR);
|
|
17653
|
+
rightStyled = import_chalk13.default.white(label.padEnd(LABEL)) + b + " " + import_chalk13.default.red(countStr);
|
|
17654
|
+
}
|
|
17655
|
+
console.log(" " + leftStyled + " " + rightStyled);
|
|
17656
|
+
}
|
|
17657
|
+
if (topBlocks.length === 0) {
|
|
17658
|
+
console.log(" " + " ".repeat(COL) + " " + import_chalk13.default.dim("nothing blocked \u2713"));
|
|
17659
|
+
}
|
|
17660
|
+
if (agentMap.size >= 1) {
|
|
16819
17661
|
console.log("");
|
|
16820
|
-
console.log(" " + import_chalk13.default.bold("
|
|
17662
|
+
console.log(" " + import_chalk13.default.bold("Agents"));
|
|
16821
17663
|
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);
|
|
17664
|
+
const maxAgent = Math.max(...agentMap.values(), 1);
|
|
17665
|
+
for (const [agent, count] of [...agentMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
17666
|
+
const label = agent.slice(0, LABEL - 1);
|
|
17667
|
+
const b = colorBar(count, maxAgent, BAR);
|
|
17668
|
+
console.log(" " + import_chalk13.default.white(label.padEnd(LABEL)) + b + " " + import_chalk13.default.white(num2(count)));
|
|
16884
17669
|
}
|
|
17670
|
+
}
|
|
17671
|
+
if (mcpMap.size > 0) {
|
|
16885
17672
|
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
|
-
}
|
|
17673
|
+
console.log(" " + import_chalk13.default.bold("MCP Servers"));
|
|
17674
|
+
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
17675
|
+
const maxMcp = Math.max(...mcpMap.values(), 1);
|
|
17676
|
+
for (const [server, count] of [...mcpMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
17677
|
+
const label = server.slice(0, LABEL - 1).padEnd(LABEL);
|
|
17678
|
+
const b = colorBar(count, maxMcp, BAR);
|
|
17679
|
+
console.log(" " + import_chalk13.default.white(label) + b + " " + import_chalk13.default.white(num2(count)));
|
|
17680
|
+
}
|
|
17681
|
+
}
|
|
17682
|
+
if (hourMap.size > 0) {
|
|
17683
|
+
const BLOCKS = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
17684
|
+
const maxHour = Math.max(...hourMap.values(), 1);
|
|
17685
|
+
const bar = Array.from({ length: 24 }, (_, h) => {
|
|
17686
|
+
const v = hourMap.get(h) ?? 0;
|
|
17687
|
+
return BLOCKS[Math.round(v / maxHour * 8)];
|
|
17688
|
+
}).join("");
|
|
17689
|
+
console.log("");
|
|
17690
|
+
console.log(" " + import_chalk13.default.bold("Hour of Day") + import_chalk13.default.dim(" (local, 0h \u2013 23h)"));
|
|
17691
|
+
console.log(" " + import_chalk13.default.cyan(bar));
|
|
17692
|
+
console.log(" " + import_chalk13.default.dim("0h" + " ".repeat(10) + "12h" + " ".repeat(7) + "23h"));
|
|
17693
|
+
}
|
|
17694
|
+
if (dailyList.length > 1) {
|
|
17695
|
+
console.log("");
|
|
17696
|
+
console.log(" " + import_chalk13.default.bold("Daily Activity"));
|
|
17697
|
+
console.log(" " + import_chalk13.default.dim("\u2500".repeat(W - 2)));
|
|
17698
|
+
const DAY_BAR = Math.max(8, Math.min(30, W - 36));
|
|
17699
|
+
for (const [dateKey, { calls, blocked: db }] of dailyList) {
|
|
17700
|
+
const label = fmtDate(dateKey).padEnd(10);
|
|
17701
|
+
const b = colorBar(calls, maxDaily, DAY_BAR);
|
|
17702
|
+
const dayCost = costByDay.get(dateKey);
|
|
17703
|
+
const costNote = dayCost ? import_chalk13.default.magenta(` ${fmtCost2(dayCost)}`) : "";
|
|
17704
|
+
const blockNote = db > 0 ? import_chalk13.default.red(` ${db} blocked`) : "";
|
|
17705
|
+
console.log(
|
|
17706
|
+
" " + import_chalk13.default.dim(label) + " " + b + " " + import_chalk13.default.white(num2(calls)) + blockNote + costNote
|
|
17707
|
+
);
|
|
17023
17708
|
}
|
|
17024
|
-
|
|
17025
|
-
|
|
17026
|
-
|
|
17027
|
-
|
|
17028
|
-
|
|
17029
|
-
|
|
17030
|
-
|
|
17709
|
+
}
|
|
17710
|
+
const totalTokens = costInputTokens + costOutputTokens + costCacheWrite + costCacheRead;
|
|
17711
|
+
if (totalTokens > 0) {
|
|
17712
|
+
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
17713
|
+
console.log("");
|
|
17714
|
+
console.log(" " + import_chalk13.default.bold("Tokens") + " " + import_chalk13.default.dim(`${num2(totalTokens)} total`));
|
|
17715
|
+
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
17716
|
+
const TOK_BAR = Math.max(6, Math.min(20, W - 30));
|
|
17717
|
+
const TOK_LABEL = 14;
|
|
17718
|
+
const maxNonCache = Math.max(costInputTokens, costOutputTokens, costCacheWrite, 1);
|
|
17719
|
+
const nonCacheRows = [
|
|
17720
|
+
["Input", costInputTokens, import_chalk13.default.cyan(num2(costInputTokens))],
|
|
17721
|
+
["Output", costOutputTokens, import_chalk13.default.white(num2(costOutputTokens))],
|
|
17722
|
+
["Cache write", costCacheWrite, import_chalk13.default.yellow(num2(costCacheWrite))]
|
|
17723
|
+
];
|
|
17724
|
+
for (const [label, count, colored] of nonCacheRows) {
|
|
17725
|
+
if (count === 0) continue;
|
|
17726
|
+
const b = colorBar(count, maxNonCache, TOK_BAR);
|
|
17727
|
+
console.log(" " + import_chalk13.default.white(label.padEnd(TOK_LABEL)) + b + " " + colored);
|
|
17728
|
+
}
|
|
17729
|
+
if (costCacheRead > 0) {
|
|
17730
|
+
const cacheBar = colorBar(costCacheRead, costCacheRead, TOK_BAR);
|
|
17731
|
+
const pct = cacheHitPct > 0 ? import_chalk13.default.dim(` ${cacheHitPct}% hit rate`) : "";
|
|
17031
17732
|
console.log(
|
|
17032
|
-
" " + import_chalk13.default.
|
|
17033
|
-
`${responseDlpEntries.length} secret${responseDlpEntries.length !== 1 ? "s" : ""} found in Claude response text`
|
|
17034
|
-
)
|
|
17733
|
+
" " + import_chalk13.default.white("Cache read".padEnd(TOK_LABEL)) + cacheBar + " " + import_chalk13.default.green(num2(costCacheRead)) + pct
|
|
17035
17734
|
);
|
|
17036
|
-
|
|
17735
|
+
}
|
|
17736
|
+
}
|
|
17737
|
+
if (costUSD > 0) {
|
|
17738
|
+
const periodDays = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 864e5));
|
|
17739
|
+
const avgPerDay = costUSD / periodDays;
|
|
17740
|
+
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
17741
|
+
const costHeaderRight = [
|
|
17742
|
+
import_chalk13.default.yellow(fmtCost2(costUSD)),
|
|
17743
|
+
import_chalk13.default.dim(`avg ${fmtCost2(avgPerDay)}/day`),
|
|
17744
|
+
cacheHitPct > 0 ? import_chalk13.default.dim(`${cacheHitPct}% cache hit`) : null
|
|
17745
|
+
].filter(Boolean).join(import_chalk13.default.dim(" \xB7 "));
|
|
17746
|
+
console.log("");
|
|
17747
|
+
console.log(" " + import_chalk13.default.bold("Cost") + " " + costHeaderRight);
|
|
17748
|
+
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
17749
|
+
if (codexUSD > 0)
|
|
17750
|
+
costByModel.set("codex (openai)", (costByModel.get("codex (openai)") ?? 0) + codexUSD);
|
|
17751
|
+
if (geminiUSD > 0)
|
|
17752
|
+
costByModel.set("gemini (google)", (costByModel.get("gemini (google)") ?? 0) + geminiUSD);
|
|
17753
|
+
const modelList = [...costByModel.entries()].sort((a, b) => b[1] - a[1]);
|
|
17754
|
+
const maxModelCost = Math.max(...modelList.map(([, v]) => v), 1e-9);
|
|
17755
|
+
const MODEL_LABEL = 22;
|
|
17756
|
+
const MODEL_BAR = Math.max(6, Math.min(20, W - MODEL_LABEL - 12));
|
|
17757
|
+
for (const [model, cost] of modelList) {
|
|
17758
|
+
const label = model.length > MODEL_LABEL - 1 ? model.slice(0, MODEL_LABEL - 2) + "\u2026" : model;
|
|
17759
|
+
const b = colorBar(cost, maxModelCost, MODEL_BAR);
|
|
17037
17760
|
console.log(
|
|
17038
|
-
" " + import_chalk13.default.
|
|
17761
|
+
" " + import_chalk13.default.white(label.padEnd(MODEL_LABEL)) + b + " " + import_chalk13.default.yellow(fmtCost2(cost))
|
|
17039
17762
|
);
|
|
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
17763
|
}
|
|
17764
|
+
}
|
|
17765
|
+
if (responseDlpEntries.length > 0) {
|
|
17051
17766
|
console.log("");
|
|
17052
17767
|
console.log(
|
|
17053
|
-
" " + import_chalk13.default.
|
|
17768
|
+
" " + import_chalk13.default.red.bold("\u26A0\uFE0F Response DLP") + import_chalk13.default.dim(" \xB7 ") + import_chalk13.default.red(
|
|
17769
|
+
`${responseDlpEntries.length} secret${responseDlpEntries.length !== 1 ? "s" : ""} found in Claude response text`
|
|
17770
|
+
)
|
|
17054
17771
|
);
|
|
17055
|
-
console.log("");
|
|
17056
|
-
|
|
17772
|
+
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(60, W - 4))));
|
|
17773
|
+
console.log(
|
|
17774
|
+
" " + import_chalk13.default.yellow("These were NOT blocked \u2014 Claude included them in response prose.")
|
|
17775
|
+
);
|
|
17776
|
+
console.log(" " + import_chalk13.default.yellow("Rotate affected keys immediately."));
|
|
17777
|
+
for (const e of responseDlpEntries.slice(0, 5)) {
|
|
17778
|
+
const ts = import_chalk13.default.dim(fmtDate(e.ts) + " ");
|
|
17779
|
+
const pattern = import_chalk13.default.red(e.dlpPattern ?? "DLP");
|
|
17780
|
+
const sample = import_chalk13.default.gray(e.dlpSample ?? "");
|
|
17781
|
+
console.log(` ${ts}${pattern} ${sample}`);
|
|
17782
|
+
}
|
|
17783
|
+
if (responseDlpEntries.length > 5) {
|
|
17784
|
+
console.log(import_chalk13.default.dim(` \u2026 and ${responseDlpEntries.length - 5} more`));
|
|
17785
|
+
}
|
|
17786
|
+
}
|
|
17787
|
+
console.log("");
|
|
17788
|
+
console.log(
|
|
17789
|
+
" " + 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")
|
|
17790
|
+
);
|
|
17791
|
+
console.log("");
|
|
17057
17792
|
}
|
|
17058
17793
|
|
|
17059
17794
|
// src/cli/commands/daemon-cmd.ts
|