@node9/proxy 1.19.3 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -98
- package/dist/cli.js +1740 -923
- package/dist/cli.mjs +1740 -922
- package/dist/dashboard.mjs +6809 -0
- package/dist/index.js +142 -16
- package/dist/index.mjs +142 -16
- package/package.json +5 -1
package/dist/cli.mjs
CHANGED
|
@@ -99,12 +99,14 @@ function appendHookDebug(toolName, args, meta, auditHashArgsEnabled) {
|
|
|
99
99
|
function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashArgsEnabled) {
|
|
100
100
|
const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
|
|
101
101
|
const testRun = isTestCall(toolName, args) || process.env.NODE9_TESTING === "1" ? { testRun: true } : {};
|
|
102
|
+
const ruleNameField = meta?.ruleName ? { ruleName: meta.ruleName } : {};
|
|
102
103
|
appendToLog(LOCAL_AUDIT_LOG, {
|
|
103
104
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
104
105
|
tool: toolName,
|
|
105
106
|
...argsField,
|
|
106
107
|
decision,
|
|
107
108
|
checkedBy,
|
|
109
|
+
...ruleNameField,
|
|
108
110
|
...testRun,
|
|
109
111
|
agent: meta?.agent,
|
|
110
112
|
mcpServer: meta?.mcpServer,
|
|
@@ -691,7 +693,12 @@ function analyzeFsOperationImpl(command) {
|
|
|
691
693
|
for (const p of paths) {
|
|
692
694
|
for (const sp of SENSITIVE_PATH_RULES) {
|
|
693
695
|
if (sp.match(p)) {
|
|
694
|
-
result = {
|
|
696
|
+
result = {
|
|
697
|
+
ruleName: sp.rule,
|
|
698
|
+
verdict: sp.verdict ?? "block",
|
|
699
|
+
reason: sp.reason,
|
|
700
|
+
path: p
|
|
701
|
+
};
|
|
695
702
|
return false;
|
|
696
703
|
}
|
|
697
704
|
}
|
|
@@ -1692,7 +1699,11 @@ function extractCanonicalFindings(call, ctx) {
|
|
|
1692
1699
|
})
|
|
1693
1700
|
);
|
|
1694
1701
|
}
|
|
1695
|
-
|
|
1702
|
+
const ast = analyzeShellCommand(command);
|
|
1703
|
+
const sudoVariant = ast.actions.includes("sudo") || ast.actions.includes("su");
|
|
1704
|
+
const chmodVariant = ast.actions.includes("chmod") && (ast.allTokens.includes("777") || ast.allTokens.includes("0777") || ast.allTokens.includes("+x"));
|
|
1705
|
+
const chownVariant = ast.actions.includes("chown") && ast.allTokens.includes("root");
|
|
1706
|
+
if (sudoVariant || chmodVariant || chownVariant) {
|
|
1696
1707
|
out.push(
|
|
1697
1708
|
makeFinding({
|
|
1698
1709
|
type: "privilege-escalation",
|
|
@@ -1824,7 +1835,7 @@ function* stringValues(obj, depth = 0) {
|
|
|
1824
1835
|
}
|
|
1825
1836
|
for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
|
|
1826
1837
|
}
|
|
1827
|
-
var ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, FS_OP_CACHE_MAX, fsOpCache, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, SQL_DML_KEYWORDS, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, COST_PER_LOOP_ITER_USD, DESTRUCTIVE_OP_RE,
|
|
1838
|
+
var ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, FS_OP_CACHE_MAX, fsOpCache, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, SQL_DML_KEYWORDS, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, COST_PER_LOOP_ITER_USD, DESTRUCTIVE_OP_RE, SENSITIVE_PATH_RE, FILE_TOOLS, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC_RE, LONG_OUTPUT_THRESHOLD_BYTES, CANONICAL_EXTRACTOR_VERSION, DEDUPE_PREVIEW_LEN, TERMINAL_ESCAPE_RE;
|
|
1828
1839
|
var init_dist = __esm({
|
|
1829
1840
|
"packages/policy-engine/dist/index.mjs"() {
|
|
1830
1841
|
"use strict";
|
|
@@ -2341,15 +2352,50 @@ var init_dist = __esm({
|
|
|
2341
2352
|
match: (p) => /(^|[\\/])\.aws[\\/]/i.test(p)
|
|
2342
2353
|
},
|
|
2343
2354
|
{
|
|
2355
|
+
// Mirrors the JSON shield's `.env` pattern (project-jail.json's
|
|
2356
|
+
// review-read-env-any-tool) so the AST FS-op path catches the
|
|
2357
|
+
// same set the regex shield does — including Next.js / Vite's
|
|
2358
|
+
// `.env.<env>.local` double-suffix overrides which are commonly
|
|
2359
|
+
// gitignored AND commonly contain real secrets.
|
|
2360
|
+
//
|
|
2361
|
+
// Intentional non-matches (dev fixtures): .env.example, .env.sample,
|
|
2362
|
+
// .env.template, .env.test, .envrc. See shields.test.ts:983-995
|
|
2363
|
+
// for the canonical test-asserted contract.
|
|
2344
2364
|
rule: "shield:project-jail:block-read-env",
|
|
2345
2365
|
reason: "Reading .env files is blocked by project-jail shield",
|
|
2346
|
-
match: (p) => /(?:^|[\\/])\.env(?:\.local
|
|
2366
|
+
match: (p) => /(?:^|[\\/])\.env(?:\.(?:local|production|staging|development|production\.local|staging\.local|development\.local))?$/i.test(
|
|
2367
|
+
p
|
|
2368
|
+
)
|
|
2347
2369
|
},
|
|
2348
2370
|
{
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2371
|
+
// verdict: 'review' (not 'block') is a deliberate design choice
|
|
2372
|
+
// documented in commit 29327a8. SSH keys and AWS credentials are
|
|
2373
|
+
// cryptographic material with no legitimate read use-case for
|
|
2374
|
+
// an AI agent → hard `block`. But .netrc / .npmrc / .docker /
|
|
2375
|
+
// .kube / gcloud are CONFIG files that hold tokens AND have
|
|
2376
|
+
// legitimate diagnostic reads ("which registry am I configured
|
|
2377
|
+
// for", "what cluster am I on"). Hard-blocking those creates
|
|
2378
|
+
// friction without much safety win because the review gate
|
|
2379
|
+
// still catches genuine exfiltration attempts.
|
|
2380
|
+
//
|
|
2381
|
+
// The review gate FAILS CLOSED on timeout (daemon.approvalTimeoutMs
|
|
2382
|
+
// returns a deny verdict via the orchestrator's timeout branch),
|
|
2383
|
+
// so a stuck or unattended approval does NOT silently grant
|
|
2384
|
+
// credential access. If the threat model demands strict block,
|
|
2385
|
+
// a future per-shield strict-mode toggle is the right fix —
|
|
2386
|
+
// not a regex-level upgrade here.
|
|
2387
|
+
rule: "shield:project-jail:review-read-credentials",
|
|
2388
|
+
reason: "Reading credential files requires approval (project-jail shield)",
|
|
2389
|
+
verdict: "review",
|
|
2390
|
+
match: (p) => (
|
|
2391
|
+
// .kube/config holds Kubernetes cluster credentials and was
|
|
2392
|
+
// flagged as missing by the node9-pr-agent review (the comment
|
|
2393
|
+
// above mentioned .kube but the regex didn't include it — a
|
|
2394
|
+
// textbook code-comment vs code drift). The JSON shield's
|
|
2395
|
+
// review-read-credentials-any-tool already had it. Now aligned.
|
|
2396
|
+
/(?:credentials\.json|\.netrc|\.npmrc|\.docker[\\/]config\.json|gcloud[\\/]credentials|\.kube[\\/]config)$/i.test(
|
|
2397
|
+
p
|
|
2398
|
+
)
|
|
2353
2399
|
)
|
|
2354
2400
|
}
|
|
2355
2401
|
];
|
|
@@ -2365,7 +2411,7 @@ var init_dist = __esm({
|
|
|
2365
2411
|
"shield:project-jail:block-read-ssh",
|
|
2366
2412
|
"shield:project-jail:block-read-aws",
|
|
2367
2413
|
"shield:project-jail:block-read-env",
|
|
2368
|
-
"shield:project-jail:
|
|
2414
|
+
"shield:project-jail:review-read-credentials"
|
|
2369
2415
|
]);
|
|
2370
2416
|
FS_OP_CACHE_MAX = 5e3;
|
|
2371
2417
|
fsOpCache = /* @__PURE__ */ new Map();
|
|
@@ -3053,7 +3099,7 @@ var init_dist = __esm({
|
|
|
3053
3099
|
{
|
|
3054
3100
|
field: "command",
|
|
3055
3101
|
op: "matches",
|
|
3056
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s
|
|
3102
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.ssh[\\/\\\\]",
|
|
3057
3103
|
flags: "i"
|
|
3058
3104
|
}
|
|
3059
3105
|
],
|
|
@@ -3067,7 +3113,7 @@ var init_dist = __esm({
|
|
|
3067
3113
|
{
|
|
3068
3114
|
field: "command",
|
|
3069
3115
|
op: "matches",
|
|
3070
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s
|
|
3116
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.aws[\\/\\\\]",
|
|
3071
3117
|
flags: "i"
|
|
3072
3118
|
}
|
|
3073
3119
|
],
|
|
@@ -3089,7 +3135,7 @@ var init_dist = __esm({
|
|
|
3089
3135
|
reason: "Reading .env files is blocked by project-jail shield"
|
|
3090
3136
|
},
|
|
3091
3137
|
{
|
|
3092
|
-
name: "shield:project-jail:
|
|
3138
|
+
name: "shield:project-jail:review-read-credentials",
|
|
3093
3139
|
tool: "bash",
|
|
3094
3140
|
conditions: [
|
|
3095
3141
|
{
|
|
@@ -3099,8 +3145,64 @@ var init_dist = __esm({
|
|
|
3099
3145
|
flags: "i"
|
|
3100
3146
|
}
|
|
3101
3147
|
],
|
|
3148
|
+
verdict: "review",
|
|
3149
|
+
reason: "Reading credential files requires approval (project-jail shield)"
|
|
3150
|
+
},
|
|
3151
|
+
{
|
|
3152
|
+
name: "shield:project-jail:block-read-ssh-any-tool",
|
|
3153
|
+
tool: "*",
|
|
3154
|
+
conditions: [
|
|
3155
|
+
{
|
|
3156
|
+
field: "file_path",
|
|
3157
|
+
op: "matches",
|
|
3158
|
+
value: "(^|[\\/\\\\])\\.ssh[\\/\\\\]",
|
|
3159
|
+
flags: "i"
|
|
3160
|
+
}
|
|
3161
|
+
],
|
|
3162
|
+
verdict: "block",
|
|
3163
|
+
reason: "Reading SSH private keys is blocked by project-jail shield"
|
|
3164
|
+
},
|
|
3165
|
+
{
|
|
3166
|
+
name: "shield:project-jail:block-read-aws-any-tool",
|
|
3167
|
+
tool: "*",
|
|
3168
|
+
conditions: [
|
|
3169
|
+
{
|
|
3170
|
+
field: "file_path",
|
|
3171
|
+
op: "matches",
|
|
3172
|
+
value: "(^|[\\/\\\\])\\.aws[\\/\\\\]",
|
|
3173
|
+
flags: "i"
|
|
3174
|
+
}
|
|
3175
|
+
],
|
|
3102
3176
|
verdict: "block",
|
|
3103
|
-
reason: "Reading
|
|
3177
|
+
reason: "Reading AWS credentials is blocked by project-jail shield"
|
|
3178
|
+
},
|
|
3179
|
+
{
|
|
3180
|
+
name: "shield:project-jail:review-read-env-any-tool",
|
|
3181
|
+
tool: "*",
|
|
3182
|
+
conditions: [
|
|
3183
|
+
{
|
|
3184
|
+
field: "file_path",
|
|
3185
|
+
op: "matches",
|
|
3186
|
+
value: "(^|[\\/\\\\])\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?$",
|
|
3187
|
+
flags: "i"
|
|
3188
|
+
}
|
|
3189
|
+
],
|
|
3190
|
+
verdict: "review",
|
|
3191
|
+
reason: "Reading .env files requires approval (project-jail shield)"
|
|
3192
|
+
},
|
|
3193
|
+
{
|
|
3194
|
+
name: "shield:project-jail:review-read-credentials-any-tool",
|
|
3195
|
+
tool: "*",
|
|
3196
|
+
conditions: [
|
|
3197
|
+
{
|
|
3198
|
+
field: "file_path",
|
|
3199
|
+
op: "matches",
|
|
3200
|
+
value: ".*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials|\\.kube[\\/\\\\]config)",
|
|
3201
|
+
flags: "i"
|
|
3202
|
+
}
|
|
3203
|
+
],
|
|
3204
|
+
verdict: "review",
|
|
3205
|
+
reason: "Reading credential files requires approval (project-jail shield)"
|
|
3104
3206
|
}
|
|
3105
3207
|
],
|
|
3106
3208
|
dangerousWords: []
|
|
@@ -3224,7 +3326,6 @@ var init_dist = __esm({
|
|
|
3224
3326
|
LOOP_THRESHOLD_FOR_WASTE = 3;
|
|
3225
3327
|
COST_PER_LOOP_ITER_USD = 6e-3;
|
|
3226
3328
|
DESTRUCTIVE_OP_RE = /\brm\s+-[rRf]+\b|\bDROP\s+(TABLE|DATABASE|COLLECTION|SCHEMA)\b|\bTRUNCATE\s+TABLE\b|\bgit\s+push\s+(--force|-f)\b|\bFLUSHALL\b|\bFLUSHDB\b|\bkubectl\s+delete\b|\bhelm\s+uninstall\b/i;
|
|
3227
|
-
PRIVILEGE_ESCALATION_RE = /\b(sudo|su)\b\s+[a-z]|\bchmod\s+(0?777|\+x)\b|\bchown\s+root\b/i;
|
|
3228
3329
|
SENSITIVE_PATH_RE = /\.aws\/(credentials|config)\b|\.ssh\/(id_rsa|id_ed25519|id_ecdsa|id_dsa)\b|\.env(\.|$|\b)|\.config\/gcloud\/credentials\.db\b|\.docker\/config\.json\b|\.netrc\b|\.npmrc\b|\.node9\/credentials\.json\b/i;
|
|
3229
3330
|
FILE_TOOLS = /* @__PURE__ */ new Set([
|
|
3230
3331
|
"read",
|
|
@@ -3244,7 +3345,7 @@ var init_dist = __esm({
|
|
|
3244
3345
|
PII_PHONE_RE = /\b(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]\d{3}[-.\s]\d{4}\b/;
|
|
3245
3346
|
PII_CC_RE = /\b(?:4\d{3}|5[1-5]\d{2}|3[47]\d{2}|6\d{3})[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/;
|
|
3246
3347
|
LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
3247
|
-
CANONICAL_EXTRACTOR_VERSION = "canonical-
|
|
3348
|
+
CANONICAL_EXTRACTOR_VERSION = "canonical-v4";
|
|
3248
3349
|
DEDUPE_PREVIEW_LEN = 120;
|
|
3249
3350
|
TERMINAL_ESCAPE_RE = // eslint-disable-next-line no-control-regex
|
|
3250
3351
|
/\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
|
|
@@ -4788,7 +4889,7 @@ async function waitForDaemonDecision(id, signal) {
|
|
|
4788
4889
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
4789
4890
|
}
|
|
4790
4891
|
}
|
|
4791
|
-
async function notifyDaemonViewer(toolName, args, meta, riskMetadata) {
|
|
4892
|
+
async function notifyDaemonViewer(toolName, args, meta, riskMetadata, activityId, socketActivitySent) {
|
|
4792
4893
|
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
4793
4894
|
const res = await fetch(`${base}/check`, {
|
|
4794
4895
|
method: "POST",
|
|
@@ -4799,7 +4900,12 @@ async function notifyDaemonViewer(toolName, args, meta, riskMetadata) {
|
|
|
4799
4900
|
slackDelegated: true,
|
|
4800
4901
|
agent: meta?.agent,
|
|
4801
4902
|
mcpServer: meta?.mcpServer,
|
|
4802
|
-
...riskMetadata && { riskMetadata }
|
|
4903
|
+
...riskMetadata && { riskMetadata },
|
|
4904
|
+
// fromCLI=true tells the daemon the CLI already sent the activity
|
|
4905
|
+
// event via socket. Same contract as registerDaemonEntry — without
|
|
4906
|
+
// it the daemon double-emits 'activity' for cloud-enforced flows.
|
|
4907
|
+
fromCLI: socketActivitySent !== false,
|
|
4908
|
+
activityId
|
|
4803
4909
|
}),
|
|
4804
4910
|
signal: AbortSignal.timeout(3e3)
|
|
4805
4911
|
});
|
|
@@ -5762,7 +5868,10 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
5762
5868
|
args,
|
|
5763
5869
|
"deny",
|
|
5764
5870
|
"smart-rule-block-override",
|
|
5765
|
-
|
|
5871
|
+
// Same rationale as the smart-rule-block path above —
|
|
5872
|
+
// pass the specific rule name so [2] SHIELDS can
|
|
5873
|
+
// attribute this override-block to its owning shield.
|
|
5874
|
+
{ ...meta, ruleName: policyResult.ruleName },
|
|
5766
5875
|
hashAuditArgs
|
|
5767
5876
|
);
|
|
5768
5877
|
if (approvers.cloud && creds?.apiKey)
|
|
@@ -5792,7 +5901,20 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
5792
5901
|
}
|
|
5793
5902
|
} else {
|
|
5794
5903
|
if (!isManual)
|
|
5795
|
-
appendLocalAudit(
|
|
5904
|
+
appendLocalAudit(
|
|
5905
|
+
toolName,
|
|
5906
|
+
args,
|
|
5907
|
+
"deny",
|
|
5908
|
+
"smart-rule-block",
|
|
5909
|
+
// Include policyResult.ruleName so the [2] Report SHIELDS
|
|
5910
|
+
// panel can attribute this block to its specific shield
|
|
5911
|
+
// (e.g. `shield:project-jail:block-read-ssh`) via the
|
|
5912
|
+
// rule→shield map. checkedBy stays as the generic
|
|
5913
|
+
// `smart-rule-block` for backward compat with existing
|
|
5914
|
+
// log readers.
|
|
5915
|
+
{ ...meta, ruleName: policyResult.ruleName },
|
|
5916
|
+
hashAuditArgs
|
|
5917
|
+
);
|
|
5796
5918
|
if (approvers.cloud && creds?.apiKey)
|
|
5797
5919
|
auditLocalAllow(toolName, args, "smart-rule-block", creds, meta, void 0, false, {
|
|
5798
5920
|
ruleName: policyResult.ruleName,
|
|
@@ -5940,7 +6062,14 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
5940
6062
|
let daemonAllowCount = 1;
|
|
5941
6063
|
if (approvers.terminal && isDaemonRunning() && !options?.calledFromDaemon) {
|
|
5942
6064
|
if (cloudEnforced && cloudRequestId) {
|
|
5943
|
-
const viewer = await notifyDaemonViewer(
|
|
6065
|
+
const viewer = await notifyDaemonViewer(
|
|
6066
|
+
toolName,
|
|
6067
|
+
args,
|
|
6068
|
+
meta,
|
|
6069
|
+
riskMetadata,
|
|
6070
|
+
options?.activityId,
|
|
6071
|
+
options?.socketActivitySent
|
|
6072
|
+
).catch(() => null);
|
|
5944
6073
|
viewerId = viewer?.id ?? null;
|
|
5945
6074
|
daemonEntryId = viewerId;
|
|
5946
6075
|
if (viewer) daemonAllowCount = viewer.allowCount;
|
|
@@ -7606,9 +7735,61 @@ function computeLoopWaste(loops, totalToolCalls) {
|
|
|
7606
7735
|
const wastePct = totalToolCalls > 0 ? Math.round(wastedCalls / totalToolCalls * 100) : 0;
|
|
7607
7736
|
return { wastedCalls, wastePct };
|
|
7608
7737
|
}
|
|
7738
|
+
function rollupByShield(sections, topRulesPerShield = 3) {
|
|
7739
|
+
const out = [];
|
|
7740
|
+
for (const section of sections) {
|
|
7741
|
+
if (section.sourceType !== "shield") continue;
|
|
7742
|
+
if (!section.shieldKey) continue;
|
|
7743
|
+
const totalCatches = section.blockedCount + section.reviewCount;
|
|
7744
|
+
const topRuleLabels = [...section.rules].sort((a, b) => b.findings.length - a.findings.length).slice(0, topRulesPerShield).map((r) => r.findings.length > 1 ? `${r.name} \xD7${r.findings.length}` : r.name);
|
|
7745
|
+
out.push({
|
|
7746
|
+
shieldName: section.shieldKey,
|
|
7747
|
+
totalCatches,
|
|
7748
|
+
blockCatches: section.blockedCount,
|
|
7749
|
+
reviewCatches: section.reviewCount,
|
|
7750
|
+
topRuleLabels
|
|
7751
|
+
});
|
|
7752
|
+
}
|
|
7753
|
+
return out.sort((a, b) => b.totalCatches - a.totalCatches);
|
|
7754
|
+
}
|
|
7755
|
+
function boxPanel(title, bodyLines, width = PANEL_WIDTH) {
|
|
7756
|
+
const inner = width - 4;
|
|
7757
|
+
const out = [];
|
|
7758
|
+
const titlePad = ` ${title} `;
|
|
7759
|
+
const titleSegment = titlePad.length <= inner ? titlePad : titlePad.slice(0, inner);
|
|
7760
|
+
const dashFill = "\u2500".repeat(Math.max(0, inner - titleSegment.length));
|
|
7761
|
+
out.push(chalk3.dim("\u256D\u2500") + chalk3.bold(titleSegment) + chalk3.dim(`${dashFill}\u2500\u256E`));
|
|
7762
|
+
for (const line of bodyLines) {
|
|
7763
|
+
const padding = " ".repeat(Math.max(0, inner - line.width));
|
|
7764
|
+
out.push(chalk3.dim("\u2502 ") + line.rendered + padding + chalk3.dim(" \u2502"));
|
|
7765
|
+
}
|
|
7766
|
+
out.push(chalk3.dim("\u2570" + "\u2500".repeat(inner + 2) + "\u256F"));
|
|
7767
|
+
return out;
|
|
7768
|
+
}
|
|
7769
|
+
function relativeDate(timestamp, now = /* @__PURE__ */ new Date()) {
|
|
7770
|
+
const t = new Date(timestamp).getTime();
|
|
7771
|
+
if (Number.isNaN(t)) return "?";
|
|
7772
|
+
const days = Math.floor((now.getTime() - t) / 864e5);
|
|
7773
|
+
if (days < 1) return "today";
|
|
7774
|
+
if (days > 90) return "90d+";
|
|
7775
|
+
return `${days}d`;
|
|
7776
|
+
}
|
|
7777
|
+
var PANEL_WIDTH;
|
|
7609
7778
|
var init_scan_derive = __esm({
|
|
7610
7779
|
"src/cli/render/scan-derive.ts"() {
|
|
7611
7780
|
"use strict";
|
|
7781
|
+
PANEL_WIDTH = 76;
|
|
7782
|
+
}
|
|
7783
|
+
});
|
|
7784
|
+
|
|
7785
|
+
// src/protection.ts
|
|
7786
|
+
var PROTECTIVE_SHIELD_DISCOUNTS;
|
|
7787
|
+
var init_protection = __esm({
|
|
7788
|
+
"src/protection.ts"() {
|
|
7789
|
+
"use strict";
|
|
7790
|
+
PROTECTIVE_SHIELD_DISCOUNTS = {
|
|
7791
|
+
"project-jail": 0.7
|
|
7792
|
+
};
|
|
7612
7793
|
}
|
|
7613
7794
|
});
|
|
7614
7795
|
|
|
@@ -7798,6 +7979,7 @@ async function ensurePricingLoaded() {
|
|
|
7798
7979
|
if (fromDisk && Object.keys(fromDisk).length > 0) {
|
|
7799
7980
|
memCache = fromDisk;
|
|
7800
7981
|
memCacheAt = Date.now();
|
|
7982
|
+
lookupCache.clear();
|
|
7801
7983
|
return;
|
|
7802
7984
|
}
|
|
7803
7985
|
const fetched = await fetchLiteLLMPricing();
|
|
@@ -7805,30 +7987,42 @@ async function ensurePricingLoaded() {
|
|
|
7805
7987
|
memCache = fetched;
|
|
7806
7988
|
memCacheAt = Date.now();
|
|
7807
7989
|
writeCache(fetched);
|
|
7990
|
+
lookupCache.clear();
|
|
7808
7991
|
return;
|
|
7809
7992
|
}
|
|
7810
7993
|
memCache = { ...BUNDLED_PRICING };
|
|
7811
7994
|
memCacheAt = Date.now();
|
|
7995
|
+
lookupCache.clear();
|
|
7812
7996
|
}
|
|
7813
7997
|
function pricingFor(model) {
|
|
7814
7998
|
const norm = normalizeModel(model);
|
|
7999
|
+
const cached = lookupCache.get(norm);
|
|
8000
|
+
if (cached !== void 0) return cached;
|
|
7815
8001
|
const sources = [];
|
|
7816
8002
|
if (memCache) sources.push(memCache);
|
|
7817
8003
|
sources.push(BUNDLED_PRICING);
|
|
8004
|
+
let resolved = null;
|
|
7818
8005
|
for (const source of sources) {
|
|
7819
8006
|
const exact = source[norm];
|
|
7820
|
-
if (exact)
|
|
8007
|
+
if (exact) {
|
|
8008
|
+
resolved = exact;
|
|
8009
|
+
break;
|
|
8010
|
+
}
|
|
7821
8011
|
let best = null;
|
|
7822
8012
|
for (const key of Object.keys(source)) {
|
|
7823
8013
|
if (norm.startsWith(key.toLowerCase()) && (best === null || key.length > best.length)) {
|
|
7824
8014
|
best = key;
|
|
7825
8015
|
}
|
|
7826
8016
|
}
|
|
7827
|
-
if (best)
|
|
8017
|
+
if (best) {
|
|
8018
|
+
resolved = source[best];
|
|
8019
|
+
break;
|
|
8020
|
+
}
|
|
7828
8021
|
}
|
|
7829
|
-
|
|
8022
|
+
lookupCache.set(norm, resolved);
|
|
8023
|
+
return resolved;
|
|
7830
8024
|
}
|
|
7831
|
-
var LITELLM_URL, BUNDLED_PRICING, CACHE_FILE, TTL_MS, memCache, memCacheAt;
|
|
8025
|
+
var LITELLM_URL, BUNDLED_PRICING, CACHE_FILE, TTL_MS, memCache, memCacheAt, lookupCache;
|
|
7832
8026
|
var init_litellm = __esm({
|
|
7833
8027
|
"src/pricing/litellm.ts"() {
|
|
7834
8028
|
"use strict";
|
|
@@ -7862,6 +8056,7 @@ var init_litellm = __esm({
|
|
|
7862
8056
|
TTL_MS = 24 * 60 * 60 * 1e3;
|
|
7863
8057
|
memCache = null;
|
|
7864
8058
|
memCacheAt = 0;
|
|
8059
|
+
lookupCache = /* @__PURE__ */ new Map();
|
|
7865
8060
|
}
|
|
7866
8061
|
});
|
|
7867
8062
|
|
|
@@ -7931,7 +8126,7 @@ function parseJSONLFile(filePath, fallbackWorkingDir) {
|
|
|
7931
8126
|
}
|
|
7932
8127
|
return daily;
|
|
7933
8128
|
}
|
|
7934
|
-
function collectEntries() {
|
|
8129
|
+
function collectEntries(sinceMs) {
|
|
7935
8130
|
const projectsDir = path18.join(os15.homedir(), ".claude", "projects");
|
|
7936
8131
|
if (!fs16.existsSync(projectsDir)) return [];
|
|
7937
8132
|
const combined = /* @__PURE__ */ new Map();
|
|
@@ -7956,7 +8151,15 @@ function collectEntries() {
|
|
|
7956
8151
|
}
|
|
7957
8152
|
const fallbackWorkingDir = decodeProjectDirName(dir);
|
|
7958
8153
|
for (const file of files) {
|
|
7959
|
-
const
|
|
8154
|
+
const filePath = path18.join(dirPath, file);
|
|
8155
|
+
if (sinceMs !== void 0) {
|
|
8156
|
+
try {
|
|
8157
|
+
if (fs16.statSync(filePath).mtimeMs < sinceMs) continue;
|
|
8158
|
+
} catch {
|
|
8159
|
+
continue;
|
|
8160
|
+
}
|
|
8161
|
+
}
|
|
8162
|
+
const entries = parseJSONLFile(filePath, fallbackWorkingDir);
|
|
7960
8163
|
for (const [key, e] of entries) {
|
|
7961
8164
|
const prev = combined.get(key);
|
|
7962
8165
|
if (prev) {
|
|
@@ -8030,6 +8233,7 @@ __export(scan_watermark_exports, {
|
|
|
8030
8233
|
markUploadComplete: () => markUploadComplete,
|
|
8031
8234
|
saveWatermark: () => saveWatermark,
|
|
8032
8235
|
scanDelta: () => scanDelta,
|
|
8236
|
+
tickForensicBroadcast: () => tickForensicBroadcast,
|
|
8033
8237
|
tickScanWatcher: () => tickScanWatcher
|
|
8034
8238
|
});
|
|
8035
8239
|
import fs17 from "fs";
|
|
@@ -8237,6 +8441,25 @@ function extractFindingsFromLine(line, sessionId, lineIndex) {
|
|
|
8237
8441
|
}
|
|
8238
8442
|
return findings;
|
|
8239
8443
|
}
|
|
8444
|
+
async function tickForensicBroadcast(offsets) {
|
|
8445
|
+
const out = [];
|
|
8446
|
+
const files = listJsonlFiles();
|
|
8447
|
+
for (const file of files) {
|
|
8448
|
+
const size = fileSize(file);
|
|
8449
|
+
const offset = offsets.get(file);
|
|
8450
|
+
if (offset === void 0) {
|
|
8451
|
+
offsets.set(file, size);
|
|
8452
|
+
continue;
|
|
8453
|
+
}
|
|
8454
|
+
if (size <= offset) continue;
|
|
8455
|
+
const sessionId = path19.basename(file, ".jsonl");
|
|
8456
|
+
const newOffset = await scanDelta(file, offset, (obj, lineIndex) => {
|
|
8457
|
+
out.push(...extractFindingsFromLine(obj, sessionId, lineIndex));
|
|
8458
|
+
});
|
|
8459
|
+
offsets.set(file, newOffset);
|
|
8460
|
+
}
|
|
8461
|
+
return out;
|
|
8462
|
+
}
|
|
8240
8463
|
function markUploadComplete() {
|
|
8241
8464
|
const state = loadWatermark();
|
|
8242
8465
|
if (state.status === "schema-future") return;
|
|
@@ -8772,7 +8995,16 @@ function buildRecurringPatternSet(findings) {
|
|
|
8772
8995
|
}
|
|
8773
8996
|
return recurring;
|
|
8774
8997
|
}
|
|
8775
|
-
function
|
|
8998
|
+
function emptyScanDedup() {
|
|
8999
|
+
return { findingsKeys: /* @__PURE__ */ new Set(), dlpKeys: /* @__PURE__ */ new Set() };
|
|
9000
|
+
}
|
|
9001
|
+
function findingKey(ruleName, inputPreview, projLabel) {
|
|
9002
|
+
return `${ruleName ?? "<unnamed>"}|${inputPreview}|${projLabel}`;
|
|
9003
|
+
}
|
|
9004
|
+
function dlpKey(patternName, redactedSample, projLabel) {
|
|
9005
|
+
return `${patternName}|${redactedSample}|${projLabel}`;
|
|
9006
|
+
}
|
|
9007
|
+
function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sessionId, agent, result, dedup) {
|
|
8776
9008
|
const fsVerdict = analyzeFsOperation(command);
|
|
8777
9009
|
if (!fsVerdict) return false;
|
|
8778
9010
|
const synthRule = {
|
|
@@ -8795,10 +9027,9 @@ function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sess
|
|
|
8795
9027
|
rule: synthRule
|
|
8796
9028
|
};
|
|
8797
9029
|
const inputPreview = preview(input, 120);
|
|
8798
|
-
const
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
if (!isDupe) {
|
|
9030
|
+
const k = findingKey(synthRule.name, inputPreview, projLabel);
|
|
9031
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9032
|
+
dedup.findingsKeys.add(k);
|
|
8802
9033
|
result.findings.push({
|
|
8803
9034
|
source: synthSource,
|
|
8804
9035
|
toolName,
|
|
@@ -8883,22 +9114,15 @@ function buildRuleSources() {
|
|
|
8883
9114
|
sources.push({ shieldName, shieldLabel: shieldName, sourceType: "shield", rule });
|
|
8884
9115
|
}
|
|
8885
9116
|
}
|
|
8886
|
-
|
|
8887
|
-
|
|
8888
|
-
|
|
8889
|
-
|
|
8890
|
-
|
|
8891
|
-
|
|
8892
|
-
|
|
8893
|
-
|
|
8894
|
-
|
|
8895
|
-
shieldName: isCloud ? "cloud" : isDefault ? "default" : "custom",
|
|
8896
|
-
shieldLabel: isCloud ? "Cloud Policy" : isDefault ? "Default Rules" : "Your Rules",
|
|
8897
|
-
sourceType,
|
|
8898
|
-
rule
|
|
8899
|
-
});
|
|
8900
|
-
}
|
|
8901
|
-
} catch {
|
|
9117
|
+
for (const rule of DEFAULT_CONFIG.policy.smartRules) {
|
|
9118
|
+
if (!rule.name) continue;
|
|
9119
|
+
if (rule.name.startsWith("shield:")) continue;
|
|
9120
|
+
sources.push({
|
|
9121
|
+
shieldName: "default",
|
|
9122
|
+
shieldLabel: "Default Rules",
|
|
9123
|
+
sourceType: "default",
|
|
9124
|
+
rule
|
|
9125
|
+
});
|
|
8902
9126
|
}
|
|
8903
9127
|
return sources;
|
|
8904
9128
|
}
|
|
@@ -8984,178 +9208,53 @@ function renderProgressBar(done, total, lines) {
|
|
|
8984
9208
|
`\r ${chalk5.cyan("Scanning")} [${chalk5.cyan(bar)}] ${chalk5.dim(fileLabel)}${lineLabel} `
|
|
8985
9209
|
);
|
|
8986
9210
|
}
|
|
8987
|
-
function
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
|
|
8991
|
-
|
|
8992
|
-
|
|
8993
|
-
bashCalls: 0,
|
|
8994
|
-
findings: [],
|
|
8995
|
-
dlpFindings: [],
|
|
8996
|
-
loopFindings: [],
|
|
8997
|
-
totalCostUSD: 0,
|
|
8998
|
-
firstDate: null,
|
|
8999
|
-
lastDate: null,
|
|
9000
|
-
sessionsWithEarlySecrets: 0
|
|
9001
|
-
};
|
|
9002
|
-
if (!fs19.existsSync(projectsDir)) return result;
|
|
9003
|
-
let projDirs;
|
|
9211
|
+
function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, result, dedup, onProgress, onLine) {
|
|
9212
|
+
result.filesScanned++;
|
|
9213
|
+
result.sessions++;
|
|
9214
|
+
onProgress?.(result.filesScanned);
|
|
9215
|
+
const sessionId = file.replace(/\.jsonl$/, "");
|
|
9216
|
+
let raw;
|
|
9004
9217
|
try {
|
|
9005
|
-
|
|
9218
|
+
raw = fs19.readFileSync(path21.join(projPath, file), "utf-8");
|
|
9006
9219
|
} catch {
|
|
9007
|
-
return
|
|
9220
|
+
return;
|
|
9008
9221
|
}
|
|
9009
|
-
const
|
|
9010
|
-
|
|
9011
|
-
|
|
9222
|
+
const sessionCalls = [];
|
|
9223
|
+
const toolUseFilePaths = /* @__PURE__ */ new Map();
|
|
9224
|
+
let firstDlpTs = null;
|
|
9225
|
+
let firstEditTs = null;
|
|
9226
|
+
for (const line of raw.split("\n")) {
|
|
9227
|
+
if (!line.trim()) continue;
|
|
9228
|
+
onLine?.();
|
|
9229
|
+
let entry;
|
|
9012
9230
|
try {
|
|
9013
|
-
|
|
9231
|
+
entry = JSON.parse(line);
|
|
9014
9232
|
} catch {
|
|
9015
9233
|
continue;
|
|
9016
9234
|
}
|
|
9017
|
-
|
|
9018
|
-
|
|
9019
|
-
|
|
9020
|
-
let files;
|
|
9021
|
-
try {
|
|
9022
|
-
files = fs19.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
9023
|
-
} catch {
|
|
9024
|
-
continue;
|
|
9235
|
+
if (entry.type !== "assistant" && entry.type !== "user") continue;
|
|
9236
|
+
if (startDate && entry.timestamp) {
|
|
9237
|
+
if (new Date(entry.timestamp) < startDate) continue;
|
|
9025
9238
|
}
|
|
9026
|
-
|
|
9027
|
-
result.
|
|
9028
|
-
|
|
9029
|
-
|
|
9030
|
-
|
|
9031
|
-
|
|
9032
|
-
|
|
9033
|
-
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
const sessionCalls = [];
|
|
9038
|
-
const toolUseFilePaths = /* @__PURE__ */ new Map();
|
|
9039
|
-
let firstDlpTs = null;
|
|
9040
|
-
let firstEditTs = null;
|
|
9041
|
-
for (const line of raw.split("\n")) {
|
|
9042
|
-
if (!line.trim()) continue;
|
|
9043
|
-
onLine?.();
|
|
9044
|
-
let entry;
|
|
9045
|
-
try {
|
|
9046
|
-
entry = JSON.parse(line);
|
|
9047
|
-
} catch {
|
|
9048
|
-
continue;
|
|
9049
|
-
}
|
|
9050
|
-
if (entry.type !== "assistant" && entry.type !== "user") continue;
|
|
9051
|
-
if (startDate && entry.timestamp) {
|
|
9052
|
-
if (new Date(entry.timestamp) < startDate) continue;
|
|
9053
|
-
}
|
|
9054
|
-
if (entry.timestamp) {
|
|
9055
|
-
if (!result.firstDate || entry.timestamp < result.firstDate)
|
|
9056
|
-
result.firstDate = entry.timestamp;
|
|
9057
|
-
if (!result.lastDate || entry.timestamp > result.lastDate)
|
|
9058
|
-
result.lastDate = entry.timestamp;
|
|
9059
|
-
}
|
|
9060
|
-
if (entry.type === "user") {
|
|
9061
|
-
const content2 = entry.message?.content;
|
|
9062
|
-
if (Array.isArray(content2)) {
|
|
9063
|
-
const text = content2.filter((b) => b.type === "text").map((b) => b["text"] ?? "").join("\n");
|
|
9064
|
-
if (text) {
|
|
9065
|
-
const dlpMatch = scanArgs({ text });
|
|
9066
|
-
if (dlpMatch) {
|
|
9067
|
-
const isDupe = result.dlpFindings.some(
|
|
9068
|
-
(f) => f.patternName === dlpMatch.patternName && f.redactedSample === dlpMatch.redactedSample && f.project === projLabel
|
|
9069
|
-
);
|
|
9070
|
-
if (!isDupe) {
|
|
9071
|
-
result.dlpFindings.push({
|
|
9072
|
-
patternName: dlpMatch.patternName,
|
|
9073
|
-
redactedSample: dlpMatch.redactedSample,
|
|
9074
|
-
toolName: "user-prompt",
|
|
9075
|
-
timestamp: entry.timestamp ?? "",
|
|
9076
|
-
project: projLabel,
|
|
9077
|
-
sessionId,
|
|
9078
|
-
agent: "claude"
|
|
9079
|
-
});
|
|
9080
|
-
}
|
|
9081
|
-
}
|
|
9082
|
-
}
|
|
9083
|
-
for (const block of content2) {
|
|
9084
|
-
if (block.type !== "tool_result") continue;
|
|
9085
|
-
const filePath = block.tool_use_id ? toolUseFilePaths.get(block.tool_use_id) : void 0;
|
|
9086
|
-
if (filePath) {
|
|
9087
|
-
const ext = path21.extname(filePath).toLowerCase();
|
|
9088
|
-
if (CODE_EXTENSIONS.has(ext)) continue;
|
|
9089
|
-
}
|
|
9090
|
-
const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
|
|
9091
|
-
if (!resultText) continue;
|
|
9092
|
-
if (isNode9SelfOutput(resultText)) continue;
|
|
9093
|
-
const dlpMatch = scanArgs({ text: resultText });
|
|
9094
|
-
if (dlpMatch) {
|
|
9095
|
-
if (looksLikeFixtureToken(dlpMatch.redactedSample)) continue;
|
|
9096
|
-
if (firstDlpTs === null) firstDlpTs = entry.timestamp ?? null;
|
|
9097
|
-
const isDupe = result.dlpFindings.some(
|
|
9098
|
-
(f) => f.patternName === dlpMatch.patternName && f.redactedSample === dlpMatch.redactedSample && f.project === projLabel
|
|
9099
|
-
);
|
|
9100
|
-
if (!isDupe) {
|
|
9101
|
-
result.dlpFindings.push({
|
|
9102
|
-
patternName: dlpMatch.patternName,
|
|
9103
|
-
redactedSample: dlpMatch.redactedSample,
|
|
9104
|
-
toolName: "tool-result",
|
|
9105
|
-
timestamp: entry.timestamp ?? "",
|
|
9106
|
-
project: projLabel,
|
|
9107
|
-
sessionId,
|
|
9108
|
-
agent: "claude"
|
|
9109
|
-
});
|
|
9110
|
-
}
|
|
9111
|
-
}
|
|
9112
|
-
}
|
|
9113
|
-
}
|
|
9114
|
-
continue;
|
|
9115
|
-
}
|
|
9116
|
-
const usage = entry.message?.usage;
|
|
9117
|
-
const model = entry.message?.model;
|
|
9118
|
-
if (usage && model) {
|
|
9119
|
-
const p = claudeModelPrice(model);
|
|
9120
|
-
if (p) {
|
|
9121
|
-
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;
|
|
9122
|
-
}
|
|
9123
|
-
}
|
|
9124
|
-
const content = entry.message?.content;
|
|
9125
|
-
if (!Array.isArray(content)) continue;
|
|
9126
|
-
for (const block of content) {
|
|
9127
|
-
if (block.type !== "tool_use") continue;
|
|
9128
|
-
result.totalToolCalls++;
|
|
9129
|
-
const toolName = block.name ?? "";
|
|
9130
|
-
const toolNameLower = toolName.toLowerCase();
|
|
9131
|
-
const input = block.input ?? {};
|
|
9132
|
-
if (block.id && typeof input.file_path === "string") {
|
|
9133
|
-
toolUseFilePaths.set(block.id, input.file_path);
|
|
9134
|
-
}
|
|
9135
|
-
sessionCalls.push({ toolName, input, timestamp: entry.timestamp ?? "" });
|
|
9136
|
-
if (toolNameLower === "bash" || toolNameLower === "execute_bash") {
|
|
9137
|
-
result.bashCalls++;
|
|
9138
|
-
}
|
|
9139
|
-
if (firstEditTs === null && (toolNameLower === "edit" || toolNameLower === "write" || toolNameLower === "write_file" || toolNameLower === "edit_file" || toolNameLower === "multiedit")) {
|
|
9140
|
-
firstEditTs = entry.timestamp ?? null;
|
|
9141
|
-
}
|
|
9142
|
-
const rawCmd = String(input.command ?? "").trimStart();
|
|
9143
|
-
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd))
|
|
9144
|
-
continue;
|
|
9145
|
-
const inputFilePath = typeof input.file_path === "string" ? input.file_path : "";
|
|
9146
|
-
const inputFileExt = inputFilePath ? path21.extname(inputFilePath).toLowerCase() : "";
|
|
9147
|
-
if (CODE_EXTENSIONS.has(inputFileExt)) continue;
|
|
9148
|
-
const dlpMatch = scanArgs(input);
|
|
9239
|
+
if (entry.timestamp) {
|
|
9240
|
+
if (!result.firstDate || entry.timestamp < result.firstDate)
|
|
9241
|
+
result.firstDate = entry.timestamp;
|
|
9242
|
+
if (!result.lastDate || entry.timestamp > result.lastDate) result.lastDate = entry.timestamp;
|
|
9243
|
+
}
|
|
9244
|
+
if (entry.type === "user") {
|
|
9245
|
+
const content2 = entry.message?.content;
|
|
9246
|
+
if (Array.isArray(content2)) {
|
|
9247
|
+
const text = content2.filter((b) => b.type === "text").map((b) => b["text"] ?? "").join("\n");
|
|
9248
|
+
if (text) {
|
|
9249
|
+
const dlpMatch = scanArgs({ text });
|
|
9149
9250
|
if (dlpMatch) {
|
|
9150
|
-
|
|
9151
|
-
|
|
9152
|
-
(
|
|
9153
|
-
);
|
|
9154
|
-
if (!isDupe) {
|
|
9251
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9252
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9253
|
+
dedup.dlpKeys.add(k);
|
|
9155
9254
|
result.dlpFindings.push({
|
|
9156
9255
|
patternName: dlpMatch.patternName,
|
|
9157
9256
|
redactedSample: dlpMatch.redactedSample,
|
|
9158
|
-
toolName,
|
|
9257
|
+
toolName: "user-prompt",
|
|
9159
9258
|
timestamp: entry.timestamp ?? "",
|
|
9160
9259
|
project: projLabel,
|
|
9161
9260
|
sessionId,
|
|
@@ -9163,85 +9262,234 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
|
|
|
9163
9262
|
});
|
|
9164
9263
|
}
|
|
9165
9264
|
}
|
|
9166
|
-
|
|
9167
|
-
|
|
9168
|
-
if (
|
|
9169
|
-
|
|
9170
|
-
|
|
9171
|
-
|
|
9172
|
-
|
|
9173
|
-
entry.timestamp ?? "",
|
|
9174
|
-
projLabel,
|
|
9175
|
-
sessionId,
|
|
9176
|
-
"claude",
|
|
9177
|
-
result
|
|
9178
|
-
);
|
|
9265
|
+
}
|
|
9266
|
+
for (const block of content2) {
|
|
9267
|
+
if (block.type !== "tool_result") continue;
|
|
9268
|
+
const filePath = block.tool_use_id ? toolUseFilePaths.get(block.tool_use_id) : void 0;
|
|
9269
|
+
if (filePath) {
|
|
9270
|
+
const ext = path21.extname(filePath).toLowerCase();
|
|
9271
|
+
if (CODE_EXTENSIONS.has(ext)) continue;
|
|
9179
9272
|
}
|
|
9180
|
-
|
|
9181
|
-
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
if (
|
|
9186
|
-
if (
|
|
9187
|
-
const
|
|
9188
|
-
|
|
9189
|
-
|
|
9190
|
-
|
|
9191
|
-
|
|
9192
|
-
|
|
9193
|
-
|
|
9194
|
-
toolName,
|
|
9195
|
-
input,
|
|
9273
|
+
const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
|
|
9274
|
+
if (!resultText) continue;
|
|
9275
|
+
if (isNode9SelfOutput(resultText)) continue;
|
|
9276
|
+
const dlpMatch = scanArgs({ text: resultText });
|
|
9277
|
+
if (dlpMatch) {
|
|
9278
|
+
if (looksLikeFixtureToken(dlpMatch.redactedSample)) continue;
|
|
9279
|
+
if (firstDlpTs === null) firstDlpTs = entry.timestamp ?? null;
|
|
9280
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9281
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9282
|
+
dedup.dlpKeys.add(k);
|
|
9283
|
+
result.dlpFindings.push({
|
|
9284
|
+
patternName: dlpMatch.patternName,
|
|
9285
|
+
redactedSample: dlpMatch.redactedSample,
|
|
9286
|
+
toolName: "tool-result",
|
|
9196
9287
|
timestamp: entry.timestamp ?? "",
|
|
9197
9288
|
project: projLabel,
|
|
9198
9289
|
sessionId,
|
|
9199
9290
|
agent: "claude"
|
|
9200
9291
|
});
|
|
9201
9292
|
}
|
|
9202
|
-
ruleMatched = true;
|
|
9203
|
-
break;
|
|
9204
|
-
}
|
|
9205
|
-
if (!ruleMatched && (toolNameLower === "bash" || toolNameLower === "execute_bash")) {
|
|
9206
|
-
const shellVerdict = detectDangerousShellExec(String(input.command ?? ""));
|
|
9207
|
-
if (shellVerdict) {
|
|
9208
|
-
const astRule = {
|
|
9209
|
-
name: `ast:bash-safe:${shellVerdict}-shell-exec-remote`,
|
|
9210
|
-
tool: "bash",
|
|
9211
|
-
conditions: [],
|
|
9212
|
-
verdict: shellVerdict,
|
|
9213
|
-
reason: `Shell execution of remote download detected by AST analysis (bash-safe)`
|
|
9214
|
-
};
|
|
9215
|
-
const inputPreview = preview(input, 120);
|
|
9216
|
-
const isDupe = result.findings.some(
|
|
9217
|
-
(f) => f.source.rule.name === astRule.name && preview(f.input, 120) === inputPreview && f.project === projLabel
|
|
9218
|
-
);
|
|
9219
|
-
if (!isDupe) {
|
|
9220
|
-
result.findings.push({
|
|
9221
|
-
source: {
|
|
9222
|
-
shieldName: "bash-safe",
|
|
9223
|
-
shieldLabel: "bash-safe (AST)",
|
|
9224
|
-
sourceType: "shield",
|
|
9225
|
-
rule: astRule
|
|
9226
|
-
},
|
|
9227
|
-
toolName,
|
|
9228
|
-
input,
|
|
9229
|
-
timestamp: entry.timestamp ?? "",
|
|
9230
|
-
project: projLabel,
|
|
9231
|
-
sessionId,
|
|
9232
|
-
agent: "claude"
|
|
9233
|
-
});
|
|
9234
|
-
}
|
|
9235
|
-
}
|
|
9236
9293
|
}
|
|
9237
9294
|
}
|
|
9238
9295
|
}
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9296
|
+
continue;
|
|
9297
|
+
}
|
|
9298
|
+
const usage = entry.message?.usage;
|
|
9299
|
+
const model = entry.message?.model;
|
|
9300
|
+
if (usage && model) {
|
|
9301
|
+
const p = claudeModelPrice(model);
|
|
9302
|
+
if (p) {
|
|
9303
|
+
result.totalCostUSD += (usage.input_tokens ?? 0) * p.i + (usage.output_tokens ?? 0) * p.o + (usage.cache_creation_input_tokens ?? 0) * p.cw + (usage.cache_read_input_tokens ?? 0) * p.cr;
|
|
9242
9304
|
}
|
|
9243
9305
|
}
|
|
9244
|
-
|
|
9306
|
+
const content = entry.message?.content;
|
|
9307
|
+
if (!Array.isArray(content)) continue;
|
|
9308
|
+
for (const block of content) {
|
|
9309
|
+
if (block.type !== "tool_use") continue;
|
|
9310
|
+
result.totalToolCalls++;
|
|
9311
|
+
const toolName = block.name ?? "";
|
|
9312
|
+
const toolNameLower = toolName.toLowerCase();
|
|
9313
|
+
const input = block.input ?? {};
|
|
9314
|
+
if (block.id && typeof input.file_path === "string") {
|
|
9315
|
+
toolUseFilePaths.set(block.id, input.file_path);
|
|
9316
|
+
}
|
|
9317
|
+
sessionCalls.push({ toolName, input, timestamp: entry.timestamp ?? "" });
|
|
9318
|
+
if (toolNameLower === "bash" || toolNameLower === "execute_bash") {
|
|
9319
|
+
result.bashCalls++;
|
|
9320
|
+
}
|
|
9321
|
+
if (firstEditTs === null && (toolNameLower === "edit" || toolNameLower === "write" || toolNameLower === "write_file" || toolNameLower === "edit_file" || toolNameLower === "multiedit")) {
|
|
9322
|
+
firstEditTs = entry.timestamp ?? null;
|
|
9323
|
+
}
|
|
9324
|
+
const rawCmd = String(input.command ?? "").trimStart();
|
|
9325
|
+
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd)) continue;
|
|
9326
|
+
const inputFilePath = typeof input.file_path === "string" ? input.file_path : "";
|
|
9327
|
+
const inputFileExt = inputFilePath ? path21.extname(inputFilePath).toLowerCase() : "";
|
|
9328
|
+
if (CODE_EXTENSIONS.has(inputFileExt)) continue;
|
|
9329
|
+
const dlpMatch = scanArgs(input);
|
|
9330
|
+
if (dlpMatch) {
|
|
9331
|
+
if (firstDlpTs === null) firstDlpTs = entry.timestamp ?? null;
|
|
9332
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9333
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9334
|
+
dedup.dlpKeys.add(k);
|
|
9335
|
+
result.dlpFindings.push({
|
|
9336
|
+
patternName: dlpMatch.patternName,
|
|
9337
|
+
redactedSample: dlpMatch.redactedSample,
|
|
9338
|
+
toolName,
|
|
9339
|
+
timestamp: entry.timestamp ?? "",
|
|
9340
|
+
project: projLabel,
|
|
9341
|
+
sessionId,
|
|
9342
|
+
agent: "claude"
|
|
9343
|
+
});
|
|
9344
|
+
}
|
|
9345
|
+
}
|
|
9346
|
+
let astFsMatched = false;
|
|
9347
|
+
const astRanForBash = toolNameLower === "bash" || toolNameLower === "execute_bash";
|
|
9348
|
+
if (astRanForBash) {
|
|
9349
|
+
astFsMatched = pushFsOpAstFinding(
|
|
9350
|
+
String(input.command ?? ""),
|
|
9351
|
+
toolName,
|
|
9352
|
+
input,
|
|
9353
|
+
entry.timestamp ?? "",
|
|
9354
|
+
projLabel,
|
|
9355
|
+
sessionId,
|
|
9356
|
+
"claude",
|
|
9357
|
+
result,
|
|
9358
|
+
dedup
|
|
9359
|
+
);
|
|
9360
|
+
}
|
|
9361
|
+
let ruleMatched = astFsMatched;
|
|
9362
|
+
for (const source of ruleSources) {
|
|
9363
|
+
const { rule } = source;
|
|
9364
|
+
if (rule.verdict === "allow") continue;
|
|
9365
|
+
if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
|
|
9366
|
+
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
9367
|
+
if (!evaluateSmartConditions(input, rule)) continue;
|
|
9368
|
+
const inputPreview = preview(input, 120);
|
|
9369
|
+
const k = findingKey(rule.name, inputPreview, projLabel);
|
|
9370
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9371
|
+
dedup.findingsKeys.add(k);
|
|
9372
|
+
result.findings.push({
|
|
9373
|
+
source,
|
|
9374
|
+
toolName,
|
|
9375
|
+
input,
|
|
9376
|
+
timestamp: entry.timestamp ?? "",
|
|
9377
|
+
project: projLabel,
|
|
9378
|
+
sessionId,
|
|
9379
|
+
agent: "claude"
|
|
9380
|
+
});
|
|
9381
|
+
}
|
|
9382
|
+
ruleMatched = true;
|
|
9383
|
+
break;
|
|
9384
|
+
}
|
|
9385
|
+
if (!ruleMatched && (toolNameLower === "bash" || toolNameLower === "execute_bash")) {
|
|
9386
|
+
const shellVerdict = detectDangerousShellExec(String(input.command ?? ""));
|
|
9387
|
+
if (shellVerdict) {
|
|
9388
|
+
const astRule = {
|
|
9389
|
+
name: `ast:bash-safe:${shellVerdict}-shell-exec-remote`,
|
|
9390
|
+
tool: "bash",
|
|
9391
|
+
conditions: [],
|
|
9392
|
+
verdict: shellVerdict,
|
|
9393
|
+
reason: `Shell execution of remote download detected by AST analysis (bash-safe)`
|
|
9394
|
+
};
|
|
9395
|
+
const inputPreview = preview(input, 120);
|
|
9396
|
+
const k = findingKey(astRule.name, inputPreview, projLabel);
|
|
9397
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9398
|
+
dedup.findingsKeys.add(k);
|
|
9399
|
+
result.findings.push({
|
|
9400
|
+
source: {
|
|
9401
|
+
shieldName: "bash-safe",
|
|
9402
|
+
shieldLabel: "bash-safe (AST)",
|
|
9403
|
+
sourceType: "shield",
|
|
9404
|
+
rule: astRule
|
|
9405
|
+
},
|
|
9406
|
+
toolName,
|
|
9407
|
+
input,
|
|
9408
|
+
timestamp: entry.timestamp ?? "",
|
|
9409
|
+
project: projLabel,
|
|
9410
|
+
sessionId,
|
|
9411
|
+
agent: "claude"
|
|
9412
|
+
});
|
|
9413
|
+
}
|
|
9414
|
+
}
|
|
9415
|
+
}
|
|
9416
|
+
}
|
|
9417
|
+
}
|
|
9418
|
+
result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "claude"));
|
|
9419
|
+
if (firstDlpTs !== null && (firstEditTs === null || firstDlpTs < firstEditTs)) {
|
|
9420
|
+
result.sessionsWithEarlySecrets++;
|
|
9421
|
+
}
|
|
9422
|
+
}
|
|
9423
|
+
function processClaudeProject(proj, projectsDir, ruleSources, startDate, result, dedup, onProgress, onLine) {
|
|
9424
|
+
const projPath = path21.join(projectsDir, proj);
|
|
9425
|
+
try {
|
|
9426
|
+
if (!fs19.statSync(projPath).isDirectory()) return;
|
|
9427
|
+
} catch {
|
|
9428
|
+
return;
|
|
9429
|
+
}
|
|
9430
|
+
const projLabel = stripTerminalEscapes(decodeURIComponent(proj).replace(os18.homedir(), "~")).slice(
|
|
9431
|
+
0,
|
|
9432
|
+
40
|
|
9433
|
+
);
|
|
9434
|
+
let files;
|
|
9435
|
+
try {
|
|
9436
|
+
files = fs19.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
9437
|
+
} catch {
|
|
9438
|
+
return;
|
|
9439
|
+
}
|
|
9440
|
+
for (const file of files) {
|
|
9441
|
+
processClaudeFile(
|
|
9442
|
+
file,
|
|
9443
|
+
projPath,
|
|
9444
|
+
projLabel,
|
|
9445
|
+
ruleSources,
|
|
9446
|
+
startDate,
|
|
9447
|
+
result,
|
|
9448
|
+
dedup,
|
|
9449
|
+
onProgress,
|
|
9450
|
+
onLine
|
|
9451
|
+
);
|
|
9452
|
+
}
|
|
9453
|
+
}
|
|
9454
|
+
function emptyClaudeScan() {
|
|
9455
|
+
return {
|
|
9456
|
+
filesScanned: 0,
|
|
9457
|
+
sessions: 0,
|
|
9458
|
+
totalToolCalls: 0,
|
|
9459
|
+
bashCalls: 0,
|
|
9460
|
+
findings: [],
|
|
9461
|
+
dlpFindings: [],
|
|
9462
|
+
loopFindings: [],
|
|
9463
|
+
totalCostUSD: 0,
|
|
9464
|
+
firstDate: null,
|
|
9465
|
+
lastDate: null,
|
|
9466
|
+
sessionsWithEarlySecrets: 0
|
|
9467
|
+
};
|
|
9468
|
+
}
|
|
9469
|
+
function scanClaudeHistory(startDate, onProgress, onLine) {
|
|
9470
|
+
const projectsDir = path21.join(os18.homedir(), ".claude", "projects");
|
|
9471
|
+
const result = emptyClaudeScan();
|
|
9472
|
+
if (!fs19.existsSync(projectsDir)) return result;
|
|
9473
|
+
let projDirs;
|
|
9474
|
+
try {
|
|
9475
|
+
projDirs = fs19.readdirSync(projectsDir);
|
|
9476
|
+
} catch {
|
|
9477
|
+
return result;
|
|
9478
|
+
}
|
|
9479
|
+
const ruleSources = buildRuleSources();
|
|
9480
|
+
const dedup = emptyScanDedup();
|
|
9481
|
+
for (const proj of projDirs) {
|
|
9482
|
+
processClaudeProject(
|
|
9483
|
+
proj,
|
|
9484
|
+
projectsDir,
|
|
9485
|
+
ruleSources,
|
|
9486
|
+
startDate,
|
|
9487
|
+
result,
|
|
9488
|
+
dedup,
|
|
9489
|
+
onProgress,
|
|
9490
|
+
onLine
|
|
9491
|
+
);
|
|
9492
|
+
}
|
|
9245
9493
|
return result;
|
|
9246
9494
|
}
|
|
9247
9495
|
function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
@@ -9259,6 +9507,7 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9259
9507
|
lastDate: null,
|
|
9260
9508
|
sessionsWithEarlySecrets: 0
|
|
9261
9509
|
};
|
|
9510
|
+
const dedup = emptyScanDedup();
|
|
9262
9511
|
if (!fs19.existsSync(tmpDir)) return result;
|
|
9263
9512
|
let slugDirs;
|
|
9264
9513
|
try {
|
|
@@ -9315,10 +9564,9 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9315
9564
|
if (text) {
|
|
9316
9565
|
const dlpMatch = scanArgs({ text });
|
|
9317
9566
|
if (dlpMatch) {
|
|
9318
|
-
const
|
|
9319
|
-
|
|
9320
|
-
|
|
9321
|
-
if (!isDupe) {
|
|
9567
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9568
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9569
|
+
dedup.dlpKeys.add(k);
|
|
9322
9570
|
result.dlpFindings.push({
|
|
9323
9571
|
patternName: dlpMatch.patternName,
|
|
9324
9572
|
redactedSample: dlpMatch.redactedSample,
|
|
@@ -9363,10 +9611,9 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9363
9611
|
continue;
|
|
9364
9612
|
const dlpMatch = scanArgs(input);
|
|
9365
9613
|
if (dlpMatch) {
|
|
9366
|
-
const
|
|
9367
|
-
|
|
9368
|
-
|
|
9369
|
-
if (!isDupe) {
|
|
9614
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9615
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9616
|
+
dedup.dlpKeys.add(k);
|
|
9370
9617
|
result.dlpFindings.push({
|
|
9371
9618
|
patternName: dlpMatch.patternName,
|
|
9372
9619
|
redactedSample: dlpMatch.redactedSample,
|
|
@@ -9389,7 +9636,8 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9389
9636
|
projLabel,
|
|
9390
9637
|
sessionId,
|
|
9391
9638
|
"gemini",
|
|
9392
|
-
result
|
|
9639
|
+
result,
|
|
9640
|
+
dedup
|
|
9393
9641
|
);
|
|
9394
9642
|
}
|
|
9395
9643
|
let ruleMatched = astFsMatched;
|
|
@@ -9400,10 +9648,9 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9400
9648
|
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
9401
9649
|
if (!evaluateSmartConditions(input, rule)) continue;
|
|
9402
9650
|
const inputPreview = preview(input, 120);
|
|
9403
|
-
const
|
|
9404
|
-
|
|
9405
|
-
|
|
9406
|
-
if (!isDupe) {
|
|
9651
|
+
const k = findingKey(rule.name, inputPreview, projLabel);
|
|
9652
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9653
|
+
dedup.findingsKeys.add(k);
|
|
9407
9654
|
result.findings.push({
|
|
9408
9655
|
source,
|
|
9409
9656
|
toolName,
|
|
@@ -9431,10 +9678,9 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9431
9678
|
reason: `Shell execution of remote download detected by AST analysis (bash-safe)`
|
|
9432
9679
|
};
|
|
9433
9680
|
const inputPreview = preview(input, 120);
|
|
9434
|
-
const
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
if (!isDupe) {
|
|
9681
|
+
const k = findingKey(astRule.name, inputPreview, projLabel);
|
|
9682
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9683
|
+
dedup.findingsKeys.add(k);
|
|
9438
9684
|
result.findings.push({
|
|
9439
9685
|
source: {
|
|
9440
9686
|
shieldName: "bash-safe",
|
|
@@ -9474,6 +9720,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9474
9720
|
lastDate: null,
|
|
9475
9721
|
sessionsWithEarlySecrets: 0
|
|
9476
9722
|
};
|
|
9723
|
+
const dedup = emptyScanDedup();
|
|
9477
9724
|
if (!fs19.existsSync(sessionsBase)) return result;
|
|
9478
9725
|
const jsonlFiles = [];
|
|
9479
9726
|
try {
|
|
@@ -9555,10 +9802,9 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9555
9802
|
if (text) {
|
|
9556
9803
|
const dlpMatch2 = scanArgs({ text });
|
|
9557
9804
|
if (dlpMatch2) {
|
|
9558
|
-
const
|
|
9559
|
-
|
|
9560
|
-
|
|
9561
|
-
if (!isDupe) {
|
|
9805
|
+
const k = dlpKey(dlpMatch2.patternName, dlpMatch2.redactedSample, projLabel);
|
|
9806
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9807
|
+
dedup.dlpKeys.add(k);
|
|
9562
9808
|
result.dlpFindings.push({
|
|
9563
9809
|
patternName: dlpMatch2.patternName,
|
|
9564
9810
|
redactedSample: dlpMatch2.redactedSample,
|
|
@@ -9600,10 +9846,9 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9600
9846
|
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd)) continue;
|
|
9601
9847
|
const dlpMatch = scanArgs(input);
|
|
9602
9848
|
if (dlpMatch) {
|
|
9603
|
-
const
|
|
9604
|
-
|
|
9605
|
-
|
|
9606
|
-
if (!isDupe) {
|
|
9849
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9850
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9851
|
+
dedup.dlpKeys.add(k);
|
|
9607
9852
|
result.dlpFindings.push({
|
|
9608
9853
|
patternName: dlpMatch.patternName,
|
|
9609
9854
|
redactedSample: dlpMatch.redactedSample,
|
|
@@ -9626,7 +9871,8 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9626
9871
|
projLabel,
|
|
9627
9872
|
sessionId,
|
|
9628
9873
|
"codex",
|
|
9629
|
-
result
|
|
9874
|
+
result,
|
|
9875
|
+
dedup
|
|
9630
9876
|
);
|
|
9631
9877
|
}
|
|
9632
9878
|
let ruleMatched = astFsMatched;
|
|
@@ -9638,10 +9884,9 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9638
9884
|
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
9639
9885
|
if (!evaluateSmartConditions(input, rule)) continue;
|
|
9640
9886
|
const inputPreview = preview(input, 120);
|
|
9641
|
-
const
|
|
9642
|
-
|
|
9643
|
-
|
|
9644
|
-
if (!isDupe) {
|
|
9887
|
+
const k = findingKey(rule.name, inputPreview, projLabel);
|
|
9888
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9889
|
+
dedup.findingsKeys.add(k);
|
|
9645
9890
|
result.findings.push({
|
|
9646
9891
|
source,
|
|
9647
9892
|
toolName,
|
|
@@ -9666,10 +9911,9 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9666
9911
|
reason: `Shell execution of remote download detected by AST analysis (bash-safe)`
|
|
9667
9912
|
};
|
|
9668
9913
|
const inputPreview = preview(input, 120);
|
|
9669
|
-
const
|
|
9670
|
-
|
|
9671
|
-
|
|
9672
|
-
if (!isDupe) {
|
|
9914
|
+
const k = findingKey(astRule.name, inputPreview, projLabel);
|
|
9915
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9916
|
+
dedup.findingsKeys.add(k);
|
|
9673
9917
|
result.findings.push({
|
|
9674
9918
|
source: {
|
|
9675
9919
|
shieldName: "bash-safe",
|
|
@@ -9700,6 +9944,7 @@ function scanShellConfig() {
|
|
|
9700
9944
|
(f) => path21.join(home, f)
|
|
9701
9945
|
);
|
|
9702
9946
|
const findings = [];
|
|
9947
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9703
9948
|
for (const filePath of configFiles) {
|
|
9704
9949
|
if (!fs19.existsSync(filePath)) continue;
|
|
9705
9950
|
let lines;
|
|
@@ -9714,10 +9959,9 @@ function scanShellConfig() {
|
|
|
9714
9959
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
9715
9960
|
const dlpMatch = scanArgs({ text: trimmed });
|
|
9716
9961
|
if (!dlpMatch) continue;
|
|
9717
|
-
const
|
|
9718
|
-
|
|
9719
|
-
|
|
9720
|
-
if (!isDupe) {
|
|
9962
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, shortPath);
|
|
9963
|
+
if (!seen.has(k)) {
|
|
9964
|
+
seen.add(k);
|
|
9721
9965
|
findings.push({
|
|
9722
9966
|
patternName: dlpMatch.patternName,
|
|
9723
9967
|
redactedSample: dlpMatch.redactedSample,
|
|
@@ -9976,6 +10220,263 @@ function renderNarrativeScorecard(input) {
|
|
|
9976
10220
|
console.log(chalk5.dim("\u2192 github.com/node9-ai/node9-proxy"));
|
|
9977
10221
|
console.log("");
|
|
9978
10222
|
}
|
|
10223
|
+
function mkLine(...parts) {
|
|
10224
|
+
let rendered = "";
|
|
10225
|
+
let width = 0;
|
|
10226
|
+
for (const [text, fmt] of parts) {
|
|
10227
|
+
rendered += fmt ? fmt(text) : text;
|
|
10228
|
+
width += text.length;
|
|
10229
|
+
}
|
|
10230
|
+
return { rendered, width };
|
|
10231
|
+
}
|
|
10232
|
+
function shortRule(name, width) {
|
|
10233
|
+
const stripped = name.replace(/^shield:[^:]+:/, "");
|
|
10234
|
+
if (stripped.length <= width) return stripped.padEnd(width);
|
|
10235
|
+
return stripped.slice(0, width - 1) + "\u2026";
|
|
10236
|
+
}
|
|
10237
|
+
function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
|
|
10238
|
+
const { scan, summary, blast, blastExposures, blockedCount, reviewCount } = input;
|
|
10239
|
+
const topLines = [];
|
|
10240
|
+
if (scan.dlpFindings.length > 0) {
|
|
10241
|
+
const latest = scan.dlpFindings[0];
|
|
10242
|
+
const rel = relativeDate(latest.timestamp, now);
|
|
10243
|
+
const noun = `credential leak${scan.dlpFindings.length !== 1 ? "s" : ""}`;
|
|
10244
|
+
topLines.push(
|
|
10245
|
+
mkLine(
|
|
10246
|
+
["\u{1F6A8} ", chalk5.red],
|
|
10247
|
+
[`${scan.dlpFindings.length} ${noun} in tool input `, chalk5.bold],
|
|
10248
|
+
[`(latest: ${rel} ago, ${latest.patternName})`, chalk5.dim]
|
|
10249
|
+
)
|
|
10250
|
+
);
|
|
10251
|
+
}
|
|
10252
|
+
if (blockedCount > 0) {
|
|
10253
|
+
const topBlocked = topRulesByVerdict(summary.sections, "block", 2).map(
|
|
10254
|
+
(r) => r.count > 1 ? `${shortRule(r.name, 20).trimEnd()} \xD7${r.count}` : shortRule(r.name, 20).trimEnd()
|
|
10255
|
+
).join(", ");
|
|
10256
|
+
topLines.push(
|
|
10257
|
+
mkLine(
|
|
10258
|
+
["\u{1F6D1} ", chalk5.red],
|
|
10259
|
+
[`${blockedCount} ops node9 would have blocked `, chalk5.bold],
|
|
10260
|
+
[`(${topBlocked})`, chalk5.dim]
|
|
10261
|
+
)
|
|
10262
|
+
);
|
|
10263
|
+
}
|
|
10264
|
+
if (scan.loopFindings.length > 0) {
|
|
10265
|
+
const { wastePct } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
|
|
10266
|
+
const byTool = /* @__PURE__ */ new Map();
|
|
10267
|
+
for (const f of scan.loopFindings) {
|
|
10268
|
+
byTool.set(f.toolName, (byTool.get(f.toolName) ?? 0) + Math.max(0, f.count - 1));
|
|
10269
|
+
}
|
|
10270
|
+
const top = [...byTool.entries()].sort((a, b) => b[1] - a[1])[0];
|
|
10271
|
+
const wasteSuffix = wastePct > 0 ? `, ${wastePct}% wasted` : "";
|
|
10272
|
+
const detail = top ? `(${top[0]} dominates${wasteSuffix})` : "";
|
|
10273
|
+
topLines.push(
|
|
10274
|
+
mkLine(
|
|
10275
|
+
["\u{1F501} ", chalk5.yellow],
|
|
10276
|
+
[`${scan.loopFindings.length} agent loops detected `, chalk5.bold],
|
|
10277
|
+
[detail, chalk5.dim]
|
|
10278
|
+
)
|
|
10279
|
+
);
|
|
10280
|
+
}
|
|
10281
|
+
if (blastExposures > 0) {
|
|
10282
|
+
const exposed2 = Math.max(0, 100 - blast.score);
|
|
10283
|
+
const pjDiscount = PROTECTIVE_SHIELD_DISCOUNTS["project-jail"] ?? 0;
|
|
10284
|
+
const pjBonus = Math.round(exposed2 * pjDiscount);
|
|
10285
|
+
const cta = pjBonus > 0 ? ` \u2192 enable project-jail (+${pjBonus} pts)` : "";
|
|
10286
|
+
topLines.push(
|
|
10287
|
+
mkLine(
|
|
10288
|
+
["\u{1F52D} ", chalk5.red],
|
|
10289
|
+
[`${blastExposures} secrets reachable on disk`, chalk5.bold],
|
|
10290
|
+
[cta, chalk5.dim]
|
|
10291
|
+
)
|
|
10292
|
+
);
|
|
10293
|
+
}
|
|
10294
|
+
if (topLines.length > 0) {
|
|
10295
|
+
for (const ln of boxPanel("TOP FINDINGS", topLines)) console.log(" " + ln);
|
|
10296
|
+
console.log("");
|
|
10297
|
+
}
|
|
10298
|
+
if (summary.leaks.length > 0) {
|
|
10299
|
+
const leakLines = [];
|
|
10300
|
+
for (const leak of summary.leaks.slice(0, 5)) {
|
|
10301
|
+
const rel = relativeDate(leak.timestamp, now);
|
|
10302
|
+
leakLines.push(
|
|
10303
|
+
mkLine(
|
|
10304
|
+
[rel.padStart(4) + " ", chalk5.dim],
|
|
10305
|
+
[leak.patternName.padEnd(14), chalk5.red.bold],
|
|
10306
|
+
[" "],
|
|
10307
|
+
[leak.redactedSample.padEnd(20), chalk5.red],
|
|
10308
|
+
[" "],
|
|
10309
|
+
[`[${leak.toolName}]`.padEnd(15), chalk5.dim],
|
|
10310
|
+
[" "],
|
|
10311
|
+
[leak.agent, chalk5.dim]
|
|
10312
|
+
)
|
|
10313
|
+
);
|
|
10314
|
+
}
|
|
10315
|
+
const remaining = summary.leaks.length - 5;
|
|
10316
|
+
if (remaining > 0) {
|
|
10317
|
+
leakLines.push(mkLine([`\u2026 +${remaining} more`, chalk5.dim]));
|
|
10318
|
+
}
|
|
10319
|
+
const title = `LEAKS \xB7 ${summary.leaks.length} secret${summary.leaks.length !== 1 ? "s" : ""} in plain text`;
|
|
10320
|
+
for (const ln of boxPanel(title, leakLines)) console.log(" " + ln);
|
|
10321
|
+
console.log("");
|
|
10322
|
+
}
|
|
10323
|
+
if (blockedCount > 0) {
|
|
10324
|
+
const blockedLines = [];
|
|
10325
|
+
const ruleEntries = topRulesByVerdict(summary.sections, "block", 12);
|
|
10326
|
+
for (const r of ruleEntries) {
|
|
10327
|
+
const origin = originForRule(r.name, summary.sections);
|
|
10328
|
+
blockedLines.push(
|
|
10329
|
+
mkLine(
|
|
10330
|
+
["\u2717 ", chalk5.red],
|
|
10331
|
+
[shortRule(r.name, 24), chalk5.bold],
|
|
10332
|
+
[" \xD7" + String(r.count).padEnd(4), chalk5.bold],
|
|
10333
|
+
[" "],
|
|
10334
|
+
[origin, chalk5.dim]
|
|
10335
|
+
)
|
|
10336
|
+
);
|
|
10337
|
+
}
|
|
10338
|
+
const title = `BLOCKED \xB7 ${blockedCount} ops node9 would have stopped`;
|
|
10339
|
+
for (const ln of boxPanel(title, blockedLines)) console.log(" " + ln);
|
|
10340
|
+
console.log("");
|
|
10341
|
+
}
|
|
10342
|
+
if (reviewCount > 0) {
|
|
10343
|
+
const reviewLines = [];
|
|
10344
|
+
const ruleEntries = topRulesByVerdict(summary.sections, "review", 12);
|
|
10345
|
+
for (const r of ruleEntries) {
|
|
10346
|
+
const origin = originForRule(r.name, summary.sections);
|
|
10347
|
+
reviewLines.push(
|
|
10348
|
+
mkLine(
|
|
10349
|
+
["\u{1F441} ", chalk5.yellow],
|
|
10350
|
+
[shortRule(r.name, 24), chalk5.bold],
|
|
10351
|
+
[" \xD7" + String(r.count).padEnd(4), chalk5.bold],
|
|
10352
|
+
[" "],
|
|
10353
|
+
[origin, chalk5.dim]
|
|
10354
|
+
)
|
|
10355
|
+
);
|
|
10356
|
+
}
|
|
10357
|
+
const title = `REVIEW QUEUE \xB7 ${reviewCount} ops flagged for approval`;
|
|
10358
|
+
for (const ln of boxPanel(title, reviewLines)) console.log(" " + ln);
|
|
10359
|
+
console.log("");
|
|
10360
|
+
}
|
|
10361
|
+
if (scan.loopFindings.length > 0) {
|
|
10362
|
+
const { wastePct } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
|
|
10363
|
+
const byTool = /* @__PURE__ */ new Map();
|
|
10364
|
+
let totalRepeats = 0;
|
|
10365
|
+
for (const f of scan.loopFindings) {
|
|
10366
|
+
const repeats = Math.max(0, f.count - 1);
|
|
10367
|
+
byTool.set(f.toolName, (byTool.get(f.toolName) ?? 0) + repeats);
|
|
10368
|
+
totalRepeats += repeats;
|
|
10369
|
+
}
|
|
10370
|
+
const toolEntries = [...byTool.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
10371
|
+
const loopLines = [];
|
|
10372
|
+
for (const [tool, repeats] of toolEntries) {
|
|
10373
|
+
const pct = totalRepeats > 0 ? Math.round(repeats / totalRepeats * 100) : 0;
|
|
10374
|
+
loopLines.push(
|
|
10375
|
+
mkLine(
|
|
10376
|
+
[tool.padEnd(10), chalk5.bold],
|
|
10377
|
+
[`\xD7${num(repeats)} repeats`.padEnd(16)],
|
|
10378
|
+
[`(${pct}%)`, chalk5.dim]
|
|
10379
|
+
)
|
|
10380
|
+
);
|
|
10381
|
+
}
|
|
10382
|
+
const topStuck = [...scan.loopFindings].sort((a, b) => b.count - a.count).slice(0, 3);
|
|
10383
|
+
if (topStuck.length > 0) {
|
|
10384
|
+
loopLines.push(mkLine([""]));
|
|
10385
|
+
loopLines.push(mkLine(["Top stuck patterns:", chalk5.dim]));
|
|
10386
|
+
for (const f of topStuck) {
|
|
10387
|
+
const raw = f.commandPreview || f.toolName;
|
|
10388
|
+
const target = raw.length > 60 ? "\u2026" + raw.slice(raw.length - 59) : raw.padEnd(60);
|
|
10389
|
+
loopLines.push(mkLine([`\xD7${num(f.count).padEnd(4)} `, chalk5.bold], [target, chalk5.dim]));
|
|
10390
|
+
}
|
|
10391
|
+
}
|
|
10392
|
+
const wasteSuffix = wastePct > 0 ? ` \xB7 ${wastePct}% wasted` : "";
|
|
10393
|
+
const title = `AGENT LOOPS \xB7 ${scan.loopFindings.length} repeated patterns${wasteSuffix}`;
|
|
10394
|
+
for (const ln of boxPanel(title, loopLines)) console.log(" " + ln);
|
|
10395
|
+
console.log("");
|
|
10396
|
+
}
|
|
10397
|
+
if (blast.reachable.length > 0 || blast.envFindings.length > 0) {
|
|
10398
|
+
const blastLines = [];
|
|
10399
|
+
const DESC_W = 33;
|
|
10400
|
+
for (const r of blast.reachable.slice(0, 8)) {
|
|
10401
|
+
const trimmed = r.description.split(" \u2014 ")[0].split(/—|--/)[0].trim();
|
|
10402
|
+
const desc = trimmed.length > DESC_W ? trimmed.slice(0, DESC_W - 1) + "\u2026" : trimmed;
|
|
10403
|
+
blastLines.push(mkLine(["\u2717 ", chalk5.red], [r.label.padEnd(36)], [desc, chalk5.dim]));
|
|
10404
|
+
}
|
|
10405
|
+
for (const e of blast.envFindings.slice(0, 3)) {
|
|
10406
|
+
blastLines.push(
|
|
10407
|
+
mkLine(["\u26A0 ", chalk5.yellow], [`${e.key} `], [`(${e.patternName})`, chalk5.dim])
|
|
10408
|
+
);
|
|
10409
|
+
}
|
|
10410
|
+
const totalExposed = blast.reachable.length + blast.envFindings.length;
|
|
10411
|
+
if (totalExposed > 8) {
|
|
10412
|
+
blastLines.push(mkLine([`\u2026 +${totalExposed - 8} more`, chalk5.dim]));
|
|
10413
|
+
}
|
|
10414
|
+
const title = `BLAST RADIUS \xB7 ${totalExposed} path${totalExposed !== 1 ? "s" : ""} reachable right now`;
|
|
10415
|
+
for (const ln of boxPanel(title, blastLines)) console.log(" " + ln);
|
|
10416
|
+
console.log("");
|
|
10417
|
+
}
|
|
10418
|
+
const shieldImpacts = rollupByShield(summary.sections);
|
|
10419
|
+
const exposed = Math.max(0, 100 - blast.score);
|
|
10420
|
+
const shieldLines = [];
|
|
10421
|
+
const ranked = [...shieldImpacts].sort((a, b) => {
|
|
10422
|
+
const aDiscount = PROTECTIVE_SHIELD_DISCOUNTS[a.shieldName] ?? 0;
|
|
10423
|
+
const bDiscount = PROTECTIVE_SHIELD_DISCOUNTS[b.shieldName] ?? 0;
|
|
10424
|
+
if (aDiscount !== bDiscount) return bDiscount - aDiscount;
|
|
10425
|
+
return b.totalCatches - a.totalCatches;
|
|
10426
|
+
});
|
|
10427
|
+
for (const impact of ranked) {
|
|
10428
|
+
if (impact.totalCatches === 0) continue;
|
|
10429
|
+
const discount = PROTECTIVE_SHIELD_DISCOUNTS[impact.shieldName] ?? 0;
|
|
10430
|
+
const bonus = Math.round(exposed * discount);
|
|
10431
|
+
const icon = discount > 0 ? "\u{1F6E1} " : "\u2610 ";
|
|
10432
|
+
const wouldCatch = `would catch ${impact.totalCatches} op${impact.totalCatches !== 1 ? "s" : ""}`;
|
|
10433
|
+
const deltaSuffix = bonus > 0 ? ` \u2192 +${bonus} pts (${blast.score} \u2192 ${blast.score + bonus})` : "";
|
|
10434
|
+
shieldLines.push(
|
|
10435
|
+
mkLine(
|
|
10436
|
+
[icon, discount > 0 ? chalk5.cyan : chalk5.dim],
|
|
10437
|
+
[impact.shieldName.padEnd(14), chalk5.bold],
|
|
10438
|
+
[wouldCatch.padEnd(22), chalk5.dim],
|
|
10439
|
+
[deltaSuffix, bonus > 0 ? chalk5.green.bold : chalk5.dim]
|
|
10440
|
+
)
|
|
10441
|
+
);
|
|
10442
|
+
if (impact.topRuleLabels.length > 0) {
|
|
10443
|
+
const rules = impact.topRuleLabels.join(", ");
|
|
10444
|
+
shieldLines.push(mkLine([" ", chalk5.dim], [rules, chalk5.dim]));
|
|
10445
|
+
}
|
|
10446
|
+
}
|
|
10447
|
+
const hitShieldSet = new Set(
|
|
10448
|
+
shieldImpacts.filter((i) => i.totalCatches > 0).map((i) => i.shieldName)
|
|
10449
|
+
);
|
|
10450
|
+
const zeroHitBuiltins = Object.keys(SHIELDS).filter((name) => !hitShieldSet.has(name)).sort();
|
|
10451
|
+
if (zeroHitBuiltins.length > 0) {
|
|
10452
|
+
shieldLines.push(mkLine([""]));
|
|
10453
|
+
shieldLines.push(mkLine([zeroHitBuiltins.join(" \xB7 "), chalk5.dim]));
|
|
10454
|
+
shieldLines.push(mkLine([" no hits in your history \u2014 install proactively", chalk5.dim]));
|
|
10455
|
+
}
|
|
10456
|
+
const topRec = ranked.find(
|
|
10457
|
+
(r) => r.totalCatches > 0 && (PROTECTIVE_SHIELD_DISCOUNTS[r.shieldName] ?? 0) > 0
|
|
10458
|
+
);
|
|
10459
|
+
if (topRec) {
|
|
10460
|
+
const bonus = Math.round(exposed * (PROTECTIVE_SHIELD_DISCOUNTS[topRec.shieldName] ?? 0));
|
|
10461
|
+
const cta = `\u2192 node9 shield enable ${topRec.shieldName} (start here \u2014 +${bonus} pts)`;
|
|
10462
|
+
shieldLines.push(mkLine([""]));
|
|
10463
|
+
shieldLines.push(mkLine([cta, chalk5.cyan]));
|
|
10464
|
+
}
|
|
10465
|
+
if (shieldLines.length > 0) {
|
|
10466
|
+
const title = "SHIELDS \xB7 install node9 + enable these to catch what we found";
|
|
10467
|
+
for (const ln of boxPanel(title, shieldLines)) console.log(" " + ln);
|
|
10468
|
+
console.log("");
|
|
10469
|
+
}
|
|
10470
|
+
}
|
|
10471
|
+
function originForRule(ruleName, sections) {
|
|
10472
|
+
for (const section of sections) {
|
|
10473
|
+
if (section.rules.some((r) => r.name === ruleName)) {
|
|
10474
|
+
if (section.sourceType === "default") return "default";
|
|
10475
|
+
if (section.sourceType === "shield") return `needs shield:${section.shieldKey ?? section.id}`;
|
|
10476
|
+
}
|
|
10477
|
+
}
|
|
10478
|
+
return "";
|
|
10479
|
+
}
|
|
9979
10480
|
function registerScanCommand(program2) {
|
|
9980
10481
|
program2.command("scan").description("Forecast: scan agent history and show what node9 would catch if installed").option("--all", "Scan all history (default: last 90 days)").option("--days <n>", "Scan last N days of history", "90").option("--top <n>", "Max findings to show per rule (default: 5)", "5").option("--drill-down", "Show all findings with full commands and session IDs").option("--compact", "Compact one-screen scorecard \u2014 for screenshots and sharing").option("--narrative", "Severity-grouped report \u2014 for video / dramatic sharing").option(
|
|
9981
10482
|
"--json",
|
|
@@ -10207,7 +10708,7 @@ function registerScanCommand(program2) {
|
|
|
10207
10708
|
" " + chalk5.dim("AI spend ") + chalk5.bold(fmtCost(scan.totalCostUSD)) + (summary.loopWastedUSD > 0 ? chalk5.dim(" \xB7 wasted on loops ") + chalk5.yellow("~" + fmtCost(summary.loopWastedUSD)) : "")
|
|
10208
10709
|
);
|
|
10209
10710
|
}
|
|
10210
|
-
if (scan.dlpFindings.length > 0 && scan.sessionsWithEarlySecrets > 0) {
|
|
10711
|
+
if (drillDown && scan.dlpFindings.length > 0 && scan.sessionsWithEarlySecrets > 0) {
|
|
10211
10712
|
console.log(
|
|
10212
10713
|
" " + chalk5.dim(
|
|
10213
10714
|
`${scan.sessionsWithEarlySecrets} session${scan.sessionsWithEarlySecrets !== 1 ? "s" : ""} loaded secrets before first edit`
|
|
@@ -10215,6 +10716,26 @@ function registerScanCommand(program2) {
|
|
|
10215
10716
|
);
|
|
10216
10717
|
}
|
|
10217
10718
|
console.log("");
|
|
10719
|
+
if (!drillDown) {
|
|
10720
|
+
renderPanelScorecard({
|
|
10721
|
+
scan,
|
|
10722
|
+
summary,
|
|
10723
|
+
blast,
|
|
10724
|
+
blastExposures,
|
|
10725
|
+
blockedCount,
|
|
10726
|
+
reviewCount
|
|
10727
|
+
});
|
|
10728
|
+
const cta = isWired ? "\u2705 node9 is active" : "\u2192 install node9 to enable protection";
|
|
10729
|
+
console.log(" " + chalk5.green(cta));
|
|
10730
|
+
console.log(
|
|
10731
|
+
" " + chalk5.dim("\u2192 ") + chalk5.cyan("node9 monitor") + chalk5.dim(" live dashboard")
|
|
10732
|
+
);
|
|
10733
|
+
console.log(
|
|
10734
|
+
" " + chalk5.dim("\u2192 ") + chalk5.cyan("node9 scan --drill-down") + chalk5.dim(" full commands + session IDs")
|
|
10735
|
+
);
|
|
10736
|
+
console.log("");
|
|
10737
|
+
return;
|
|
10738
|
+
}
|
|
10218
10739
|
if (scan.dlpFindings.length > 0) {
|
|
10219
10740
|
console.log(" " + chalk5.dim("\u2500".repeat(70)));
|
|
10220
10741
|
console.log(
|
|
@@ -10403,7 +10924,7 @@ function registerScanCommand(program2) {
|
|
|
10403
10924
|
}
|
|
10404
10925
|
);
|
|
10405
10926
|
}
|
|
10406
|
-
var CLAUDE_PRICING, GEMINI_PRICING, CODE_EXTENSIONS, SELF_OUTPUT_MARKERS, FIXTURE_TOKEN_PATTERNS, TERMINAL_ESCAPE_RE2, LOOP_TOOLS, LOOP_THRESHOLD, LOOP_TIMESPAN_THRESHOLD_MS, STUCK_TOOLS_MIN_WASTE, STUCK_TOOLS_LIMIT, RECURRING_SESSION_THRESHOLD, STALE_AGE_DAYS,
|
|
10927
|
+
var CLAUDE_PRICING, GEMINI_PRICING, CODE_EXTENSIONS, SELF_OUTPUT_MARKERS, FIXTURE_TOKEN_PATTERNS, TERMINAL_ESCAPE_RE2, LOOP_TOOLS, LOOP_THRESHOLD, LOOP_TIMESPAN_THRESHOLD_MS, STUCK_TOOLS_MIN_WASTE, STUCK_TOOLS_LIMIT, RECURRING_SESSION_THRESHOLD, STALE_AGE_DAYS, classifyRuleSeverity2, narrativeRuleLabel2;
|
|
10407
10928
|
var init_scan = __esm({
|
|
10408
10929
|
"src/cli/commands/scan.ts"() {
|
|
10409
10930
|
"use strict";
|
|
@@ -10417,6 +10938,7 @@ var init_scan = __esm({
|
|
|
10417
10938
|
init_setup();
|
|
10418
10939
|
init_blast();
|
|
10419
10940
|
init_scan_derive();
|
|
10941
|
+
init_protection();
|
|
10420
10942
|
init_scan_json();
|
|
10421
10943
|
init_scan_history();
|
|
10422
10944
|
CLAUDE_PRICING = {
|
|
@@ -10499,9 +11021,6 @@ var init_scan = __esm({
|
|
|
10499
11021
|
STUCK_TOOLS_LIMIT = 3;
|
|
10500
11022
|
RECURRING_SESSION_THRESHOLD = 3;
|
|
10501
11023
|
STALE_AGE_DAYS = 30;
|
|
10502
|
-
DEFAULT_RULE_NAMES = new Set(
|
|
10503
|
-
DEFAULT_CONFIG.policy.smartRules.map((r) => r.name).filter(Boolean)
|
|
10504
|
-
);
|
|
10505
11024
|
classifyRuleSeverity2 = classifyRuleSeverity;
|
|
10506
11025
|
narrativeRuleLabel2 = narrativeRuleLabel;
|
|
10507
11026
|
}
|
|
@@ -11006,6 +11525,19 @@ data: ${JSON.stringify(data)}
|
|
|
11006
11525
|
}
|
|
11007
11526
|
});
|
|
11008
11527
|
}
|
|
11528
|
+
function broadcastForensic(finding) {
|
|
11529
|
+
const severity = CRITICAL_FORENSIC_CATEGORIES.has(finding.type) ? "critical" : "warning";
|
|
11530
|
+
const event = {
|
|
11531
|
+
type: "forensic",
|
|
11532
|
+
id: `fnd_${randomUUID3()}`,
|
|
11533
|
+
ts: Date.now(),
|
|
11534
|
+
sessionId: finding.sessionId,
|
|
11535
|
+
category: finding.type,
|
|
11536
|
+
severity
|
|
11537
|
+
};
|
|
11538
|
+
if (finding.patternName !== void 0) event.patternName = finding.patternName;
|
|
11539
|
+
broadcast("forensic", event);
|
|
11540
|
+
}
|
|
11009
11541
|
function abandonPending() {
|
|
11010
11542
|
setAbandonTimer(null);
|
|
11011
11543
|
pending.forEach((entry, id) => {
|
|
@@ -11189,7 +11721,7 @@ function bindActivitySocket() {
|
|
|
11189
11721
|
});
|
|
11190
11722
|
activitySocketServer = unixServer;
|
|
11191
11723
|
}
|
|
11192
|
-
var homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, pending, sseClients, suggestionTracker, taintStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, LARGE_RESPONSE_RING_SIZE, largeResponseRing, cachedScanResult, cachedScanTs, SCAN_CACHE_TTL_MS, SECRET_KEY_RE, INPUT_PRICE_PER_1M, OUTPUT_PRICE_PER_1M, BYTES_PER_TOKEN, WRITE_TOOL_NAMES, ACTIVITY_REBIND_MAX_ATTEMPTS, ACTIVITY_REBIND_WINDOW_MS, ACTIVITY_HEALTH_PROBE_MS, activitySocketServer, activityHealthInterval, activityRebindAttempts, activityCircuitTripped;
|
|
11724
|
+
var homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, pending, sseClients, suggestionTracker, taintStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, LARGE_RESPONSE_RING_SIZE, largeResponseRing, cachedScanResult, cachedScanTs, SCAN_CACHE_TTL_MS, SECRET_KEY_RE, INPUT_PRICE_PER_1M, OUTPUT_PRICE_PER_1M, BYTES_PER_TOKEN, CRITICAL_FORENSIC_CATEGORIES, WRITE_TOOL_NAMES, ACTIVITY_REBIND_MAX_ATTEMPTS, ACTIVITY_REBIND_WINDOW_MS, ACTIVITY_HEALTH_PROBE_MS, activitySocketServer, activityHealthInterval, activityRebindAttempts, activityCircuitTripped;
|
|
11193
11725
|
var init_state2 = __esm({
|
|
11194
11726
|
"src/daemon/state.ts"() {
|
|
11195
11727
|
"use strict";
|
|
@@ -11234,6 +11766,11 @@ var init_state2 = __esm({
|
|
|
11234
11766
|
INPUT_PRICE_PER_1M = 3;
|
|
11235
11767
|
OUTPUT_PRICE_PER_1M = 15;
|
|
11236
11768
|
BYTES_PER_TOKEN = 4;
|
|
11769
|
+
CRITICAL_FORENSIC_CATEGORIES = /* @__PURE__ */ new Set([
|
|
11770
|
+
"privilege-escalation",
|
|
11771
|
+
"destructive-op",
|
|
11772
|
+
"eval-of-remote"
|
|
11773
|
+
]);
|
|
11237
11774
|
WRITE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
11238
11775
|
"write",
|
|
11239
11776
|
"write_file",
|
|
@@ -11578,7 +12115,26 @@ function startCloudSync() {
|
|
|
11578
12115
|
const recurring = setInterval(() => void syncOnce(), intervalMs);
|
|
11579
12116
|
recurring.unref();
|
|
11580
12117
|
}
|
|
11581
|
-
|
|
12118
|
+
function startForensicBroadcast() {
|
|
12119
|
+
const tick = async () => {
|
|
12120
|
+
try {
|
|
12121
|
+
const findings = await tickForensicBroadcast(forensicBroadcastOffsets);
|
|
12122
|
+
for (const f of findings) broadcastForensic(f);
|
|
12123
|
+
} catch (err2) {
|
|
12124
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
12125
|
+
appendToLog(HOOK_DEBUG_LOG, {
|
|
12126
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12127
|
+
kind: "forensic-broadcast-error",
|
|
12128
|
+
error: msg
|
|
12129
|
+
});
|
|
12130
|
+
}
|
|
12131
|
+
};
|
|
12132
|
+
const initial = setTimeout(() => void tick(), FORENSIC_INITIAL_DELAY_MS);
|
|
12133
|
+
initial.unref();
|
|
12134
|
+
const recurring = setInterval(() => void tick(), FORENSIC_BROADCAST_INTERVAL_MS);
|
|
12135
|
+
recurring.unref();
|
|
12136
|
+
}
|
|
12137
|
+
var FINDING_TO_SIGNAL3, rulesCacheFile, DEFAULT_API_URL, DEFAULT_INTERVAL_HOURS, MIN_INTERVAL_HOURS, FORENSIC_BROADCAST_INTERVAL_MS, FORENSIC_INITIAL_DELAY_MS, forensicBroadcastOffsets;
|
|
11582
12138
|
var init_sync = __esm({
|
|
11583
12139
|
"src/daemon/sync.ts"() {
|
|
11584
12140
|
"use strict";
|
|
@@ -11586,6 +12142,8 @@ var init_sync = __esm({
|
|
|
11586
12142
|
init_blast();
|
|
11587
12143
|
init_dist();
|
|
11588
12144
|
init_scan_watermark();
|
|
12145
|
+
init_state2();
|
|
12146
|
+
init_audit();
|
|
11589
12147
|
FINDING_TO_SIGNAL3 = {
|
|
11590
12148
|
dlp: "dlpFindings",
|
|
11591
12149
|
pii: "piiFindings",
|
|
@@ -11602,6 +12160,9 @@ var init_sync = __esm({
|
|
|
11602
12160
|
DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept/policies/sync";
|
|
11603
12161
|
DEFAULT_INTERVAL_HOURS = 5;
|
|
11604
12162
|
MIN_INTERVAL_HOURS = 1;
|
|
12163
|
+
FORENSIC_BROADCAST_INTERVAL_MS = 3e4;
|
|
12164
|
+
FORENSIC_INITIAL_DELAY_MS = 5e3;
|
|
12165
|
+
forensicBroadcastOffsets = /* @__PURE__ */ new Map();
|
|
11605
12166
|
}
|
|
11606
12167
|
});
|
|
11607
12168
|
|
|
@@ -11835,6 +12396,7 @@ import chalk6 from "chalk";
|
|
|
11835
12396
|
function startDaemon() {
|
|
11836
12397
|
startCostSync();
|
|
11837
12398
|
startCloudSync();
|
|
12399
|
+
startForensicBroadcast();
|
|
11838
12400
|
startDlpScanner();
|
|
11839
12401
|
loadInsightCounts();
|
|
11840
12402
|
const internalToken = randomUUID4();
|
|
@@ -13029,6 +13591,7 @@ var tail_exports = {};
|
|
|
13029
13591
|
__export(tail_exports, {
|
|
13030
13592
|
agentLabel: () => agentLabel,
|
|
13031
13593
|
sessionTag: () => sessionTag,
|
|
13594
|
+
shortenPathSummary: () => shortenPathSummary,
|
|
13032
13595
|
startTail: () => startTail
|
|
13033
13596
|
});
|
|
13034
13597
|
import http2 from "http";
|
|
@@ -13038,6 +13601,12 @@ import os41 from "os";
|
|
|
13038
13601
|
import path47 from "path";
|
|
13039
13602
|
import readline6 from "readline";
|
|
13040
13603
|
import { spawn as spawn9 } from "child_process";
|
|
13604
|
+
function shortenPathSummary(s) {
|
|
13605
|
+
if (!s || !s.startsWith("/")) return s;
|
|
13606
|
+
const parts = s.split("/").filter(Boolean);
|
|
13607
|
+
if (parts.length <= 2) return s;
|
|
13608
|
+
return `\u2026/${parts.slice(-2).join("/")}`;
|
|
13609
|
+
}
|
|
13041
13610
|
function getIcon(tool) {
|
|
13042
13611
|
const t = tool.toLowerCase();
|
|
13043
13612
|
for (const [k, v] of Object.entries(ICONS)) {
|
|
@@ -13791,7 +14360,8 @@ async function startTail(options = {}) {
|
|
|
13791
14360
|
if (event === "snapshot") {
|
|
13792
14361
|
const time = new Date(data.ts).toLocaleTimeString([], { hour12: false });
|
|
13793
14362
|
const hash = data.hash ?? "";
|
|
13794
|
-
const
|
|
14363
|
+
const rawSummary = data.argsSummary ?? data.tool;
|
|
14364
|
+
const summary = shortenPathSummary(rawSummary);
|
|
13795
14365
|
const fileCount = data.fileCount ?? 0;
|
|
13796
14366
|
const files = fileCount > 0 ? chalk30.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
|
|
13797
14367
|
process.stdout.write(
|
|
@@ -16173,63 +16743,13 @@ function registerAuditCommand(program2) {
|
|
|
16173
16743
|
|
|
16174
16744
|
// src/cli/commands/report.ts
|
|
16175
16745
|
import chalk13 from "chalk";
|
|
16746
|
+
|
|
16747
|
+
// src/cli/aggregate/report-audit.ts
|
|
16748
|
+
init_costSync();
|
|
16749
|
+
init_litellm();
|
|
16176
16750
|
import fs35 from "fs";
|
|
16177
|
-
import path36 from "path";
|
|
16178
16751
|
import os31 from "os";
|
|
16179
|
-
|
|
16180
|
-
// src/cli/render/report-json.ts
|
|
16181
|
-
function buildReportJson(input) {
|
|
16182
|
-
const totalBlocked = input.timedOut + input.hardBlocked + input.dlpBlocked + input.loopHits + input.userDenied;
|
|
16183
|
-
const blockRate = input.total > 0 ? totalBlocked / input.total : 0;
|
|
16184
|
-
const deltaPct = input.priorBlockRate === null ? null : Math.round((blockRate - input.priorBlockRate) * 100);
|
|
16185
|
-
return {
|
|
16186
|
-
schemaVersion: 1,
|
|
16187
|
-
generatedAt: input.generatedAt,
|
|
16188
|
-
period: input.period,
|
|
16189
|
-
range: { start: input.start.toISOString(), end: input.end.toISOString() },
|
|
16190
|
-
excludedTests: input.excludedTests,
|
|
16191
|
-
totals: {
|
|
16192
|
-
events: input.total,
|
|
16193
|
-
blocked: totalBlocked,
|
|
16194
|
-
blockRate,
|
|
16195
|
-
userApproved: input.userApproved,
|
|
16196
|
-
userDenied: input.userDenied,
|
|
16197
|
-
timedOut: input.timedOut,
|
|
16198
|
-
hardBlocked: input.hardBlocked,
|
|
16199
|
-
dlpBlocked: input.dlpBlocked,
|
|
16200
|
-
observeDlp: input.observeDlp,
|
|
16201
|
-
loopHits: input.loopHits,
|
|
16202
|
-
unackedDlp: input.unackedDlp
|
|
16203
|
-
},
|
|
16204
|
-
tests: {
|
|
16205
|
-
passes: input.testPasses,
|
|
16206
|
-
fails: input.testFails
|
|
16207
|
-
},
|
|
16208
|
-
cost: {
|
|
16209
|
-
totalUSD: input.cost.claudeUSD + input.cost.codexUSD,
|
|
16210
|
-
claudeUSD: input.cost.claudeUSD,
|
|
16211
|
-
codexUSD: input.cost.codexUSD,
|
|
16212
|
-
inputTokens: input.cost.inputTokens,
|
|
16213
|
-
outputTokens: input.cost.outputTokens,
|
|
16214
|
-
cacheWriteTokens: input.cost.cacheWriteTokens,
|
|
16215
|
-
cacheReadTokens: input.cost.cacheReadTokens,
|
|
16216
|
-
byDay: [...input.cost.byDay.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([day, usd]) => ({ day, usd })),
|
|
16217
|
-
byModel: [...input.cost.byModel.entries()].sort((a, b) => b[1] - a[1]).map(([model, usd]) => ({ model, usd }))
|
|
16218
|
-
},
|
|
16219
|
-
byTool: [...input.toolMap.entries()].sort((a, b) => b[1].calls - a[1].calls).map(([tool, v]) => ({ tool, calls: v.calls, blocked: v.blocked })),
|
|
16220
|
-
byBlock: [...input.blockMap.entries()].sort((a, b) => b[1] - a[1]).map(([rule, count]) => ({ rule, count })),
|
|
16221
|
-
byAgent: [...input.agentMap.entries()].sort((a, b) => b[1] - a[1]).map(([agent, calls]) => ({ agent, calls })),
|
|
16222
|
-
byMcp: [...input.mcpMap.entries()].sort((a, b) => b[1] - a[1]).map(([server, calls]) => ({ server, calls })),
|
|
16223
|
-
byDay: [...input.dailyMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([day, v]) => ({ day, calls: v.calls, blocked: v.blocked })),
|
|
16224
|
-
byHour: [...input.hourMap.entries()].sort((a, b) => a[0] - b[0]).map(([hour, calls]) => ({ hour, calls })),
|
|
16225
|
-
trend: {
|
|
16226
|
-
priorBlockRate: input.priorBlockRate,
|
|
16227
|
-
deltaPct
|
|
16228
|
-
}
|
|
16229
|
-
};
|
|
16230
|
-
}
|
|
16231
|
-
|
|
16232
|
-
// src/cli/commands/report.ts
|
|
16752
|
+
import path36 from "path";
|
|
16233
16753
|
var TEST_COMMAND_RE3 = /(?:^|\s)(npm\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|yarn\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|vitest|jest|mocha|pytest|py\.test|cargo\s+test|go\s+test|bundle\s+exec\s+rspec|rspec|phpunit|dotnet\s+test)\b/i;
|
|
16234
16754
|
function buildTestTimestamps(allEntries) {
|
|
16235
16755
|
const testTs = /* @__PURE__ */ new Set();
|
|
@@ -16254,8 +16774,7 @@ function isTestEntry(entry, testTs) {
|
|
|
16254
16774
|
}
|
|
16255
16775
|
return false;
|
|
16256
16776
|
}
|
|
16257
|
-
function getDateRange(period) {
|
|
16258
|
-
const now = /* @__PURE__ */ new Date();
|
|
16777
|
+
function getDateRange(period, now) {
|
|
16259
16778
|
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
16260
16779
|
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
|
|
16261
16780
|
switch (period) {
|
|
@@ -16271,6 +16790,11 @@ function getDateRange(period) {
|
|
|
16271
16790
|
s.setDate(s.getDate() - 29);
|
|
16272
16791
|
return { start: s, end };
|
|
16273
16792
|
}
|
|
16793
|
+
case "90d": {
|
|
16794
|
+
const s = new Date(todayStart);
|
|
16795
|
+
s.setDate(s.getDate() - 89);
|
|
16796
|
+
return { start: s, end };
|
|
16797
|
+
}
|
|
16274
16798
|
case "month":
|
|
16275
16799
|
return { start: new Date(now.getFullYear(), now.getMonth(), 1), end };
|
|
16276
16800
|
}
|
|
@@ -16293,40 +16817,6 @@ function isAllow(decision) {
|
|
|
16293
16817
|
function isDlp(checkedBy) {
|
|
16294
16818
|
return !!checkedBy?.includes("dlp");
|
|
16295
16819
|
}
|
|
16296
|
-
var BLOCK_REASON_LABELS = {
|
|
16297
|
-
timeout: "Popup timeout",
|
|
16298
|
-
"smart-rule-block": "Smart rule",
|
|
16299
|
-
"observe-mode-dlp-would-block": "DLP (observe)",
|
|
16300
|
-
"persistent-deny": "Persistent deny",
|
|
16301
|
-
"local-decision": "User denied",
|
|
16302
|
-
"dlp-block": "DLP block",
|
|
16303
|
-
"loop-detected": "Loop detected"
|
|
16304
|
-
};
|
|
16305
|
-
function humanBlockReason(reason) {
|
|
16306
|
-
return BLOCK_REASON_LABELS[reason] ?? reason;
|
|
16307
|
-
}
|
|
16308
|
-
function barStr(value, max, width) {
|
|
16309
|
-
if (max === 0 || width <= 0) return "\u2591".repeat(width);
|
|
16310
|
-
const filled = Math.max(1, Math.round(value / max * width));
|
|
16311
|
-
return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
16312
|
-
}
|
|
16313
|
-
function colorBar(value, max, width) {
|
|
16314
|
-
const s = barStr(value, max, width);
|
|
16315
|
-
const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
|
|
16316
|
-
return chalk13.cyan(s.slice(0, filled)) + chalk13.dim(s.slice(filled));
|
|
16317
|
-
}
|
|
16318
|
-
function fmtDate(d) {
|
|
16319
|
-
const date = typeof d === "string" ? /* @__PURE__ */ new Date(d + "T12:00:00") : d;
|
|
16320
|
-
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
16321
|
-
}
|
|
16322
|
-
function num2(n) {
|
|
16323
|
-
return n.toLocaleString();
|
|
16324
|
-
}
|
|
16325
|
-
function fmtCost2(usd) {
|
|
16326
|
-
if (usd < 1e-3) return "< $0.001";
|
|
16327
|
-
if (usd < 1) return "$" + usd.toFixed(4);
|
|
16328
|
-
return "$" + usd.toFixed(2);
|
|
16329
|
-
}
|
|
16330
16820
|
var CLAUDE_PRICING2 = {
|
|
16331
16821
|
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
16332
16822
|
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
@@ -16346,90 +16836,160 @@ function claudeModelPrice2(model) {
|
|
|
16346
16836
|
}
|
|
16347
16837
|
return null;
|
|
16348
16838
|
}
|
|
16349
|
-
function
|
|
16350
|
-
|
|
16839
|
+
function emptyClaudeCostAccumulator() {
|
|
16840
|
+
return {
|
|
16351
16841
|
total: 0,
|
|
16352
|
-
byDay: /* @__PURE__ */ new Map(),
|
|
16353
|
-
byModel: /* @__PURE__ */ new Map(),
|
|
16354
16842
|
inputTokens: 0,
|
|
16355
16843
|
outputTokens: 0,
|
|
16356
16844
|
cacheWriteTokens: 0,
|
|
16357
|
-
cacheReadTokens: 0
|
|
16845
|
+
cacheReadTokens: 0,
|
|
16846
|
+
byDay: /* @__PURE__ */ new Map(),
|
|
16847
|
+
byModel: /* @__PURE__ */ new Map(),
|
|
16848
|
+
byProject: /* @__PURE__ */ new Map()
|
|
16849
|
+
};
|
|
16850
|
+
}
|
|
16851
|
+
function freezeClaudeCost(acc) {
|
|
16852
|
+
return {
|
|
16853
|
+
total: acc.total,
|
|
16854
|
+
byDay: acc.byDay,
|
|
16855
|
+
byModel: acc.byModel,
|
|
16856
|
+
byProject: acc.byProject,
|
|
16857
|
+
inputTokens: acc.inputTokens,
|
|
16858
|
+
outputTokens: acc.outputTokens,
|
|
16859
|
+
cacheWriteTokens: acc.cacheWriteTokens,
|
|
16860
|
+
cacheReadTokens: acc.cacheReadTokens
|
|
16358
16861
|
};
|
|
16359
|
-
|
|
16360
|
-
|
|
16862
|
+
}
|
|
16863
|
+
function processClaudeCostProject(proj, projectsDir, start, end, acc) {
|
|
16864
|
+
const projPath = path36.join(projectsDir, proj);
|
|
16865
|
+
let files;
|
|
16866
|
+
try {
|
|
16867
|
+
const stat = fs35.statSync(projPath);
|
|
16868
|
+
if (!stat.isDirectory()) return;
|
|
16869
|
+
files = fs35.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
16870
|
+
} catch {
|
|
16871
|
+
return;
|
|
16872
|
+
}
|
|
16873
|
+
const startMs = start.getTime();
|
|
16874
|
+
for (const file of files) {
|
|
16875
|
+
const filePath = path36.join(projPath, file);
|
|
16876
|
+
try {
|
|
16877
|
+
if (fs35.statSync(filePath).mtimeMs < startMs) continue;
|
|
16878
|
+
} catch {
|
|
16879
|
+
continue;
|
|
16880
|
+
}
|
|
16881
|
+
try {
|
|
16882
|
+
const raw = fs35.readFileSync(filePath, "utf-8");
|
|
16883
|
+
for (const line of raw.split("\n")) {
|
|
16884
|
+
if (!line.trim()) continue;
|
|
16885
|
+
let entry;
|
|
16886
|
+
try {
|
|
16887
|
+
entry = JSON.parse(line);
|
|
16888
|
+
} catch {
|
|
16889
|
+
continue;
|
|
16890
|
+
}
|
|
16891
|
+
if (entry.type !== "assistant") continue;
|
|
16892
|
+
if (!entry.timestamp) continue;
|
|
16893
|
+
const ts = new Date(entry.timestamp);
|
|
16894
|
+
if (ts < start || ts > end) continue;
|
|
16895
|
+
const usage = entry.message?.usage;
|
|
16896
|
+
const model = entry.message?.model;
|
|
16897
|
+
if (!usage || !model) continue;
|
|
16898
|
+
const p = claudeModelPrice2(model);
|
|
16899
|
+
if (!p) continue;
|
|
16900
|
+
const inp = usage.input_tokens ?? 0;
|
|
16901
|
+
const out = usage.output_tokens ?? 0;
|
|
16902
|
+
const cw = usage.cache_creation_input_tokens ?? 0;
|
|
16903
|
+
const cr = usage.cache_read_input_tokens ?? 0;
|
|
16904
|
+
const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
|
|
16905
|
+
acc.total += cost;
|
|
16906
|
+
acc.inputTokens += inp;
|
|
16907
|
+
acc.outputTokens += out;
|
|
16908
|
+
acc.cacheWriteTokens += cw;
|
|
16909
|
+
acc.cacheReadTokens += cr;
|
|
16910
|
+
const dateKey = entry.timestamp.slice(0, 10);
|
|
16911
|
+
acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
|
|
16912
|
+
const normModel = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
|
|
16913
|
+
acc.byModel.set(normModel, (acc.byModel.get(normModel) ?? 0) + cost);
|
|
16914
|
+
const projectKey = decodeProjectDirName(proj);
|
|
16915
|
+
const projectRollup = acc.byProject.get(projectKey) ?? {
|
|
16916
|
+
cost: 0,
|
|
16917
|
+
inputTokens: 0,
|
|
16918
|
+
outputTokens: 0
|
|
16919
|
+
};
|
|
16920
|
+
projectRollup.cost += cost;
|
|
16921
|
+
projectRollup.inputTokens += inp;
|
|
16922
|
+
projectRollup.outputTokens += out;
|
|
16923
|
+
acc.byProject.set(projectKey, projectRollup);
|
|
16924
|
+
}
|
|
16925
|
+
} catch {
|
|
16926
|
+
continue;
|
|
16927
|
+
}
|
|
16928
|
+
}
|
|
16929
|
+
}
|
|
16930
|
+
function loadClaudeCost(start, end, projectsDir) {
|
|
16931
|
+
const acc = emptyClaudeCostAccumulator();
|
|
16932
|
+
if (!fs35.existsSync(projectsDir)) return freezeClaudeCost(acc);
|
|
16361
16933
|
let dirs;
|
|
16362
16934
|
try {
|
|
16363
16935
|
dirs = fs35.readdirSync(projectsDir);
|
|
16364
16936
|
} catch {
|
|
16365
|
-
return
|
|
16937
|
+
return freezeClaudeCost(acc);
|
|
16366
16938
|
}
|
|
16367
|
-
let total = 0;
|
|
16368
|
-
let inputTokens = 0;
|
|
16369
|
-
let outputTokens = 0;
|
|
16370
|
-
let cacheWriteTokens = 0;
|
|
16371
|
-
let cacheReadTokens = 0;
|
|
16372
|
-
const byDay = /* @__PURE__ */ new Map();
|
|
16373
|
-
const byModel = /* @__PURE__ */ new Map();
|
|
16374
16939
|
for (const proj of dirs) {
|
|
16375
|
-
|
|
16376
|
-
|
|
16940
|
+
processClaudeCostProject(proj, projectsDir, start, end, acc);
|
|
16941
|
+
}
|
|
16942
|
+
return freezeClaudeCost(acc);
|
|
16943
|
+
}
|
|
16944
|
+
function processCodexCostFile(filePath, start, end, acc) {
|
|
16945
|
+
let lines;
|
|
16946
|
+
try {
|
|
16947
|
+
lines = fs35.readFileSync(filePath, "utf-8").split("\n");
|
|
16948
|
+
} catch {
|
|
16949
|
+
return;
|
|
16950
|
+
}
|
|
16951
|
+
let sessionStart2 = "";
|
|
16952
|
+
let lastTotalInput = 0;
|
|
16953
|
+
let lastTotalCached = 0;
|
|
16954
|
+
let lastTotalOutput = 0;
|
|
16955
|
+
let sessionToolCalls = 0;
|
|
16956
|
+
for (const line of lines) {
|
|
16957
|
+
if (!line.trim()) continue;
|
|
16958
|
+
let entry;
|
|
16377
16959
|
try {
|
|
16378
|
-
|
|
16379
|
-
if (!stat.isDirectory()) continue;
|
|
16380
|
-
files = fs35.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
16960
|
+
entry = JSON.parse(line);
|
|
16381
16961
|
} catch {
|
|
16382
16962
|
continue;
|
|
16383
16963
|
}
|
|
16384
|
-
|
|
16385
|
-
|
|
16386
|
-
|
|
16387
|
-
|
|
16388
|
-
|
|
16389
|
-
|
|
16390
|
-
|
|
16391
|
-
|
|
16392
|
-
|
|
16393
|
-
|
|
16394
|
-
|
|
16395
|
-
|
|
16396
|
-
|
|
16397
|
-
|
|
16398
|
-
if (ts < start || ts > end) continue;
|
|
16399
|
-
const usage = entry.message?.usage;
|
|
16400
|
-
const model = entry.message?.model;
|
|
16401
|
-
if (!usage || !model) continue;
|
|
16402
|
-
const p = claudeModelPrice2(model);
|
|
16403
|
-
if (!p) continue;
|
|
16404
|
-
const inp = usage.input_tokens ?? 0;
|
|
16405
|
-
const out = usage.output_tokens ?? 0;
|
|
16406
|
-
const cw = usage.cache_creation_input_tokens ?? 0;
|
|
16407
|
-
const cr = usage.cache_read_input_tokens ?? 0;
|
|
16408
|
-
const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
|
|
16409
|
-
total += cost;
|
|
16410
|
-
inputTokens += inp;
|
|
16411
|
-
outputTokens += out;
|
|
16412
|
-
cacheWriteTokens += cw;
|
|
16413
|
-
cacheReadTokens += cr;
|
|
16414
|
-
const dateKey = entry.timestamp.slice(0, 10);
|
|
16415
|
-
byDay.set(dateKey, (byDay.get(dateKey) ?? 0) + cost);
|
|
16416
|
-
const normModel = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
|
|
16417
|
-
byModel.set(normModel, (byModel.get(normModel) ?? 0) + cost);
|
|
16418
|
-
}
|
|
16419
|
-
} catch {
|
|
16420
|
-
continue;
|
|
16421
|
-
}
|
|
16964
|
+
const p = entry.payload ?? {};
|
|
16965
|
+
if (entry.type === "session_meta") {
|
|
16966
|
+
sessionStart2 = String(p["timestamp"] ?? "");
|
|
16967
|
+
continue;
|
|
16968
|
+
}
|
|
16969
|
+
if (entry.type === "event_msg" && p["type"] === "token_count") {
|
|
16970
|
+
const info = p["info"] ?? {};
|
|
16971
|
+
const usage = info["total_token_usage"] ?? {};
|
|
16972
|
+
lastTotalInput = usage["input_tokens"] ?? lastTotalInput;
|
|
16973
|
+
lastTotalCached = usage["cached_input_tokens"] ?? lastTotalCached;
|
|
16974
|
+
lastTotalOutput = usage["output_tokens"] ?? lastTotalOutput;
|
|
16975
|
+
}
|
|
16976
|
+
if (entry.type === "response_item" && p["type"] === "function_call") {
|
|
16977
|
+
sessionToolCalls++;
|
|
16422
16978
|
}
|
|
16423
16979
|
}
|
|
16424
|
-
|
|
16980
|
+
if (!sessionStart2) return;
|
|
16981
|
+
const ts = new Date(sessionStart2);
|
|
16982
|
+
if (ts < start || ts > end) return;
|
|
16983
|
+
const nonCached = Math.max(0, lastTotalInput - lastTotalCached);
|
|
16984
|
+
const cost = nonCached * 5e-6 + lastTotalCached * 25e-7 + lastTotalOutput * 15e-6;
|
|
16985
|
+
acc.total += cost;
|
|
16986
|
+
acc.toolCalls += sessionToolCalls;
|
|
16987
|
+
const dateKey = sessionStart2.slice(0, 10);
|
|
16988
|
+
acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
|
|
16425
16989
|
}
|
|
16426
|
-
function
|
|
16427
|
-
const sessionsBase = path36.join(os31.homedir(), ".codex", "sessions");
|
|
16428
|
-
const byDay = /* @__PURE__ */ new Map();
|
|
16429
|
-
let total = 0;
|
|
16430
|
-
let toolCalls = 0;
|
|
16431
|
-
if (!fs35.existsSync(sessionsBase)) return { total, byDay, toolCalls };
|
|
16990
|
+
function listCodexSessionFiles(sessionsBase) {
|
|
16432
16991
|
const jsonlFiles = [];
|
|
16992
|
+
if (!fs35.existsSync(sessionsBase)) return jsonlFiles;
|
|
16433
16993
|
try {
|
|
16434
16994
|
for (const year of fs35.readdirSync(sessionsBase)) {
|
|
16435
16995
|
const yearPath = path36.join(sessionsBase, year);
|
|
@@ -16459,495 +17019,742 @@ function loadCodexCost(start, end) {
|
|
|
16459
17019
|
}
|
|
16460
17020
|
}
|
|
16461
17021
|
} catch {
|
|
16462
|
-
return
|
|
17022
|
+
return [];
|
|
16463
17023
|
}
|
|
16464
|
-
|
|
16465
|
-
|
|
17024
|
+
return jsonlFiles;
|
|
17025
|
+
}
|
|
17026
|
+
function loadCodexCost(start, end, sessionsBase) {
|
|
17027
|
+
const acc = { total: 0, toolCalls: 0, byDay: /* @__PURE__ */ new Map() };
|
|
17028
|
+
const files = listCodexSessionFiles(sessionsBase);
|
|
17029
|
+
for (const filePath of files) {
|
|
17030
|
+
processCodexCostFile(filePath, start, end, acc);
|
|
17031
|
+
}
|
|
17032
|
+
return { total: acc.total, byDay: acc.byDay, toolCalls: acc.toolCalls };
|
|
17033
|
+
}
|
|
17034
|
+
var GEMINI_FALLBACK_MODELS = ["gemini-2.5-flash", "gemini-2.0-flash"];
|
|
17035
|
+
function geminiPriceFor(model) {
|
|
17036
|
+
let tuple = pricingFor(model);
|
|
17037
|
+
if (!tuple && /^gemini-/i.test(model)) {
|
|
17038
|
+
for (const proxy of GEMINI_FALLBACK_MODELS) {
|
|
17039
|
+
tuple = pricingFor(proxy);
|
|
17040
|
+
if (tuple) break;
|
|
17041
|
+
}
|
|
17042
|
+
}
|
|
17043
|
+
if (!tuple) return null;
|
|
17044
|
+
return { input: tuple[0], output: tuple[1], cacheRead: tuple[3] || tuple[0] };
|
|
17045
|
+
}
|
|
17046
|
+
function emptyGeminiAccumulator() {
|
|
17047
|
+
return {
|
|
17048
|
+
total: 0,
|
|
17049
|
+
inputTokens: 0,
|
|
17050
|
+
outputTokens: 0,
|
|
17051
|
+
cacheReadTokens: 0,
|
|
17052
|
+
byDay: /* @__PURE__ */ new Map(),
|
|
17053
|
+
byProject: /* @__PURE__ */ new Map()
|
|
17054
|
+
};
|
|
17055
|
+
}
|
|
17056
|
+
function freezeGeminiCost(acc) {
|
|
17057
|
+
return {
|
|
17058
|
+
total: acc.total,
|
|
17059
|
+
byDay: acc.byDay,
|
|
17060
|
+
byProject: acc.byProject,
|
|
17061
|
+
inputTokens: acc.inputTokens,
|
|
17062
|
+
outputTokens: acc.outputTokens,
|
|
17063
|
+
cacheReadTokens: acc.cacheReadTokens
|
|
17064
|
+
};
|
|
17065
|
+
}
|
|
17066
|
+
function processGeminiCostFile(filePath, projectKey, start, end, acc) {
|
|
17067
|
+
const startMs = start.getTime();
|
|
17068
|
+
try {
|
|
17069
|
+
if (fs35.statSync(filePath).mtimeMs < startMs) return;
|
|
17070
|
+
} catch {
|
|
17071
|
+
return;
|
|
17072
|
+
}
|
|
17073
|
+
let raw;
|
|
17074
|
+
try {
|
|
17075
|
+
raw = fs35.readFileSync(filePath, "utf-8");
|
|
17076
|
+
} catch {
|
|
17077
|
+
return;
|
|
17078
|
+
}
|
|
17079
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
17080
|
+
for (const line of raw.split("\n")) {
|
|
17081
|
+
if (!line.trim()) continue;
|
|
17082
|
+
let entry;
|
|
16466
17083
|
try {
|
|
16467
|
-
|
|
17084
|
+
entry = JSON.parse(line);
|
|
16468
17085
|
} catch {
|
|
16469
17086
|
continue;
|
|
16470
17087
|
}
|
|
16471
|
-
|
|
16472
|
-
|
|
16473
|
-
|
|
16474
|
-
|
|
16475
|
-
|
|
16476
|
-
for (const line of lines) {
|
|
16477
|
-
if (!line.trim()) continue;
|
|
16478
|
-
let entry;
|
|
16479
|
-
try {
|
|
16480
|
-
entry = JSON.parse(line);
|
|
16481
|
-
} catch {
|
|
16482
|
-
continue;
|
|
16483
|
-
}
|
|
16484
|
-
const p = entry.payload ?? {};
|
|
16485
|
-
if (entry.type === "session_meta") {
|
|
16486
|
-
sessionStart2 = String(p["timestamp"] ?? "");
|
|
16487
|
-
continue;
|
|
16488
|
-
}
|
|
16489
|
-
if (entry.type === "event_msg" && p["type"] === "token_count") {
|
|
16490
|
-
const info = p["info"] ?? {};
|
|
16491
|
-
const usage = info["total_token_usage"] ?? {};
|
|
16492
|
-
lastTotalInput = usage["input_tokens"] ?? lastTotalInput;
|
|
16493
|
-
lastTotalCached = usage["cached_input_tokens"] ?? lastTotalCached;
|
|
16494
|
-
lastTotalOutput = usage["output_tokens"] ?? lastTotalOutput;
|
|
16495
|
-
}
|
|
16496
|
-
if (entry.type === "response_item" && p["type"] === "function_call") {
|
|
16497
|
-
sessionToolCalls++;
|
|
16498
|
-
}
|
|
17088
|
+
if (entry.type !== "gemini") continue;
|
|
17089
|
+
if (!entry.tokens || !entry.model || !entry.timestamp) continue;
|
|
17090
|
+
if (entry.id) {
|
|
17091
|
+
if (seenIds.has(entry.id)) continue;
|
|
17092
|
+
seenIds.add(entry.id);
|
|
16499
17093
|
}
|
|
16500
|
-
|
|
16501
|
-
const ts = new Date(sessionStart2);
|
|
17094
|
+
const ts = new Date(entry.timestamp);
|
|
16502
17095
|
if (ts < start || ts > end) continue;
|
|
16503
|
-
const
|
|
16504
|
-
|
|
16505
|
-
|
|
16506
|
-
|
|
16507
|
-
const
|
|
16508
|
-
|
|
17096
|
+
const price = geminiPriceFor(entry.model);
|
|
17097
|
+
if (!price) continue;
|
|
17098
|
+
const inp = entry.tokens.input ?? 0;
|
|
17099
|
+
const out = entry.tokens.output ?? 0;
|
|
17100
|
+
const cached = Math.min(entry.tokens.cached ?? 0, inp);
|
|
17101
|
+
const fresh = Math.max(0, inp - cached);
|
|
17102
|
+
const cost = fresh * price.input + cached * price.cacheRead + out * price.output;
|
|
17103
|
+
acc.total += cost;
|
|
17104
|
+
acc.inputTokens += inp;
|
|
17105
|
+
acc.outputTokens += out;
|
|
17106
|
+
acc.cacheReadTokens += cached;
|
|
17107
|
+
const dateKey = entry.timestamp.slice(0, 10);
|
|
17108
|
+
acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
|
|
17109
|
+
const rollup = acc.byProject.get(projectKey) ?? {
|
|
17110
|
+
cost: 0,
|
|
17111
|
+
inputTokens: 0,
|
|
17112
|
+
outputTokens: 0
|
|
17113
|
+
};
|
|
17114
|
+
rollup.cost += cost;
|
|
17115
|
+
rollup.inputTokens += inp;
|
|
17116
|
+
rollup.outputTokens += out;
|
|
17117
|
+
acc.byProject.set(projectKey, rollup);
|
|
16509
17118
|
}
|
|
16510
|
-
|
|
17119
|
+
}
|
|
17120
|
+
function listGeminiSessionFiles(geminiTmpDir) {
|
|
17121
|
+
const out = [];
|
|
17122
|
+
let dirs;
|
|
17123
|
+
try {
|
|
17124
|
+
if (!fs35.statSync(geminiTmpDir).isDirectory()) return out;
|
|
17125
|
+
dirs = fs35.readdirSync(geminiTmpDir);
|
|
17126
|
+
} catch {
|
|
17127
|
+
return out;
|
|
17128
|
+
}
|
|
17129
|
+
for (const proj of dirs) {
|
|
17130
|
+
const chatsDir = path36.join(geminiTmpDir, proj, "chats");
|
|
17131
|
+
let files;
|
|
17132
|
+
try {
|
|
17133
|
+
if (!fs35.statSync(chatsDir).isDirectory()) continue;
|
|
17134
|
+
files = fs35.readdirSync(chatsDir);
|
|
17135
|
+
} catch {
|
|
17136
|
+
continue;
|
|
17137
|
+
}
|
|
17138
|
+
for (const f of files) {
|
|
17139
|
+
if (!f.endsWith(".jsonl")) continue;
|
|
17140
|
+
out.push({ projectKey: proj, file: path36.join(chatsDir, f) });
|
|
17141
|
+
}
|
|
17142
|
+
}
|
|
17143
|
+
return out;
|
|
17144
|
+
}
|
|
17145
|
+
function loadGeminiCost(start, end, geminiTmpDir) {
|
|
17146
|
+
const acc = emptyGeminiAccumulator();
|
|
17147
|
+
if (!fs35.existsSync(geminiTmpDir)) return freezeGeminiCost(acc);
|
|
17148
|
+
for (const { projectKey, file } of listGeminiSessionFiles(geminiTmpDir)) {
|
|
17149
|
+
processGeminiCostFile(file, projectKey, start, end, acc);
|
|
17150
|
+
}
|
|
17151
|
+
return freezeGeminiCost(acc);
|
|
17152
|
+
}
|
|
17153
|
+
function aggregateReportFromAudit(period, opts = {}) {
|
|
17154
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
17155
|
+
const auditLogPath = opts.auditLogPath ?? path36.join(os31.homedir(), ".node9", "audit.log");
|
|
17156
|
+
const claudeProjectsDir = opts.claudeProjectsDir ?? path36.join(os31.homedir(), ".claude", "projects");
|
|
17157
|
+
const codexSessionsDir = opts.codexSessionsDir ?? path36.join(os31.homedir(), ".codex", "sessions");
|
|
17158
|
+
const geminiTmpDir = opts.geminiTmpDir ?? path36.join(os31.homedir(), ".gemini", "tmp");
|
|
17159
|
+
const hasAuditFile = fs35.existsSync(auditLogPath);
|
|
17160
|
+
const allEntries = opts.preloadedAuditEntries ?? parseAuditLog(auditLogPath);
|
|
17161
|
+
const unackedDlp = allEntries.filter((e) => e.source === "response-dlp");
|
|
17162
|
+
const { start, end } = getDateRange(period, now);
|
|
17163
|
+
const responseDlpEntries = allEntries.filter((e) => {
|
|
17164
|
+
if (e.source !== "response-dlp") return false;
|
|
17165
|
+
const ts = new Date(e.ts);
|
|
17166
|
+
return ts >= start && ts <= end;
|
|
17167
|
+
}).map((e) => {
|
|
17168
|
+
const raw = e;
|
|
17169
|
+
return {
|
|
17170
|
+
ts: e.ts,
|
|
17171
|
+
dlpPattern: typeof raw.dlpPattern === "string" ? raw.dlpPattern : void 0,
|
|
17172
|
+
dlpSample: typeof raw.dlpSample === "string" ? raw.dlpSample : void 0
|
|
17173
|
+
};
|
|
17174
|
+
});
|
|
17175
|
+
const claudeCost = opts.preloadedClaudeCost ?? loadClaudeCost(start, end, claudeProjectsDir);
|
|
17176
|
+
const codexCost = opts.preloadedCodexCost ?? loadCodexCost(start, end, codexSessionsDir);
|
|
17177
|
+
const geminiCost = opts.preloadedGeminiCost ?? loadGeminiCost(start, end, geminiTmpDir);
|
|
17178
|
+
for (const [day, c] of codexCost.byDay) {
|
|
17179
|
+
claudeCost.byDay.set(day, (claudeCost.byDay.get(day) ?? 0) + c);
|
|
17180
|
+
}
|
|
17181
|
+
for (const [day, c] of geminiCost.byDay) {
|
|
17182
|
+
claudeCost.byDay.set(day, (claudeCost.byDay.get(day) ?? 0) + c);
|
|
17183
|
+
}
|
|
17184
|
+
for (const [geminiKey, gRollup] of geminiCost.byProject) {
|
|
17185
|
+
let mergedInto = null;
|
|
17186
|
+
for (const claudeKey of claudeCost.byProject.keys()) {
|
|
17187
|
+
const claudeBase = claudeKey.match(/[^/\\]+$/)?.[0] ?? claudeKey;
|
|
17188
|
+
if (claudeBase === geminiKey) {
|
|
17189
|
+
mergedInto = claudeKey;
|
|
17190
|
+
break;
|
|
17191
|
+
}
|
|
17192
|
+
}
|
|
17193
|
+
const targetKey = mergedInto ?? geminiKey;
|
|
17194
|
+
const existing = claudeCost.byProject.get(targetKey) ?? {
|
|
17195
|
+
cost: 0,
|
|
17196
|
+
inputTokens: 0,
|
|
17197
|
+
outputTokens: 0
|
|
17198
|
+
};
|
|
17199
|
+
existing.cost += gRollup.cost;
|
|
17200
|
+
existing.inputTokens += gRollup.inputTokens;
|
|
17201
|
+
existing.outputTokens += gRollup.outputTokens;
|
|
17202
|
+
claudeCost.byProject.set(targetKey, existing);
|
|
17203
|
+
}
|
|
17204
|
+
const periodMs = end.getTime() - start.getTime();
|
|
17205
|
+
const priorEnd = new Date(start.getTime() - 1);
|
|
17206
|
+
const priorStart = new Date(start.getTime() - periodMs);
|
|
17207
|
+
const priorEntries = allEntries.filter((e) => {
|
|
17208
|
+
if (e.source === "post-hook") return false;
|
|
17209
|
+
const ts = new Date(e.ts);
|
|
17210
|
+
return ts >= priorStart && ts <= priorEnd;
|
|
17211
|
+
});
|
|
17212
|
+
const priorBlocked = priorEntries.filter((e) => !isAllow(e.decision)).length;
|
|
17213
|
+
const priorBlockRate = priorEntries.length > 0 ? priorBlocked / priorEntries.length : null;
|
|
17214
|
+
const excludeTests = opts.excludeTests === true;
|
|
17215
|
+
const testTs = excludeTests ? buildTestTimestamps(allEntries) : /* @__PURE__ */ new Set();
|
|
17216
|
+
let excludedTests = 0;
|
|
17217
|
+
const entries = allEntries.filter((e) => {
|
|
17218
|
+
if (e.source === "post-hook") return false;
|
|
17219
|
+
if (e.source === "response-dlp") return false;
|
|
17220
|
+
const ts = new Date(e.ts);
|
|
17221
|
+
if (ts < start || ts > end) return false;
|
|
17222
|
+
if (excludeTests && isTestEntry(e, testTs)) {
|
|
17223
|
+
excludedTests++;
|
|
17224
|
+
return false;
|
|
17225
|
+
}
|
|
17226
|
+
return true;
|
|
17227
|
+
});
|
|
17228
|
+
let userApproved = 0;
|
|
17229
|
+
let userDenied = 0;
|
|
17230
|
+
let timedOut = 0;
|
|
17231
|
+
let hardBlocked = 0;
|
|
17232
|
+
let dlpBlocked = 0;
|
|
17233
|
+
let observeDlp = 0;
|
|
17234
|
+
let loopHits = 0;
|
|
17235
|
+
let testPasses = 0;
|
|
17236
|
+
let testFails = 0;
|
|
17237
|
+
const toolMap = /* @__PURE__ */ new Map();
|
|
17238
|
+
const blockMap = /* @__PURE__ */ new Map();
|
|
17239
|
+
const ruleMap = /* @__PURE__ */ new Map();
|
|
17240
|
+
const agentMap = /* @__PURE__ */ new Map();
|
|
17241
|
+
const mcpMap = /* @__PURE__ */ new Map();
|
|
17242
|
+
const dailyMap = /* @__PURE__ */ new Map();
|
|
17243
|
+
const hourMap = /* @__PURE__ */ new Map();
|
|
17244
|
+
for (const e of entries) {
|
|
17245
|
+
const allow = isAllow(e.decision);
|
|
17246
|
+
const dateKey = e.ts.slice(0, 10);
|
|
17247
|
+
const userInteracted = e.source === "daemon";
|
|
17248
|
+
if (userInteracted) {
|
|
17249
|
+
if (allow) userApproved++;
|
|
17250
|
+
else userDenied++;
|
|
17251
|
+
} else if (!allow) {
|
|
17252
|
+
if (e.checkedBy === "timeout") timedOut++;
|
|
17253
|
+
else if (e.checkedBy === "observe-mode-dlp-would-block") observeDlp++;
|
|
17254
|
+
else if (isDlp(e.checkedBy)) dlpBlocked++;
|
|
17255
|
+
else if (e.checkedBy !== "loop-detected") hardBlocked++;
|
|
17256
|
+
}
|
|
17257
|
+
if (e.checkedBy === "loop-detected") loopHits++;
|
|
17258
|
+
const t = toolMap.get(e.tool) ?? { calls: 0, blocked: 0 };
|
|
17259
|
+
t.calls++;
|
|
17260
|
+
if (!allow) t.blocked++;
|
|
17261
|
+
toolMap.set(e.tool, t);
|
|
17262
|
+
if (!allow && e.checkedBy) {
|
|
17263
|
+
blockMap.set(e.checkedBy, (blockMap.get(e.checkedBy) ?? 0) + 1);
|
|
17264
|
+
}
|
|
17265
|
+
if (!allow && e.ruleName) {
|
|
17266
|
+
ruleMap.set(e.ruleName, (ruleMap.get(e.ruleName) ?? 0) + 1);
|
|
17267
|
+
}
|
|
17268
|
+
if (e.agent) agentMap.set(e.agent, (agentMap.get(e.agent) ?? 0) + 1);
|
|
17269
|
+
if (e.mcpServer) mcpMap.set(e.mcpServer, (mcpMap.get(e.mcpServer) ?? 0) + 1);
|
|
17270
|
+
const hour = new Date(e.ts).getHours();
|
|
17271
|
+
hourMap.set(hour, (hourMap.get(hour) ?? 0) + 1);
|
|
17272
|
+
const d = dailyMap.get(dateKey) ?? { calls: 0, blocked: 0 };
|
|
17273
|
+
d.calls++;
|
|
17274
|
+
if (!allow) d.blocked++;
|
|
17275
|
+
dailyMap.set(dateKey, d);
|
|
17276
|
+
}
|
|
17277
|
+
for (const e of allEntries) {
|
|
17278
|
+
if (e.source !== "test-result") continue;
|
|
17279
|
+
const ts = new Date(e.ts);
|
|
17280
|
+
if (ts < start || ts > end) continue;
|
|
17281
|
+
if (e.testResult === "pass") testPasses++;
|
|
17282
|
+
else if (e.testResult === "fail") testFails++;
|
|
17283
|
+
}
|
|
17284
|
+
if (codexCost.toolCalls > 0) {
|
|
17285
|
+
agentMap.set("Codex", (agentMap.get("Codex") ?? 0) + codexCost.toolCalls);
|
|
17286
|
+
}
|
|
17287
|
+
const data = {
|
|
17288
|
+
period,
|
|
17289
|
+
start,
|
|
17290
|
+
end,
|
|
17291
|
+
excludedTests,
|
|
17292
|
+
total: entries.length,
|
|
17293
|
+
userApproved,
|
|
17294
|
+
userDenied,
|
|
17295
|
+
timedOut,
|
|
17296
|
+
hardBlocked,
|
|
17297
|
+
dlpBlocked,
|
|
17298
|
+
observeDlp,
|
|
17299
|
+
loopHits,
|
|
17300
|
+
testPasses,
|
|
17301
|
+
testFails,
|
|
17302
|
+
unackedDlp: unackedDlp.length,
|
|
17303
|
+
priorBlockRate,
|
|
17304
|
+
cost: {
|
|
17305
|
+
claudeUSD: claudeCost.total,
|
|
17306
|
+
codexUSD: codexCost.total,
|
|
17307
|
+
geminiUSD: geminiCost.total,
|
|
17308
|
+
inputTokens: claudeCost.inputTokens + geminiCost.inputTokens,
|
|
17309
|
+
outputTokens: claudeCost.outputTokens + geminiCost.outputTokens,
|
|
17310
|
+
cacheWriteTokens: claudeCost.cacheWriteTokens,
|
|
17311
|
+
cacheReadTokens: claudeCost.cacheReadTokens + geminiCost.cacheReadTokens,
|
|
17312
|
+
byDay: claudeCost.byDay,
|
|
17313
|
+
byModel: claudeCost.byModel,
|
|
17314
|
+
byProject: claudeCost.byProject
|
|
17315
|
+
},
|
|
17316
|
+
toolMap,
|
|
17317
|
+
blockMap,
|
|
17318
|
+
ruleMap,
|
|
17319
|
+
agentMap,
|
|
17320
|
+
mcpMap,
|
|
17321
|
+
dailyMap,
|
|
17322
|
+
hourMap,
|
|
17323
|
+
generatedAt: now.toISOString()
|
|
17324
|
+
};
|
|
17325
|
+
return { data, hasAuditFile, responseDlpEntries };
|
|
17326
|
+
}
|
|
17327
|
+
|
|
17328
|
+
// src/cli/render/report-json.ts
|
|
17329
|
+
function buildReportJson(input) {
|
|
17330
|
+
const totalBlocked = input.timedOut + input.hardBlocked + input.dlpBlocked + input.loopHits + input.userDenied;
|
|
17331
|
+
const blockRate = input.total > 0 ? totalBlocked / input.total : 0;
|
|
17332
|
+
const deltaPct = input.priorBlockRate === null ? null : Math.round((blockRate - input.priorBlockRate) * 100);
|
|
17333
|
+
return {
|
|
17334
|
+
schemaVersion: 1,
|
|
17335
|
+
generatedAt: input.generatedAt,
|
|
17336
|
+
period: input.period,
|
|
17337
|
+
range: { start: input.start.toISOString(), end: input.end.toISOString() },
|
|
17338
|
+
excludedTests: input.excludedTests,
|
|
17339
|
+
totals: {
|
|
17340
|
+
events: input.total,
|
|
17341
|
+
blocked: totalBlocked,
|
|
17342
|
+
blockRate,
|
|
17343
|
+
userApproved: input.userApproved,
|
|
17344
|
+
userDenied: input.userDenied,
|
|
17345
|
+
timedOut: input.timedOut,
|
|
17346
|
+
hardBlocked: input.hardBlocked,
|
|
17347
|
+
dlpBlocked: input.dlpBlocked,
|
|
17348
|
+
observeDlp: input.observeDlp,
|
|
17349
|
+
loopHits: input.loopHits,
|
|
17350
|
+
unackedDlp: input.unackedDlp
|
|
17351
|
+
},
|
|
17352
|
+
tests: {
|
|
17353
|
+
passes: input.testPasses,
|
|
17354
|
+
fails: input.testFails
|
|
17355
|
+
},
|
|
17356
|
+
cost: {
|
|
17357
|
+
totalUSD: input.cost.claudeUSD + input.cost.codexUSD + input.cost.geminiUSD,
|
|
17358
|
+
claudeUSD: input.cost.claudeUSD,
|
|
17359
|
+
codexUSD: input.cost.codexUSD,
|
|
17360
|
+
geminiUSD: input.cost.geminiUSD,
|
|
17361
|
+
inputTokens: input.cost.inputTokens,
|
|
17362
|
+
outputTokens: input.cost.outputTokens,
|
|
17363
|
+
cacheWriteTokens: input.cost.cacheWriteTokens,
|
|
17364
|
+
cacheReadTokens: input.cost.cacheReadTokens,
|
|
17365
|
+
byDay: [...input.cost.byDay.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([day, usd]) => ({ day, usd })),
|
|
17366
|
+
byModel: [...input.cost.byModel.entries()].sort((a, b) => b[1] - a[1]).map(([model, usd]) => ({ model, usd }))
|
|
17367
|
+
},
|
|
17368
|
+
byTool: [...input.toolMap.entries()].sort((a, b) => b[1].calls - a[1].calls).map(([tool, v]) => ({ tool, calls: v.calls, blocked: v.blocked })),
|
|
17369
|
+
byBlock: [...input.blockMap.entries()].sort((a, b) => b[1] - a[1]).map(([rule, count]) => ({ rule, count })),
|
|
17370
|
+
byAgent: [...input.agentMap.entries()].sort((a, b) => b[1] - a[1]).map(([agent, calls]) => ({ agent, calls })),
|
|
17371
|
+
byMcp: [...input.mcpMap.entries()].sort((a, b) => b[1] - a[1]).map(([server, calls]) => ({ server, calls })),
|
|
17372
|
+
byDay: [...input.dailyMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([day, v]) => ({ day, calls: v.calls, blocked: v.blocked })),
|
|
17373
|
+
byHour: [...input.hourMap.entries()].sort((a, b) => a[0] - b[0]).map(([hour, calls]) => ({ hour, calls })),
|
|
17374
|
+
trend: {
|
|
17375
|
+
priorBlockRate: input.priorBlockRate,
|
|
17376
|
+
deltaPct
|
|
17377
|
+
}
|
|
17378
|
+
};
|
|
17379
|
+
}
|
|
17380
|
+
|
|
17381
|
+
// src/cli/commands/report.ts
|
|
17382
|
+
var BLOCK_REASON_LABELS = {
|
|
17383
|
+
timeout: "Approval timeout",
|
|
17384
|
+
"smart-rule-block": "Smart rule",
|
|
17385
|
+
"observe-mode-dlp-would-block": "DLP (observe)",
|
|
17386
|
+
"persistent-deny": "Persistent deny",
|
|
17387
|
+
"local-decision": "User denied",
|
|
17388
|
+
"dlp-block": "DLP block",
|
|
17389
|
+
"loop-detected": "Loop detected"
|
|
17390
|
+
};
|
|
17391
|
+
function humanBlockReason(reason) {
|
|
17392
|
+
return BLOCK_REASON_LABELS[reason] ?? reason;
|
|
17393
|
+
}
|
|
17394
|
+
function barStr(value, max, width) {
|
|
17395
|
+
if (max === 0 || width <= 0) return "\u2591".repeat(width);
|
|
17396
|
+
const filled = Math.max(1, Math.round(value / max * width));
|
|
17397
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
17398
|
+
}
|
|
17399
|
+
function colorBar(value, max, width) {
|
|
17400
|
+
const s = barStr(value, max, width);
|
|
17401
|
+
const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
|
|
17402
|
+
return chalk13.cyan(s.slice(0, filled)) + chalk13.dim(s.slice(filled));
|
|
17403
|
+
}
|
|
17404
|
+
function fmtDate(d) {
|
|
17405
|
+
const date = typeof d === "string" ? /* @__PURE__ */ new Date(d + "T12:00:00") : d;
|
|
17406
|
+
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
17407
|
+
}
|
|
17408
|
+
function num2(n) {
|
|
17409
|
+
return n.toLocaleString();
|
|
17410
|
+
}
|
|
17411
|
+
function fmtCost2(usd) {
|
|
17412
|
+
if (usd < 1e-3) return "< $0.001";
|
|
17413
|
+
if (usd < 1) return "$" + usd.toFixed(4);
|
|
17414
|
+
return "$" + usd.toFixed(2);
|
|
16511
17415
|
}
|
|
16512
17416
|
function registerReportCommand(program2) {
|
|
16513
17417
|
program2.command("report").description("Activity and security report \u2014 what Claude did, what was blocked").option("--period <period>", "today | 7d | 30d | month", "7d").option("--no-tests", "exclude test runner calls (npm test, vitest, pytest\u2026) from stats").option("--json", "Emit machine-readable JSON to stdout (suppresses renderer)").action((options) => {
|
|
16514
|
-
const period = ["today", "7d", "30d", "month"].includes(
|
|
17418
|
+
const period = ["today", "7d", "30d", "90d", "month"].includes(
|
|
16515
17419
|
options.period
|
|
16516
17420
|
) ? options.period : "7d";
|
|
16517
|
-
const
|
|
16518
|
-
const
|
|
16519
|
-
|
|
16520
|
-
|
|
17421
|
+
const excludeTests = options.tests === false;
|
|
17422
|
+
const { data, hasAuditFile, responseDlpEntries } = aggregateReportFromAudit(period, {
|
|
17423
|
+
excludeTests
|
|
17424
|
+
});
|
|
17425
|
+
if (data.unackedDlp > 0 && !options.json) {
|
|
16521
17426
|
console.log("");
|
|
16522
17427
|
console.log(
|
|
16523
17428
|
chalk13.bgRed.white.bold(
|
|
16524
|
-
` \u26A0\uFE0F DLP ALERT: ${unackedDlp
|
|
17429
|
+
` \u26A0\uFE0F DLP ALERT: ${data.unackedDlp} secret${data.unackedDlp !== 1 ? "s" : ""} found in Claude response text `
|
|
16525
17430
|
) + " " + chalk13.yellow("\u2192 run: node9 dlp")
|
|
16526
17431
|
);
|
|
16527
17432
|
}
|
|
16528
|
-
if (
|
|
17433
|
+
if (!hasAuditFile && !options.json) {
|
|
16529
17434
|
console.log(
|
|
16530
17435
|
chalk13.yellow("\n No audit data found. Run node9 with Claude Code to generate entries.\n")
|
|
16531
17436
|
);
|
|
16532
17437
|
return;
|
|
16533
17438
|
}
|
|
16534
|
-
|
|
16535
|
-
|
|
16536
|
-
|
|
16537
|
-
|
|
16538
|
-
|
|
16539
|
-
|
|
16540
|
-
outputTokens: costOutputTokens,
|
|
16541
|
-
cacheWriteTokens: costCacheWrite,
|
|
16542
|
-
cacheReadTokens: costCacheRead
|
|
16543
|
-
} = loadClaudeCost(start, end);
|
|
16544
|
-
const {
|
|
16545
|
-
total: codexCostUSD,
|
|
16546
|
-
byDay: codexCostByDay,
|
|
16547
|
-
toolCalls: codexToolCalls
|
|
16548
|
-
} = loadCodexCost(start, end);
|
|
16549
|
-
const costUSD = claudeCostUSD + codexCostUSD;
|
|
16550
|
-
for (const [day, c] of codexCostByDay) {
|
|
16551
|
-
costByDay.set(day, (costByDay.get(day) ?? 0) + c);
|
|
16552
|
-
}
|
|
16553
|
-
const periodMs = end.getTime() - start.getTime();
|
|
16554
|
-
const priorEnd = new Date(start.getTime() - 1);
|
|
16555
|
-
const priorStart = new Date(start.getTime() - periodMs);
|
|
16556
|
-
const priorEntries = allEntries.filter((e) => {
|
|
16557
|
-
if (e.source === "post-hook") return false;
|
|
16558
|
-
const ts = new Date(e.ts);
|
|
16559
|
-
return ts >= priorStart && ts <= priorEnd;
|
|
16560
|
-
});
|
|
16561
|
-
const priorBlocked = priorEntries.filter((e) => !isAllow(e.decision)).length;
|
|
16562
|
-
const priorBlockRate = priorEntries.length > 0 ? priorBlocked / priorEntries.length : null;
|
|
16563
|
-
const excludeTests = options.tests === false;
|
|
16564
|
-
const testTs = excludeTests ? buildTestTimestamps(allEntries) : /* @__PURE__ */ new Set();
|
|
16565
|
-
let filteredTestCount = 0;
|
|
16566
|
-
const entries = allEntries.filter((e) => {
|
|
16567
|
-
if (e.source === "post-hook") return false;
|
|
16568
|
-
if (e.source === "response-dlp") return false;
|
|
16569
|
-
const ts = new Date(e.ts);
|
|
16570
|
-
if (ts < start || ts > end) return false;
|
|
16571
|
-
if (excludeTests && isTestEntry(e, testTs)) {
|
|
16572
|
-
filteredTestCount++;
|
|
16573
|
-
return false;
|
|
16574
|
-
}
|
|
16575
|
-
return true;
|
|
16576
|
-
});
|
|
16577
|
-
if (entries.length === 0 && !options.json) {
|
|
17439
|
+
if (options.json) {
|
|
17440
|
+
const envelope = buildReportJson(data);
|
|
17441
|
+
process.stdout.write(JSON.stringify(envelope, null, 2) + "\n");
|
|
17442
|
+
return;
|
|
17443
|
+
}
|
|
17444
|
+
if (data.total === 0) {
|
|
16578
17445
|
console.log(chalk13.yellow(`
|
|
16579
17446
|
No activity for period "${period}".
|
|
16580
17447
|
`));
|
|
16581
17448
|
return;
|
|
16582
17449
|
}
|
|
16583
|
-
|
|
16584
|
-
|
|
16585
|
-
|
|
16586
|
-
|
|
16587
|
-
|
|
16588
|
-
|
|
16589
|
-
|
|
16590
|
-
|
|
16591
|
-
|
|
16592
|
-
|
|
16593
|
-
|
|
16594
|
-
|
|
16595
|
-
|
|
16596
|
-
|
|
16597
|
-
|
|
16598
|
-
|
|
16599
|
-
|
|
16600
|
-
|
|
16601
|
-
|
|
16602
|
-
|
|
16603
|
-
|
|
16604
|
-
|
|
16605
|
-
|
|
16606
|
-
|
|
16607
|
-
|
|
16608
|
-
|
|
16609
|
-
|
|
16610
|
-
|
|
16611
|
-
|
|
16612
|
-
|
|
16613
|
-
|
|
16614
|
-
|
|
16615
|
-
|
|
16616
|
-
|
|
16617
|
-
|
|
16618
|
-
|
|
16619
|
-
|
|
16620
|
-
|
|
16621
|
-
|
|
16622
|
-
|
|
16623
|
-
|
|
16624
|
-
|
|
16625
|
-
|
|
16626
|
-
|
|
16627
|
-
|
|
16628
|
-
|
|
16629
|
-
|
|
16630
|
-
|
|
16631
|
-
|
|
16632
|
-
|
|
16633
|
-
|
|
16634
|
-
|
|
16635
|
-
|
|
16636
|
-
|
|
16637
|
-
|
|
16638
|
-
|
|
16639
|
-
|
|
16640
|
-
|
|
16641
|
-
|
|
16642
|
-
|
|
16643
|
-
|
|
16644
|
-
|
|
16645
|
-
|
|
16646
|
-
|
|
16647
|
-
|
|
16648
|
-
|
|
16649
|
-
|
|
16650
|
-
|
|
16651
|
-
|
|
16652
|
-
|
|
16653
|
-
|
|
16654
|
-
|
|
16655
|
-
|
|
16656
|
-
|
|
16657
|
-
|
|
16658
|
-
|
|
16659
|
-
|
|
16660
|
-
|
|
16661
|
-
|
|
16662
|
-
|
|
16663
|
-
|
|
16664
|
-
|
|
16665
|
-
|
|
16666
|
-
|
|
16667
|
-
|
|
16668
|
-
|
|
16669
|
-
|
|
16670
|
-
|
|
16671
|
-
|
|
16672
|
-
|
|
16673
|
-
|
|
16674
|
-
|
|
16675
|
-
|
|
16676
|
-
|
|
16677
|
-
|
|
16678
|
-
|
|
16679
|
-
|
|
16680
|
-
|
|
16681
|
-
|
|
16682
|
-
|
|
16683
|
-
|
|
16684
|
-
|
|
16685
|
-
|
|
16686
|
-
|
|
16687
|
-
|
|
16688
|
-
|
|
16689
|
-
|
|
16690
|
-
|
|
16691
|
-
|
|
16692
|
-
|
|
16693
|
-
|
|
16694
|
-
|
|
16695
|
-
}
|
|
17450
|
+
renderTerminalReport(data, responseDlpEntries, excludeTests);
|
|
17451
|
+
});
|
|
17452
|
+
}
|
|
17453
|
+
function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
17454
|
+
const {
|
|
17455
|
+
period,
|
|
17456
|
+
start,
|
|
17457
|
+
end,
|
|
17458
|
+
total,
|
|
17459
|
+
excludedTests,
|
|
17460
|
+
userApproved,
|
|
17461
|
+
userDenied,
|
|
17462
|
+
timedOut,
|
|
17463
|
+
hardBlocked,
|
|
17464
|
+
dlpBlocked,
|
|
17465
|
+
observeDlp,
|
|
17466
|
+
loopHits,
|
|
17467
|
+
testPasses,
|
|
17468
|
+
testFails,
|
|
17469
|
+
priorBlockRate,
|
|
17470
|
+
cost: {
|
|
17471
|
+
claudeUSD,
|
|
17472
|
+
codexUSD,
|
|
17473
|
+
geminiUSD,
|
|
17474
|
+
inputTokens: costInputTokens,
|
|
17475
|
+
outputTokens: costOutputTokens,
|
|
17476
|
+
cacheWriteTokens: costCacheWrite,
|
|
17477
|
+
cacheReadTokens: costCacheRead,
|
|
17478
|
+
byDay: costByDay,
|
|
17479
|
+
byModel: costByModel
|
|
17480
|
+
},
|
|
17481
|
+
toolMap,
|
|
17482
|
+
blockMap,
|
|
17483
|
+
agentMap,
|
|
17484
|
+
mcpMap,
|
|
17485
|
+
dailyMap,
|
|
17486
|
+
hourMap
|
|
17487
|
+
} = data;
|
|
17488
|
+
const costUSD = claudeUSD + codexUSD + geminiUSD;
|
|
17489
|
+
const topTools = [...toolMap.entries()].sort((a, b) => b[1].calls - a[1].calls).slice(0, 8);
|
|
17490
|
+
const topBlocks = [...blockMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
|
|
17491
|
+
const dailyList = [...dailyMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).slice(-14);
|
|
17492
|
+
const maxTool = Math.max(...topTools.map(([, v]) => v.calls), 1);
|
|
17493
|
+
const maxBlock = Math.max(...topBlocks.map(([, v]) => v), 1);
|
|
17494
|
+
const maxDaily = Math.max(...dailyList.map(([, v]) => v.calls), 1);
|
|
17495
|
+
const W = Math.min(process.stdout.columns || 80, 100);
|
|
17496
|
+
const INNER = W - 4;
|
|
17497
|
+
const COL = Math.floor(INNER / 2) - 1;
|
|
17498
|
+
const LABEL = 24;
|
|
17499
|
+
const BAR = Math.max(6, Math.min(14, COL - LABEL - 8));
|
|
17500
|
+
const TOOL_COUNT_W = Math.max(...topTools.map(([, v]) => num2(v.calls).length), 1);
|
|
17501
|
+
const BLOCK_COUNT_W = Math.max(...topBlocks.map(([, v]) => num2(v).length), 1);
|
|
17502
|
+
const line = chalk13.dim("\u2500".repeat(W - 2));
|
|
17503
|
+
const periodLabel = {
|
|
17504
|
+
today: "Today",
|
|
17505
|
+
"7d": "Last 7 Days",
|
|
17506
|
+
"30d": "Last 30 Days",
|
|
17507
|
+
"90d": "Last 90 Days",
|
|
17508
|
+
month: "This Month"
|
|
17509
|
+
};
|
|
17510
|
+
console.log("");
|
|
17511
|
+
console.log(
|
|
17512
|
+
" " + chalk13.bold.cyan("\u{1F6E1} node9 Report") + chalk13.dim(" \xB7 ") + chalk13.white(periodLabel[period]) + chalk13.dim(` ${fmtDate(start)} \u2013 ${fmtDate(end)}`) + chalk13.dim(` ${num2(total)} events`) + (excludeTests ? chalk13.dim(` \u2013tests (\u2013${excludedTests})`) : "")
|
|
17513
|
+
);
|
|
17514
|
+
console.log(" " + line);
|
|
17515
|
+
const totalBlocked = timedOut + hardBlocked + dlpBlocked + loopHits + userDenied;
|
|
17516
|
+
const currentRate = total > 0 ? totalBlocked / total : 0;
|
|
17517
|
+
const trendLabel = (() => {
|
|
17518
|
+
if (priorBlockRate === null) return "";
|
|
17519
|
+
const delta = Math.round((currentRate - priorBlockRate) * 100);
|
|
17520
|
+
if (delta === 0) return "";
|
|
17521
|
+
return " " + (delta > 0 ? chalk13.red(`\u25B2${delta}%`) : chalk13.green(`\u25BC${Math.abs(delta)}%`)) + chalk13.dim(" vs prior");
|
|
17522
|
+
})();
|
|
17523
|
+
const reads = toolMap.get("Read")?.calls ?? 0;
|
|
17524
|
+
const edits = (toolMap.get("Edit")?.calls ?? 0) + (toolMap.get("Write")?.calls ?? 0);
|
|
17525
|
+
const ratioLabel = reads > 0 ? chalk13.dim(`edit/read ${(edits / reads).toFixed(1)}`) : chalk13.dim("edit/read \u2013");
|
|
17526
|
+
const testLabel = testPasses + testFails > 0 ? chalk13.dim("tests ") + chalk13.green(`${testPasses}\u2713`) + (testFails > 0 ? " " + chalk13.red(`${testFails}\u2717`) : "") : chalk13.dim("tests \u2013");
|
|
17527
|
+
console.log("");
|
|
17528
|
+
console.log(" " + chalk13.bold("Protection Summary"));
|
|
17529
|
+
console.log(" " + chalk13.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
17530
|
+
console.log(
|
|
17531
|
+
" " + chalk13.dim("Intercepted") + " " + chalk13.white(num2(total)) + chalk13.dim(" tool calls")
|
|
17532
|
+
);
|
|
17533
|
+
console.log("");
|
|
17534
|
+
const COL1 = 18;
|
|
17535
|
+
const summaryRow = (icon, label, count, note, colorFn = (s) => s) => {
|
|
17536
|
+
const countStr = colorFn(num2(count));
|
|
17537
|
+
const noteStr = note ? chalk13.dim(" " + note) : "";
|
|
17538
|
+
console.log(" " + icon + " " + chalk13.white(label.padEnd(COL1)) + countStr + noteStr);
|
|
17539
|
+
};
|
|
17540
|
+
summaryRow(
|
|
17541
|
+
userApproved > 0 ? chalk13.green("\u2705") : chalk13.dim("\u2705"),
|
|
17542
|
+
"User approved",
|
|
17543
|
+
userApproved,
|
|
17544
|
+
userApproved === 0 ? "no popups this period" : void 0,
|
|
17545
|
+
userApproved > 0 ? (s) => chalk13.green(s) : (s) => chalk13.dim(s)
|
|
17546
|
+
);
|
|
17547
|
+
summaryRow(
|
|
17548
|
+
userDenied > 0 ? chalk13.red("\u{1F6AB}") : chalk13.dim("\u{1F6AB}"),
|
|
17549
|
+
"User denied",
|
|
17550
|
+
userDenied,
|
|
17551
|
+
void 0,
|
|
17552
|
+
userDenied > 0 ? (s) => chalk13.red(s) : (s) => chalk13.dim(s)
|
|
17553
|
+
);
|
|
17554
|
+
summaryRow(
|
|
17555
|
+
timedOut > 0 ? chalk13.yellow("\u23F1") : chalk13.dim("\u23F1"),
|
|
17556
|
+
"Timed out",
|
|
17557
|
+
timedOut,
|
|
17558
|
+
timedOut > 0 ? "no approval response" : void 0,
|
|
17559
|
+
timedOut > 0 ? (s) => chalk13.yellow(s) : (s) => chalk13.dim(s)
|
|
17560
|
+
);
|
|
17561
|
+
summaryRow(
|
|
17562
|
+
hardBlocked > 0 ? chalk13.red("\u{1F6D1}") : chalk13.dim("\u{1F6D1}"),
|
|
17563
|
+
"Auto-blocked",
|
|
17564
|
+
hardBlocked,
|
|
17565
|
+
void 0,
|
|
17566
|
+
hardBlocked > 0 ? (s) => chalk13.red(s) : (s) => chalk13.dim(s)
|
|
17567
|
+
);
|
|
17568
|
+
summaryRow(
|
|
17569
|
+
dlpBlocked > 0 ? chalk13.yellow("\u{1F6A8}") : chalk13.dim("\u{1F6A8}"),
|
|
17570
|
+
"DLP blocked",
|
|
17571
|
+
dlpBlocked,
|
|
17572
|
+
void 0,
|
|
17573
|
+
dlpBlocked > 0 ? (s) => chalk13.yellow(s) : (s) => chalk13.dim(s)
|
|
17574
|
+
);
|
|
17575
|
+
summaryRow(
|
|
17576
|
+
observeDlp > 0 ? chalk13.blue("\u{1F441}") : chalk13.dim("\u{1F441}"),
|
|
17577
|
+
"DLP (observe)",
|
|
17578
|
+
observeDlp,
|
|
17579
|
+
observeDlp > 0 ? "would-block in strict mode" : void 0,
|
|
17580
|
+
observeDlp > 0 ? (s) => chalk13.blue(s) : (s) => chalk13.dim(s)
|
|
17581
|
+
);
|
|
17582
|
+
summaryRow(
|
|
17583
|
+
loopHits > 0 ? chalk13.yellow("\u{1F504}") : chalk13.dim("\u{1F504}"),
|
|
17584
|
+
"Loops detected",
|
|
17585
|
+
loopHits,
|
|
17586
|
+
void 0,
|
|
17587
|
+
loopHits > 0 ? (s) => chalk13.yellow(s) : (s) => chalk13.dim(s)
|
|
17588
|
+
);
|
|
17589
|
+
if (trendLabel || ratioLabel || testPasses + testFails > 0) {
|
|
16696
17590
|
console.log("");
|
|
16697
|
-
console.log(
|
|
16698
|
-
|
|
16699
|
-
|
|
16700
|
-
|
|
16701
|
-
|
|
16702
|
-
|
|
16703
|
-
|
|
16704
|
-
|
|
16705
|
-
|
|
16706
|
-
|
|
16707
|
-
|
|
16708
|
-
|
|
16709
|
-
|
|
16710
|
-
|
|
16711
|
-
|
|
16712
|
-
|
|
17591
|
+
console.log(" " + ratioLabel + " " + testLabel + trendLabel);
|
|
17592
|
+
}
|
|
17593
|
+
console.log("");
|
|
17594
|
+
const toolHeaderRaw = "Top Tools";
|
|
17595
|
+
const blockHeaderRaw = "Top Blocks";
|
|
17596
|
+
console.log(
|
|
17597
|
+
" " + chalk13.bold(toolHeaderRaw) + " ".repeat(COL - toolHeaderRaw.length) + " " + chalk13.bold(blockHeaderRaw)
|
|
17598
|
+
);
|
|
17599
|
+
console.log(" " + chalk13.dim("\u2500".repeat(COL)) + " " + chalk13.dim("\u2500".repeat(COL)));
|
|
17600
|
+
const rows = Math.max(topTools.length, topBlocks.length, 1);
|
|
17601
|
+
for (let i = 0; i < rows; i++) {
|
|
17602
|
+
let leftStyled = " ".repeat(COL);
|
|
17603
|
+
if (i < topTools.length) {
|
|
17604
|
+
const [tool, { calls }] = topTools[i];
|
|
17605
|
+
const label = tool.length > LABEL - 1 ? tool.slice(0, LABEL - 2) + "\u2026" : tool;
|
|
17606
|
+
const countStr = num2(calls).padStart(TOOL_COUNT_W);
|
|
17607
|
+
const b = colorBar(calls, maxTool, BAR);
|
|
17608
|
+
const rawLen = LABEL + BAR + 1 + TOOL_COUNT_W;
|
|
17609
|
+
const pad = Math.max(0, COL - rawLen);
|
|
17610
|
+
leftStyled = chalk13.white(label.padEnd(LABEL)) + b + " " + chalk13.white(countStr) + " ".repeat(pad);
|
|
17611
|
+
}
|
|
17612
|
+
let rightStyled = "";
|
|
17613
|
+
if (i < topBlocks.length) {
|
|
17614
|
+
const [reason, count] = topBlocks[i];
|
|
17615
|
+
const readable = humanBlockReason(reason);
|
|
17616
|
+
const label = readable.length > LABEL - 1 ? readable.slice(0, LABEL - 2) + "\u2026" : readable;
|
|
17617
|
+
const countStr = num2(count).padStart(BLOCK_COUNT_W);
|
|
17618
|
+
const b = colorBar(count, maxBlock, BAR);
|
|
17619
|
+
rightStyled = chalk13.white(label.padEnd(LABEL)) + b + " " + chalk13.red(countStr);
|
|
17620
|
+
}
|
|
17621
|
+
console.log(" " + leftStyled + " " + rightStyled);
|
|
17622
|
+
}
|
|
17623
|
+
if (topBlocks.length === 0) {
|
|
17624
|
+
console.log(" " + " ".repeat(COL) + " " + chalk13.dim("nothing blocked \u2713"));
|
|
17625
|
+
}
|
|
17626
|
+
if (agentMap.size >= 1) {
|
|
16713
17627
|
console.log("");
|
|
16714
|
-
console.log(" " + chalk13.bold("
|
|
17628
|
+
console.log(" " + chalk13.bold("Agents"));
|
|
16715
17629
|
console.log(" " + chalk13.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
16716
|
-
|
|
16717
|
-
|
|
16718
|
-
|
|
16719
|
-
|
|
16720
|
-
|
|
16721
|
-
const summaryRow = (icon, label, count, note, colorFn = (s) => s) => {
|
|
16722
|
-
const countStr = colorFn(num2(count));
|
|
16723
|
-
const noteStr = note ? chalk13.dim(" " + note) : "";
|
|
16724
|
-
console.log(" " + icon + " " + chalk13.white(label.padEnd(COL1)) + countStr + noteStr);
|
|
16725
|
-
};
|
|
16726
|
-
summaryRow(
|
|
16727
|
-
userApproved > 0 ? chalk13.green("\u2705") : chalk13.dim("\u2705"),
|
|
16728
|
-
"User approved",
|
|
16729
|
-
userApproved,
|
|
16730
|
-
userApproved === 0 ? "no popups this period" : void 0,
|
|
16731
|
-
userApproved > 0 ? (s) => chalk13.green(s) : (s) => chalk13.dim(s)
|
|
16732
|
-
);
|
|
16733
|
-
summaryRow(
|
|
16734
|
-
userDenied > 0 ? chalk13.red("\u{1F6AB}") : chalk13.dim("\u{1F6AB}"),
|
|
16735
|
-
"User denied",
|
|
16736
|
-
userDenied,
|
|
16737
|
-
void 0,
|
|
16738
|
-
userDenied > 0 ? (s) => chalk13.red(s) : (s) => chalk13.dim(s)
|
|
16739
|
-
);
|
|
16740
|
-
summaryRow(
|
|
16741
|
-
timedOut > 0 ? chalk13.yellow("\u23F1") : chalk13.dim("\u23F1"),
|
|
16742
|
-
"Timed out",
|
|
16743
|
-
timedOut,
|
|
16744
|
-
timedOut > 0 ? "no approval response" : void 0,
|
|
16745
|
-
timedOut > 0 ? (s) => chalk13.yellow(s) : (s) => chalk13.dim(s)
|
|
16746
|
-
);
|
|
16747
|
-
summaryRow(
|
|
16748
|
-
hardBlocked > 0 ? chalk13.red("\u{1F6D1}") : chalk13.dim("\u{1F6D1}"),
|
|
16749
|
-
"Auto-blocked",
|
|
16750
|
-
hardBlocked,
|
|
16751
|
-
void 0,
|
|
16752
|
-
hardBlocked > 0 ? (s) => chalk13.red(s) : (s) => chalk13.dim(s)
|
|
16753
|
-
);
|
|
16754
|
-
summaryRow(
|
|
16755
|
-
dlpBlocked > 0 ? chalk13.yellow("\u{1F6A8}") : chalk13.dim("\u{1F6A8}"),
|
|
16756
|
-
"DLP blocked",
|
|
16757
|
-
dlpBlocked,
|
|
16758
|
-
void 0,
|
|
16759
|
-
dlpBlocked > 0 ? (s) => chalk13.yellow(s) : (s) => chalk13.dim(s)
|
|
16760
|
-
);
|
|
16761
|
-
summaryRow(
|
|
16762
|
-
observeDlp > 0 ? chalk13.blue("\u{1F441}") : chalk13.dim("\u{1F441}"),
|
|
16763
|
-
"DLP (observe)",
|
|
16764
|
-
observeDlp,
|
|
16765
|
-
observeDlp > 0 ? "would-block in strict mode" : void 0,
|
|
16766
|
-
observeDlp > 0 ? (s) => chalk13.blue(s) : (s) => chalk13.dim(s)
|
|
16767
|
-
);
|
|
16768
|
-
summaryRow(
|
|
16769
|
-
loopHits > 0 ? chalk13.yellow("\u{1F504}") : chalk13.dim("\u{1F504}"),
|
|
16770
|
-
"Loops detected",
|
|
16771
|
-
loopHits,
|
|
16772
|
-
void 0,
|
|
16773
|
-
loopHits > 0 ? (s) => chalk13.yellow(s) : (s) => chalk13.dim(s)
|
|
16774
|
-
);
|
|
16775
|
-
if (trendLabel || ratioLabel || testPasses + testFails > 0) {
|
|
16776
|
-
console.log("");
|
|
16777
|
-
console.log(" " + ratioLabel + " " + testLabel + trendLabel);
|
|
17630
|
+
const maxAgent = Math.max(...agentMap.values(), 1);
|
|
17631
|
+
for (const [agent, count] of [...agentMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
17632
|
+
const label = agent.slice(0, LABEL - 1);
|
|
17633
|
+
const b = colorBar(count, maxAgent, BAR);
|
|
17634
|
+
console.log(" " + chalk13.white(label.padEnd(LABEL)) + b + " " + chalk13.white(num2(count)));
|
|
16778
17635
|
}
|
|
17636
|
+
}
|
|
17637
|
+
if (mcpMap.size > 0) {
|
|
16779
17638
|
console.log("");
|
|
16780
|
-
|
|
16781
|
-
|
|
16782
|
-
|
|
16783
|
-
|
|
16784
|
-
|
|
16785
|
-
|
|
16786
|
-
|
|
16787
|
-
|
|
16788
|
-
|
|
16789
|
-
|
|
16790
|
-
|
|
16791
|
-
|
|
16792
|
-
|
|
16793
|
-
|
|
16794
|
-
|
|
16795
|
-
|
|
16796
|
-
|
|
16797
|
-
|
|
16798
|
-
|
|
16799
|
-
|
|
16800
|
-
|
|
16801
|
-
|
|
16802
|
-
|
|
16803
|
-
|
|
16804
|
-
|
|
16805
|
-
|
|
16806
|
-
|
|
16807
|
-
|
|
16808
|
-
|
|
16809
|
-
|
|
16810
|
-
|
|
16811
|
-
|
|
16812
|
-
|
|
16813
|
-
|
|
16814
|
-
|
|
16815
|
-
console.log(" " + chalk13.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
16816
|
-
const maxAgent = Math.max(...agentMap.values(), 1);
|
|
16817
|
-
for (const [agent, count] of [...agentMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
16818
|
-
const label = agent.slice(0, LABEL - 1);
|
|
16819
|
-
const b = colorBar(count, maxAgent, BAR);
|
|
16820
|
-
console.log(" " + chalk13.white(label.padEnd(LABEL)) + b + " " + chalk13.white(num2(count)));
|
|
16821
|
-
}
|
|
16822
|
-
}
|
|
16823
|
-
if (mcpMap.size > 0) {
|
|
16824
|
-
console.log("");
|
|
16825
|
-
console.log(" " + chalk13.bold("MCP Servers"));
|
|
16826
|
-
console.log(" " + chalk13.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
16827
|
-
const maxMcp = Math.max(...mcpMap.values(), 1);
|
|
16828
|
-
for (const [server, count] of [...mcpMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
16829
|
-
const label = server.slice(0, LABEL - 1).padEnd(LABEL);
|
|
16830
|
-
const b = colorBar(count, maxMcp, BAR);
|
|
16831
|
-
console.log(" " + chalk13.white(label) + b + " " + chalk13.white(num2(count)));
|
|
16832
|
-
}
|
|
16833
|
-
}
|
|
16834
|
-
if (hourMap.size > 0) {
|
|
16835
|
-
const BLOCKS = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
16836
|
-
const maxHour = Math.max(...hourMap.values(), 1);
|
|
16837
|
-
const bar = Array.from({ length: 24 }, (_, h) => {
|
|
16838
|
-
const v = hourMap.get(h) ?? 0;
|
|
16839
|
-
return BLOCKS[Math.round(v / maxHour * 8)];
|
|
16840
|
-
}).join("");
|
|
16841
|
-
console.log("");
|
|
16842
|
-
console.log(" " + chalk13.bold("Hour of Day") + chalk13.dim(" (local, 0h \u2013 23h)"));
|
|
16843
|
-
console.log(" " + chalk13.cyan(bar));
|
|
16844
|
-
console.log(" " + chalk13.dim("0h" + " ".repeat(10) + "12h" + " ".repeat(7) + "23h"));
|
|
16845
|
-
}
|
|
16846
|
-
if (dailyList.length > 1) {
|
|
16847
|
-
console.log("");
|
|
16848
|
-
console.log(" " + chalk13.bold("Daily Activity"));
|
|
16849
|
-
console.log(" " + chalk13.dim("\u2500".repeat(W - 2)));
|
|
16850
|
-
const DAY_BAR = Math.max(8, Math.min(30, W - 36));
|
|
16851
|
-
for (const [dateKey, { calls, blocked: db }] of dailyList) {
|
|
16852
|
-
const label = fmtDate(dateKey).padEnd(10);
|
|
16853
|
-
const b = colorBar(calls, maxDaily, DAY_BAR);
|
|
16854
|
-
const dayCost = costByDay.get(dateKey);
|
|
16855
|
-
const costNote = dayCost ? chalk13.magenta(` ${fmtCost2(dayCost)}`) : "";
|
|
16856
|
-
const blockNote = db > 0 ? chalk13.red(` ${db} blocked`) : "";
|
|
16857
|
-
console.log(
|
|
16858
|
-
" " + chalk13.dim(label) + " " + b + " " + chalk13.white(num2(calls)) + blockNote + costNote
|
|
16859
|
-
);
|
|
16860
|
-
}
|
|
16861
|
-
}
|
|
16862
|
-
const totalTokens = costInputTokens + costOutputTokens + costCacheWrite + costCacheRead;
|
|
16863
|
-
if (totalTokens > 0) {
|
|
16864
|
-
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
16865
|
-
console.log("");
|
|
16866
|
-
console.log(" " + chalk13.bold("Tokens") + " " + chalk13.dim(`${num2(totalTokens)} total`));
|
|
16867
|
-
console.log(" " + chalk13.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
16868
|
-
const TOK_BAR = Math.max(6, Math.min(20, W - 30));
|
|
16869
|
-
const TOK_LABEL = 14;
|
|
16870
|
-
const maxNonCache = Math.max(costInputTokens, costOutputTokens, costCacheWrite, 1);
|
|
16871
|
-
const nonCacheRows = [
|
|
16872
|
-
["Input", costInputTokens, chalk13.cyan(num2(costInputTokens))],
|
|
16873
|
-
["Output", costOutputTokens, chalk13.white(num2(costOutputTokens))],
|
|
16874
|
-
["Cache write", costCacheWrite, chalk13.yellow(num2(costCacheWrite))]
|
|
16875
|
-
];
|
|
16876
|
-
for (const [label, count, colored] of nonCacheRows) {
|
|
16877
|
-
if (count === 0) continue;
|
|
16878
|
-
const b = colorBar(count, maxNonCache, TOK_BAR);
|
|
16879
|
-
console.log(" " + chalk13.white(label.padEnd(TOK_LABEL)) + b + " " + colored);
|
|
16880
|
-
}
|
|
16881
|
-
if (costCacheRead > 0) {
|
|
16882
|
-
const cacheBar = colorBar(costCacheRead, costCacheRead, TOK_BAR);
|
|
16883
|
-
const pct = cacheHitPct > 0 ? chalk13.dim(` ${cacheHitPct}% hit rate`) : "";
|
|
16884
|
-
console.log(
|
|
16885
|
-
" " + chalk13.white("Cache read".padEnd(TOK_LABEL)) + cacheBar + " " + chalk13.green(num2(costCacheRead)) + pct
|
|
16886
|
-
);
|
|
16887
|
-
}
|
|
16888
|
-
}
|
|
16889
|
-
if (costUSD > 0) {
|
|
16890
|
-
const periodDays = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 864e5));
|
|
16891
|
-
const avgPerDay = costUSD / periodDays;
|
|
16892
|
-
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
16893
|
-
const costHeaderRight = [
|
|
16894
|
-
chalk13.yellow(fmtCost2(costUSD)),
|
|
16895
|
-
chalk13.dim(`avg ${fmtCost2(avgPerDay)}/day`),
|
|
16896
|
-
cacheHitPct > 0 ? chalk13.dim(`${cacheHitPct}% cache hit`) : null
|
|
16897
|
-
].filter(Boolean).join(chalk13.dim(" \xB7 "));
|
|
16898
|
-
console.log("");
|
|
16899
|
-
console.log(" " + chalk13.bold("Cost") + " " + costHeaderRight);
|
|
16900
|
-
console.log(" " + chalk13.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
16901
|
-
if (codexCostUSD > 0)
|
|
16902
|
-
costByModel.set(
|
|
16903
|
-
"codex (openai)",
|
|
16904
|
-
(costByModel.get("codex (openai)") ?? 0) + codexCostUSD
|
|
16905
|
-
);
|
|
16906
|
-
const modelList = [...costByModel.entries()].sort((a, b) => b[1] - a[1]);
|
|
16907
|
-
const maxModelCost = Math.max(...modelList.map(([, v]) => v), 1e-9);
|
|
16908
|
-
const MODEL_LABEL = 22;
|
|
16909
|
-
const MODEL_BAR = Math.max(6, Math.min(20, W - MODEL_LABEL - 12));
|
|
16910
|
-
for (const [model, cost] of modelList) {
|
|
16911
|
-
const label = model.length > MODEL_LABEL - 1 ? model.slice(0, MODEL_LABEL - 2) + "\u2026" : model;
|
|
16912
|
-
const b = colorBar(cost, maxModelCost, MODEL_BAR);
|
|
16913
|
-
console.log(
|
|
16914
|
-
" " + chalk13.white(label.padEnd(MODEL_LABEL)) + b + " " + chalk13.yellow(fmtCost2(cost))
|
|
16915
|
-
);
|
|
16916
|
-
}
|
|
17639
|
+
console.log(" " + chalk13.bold("MCP Servers"));
|
|
17640
|
+
console.log(" " + chalk13.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
17641
|
+
const maxMcp = Math.max(...mcpMap.values(), 1);
|
|
17642
|
+
for (const [server, count] of [...mcpMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
17643
|
+
const label = server.slice(0, LABEL - 1).padEnd(LABEL);
|
|
17644
|
+
const b = colorBar(count, maxMcp, BAR);
|
|
17645
|
+
console.log(" " + chalk13.white(label) + b + " " + chalk13.white(num2(count)));
|
|
17646
|
+
}
|
|
17647
|
+
}
|
|
17648
|
+
if (hourMap.size > 0) {
|
|
17649
|
+
const BLOCKS = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
17650
|
+
const maxHour = Math.max(...hourMap.values(), 1);
|
|
17651
|
+
const bar = Array.from({ length: 24 }, (_, h) => {
|
|
17652
|
+
const v = hourMap.get(h) ?? 0;
|
|
17653
|
+
return BLOCKS[Math.round(v / maxHour * 8)];
|
|
17654
|
+
}).join("");
|
|
17655
|
+
console.log("");
|
|
17656
|
+
console.log(" " + chalk13.bold("Hour of Day") + chalk13.dim(" (local, 0h \u2013 23h)"));
|
|
17657
|
+
console.log(" " + chalk13.cyan(bar));
|
|
17658
|
+
console.log(" " + chalk13.dim("0h" + " ".repeat(10) + "12h" + " ".repeat(7) + "23h"));
|
|
17659
|
+
}
|
|
17660
|
+
if (dailyList.length > 1) {
|
|
17661
|
+
console.log("");
|
|
17662
|
+
console.log(" " + chalk13.bold("Daily Activity"));
|
|
17663
|
+
console.log(" " + chalk13.dim("\u2500".repeat(W - 2)));
|
|
17664
|
+
const DAY_BAR = Math.max(8, Math.min(30, W - 36));
|
|
17665
|
+
for (const [dateKey, { calls, blocked: db }] of dailyList) {
|
|
17666
|
+
const label = fmtDate(dateKey).padEnd(10);
|
|
17667
|
+
const b = colorBar(calls, maxDaily, DAY_BAR);
|
|
17668
|
+
const dayCost = costByDay.get(dateKey);
|
|
17669
|
+
const costNote = dayCost ? chalk13.magenta(` ${fmtCost2(dayCost)}`) : "";
|
|
17670
|
+
const blockNote = db > 0 ? chalk13.red(` ${db} blocked`) : "";
|
|
17671
|
+
console.log(
|
|
17672
|
+
" " + chalk13.dim(label) + " " + b + " " + chalk13.white(num2(calls)) + blockNote + costNote
|
|
17673
|
+
);
|
|
16917
17674
|
}
|
|
16918
|
-
|
|
16919
|
-
|
|
16920
|
-
|
|
16921
|
-
|
|
16922
|
-
|
|
16923
|
-
|
|
16924
|
-
|
|
17675
|
+
}
|
|
17676
|
+
const totalTokens = costInputTokens + costOutputTokens + costCacheWrite + costCacheRead;
|
|
17677
|
+
if (totalTokens > 0) {
|
|
17678
|
+
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
17679
|
+
console.log("");
|
|
17680
|
+
console.log(" " + chalk13.bold("Tokens") + " " + chalk13.dim(`${num2(totalTokens)} total`));
|
|
17681
|
+
console.log(" " + chalk13.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
17682
|
+
const TOK_BAR = Math.max(6, Math.min(20, W - 30));
|
|
17683
|
+
const TOK_LABEL = 14;
|
|
17684
|
+
const maxNonCache = Math.max(costInputTokens, costOutputTokens, costCacheWrite, 1);
|
|
17685
|
+
const nonCacheRows = [
|
|
17686
|
+
["Input", costInputTokens, chalk13.cyan(num2(costInputTokens))],
|
|
17687
|
+
["Output", costOutputTokens, chalk13.white(num2(costOutputTokens))],
|
|
17688
|
+
["Cache write", costCacheWrite, chalk13.yellow(num2(costCacheWrite))]
|
|
17689
|
+
];
|
|
17690
|
+
for (const [label, count, colored] of nonCacheRows) {
|
|
17691
|
+
if (count === 0) continue;
|
|
17692
|
+
const b = colorBar(count, maxNonCache, TOK_BAR);
|
|
17693
|
+
console.log(" " + chalk13.white(label.padEnd(TOK_LABEL)) + b + " " + colored);
|
|
17694
|
+
}
|
|
17695
|
+
if (costCacheRead > 0) {
|
|
17696
|
+
const cacheBar = colorBar(costCacheRead, costCacheRead, TOK_BAR);
|
|
17697
|
+
const pct = cacheHitPct > 0 ? chalk13.dim(` ${cacheHitPct}% hit rate`) : "";
|
|
16925
17698
|
console.log(
|
|
16926
|
-
" " + chalk13.
|
|
16927
|
-
`${responseDlpEntries.length} secret${responseDlpEntries.length !== 1 ? "s" : ""} found in Claude response text`
|
|
16928
|
-
)
|
|
17699
|
+
" " + chalk13.white("Cache read".padEnd(TOK_LABEL)) + cacheBar + " " + chalk13.green(num2(costCacheRead)) + pct
|
|
16929
17700
|
);
|
|
16930
|
-
|
|
17701
|
+
}
|
|
17702
|
+
}
|
|
17703
|
+
if (costUSD > 0) {
|
|
17704
|
+
const periodDays = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 864e5));
|
|
17705
|
+
const avgPerDay = costUSD / periodDays;
|
|
17706
|
+
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
17707
|
+
const costHeaderRight = [
|
|
17708
|
+
chalk13.yellow(fmtCost2(costUSD)),
|
|
17709
|
+
chalk13.dim(`avg ${fmtCost2(avgPerDay)}/day`),
|
|
17710
|
+
cacheHitPct > 0 ? chalk13.dim(`${cacheHitPct}% cache hit`) : null
|
|
17711
|
+
].filter(Boolean).join(chalk13.dim(" \xB7 "));
|
|
17712
|
+
console.log("");
|
|
17713
|
+
console.log(" " + chalk13.bold("Cost") + " " + costHeaderRight);
|
|
17714
|
+
console.log(" " + chalk13.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
17715
|
+
if (codexUSD > 0)
|
|
17716
|
+
costByModel.set("codex (openai)", (costByModel.get("codex (openai)") ?? 0) + codexUSD);
|
|
17717
|
+
if (geminiUSD > 0)
|
|
17718
|
+
costByModel.set("gemini (google)", (costByModel.get("gemini (google)") ?? 0) + geminiUSD);
|
|
17719
|
+
const modelList = [...costByModel.entries()].sort((a, b) => b[1] - a[1]);
|
|
17720
|
+
const maxModelCost = Math.max(...modelList.map(([, v]) => v), 1e-9);
|
|
17721
|
+
const MODEL_LABEL = 22;
|
|
17722
|
+
const MODEL_BAR = Math.max(6, Math.min(20, W - MODEL_LABEL - 12));
|
|
17723
|
+
for (const [model, cost] of modelList) {
|
|
17724
|
+
const label = model.length > MODEL_LABEL - 1 ? model.slice(0, MODEL_LABEL - 2) + "\u2026" : model;
|
|
17725
|
+
const b = colorBar(cost, maxModelCost, MODEL_BAR);
|
|
16931
17726
|
console.log(
|
|
16932
|
-
" " + chalk13.
|
|
17727
|
+
" " + chalk13.white(label.padEnd(MODEL_LABEL)) + b + " " + chalk13.yellow(fmtCost2(cost))
|
|
16933
17728
|
);
|
|
16934
|
-
console.log(" " + chalk13.yellow("Rotate affected keys immediately."));
|
|
16935
|
-
for (const e of responseDlpEntries.slice(0, 5)) {
|
|
16936
|
-
const ts = chalk13.dim(fmtDate(e.ts) + " ");
|
|
16937
|
-
const pattern = chalk13.red(e.dlpPattern ?? "DLP");
|
|
16938
|
-
const sample = chalk13.gray(e.dlpSample ?? "");
|
|
16939
|
-
console.log(` ${ts}${pattern} ${sample}`);
|
|
16940
|
-
}
|
|
16941
|
-
if (responseDlpEntries.length > 5) {
|
|
16942
|
-
console.log(chalk13.dim(` \u2026 and ${responseDlpEntries.length - 5} more`));
|
|
16943
|
-
}
|
|
16944
17729
|
}
|
|
17730
|
+
}
|
|
17731
|
+
if (responseDlpEntries.length > 0) {
|
|
16945
17732
|
console.log("");
|
|
16946
17733
|
console.log(
|
|
16947
|
-
" " + chalk13.
|
|
17734
|
+
" " + chalk13.red.bold("\u26A0\uFE0F Response DLP") + chalk13.dim(" \xB7 ") + chalk13.red(
|
|
17735
|
+
`${responseDlpEntries.length} secret${responseDlpEntries.length !== 1 ? "s" : ""} found in Claude response text`
|
|
17736
|
+
)
|
|
16948
17737
|
);
|
|
16949
|
-
console.log("");
|
|
16950
|
-
|
|
17738
|
+
console.log(" " + chalk13.dim("\u2500".repeat(Math.min(60, W - 4))));
|
|
17739
|
+
console.log(
|
|
17740
|
+
" " + chalk13.yellow("These were NOT blocked \u2014 Claude included them in response prose.")
|
|
17741
|
+
);
|
|
17742
|
+
console.log(" " + chalk13.yellow("Rotate affected keys immediately."));
|
|
17743
|
+
for (const e of responseDlpEntries.slice(0, 5)) {
|
|
17744
|
+
const ts = chalk13.dim(fmtDate(e.ts) + " ");
|
|
17745
|
+
const pattern = chalk13.red(e.dlpPattern ?? "DLP");
|
|
17746
|
+
const sample = chalk13.gray(e.dlpSample ?? "");
|
|
17747
|
+
console.log(` ${ts}${pattern} ${sample}`);
|
|
17748
|
+
}
|
|
17749
|
+
if (responseDlpEntries.length > 5) {
|
|
17750
|
+
console.log(chalk13.dim(` \u2026 and ${responseDlpEntries.length - 5} more`));
|
|
17751
|
+
}
|
|
17752
|
+
}
|
|
17753
|
+
console.log("");
|
|
17754
|
+
console.log(
|
|
17755
|
+
" " + chalk13.dim("node9 audit --deny") + chalk13.dim(" \xB7 ") + chalk13.dim("node9 report --period today|7d|30d|month --no-tests")
|
|
17756
|
+
);
|
|
17757
|
+
console.log("");
|
|
16951
17758
|
}
|
|
16952
17759
|
|
|
16953
17760
|
// src/cli/commands/daemon-cmd.ts
|
|
@@ -20757,6 +21564,17 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
20757
21564
|
process.exit(1);
|
|
20758
21565
|
}
|
|
20759
21566
|
});
|
|
21567
|
+
program.command("monitor").description("Live interactive dashboard \u2014 activity feed, approvals, security signals").action(async () => {
|
|
21568
|
+
try {
|
|
21569
|
+
const dashboardPath = path49.join(__dirname, "dashboard.mjs");
|
|
21570
|
+
const dynamicImport = new Function("id", "return import(id)");
|
|
21571
|
+
const mod = await dynamicImport(`file://${dashboardPath}`);
|
|
21572
|
+
await mod.startMonitor();
|
|
21573
|
+
} catch (err2) {
|
|
21574
|
+
console.error(chalk31.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
21575
|
+
process.exit(1);
|
|
21576
|
+
}
|
|
21577
|
+
});
|
|
20760
21578
|
registerWatchCommand(program);
|
|
20761
21579
|
registerMcpGatewayCommand(program);
|
|
20762
21580
|
registerMcpServerCommand(program);
|