@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.js
CHANGED
|
@@ -118,12 +118,14 @@ function appendHookDebug(toolName, args, meta, auditHashArgsEnabled) {
|
|
|
118
118
|
function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashArgsEnabled) {
|
|
119
119
|
const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
|
|
120
120
|
const testRun = isTestCall(toolName, args) || process.env.NODE9_TESTING === "1" ? { testRun: true } : {};
|
|
121
|
+
const ruleNameField = meta?.ruleName ? { ruleName: meta.ruleName } : {};
|
|
121
122
|
appendToLog(LOCAL_AUDIT_LOG, {
|
|
122
123
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
123
124
|
tool: toolName,
|
|
124
125
|
...argsField,
|
|
125
126
|
decision,
|
|
126
127
|
checkedBy,
|
|
128
|
+
...ruleNameField,
|
|
127
129
|
...testRun,
|
|
128
130
|
agent: meta?.agent,
|
|
129
131
|
mcpServer: meta?.mcpServer,
|
|
@@ -707,7 +709,12 @@ function analyzeFsOperationImpl(command) {
|
|
|
707
709
|
for (const p of paths) {
|
|
708
710
|
for (const sp of SENSITIVE_PATH_RULES) {
|
|
709
711
|
if (sp.match(p)) {
|
|
710
|
-
result = {
|
|
712
|
+
result = {
|
|
713
|
+
ruleName: sp.rule,
|
|
714
|
+
verdict: sp.verdict ?? "block",
|
|
715
|
+
reason: sp.reason,
|
|
716
|
+
path: p
|
|
717
|
+
};
|
|
711
718
|
return false;
|
|
712
719
|
}
|
|
713
720
|
}
|
|
@@ -1708,7 +1715,11 @@ function extractCanonicalFindings(call, ctx) {
|
|
|
1708
1715
|
})
|
|
1709
1716
|
);
|
|
1710
1717
|
}
|
|
1711
|
-
|
|
1718
|
+
const ast = analyzeShellCommand(command);
|
|
1719
|
+
const sudoVariant = ast.actions.includes("sudo") || ast.actions.includes("su");
|
|
1720
|
+
const chmodVariant = ast.actions.includes("chmod") && (ast.allTokens.includes("777") || ast.allTokens.includes("0777") || ast.allTokens.includes("+x"));
|
|
1721
|
+
const chownVariant = ast.actions.includes("chown") && ast.allTokens.includes("root");
|
|
1722
|
+
if (sudoVariant || chmodVariant || chownVariant) {
|
|
1712
1723
|
out.push(
|
|
1713
1724
|
makeFinding({
|
|
1714
1725
|
type: "privilege-escalation",
|
|
@@ -1840,7 +1851,7 @@ function* stringValues(obj, depth = 0) {
|
|
|
1840
1851
|
}
|
|
1841
1852
|
for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
|
|
1842
1853
|
}
|
|
1843
|
-
var import_safe_regex2, import_mvdan_sh, import_picomatch, import_safe_regex22, import_safe_regex23, import_crypto2, 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,
|
|
1854
|
+
var import_safe_regex2, import_mvdan_sh, import_picomatch, import_safe_regex22, import_safe_regex23, import_crypto2, 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;
|
|
1844
1855
|
var init_dist = __esm({
|
|
1845
1856
|
"packages/policy-engine/dist/index.mjs"() {
|
|
1846
1857
|
"use strict";
|
|
@@ -2363,15 +2374,50 @@ var init_dist = __esm({
|
|
|
2363
2374
|
match: (p) => /(^|[\\/])\.aws[\\/]/i.test(p)
|
|
2364
2375
|
},
|
|
2365
2376
|
{
|
|
2377
|
+
// Mirrors the JSON shield's `.env` pattern (project-jail.json's
|
|
2378
|
+
// review-read-env-any-tool) so the AST FS-op path catches the
|
|
2379
|
+
// same set the regex shield does — including Next.js / Vite's
|
|
2380
|
+
// `.env.<env>.local` double-suffix overrides which are commonly
|
|
2381
|
+
// gitignored AND commonly contain real secrets.
|
|
2382
|
+
//
|
|
2383
|
+
// Intentional non-matches (dev fixtures): .env.example, .env.sample,
|
|
2384
|
+
// .env.template, .env.test, .envrc. See shields.test.ts:983-995
|
|
2385
|
+
// for the canonical test-asserted contract.
|
|
2366
2386
|
rule: "shield:project-jail:block-read-env",
|
|
2367
2387
|
reason: "Reading .env files is blocked by project-jail shield",
|
|
2368
|
-
match: (p) => /(?:^|[\\/])\.env(?:\.local
|
|
2388
|
+
match: (p) => /(?:^|[\\/])\.env(?:\.(?:local|production|staging|development|production\.local|staging\.local|development\.local))?$/i.test(
|
|
2389
|
+
p
|
|
2390
|
+
)
|
|
2369
2391
|
},
|
|
2370
2392
|
{
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2393
|
+
// verdict: 'review' (not 'block') is a deliberate design choice
|
|
2394
|
+
// documented in commit 29327a8. SSH keys and AWS credentials are
|
|
2395
|
+
// cryptographic material with no legitimate read use-case for
|
|
2396
|
+
// an AI agent → hard `block`. But .netrc / .npmrc / .docker /
|
|
2397
|
+
// .kube / gcloud are CONFIG files that hold tokens AND have
|
|
2398
|
+
// legitimate diagnostic reads ("which registry am I configured
|
|
2399
|
+
// for", "what cluster am I on"). Hard-blocking those creates
|
|
2400
|
+
// friction without much safety win because the review gate
|
|
2401
|
+
// still catches genuine exfiltration attempts.
|
|
2402
|
+
//
|
|
2403
|
+
// The review gate FAILS CLOSED on timeout (daemon.approvalTimeoutMs
|
|
2404
|
+
// returns a deny verdict via the orchestrator's timeout branch),
|
|
2405
|
+
// so a stuck or unattended approval does NOT silently grant
|
|
2406
|
+
// credential access. If the threat model demands strict block,
|
|
2407
|
+
// a future per-shield strict-mode toggle is the right fix —
|
|
2408
|
+
// not a regex-level upgrade here.
|
|
2409
|
+
rule: "shield:project-jail:review-read-credentials",
|
|
2410
|
+
reason: "Reading credential files requires approval (project-jail shield)",
|
|
2411
|
+
verdict: "review",
|
|
2412
|
+
match: (p) => (
|
|
2413
|
+
// .kube/config holds Kubernetes cluster credentials and was
|
|
2414
|
+
// flagged as missing by the node9-pr-agent review (the comment
|
|
2415
|
+
// above mentioned .kube but the regex didn't include it — a
|
|
2416
|
+
// textbook code-comment vs code drift). The JSON shield's
|
|
2417
|
+
// review-read-credentials-any-tool already had it. Now aligned.
|
|
2418
|
+
/(?:credentials\.json|\.netrc|\.npmrc|\.docker[\\/]config\.json|gcloud[\\/]credentials|\.kube[\\/]config)$/i.test(
|
|
2419
|
+
p
|
|
2420
|
+
)
|
|
2375
2421
|
)
|
|
2376
2422
|
}
|
|
2377
2423
|
];
|
|
@@ -2387,7 +2433,7 @@ var init_dist = __esm({
|
|
|
2387
2433
|
"shield:project-jail:block-read-ssh",
|
|
2388
2434
|
"shield:project-jail:block-read-aws",
|
|
2389
2435
|
"shield:project-jail:block-read-env",
|
|
2390
|
-
"shield:project-jail:
|
|
2436
|
+
"shield:project-jail:review-read-credentials"
|
|
2391
2437
|
]);
|
|
2392
2438
|
FS_OP_CACHE_MAX = 5e3;
|
|
2393
2439
|
fsOpCache = /* @__PURE__ */ new Map();
|
|
@@ -3075,7 +3121,7 @@ var init_dist = __esm({
|
|
|
3075
3121
|
{
|
|
3076
3122
|
field: "command",
|
|
3077
3123
|
op: "matches",
|
|
3078
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s
|
|
3124
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.ssh[\\/\\\\]",
|
|
3079
3125
|
flags: "i"
|
|
3080
3126
|
}
|
|
3081
3127
|
],
|
|
@@ -3089,7 +3135,7 @@ var init_dist = __esm({
|
|
|
3089
3135
|
{
|
|
3090
3136
|
field: "command",
|
|
3091
3137
|
op: "matches",
|
|
3092
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s
|
|
3138
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.aws[\\/\\\\]",
|
|
3093
3139
|
flags: "i"
|
|
3094
3140
|
}
|
|
3095
3141
|
],
|
|
@@ -3111,7 +3157,7 @@ var init_dist = __esm({
|
|
|
3111
3157
|
reason: "Reading .env files is blocked by project-jail shield"
|
|
3112
3158
|
},
|
|
3113
3159
|
{
|
|
3114
|
-
name: "shield:project-jail:
|
|
3160
|
+
name: "shield:project-jail:review-read-credentials",
|
|
3115
3161
|
tool: "bash",
|
|
3116
3162
|
conditions: [
|
|
3117
3163
|
{
|
|
@@ -3121,8 +3167,64 @@ var init_dist = __esm({
|
|
|
3121
3167
|
flags: "i"
|
|
3122
3168
|
}
|
|
3123
3169
|
],
|
|
3170
|
+
verdict: "review",
|
|
3171
|
+
reason: "Reading credential files requires approval (project-jail shield)"
|
|
3172
|
+
},
|
|
3173
|
+
{
|
|
3174
|
+
name: "shield:project-jail:block-read-ssh-any-tool",
|
|
3175
|
+
tool: "*",
|
|
3176
|
+
conditions: [
|
|
3177
|
+
{
|
|
3178
|
+
field: "file_path",
|
|
3179
|
+
op: "matches",
|
|
3180
|
+
value: "(^|[\\/\\\\])\\.ssh[\\/\\\\]",
|
|
3181
|
+
flags: "i"
|
|
3182
|
+
}
|
|
3183
|
+
],
|
|
3184
|
+
verdict: "block",
|
|
3185
|
+
reason: "Reading SSH private keys is blocked by project-jail shield"
|
|
3186
|
+
},
|
|
3187
|
+
{
|
|
3188
|
+
name: "shield:project-jail:block-read-aws-any-tool",
|
|
3189
|
+
tool: "*",
|
|
3190
|
+
conditions: [
|
|
3191
|
+
{
|
|
3192
|
+
field: "file_path",
|
|
3193
|
+
op: "matches",
|
|
3194
|
+
value: "(^|[\\/\\\\])\\.aws[\\/\\\\]",
|
|
3195
|
+
flags: "i"
|
|
3196
|
+
}
|
|
3197
|
+
],
|
|
3124
3198
|
verdict: "block",
|
|
3125
|
-
reason: "Reading
|
|
3199
|
+
reason: "Reading AWS credentials is blocked by project-jail shield"
|
|
3200
|
+
},
|
|
3201
|
+
{
|
|
3202
|
+
name: "shield:project-jail:review-read-env-any-tool",
|
|
3203
|
+
tool: "*",
|
|
3204
|
+
conditions: [
|
|
3205
|
+
{
|
|
3206
|
+
field: "file_path",
|
|
3207
|
+
op: "matches",
|
|
3208
|
+
value: "(^|[\\/\\\\])\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?$",
|
|
3209
|
+
flags: "i"
|
|
3210
|
+
}
|
|
3211
|
+
],
|
|
3212
|
+
verdict: "review",
|
|
3213
|
+
reason: "Reading .env files requires approval (project-jail shield)"
|
|
3214
|
+
},
|
|
3215
|
+
{
|
|
3216
|
+
name: "shield:project-jail:review-read-credentials-any-tool",
|
|
3217
|
+
tool: "*",
|
|
3218
|
+
conditions: [
|
|
3219
|
+
{
|
|
3220
|
+
field: "file_path",
|
|
3221
|
+
op: "matches",
|
|
3222
|
+
value: ".*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials|\\.kube[\\/\\\\]config)",
|
|
3223
|
+
flags: "i"
|
|
3224
|
+
}
|
|
3225
|
+
],
|
|
3226
|
+
verdict: "review",
|
|
3227
|
+
reason: "Reading credential files requires approval (project-jail shield)"
|
|
3126
3228
|
}
|
|
3127
3229
|
],
|
|
3128
3230
|
dangerousWords: []
|
|
@@ -3246,7 +3348,6 @@ var init_dist = __esm({
|
|
|
3246
3348
|
LOOP_THRESHOLD_FOR_WASTE = 3;
|
|
3247
3349
|
COST_PER_LOOP_ITER_USD = 6e-3;
|
|
3248
3350
|
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;
|
|
3249
|
-
PRIVILEGE_ESCALATION_RE = /\b(sudo|su)\b\s+[a-z]|\bchmod\s+(0?777|\+x)\b|\bchown\s+root\b/i;
|
|
3250
3351
|
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;
|
|
3251
3352
|
FILE_TOOLS = /* @__PURE__ */ new Set([
|
|
3252
3353
|
"read",
|
|
@@ -3266,7 +3367,7 @@ var init_dist = __esm({
|
|
|
3266
3367
|
PII_PHONE_RE = /\b(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]\d{3}[-.\s]\d{4}\b/;
|
|
3267
3368
|
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/;
|
|
3268
3369
|
LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
3269
|
-
CANONICAL_EXTRACTOR_VERSION = "canonical-
|
|
3370
|
+
CANONICAL_EXTRACTOR_VERSION = "canonical-v4";
|
|
3270
3371
|
DEDUPE_PREVIEW_LEN = 120;
|
|
3271
3372
|
TERMINAL_ESCAPE_RE = // eslint-disable-next-line no-control-regex
|
|
3272
3373
|
/\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
|
|
@@ -4807,7 +4908,7 @@ async function waitForDaemonDecision(id, signal) {
|
|
|
4807
4908
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
4808
4909
|
}
|
|
4809
4910
|
}
|
|
4810
|
-
async function notifyDaemonViewer(toolName, args, meta, riskMetadata) {
|
|
4911
|
+
async function notifyDaemonViewer(toolName, args, meta, riskMetadata, activityId, socketActivitySent) {
|
|
4811
4912
|
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
4812
4913
|
const res = await fetch(`${base}/check`, {
|
|
4813
4914
|
method: "POST",
|
|
@@ -4818,7 +4919,12 @@ async function notifyDaemonViewer(toolName, args, meta, riskMetadata) {
|
|
|
4818
4919
|
slackDelegated: true,
|
|
4819
4920
|
agent: meta?.agent,
|
|
4820
4921
|
mcpServer: meta?.mcpServer,
|
|
4821
|
-
...riskMetadata && { riskMetadata }
|
|
4922
|
+
...riskMetadata && { riskMetadata },
|
|
4923
|
+
// fromCLI=true tells the daemon the CLI already sent the activity
|
|
4924
|
+
// event via socket. Same contract as registerDaemonEntry — without
|
|
4925
|
+
// it the daemon double-emits 'activity' for cloud-enforced flows.
|
|
4926
|
+
fromCLI: socketActivitySent !== false,
|
|
4927
|
+
activityId
|
|
4822
4928
|
}),
|
|
4823
4929
|
signal: AbortSignal.timeout(3e3)
|
|
4824
4930
|
});
|
|
@@ -5785,7 +5891,10 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
5785
5891
|
args,
|
|
5786
5892
|
"deny",
|
|
5787
5893
|
"smart-rule-block-override",
|
|
5788
|
-
|
|
5894
|
+
// Same rationale as the smart-rule-block path above —
|
|
5895
|
+
// pass the specific rule name so [2] SHIELDS can
|
|
5896
|
+
// attribute this override-block to its owning shield.
|
|
5897
|
+
{ ...meta, ruleName: policyResult.ruleName },
|
|
5789
5898
|
hashAuditArgs
|
|
5790
5899
|
);
|
|
5791
5900
|
if (approvers.cloud && creds?.apiKey)
|
|
@@ -5815,7 +5924,20 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
5815
5924
|
}
|
|
5816
5925
|
} else {
|
|
5817
5926
|
if (!isManual)
|
|
5818
|
-
appendLocalAudit(
|
|
5927
|
+
appendLocalAudit(
|
|
5928
|
+
toolName,
|
|
5929
|
+
args,
|
|
5930
|
+
"deny",
|
|
5931
|
+
"smart-rule-block",
|
|
5932
|
+
// Include policyResult.ruleName so the [2] Report SHIELDS
|
|
5933
|
+
// panel can attribute this block to its specific shield
|
|
5934
|
+
// (e.g. `shield:project-jail:block-read-ssh`) via the
|
|
5935
|
+
// rule→shield map. checkedBy stays as the generic
|
|
5936
|
+
// `smart-rule-block` for backward compat with existing
|
|
5937
|
+
// log readers.
|
|
5938
|
+
{ ...meta, ruleName: policyResult.ruleName },
|
|
5939
|
+
hashAuditArgs
|
|
5940
|
+
);
|
|
5819
5941
|
if (approvers.cloud && creds?.apiKey)
|
|
5820
5942
|
auditLocalAllow(toolName, args, "smart-rule-block", creds, meta, void 0, false, {
|
|
5821
5943
|
ruleName: policyResult.ruleName,
|
|
@@ -5963,7 +6085,14 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
5963
6085
|
let daemonAllowCount = 1;
|
|
5964
6086
|
if (approvers.terminal && isDaemonRunning() && !options?.calledFromDaemon) {
|
|
5965
6087
|
if (cloudEnforced && cloudRequestId) {
|
|
5966
|
-
const viewer = await notifyDaemonViewer(
|
|
6088
|
+
const viewer = await notifyDaemonViewer(
|
|
6089
|
+
toolName,
|
|
6090
|
+
args,
|
|
6091
|
+
meta,
|
|
6092
|
+
riskMetadata,
|
|
6093
|
+
options?.activityId,
|
|
6094
|
+
options?.socketActivitySent
|
|
6095
|
+
).catch(() => null);
|
|
5967
6096
|
viewerId = viewer?.id ?? null;
|
|
5968
6097
|
daemonEntryId = viewerId;
|
|
5969
6098
|
if (viewer) daemonAllowCount = viewer.allowCount;
|
|
@@ -7630,11 +7759,62 @@ function computeLoopWaste(loops, totalToolCalls) {
|
|
|
7630
7759
|
const wastePct = totalToolCalls > 0 ? Math.round(wastedCalls / totalToolCalls * 100) : 0;
|
|
7631
7760
|
return { wastedCalls, wastePct };
|
|
7632
7761
|
}
|
|
7633
|
-
|
|
7762
|
+
function rollupByShield(sections, topRulesPerShield = 3) {
|
|
7763
|
+
const out = [];
|
|
7764
|
+
for (const section of sections) {
|
|
7765
|
+
if (section.sourceType !== "shield") continue;
|
|
7766
|
+
if (!section.shieldKey) continue;
|
|
7767
|
+
const totalCatches = section.blockedCount + section.reviewCount;
|
|
7768
|
+
const topRuleLabels = [...section.rules].sort((a, b) => b.findings.length - a.findings.length).slice(0, topRulesPerShield).map((r) => r.findings.length > 1 ? `${r.name} \xD7${r.findings.length}` : r.name);
|
|
7769
|
+
out.push({
|
|
7770
|
+
shieldName: section.shieldKey,
|
|
7771
|
+
totalCatches,
|
|
7772
|
+
blockCatches: section.blockedCount,
|
|
7773
|
+
reviewCatches: section.reviewCount,
|
|
7774
|
+
topRuleLabels
|
|
7775
|
+
});
|
|
7776
|
+
}
|
|
7777
|
+
return out.sort((a, b) => b.totalCatches - a.totalCatches);
|
|
7778
|
+
}
|
|
7779
|
+
function boxPanel(title, bodyLines, width = PANEL_WIDTH) {
|
|
7780
|
+
const inner = width - 4;
|
|
7781
|
+
const out = [];
|
|
7782
|
+
const titlePad = ` ${title} `;
|
|
7783
|
+
const titleSegment = titlePad.length <= inner ? titlePad : titlePad.slice(0, inner);
|
|
7784
|
+
const dashFill = "\u2500".repeat(Math.max(0, inner - titleSegment.length));
|
|
7785
|
+
out.push(import_chalk3.default.dim("\u256D\u2500") + import_chalk3.default.bold(titleSegment) + import_chalk3.default.dim(`${dashFill}\u2500\u256E`));
|
|
7786
|
+
for (const line of bodyLines) {
|
|
7787
|
+
const padding = " ".repeat(Math.max(0, inner - line.width));
|
|
7788
|
+
out.push(import_chalk3.default.dim("\u2502 ") + line.rendered + padding + import_chalk3.default.dim(" \u2502"));
|
|
7789
|
+
}
|
|
7790
|
+
out.push(import_chalk3.default.dim("\u2570" + "\u2500".repeat(inner + 2) + "\u256F"));
|
|
7791
|
+
return out;
|
|
7792
|
+
}
|
|
7793
|
+
function relativeDate(timestamp, now = /* @__PURE__ */ new Date()) {
|
|
7794
|
+
const t = new Date(timestamp).getTime();
|
|
7795
|
+
if (Number.isNaN(t)) return "?";
|
|
7796
|
+
const days = Math.floor((now.getTime() - t) / 864e5);
|
|
7797
|
+
if (days < 1) return "today";
|
|
7798
|
+
if (days > 90) return "90d+";
|
|
7799
|
+
return `${days}d`;
|
|
7800
|
+
}
|
|
7801
|
+
var import_chalk3, PANEL_WIDTH;
|
|
7634
7802
|
var init_scan_derive = __esm({
|
|
7635
7803
|
"src/cli/render/scan-derive.ts"() {
|
|
7636
7804
|
"use strict";
|
|
7637
7805
|
import_chalk3 = __toESM(require("chalk"));
|
|
7806
|
+
PANEL_WIDTH = 76;
|
|
7807
|
+
}
|
|
7808
|
+
});
|
|
7809
|
+
|
|
7810
|
+
// src/protection.ts
|
|
7811
|
+
var PROTECTIVE_SHIELD_DISCOUNTS;
|
|
7812
|
+
var init_protection = __esm({
|
|
7813
|
+
"src/protection.ts"() {
|
|
7814
|
+
"use strict";
|
|
7815
|
+
PROTECTIVE_SHIELD_DISCOUNTS = {
|
|
7816
|
+
"project-jail": 0.7
|
|
7817
|
+
};
|
|
7638
7818
|
}
|
|
7639
7819
|
});
|
|
7640
7820
|
|
|
@@ -7821,6 +8001,7 @@ async function ensurePricingLoaded() {
|
|
|
7821
8001
|
if (fromDisk && Object.keys(fromDisk).length > 0) {
|
|
7822
8002
|
memCache = fromDisk;
|
|
7823
8003
|
memCacheAt = Date.now();
|
|
8004
|
+
lookupCache.clear();
|
|
7824
8005
|
return;
|
|
7825
8006
|
}
|
|
7826
8007
|
const fetched = await fetchLiteLLMPricing();
|
|
@@ -7828,30 +8009,42 @@ async function ensurePricingLoaded() {
|
|
|
7828
8009
|
memCache = fetched;
|
|
7829
8010
|
memCacheAt = Date.now();
|
|
7830
8011
|
writeCache(fetched);
|
|
8012
|
+
lookupCache.clear();
|
|
7831
8013
|
return;
|
|
7832
8014
|
}
|
|
7833
8015
|
memCache = { ...BUNDLED_PRICING };
|
|
7834
8016
|
memCacheAt = Date.now();
|
|
8017
|
+
lookupCache.clear();
|
|
7835
8018
|
}
|
|
7836
8019
|
function pricingFor(model) {
|
|
7837
8020
|
const norm = normalizeModel(model);
|
|
8021
|
+
const cached = lookupCache.get(norm);
|
|
8022
|
+
if (cached !== void 0) return cached;
|
|
7838
8023
|
const sources = [];
|
|
7839
8024
|
if (memCache) sources.push(memCache);
|
|
7840
8025
|
sources.push(BUNDLED_PRICING);
|
|
8026
|
+
let resolved = null;
|
|
7841
8027
|
for (const source of sources) {
|
|
7842
8028
|
const exact = source[norm];
|
|
7843
|
-
if (exact)
|
|
8029
|
+
if (exact) {
|
|
8030
|
+
resolved = exact;
|
|
8031
|
+
break;
|
|
8032
|
+
}
|
|
7844
8033
|
let best = null;
|
|
7845
8034
|
for (const key of Object.keys(source)) {
|
|
7846
8035
|
if (norm.startsWith(key.toLowerCase()) && (best === null || key.length > best.length)) {
|
|
7847
8036
|
best = key;
|
|
7848
8037
|
}
|
|
7849
8038
|
}
|
|
7850
|
-
if (best)
|
|
8039
|
+
if (best) {
|
|
8040
|
+
resolved = source[best];
|
|
8041
|
+
break;
|
|
8042
|
+
}
|
|
7851
8043
|
}
|
|
7852
|
-
|
|
8044
|
+
lookupCache.set(norm, resolved);
|
|
8045
|
+
return resolved;
|
|
7853
8046
|
}
|
|
7854
|
-
var import_fs15, import_path17, import_os14, LITELLM_URL, BUNDLED_PRICING, CACHE_FILE, TTL_MS, memCache, memCacheAt;
|
|
8047
|
+
var import_fs15, import_path17, import_os14, LITELLM_URL, BUNDLED_PRICING, CACHE_FILE, TTL_MS, memCache, memCacheAt, lookupCache;
|
|
7855
8048
|
var init_litellm = __esm({
|
|
7856
8049
|
"src/pricing/litellm.ts"() {
|
|
7857
8050
|
"use strict";
|
|
@@ -7888,6 +8081,7 @@ var init_litellm = __esm({
|
|
|
7888
8081
|
TTL_MS = 24 * 60 * 60 * 1e3;
|
|
7889
8082
|
memCache = null;
|
|
7890
8083
|
memCacheAt = 0;
|
|
8084
|
+
lookupCache = /* @__PURE__ */ new Map();
|
|
7891
8085
|
}
|
|
7892
8086
|
});
|
|
7893
8087
|
|
|
@@ -7954,7 +8148,7 @@ function parseJSONLFile(filePath, fallbackWorkingDir) {
|
|
|
7954
8148
|
}
|
|
7955
8149
|
return daily;
|
|
7956
8150
|
}
|
|
7957
|
-
function collectEntries() {
|
|
8151
|
+
function collectEntries(sinceMs) {
|
|
7958
8152
|
const projectsDir = import_path18.default.join(import_os15.default.homedir(), ".claude", "projects");
|
|
7959
8153
|
if (!import_fs16.default.existsSync(projectsDir)) return [];
|
|
7960
8154
|
const combined = /* @__PURE__ */ new Map();
|
|
@@ -7979,7 +8173,15 @@ function collectEntries() {
|
|
|
7979
8173
|
}
|
|
7980
8174
|
const fallbackWorkingDir = decodeProjectDirName(dir);
|
|
7981
8175
|
for (const file of files) {
|
|
7982
|
-
const
|
|
8176
|
+
const filePath = import_path18.default.join(dirPath, file);
|
|
8177
|
+
if (sinceMs !== void 0) {
|
|
8178
|
+
try {
|
|
8179
|
+
if (import_fs16.default.statSync(filePath).mtimeMs < sinceMs) continue;
|
|
8180
|
+
} catch {
|
|
8181
|
+
continue;
|
|
8182
|
+
}
|
|
8183
|
+
}
|
|
8184
|
+
const entries = parseJSONLFile(filePath, fallbackWorkingDir);
|
|
7983
8185
|
for (const [key, e] of entries) {
|
|
7984
8186
|
const prev = combined.get(key);
|
|
7985
8187
|
if (prev) {
|
|
@@ -8056,6 +8258,7 @@ __export(scan_watermark_exports, {
|
|
|
8056
8258
|
markUploadComplete: () => markUploadComplete,
|
|
8057
8259
|
saveWatermark: () => saveWatermark,
|
|
8058
8260
|
scanDelta: () => scanDelta,
|
|
8261
|
+
tickForensicBroadcast: () => tickForensicBroadcast,
|
|
8059
8262
|
tickScanWatcher: () => tickScanWatcher
|
|
8060
8263
|
});
|
|
8061
8264
|
function freshWatermark() {
|
|
@@ -8259,6 +8462,25 @@ function extractFindingsFromLine(line, sessionId, lineIndex) {
|
|
|
8259
8462
|
}
|
|
8260
8463
|
return findings;
|
|
8261
8464
|
}
|
|
8465
|
+
async function tickForensicBroadcast(offsets) {
|
|
8466
|
+
const out = [];
|
|
8467
|
+
const files = listJsonlFiles();
|
|
8468
|
+
for (const file of files) {
|
|
8469
|
+
const size = fileSize(file);
|
|
8470
|
+
const offset = offsets.get(file);
|
|
8471
|
+
if (offset === void 0) {
|
|
8472
|
+
offsets.set(file, size);
|
|
8473
|
+
continue;
|
|
8474
|
+
}
|
|
8475
|
+
if (size <= offset) continue;
|
|
8476
|
+
const sessionId = import_path19.default.basename(file, ".jsonl");
|
|
8477
|
+
const newOffset = await scanDelta(file, offset, (obj, lineIndex) => {
|
|
8478
|
+
out.push(...extractFindingsFromLine(obj, sessionId, lineIndex));
|
|
8479
|
+
});
|
|
8480
|
+
offsets.set(file, newOffset);
|
|
8481
|
+
}
|
|
8482
|
+
return out;
|
|
8483
|
+
}
|
|
8262
8484
|
function markUploadComplete() {
|
|
8263
8485
|
const state = loadWatermark();
|
|
8264
8486
|
if (state.status === "schema-future") return;
|
|
@@ -8794,7 +9016,16 @@ function buildRecurringPatternSet(findings) {
|
|
|
8794
9016
|
}
|
|
8795
9017
|
return recurring;
|
|
8796
9018
|
}
|
|
8797
|
-
function
|
|
9019
|
+
function emptyScanDedup() {
|
|
9020
|
+
return { findingsKeys: /* @__PURE__ */ new Set(), dlpKeys: /* @__PURE__ */ new Set() };
|
|
9021
|
+
}
|
|
9022
|
+
function findingKey(ruleName, inputPreview, projLabel) {
|
|
9023
|
+
return `${ruleName ?? "<unnamed>"}|${inputPreview}|${projLabel}`;
|
|
9024
|
+
}
|
|
9025
|
+
function dlpKey(patternName, redactedSample, projLabel) {
|
|
9026
|
+
return `${patternName}|${redactedSample}|${projLabel}`;
|
|
9027
|
+
}
|
|
9028
|
+
function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sessionId, agent, result, dedup) {
|
|
8798
9029
|
const fsVerdict = analyzeFsOperation(command);
|
|
8799
9030
|
if (!fsVerdict) return false;
|
|
8800
9031
|
const synthRule = {
|
|
@@ -8817,10 +9048,9 @@ function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sess
|
|
|
8817
9048
|
rule: synthRule
|
|
8818
9049
|
};
|
|
8819
9050
|
const inputPreview = preview(input, 120);
|
|
8820
|
-
const
|
|
8821
|
-
|
|
8822
|
-
|
|
8823
|
-
if (!isDupe) {
|
|
9051
|
+
const k = findingKey(synthRule.name, inputPreview, projLabel);
|
|
9052
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9053
|
+
dedup.findingsKeys.add(k);
|
|
8824
9054
|
result.findings.push({
|
|
8825
9055
|
source: synthSource,
|
|
8826
9056
|
toolName,
|
|
@@ -8905,22 +9135,15 @@ function buildRuleSources() {
|
|
|
8905
9135
|
sources.push({ shieldName, shieldLabel: shieldName, sourceType: "shield", rule });
|
|
8906
9136
|
}
|
|
8907
9137
|
}
|
|
8908
|
-
|
|
8909
|
-
|
|
8910
|
-
|
|
8911
|
-
|
|
8912
|
-
|
|
8913
|
-
|
|
8914
|
-
|
|
8915
|
-
|
|
8916
|
-
|
|
8917
|
-
shieldName: isCloud ? "cloud" : isDefault ? "default" : "custom",
|
|
8918
|
-
shieldLabel: isCloud ? "Cloud Policy" : isDefault ? "Default Rules" : "Your Rules",
|
|
8919
|
-
sourceType,
|
|
8920
|
-
rule
|
|
8921
|
-
});
|
|
8922
|
-
}
|
|
8923
|
-
} catch {
|
|
9138
|
+
for (const rule of DEFAULT_CONFIG.policy.smartRules) {
|
|
9139
|
+
if (!rule.name) continue;
|
|
9140
|
+
if (rule.name.startsWith("shield:")) continue;
|
|
9141
|
+
sources.push({
|
|
9142
|
+
shieldName: "default",
|
|
9143
|
+
shieldLabel: "Default Rules",
|
|
9144
|
+
sourceType: "default",
|
|
9145
|
+
rule
|
|
9146
|
+
});
|
|
8924
9147
|
}
|
|
8925
9148
|
return sources;
|
|
8926
9149
|
}
|
|
@@ -9006,178 +9229,53 @@ function renderProgressBar(done, total, lines) {
|
|
|
9006
9229
|
`\r ${import_chalk5.default.cyan("Scanning")} [${import_chalk5.default.cyan(bar)}] ${import_chalk5.default.dim(fileLabel)}${lineLabel} `
|
|
9007
9230
|
);
|
|
9008
9231
|
}
|
|
9009
|
-
function
|
|
9010
|
-
|
|
9011
|
-
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
|
|
9015
|
-
bashCalls: 0,
|
|
9016
|
-
findings: [],
|
|
9017
|
-
dlpFindings: [],
|
|
9018
|
-
loopFindings: [],
|
|
9019
|
-
totalCostUSD: 0,
|
|
9020
|
-
firstDate: null,
|
|
9021
|
-
lastDate: null,
|
|
9022
|
-
sessionsWithEarlySecrets: 0
|
|
9023
|
-
};
|
|
9024
|
-
if (!import_fs19.default.existsSync(projectsDir)) return result;
|
|
9025
|
-
let projDirs;
|
|
9232
|
+
function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, result, dedup, onProgress, onLine) {
|
|
9233
|
+
result.filesScanned++;
|
|
9234
|
+
result.sessions++;
|
|
9235
|
+
onProgress?.(result.filesScanned);
|
|
9236
|
+
const sessionId = file.replace(/\.jsonl$/, "");
|
|
9237
|
+
let raw;
|
|
9026
9238
|
try {
|
|
9027
|
-
|
|
9239
|
+
raw = import_fs19.default.readFileSync(import_path21.default.join(projPath, file), "utf-8");
|
|
9028
9240
|
} catch {
|
|
9029
|
-
return
|
|
9241
|
+
return;
|
|
9030
9242
|
}
|
|
9031
|
-
const
|
|
9032
|
-
|
|
9033
|
-
|
|
9243
|
+
const sessionCalls = [];
|
|
9244
|
+
const toolUseFilePaths = /* @__PURE__ */ new Map();
|
|
9245
|
+
let firstDlpTs = null;
|
|
9246
|
+
let firstEditTs = null;
|
|
9247
|
+
for (const line of raw.split("\n")) {
|
|
9248
|
+
if (!line.trim()) continue;
|
|
9249
|
+
onLine?.();
|
|
9250
|
+
let entry;
|
|
9034
9251
|
try {
|
|
9035
|
-
|
|
9252
|
+
entry = JSON.parse(line);
|
|
9036
9253
|
} catch {
|
|
9037
9254
|
continue;
|
|
9038
9255
|
}
|
|
9039
|
-
|
|
9040
|
-
|
|
9041
|
-
|
|
9042
|
-
let files;
|
|
9043
|
-
try {
|
|
9044
|
-
files = import_fs19.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
9045
|
-
} catch {
|
|
9046
|
-
continue;
|
|
9256
|
+
if (entry.type !== "assistant" && entry.type !== "user") continue;
|
|
9257
|
+
if (startDate && entry.timestamp) {
|
|
9258
|
+
if (new Date(entry.timestamp) < startDate) continue;
|
|
9047
9259
|
}
|
|
9048
|
-
|
|
9049
|
-
result.
|
|
9050
|
-
|
|
9051
|
-
|
|
9052
|
-
|
|
9053
|
-
|
|
9054
|
-
|
|
9055
|
-
|
|
9056
|
-
|
|
9057
|
-
|
|
9058
|
-
|
|
9059
|
-
const sessionCalls = [];
|
|
9060
|
-
const toolUseFilePaths = /* @__PURE__ */ new Map();
|
|
9061
|
-
let firstDlpTs = null;
|
|
9062
|
-
let firstEditTs = null;
|
|
9063
|
-
for (const line of raw.split("\n")) {
|
|
9064
|
-
if (!line.trim()) continue;
|
|
9065
|
-
onLine?.();
|
|
9066
|
-
let entry;
|
|
9067
|
-
try {
|
|
9068
|
-
entry = JSON.parse(line);
|
|
9069
|
-
} catch {
|
|
9070
|
-
continue;
|
|
9071
|
-
}
|
|
9072
|
-
if (entry.type !== "assistant" && entry.type !== "user") continue;
|
|
9073
|
-
if (startDate && entry.timestamp) {
|
|
9074
|
-
if (new Date(entry.timestamp) < startDate) continue;
|
|
9075
|
-
}
|
|
9076
|
-
if (entry.timestamp) {
|
|
9077
|
-
if (!result.firstDate || entry.timestamp < result.firstDate)
|
|
9078
|
-
result.firstDate = entry.timestamp;
|
|
9079
|
-
if (!result.lastDate || entry.timestamp > result.lastDate)
|
|
9080
|
-
result.lastDate = entry.timestamp;
|
|
9081
|
-
}
|
|
9082
|
-
if (entry.type === "user") {
|
|
9083
|
-
const content2 = entry.message?.content;
|
|
9084
|
-
if (Array.isArray(content2)) {
|
|
9085
|
-
const text = content2.filter((b) => b.type === "text").map((b) => b["text"] ?? "").join("\n");
|
|
9086
|
-
if (text) {
|
|
9087
|
-
const dlpMatch = scanArgs({ text });
|
|
9088
|
-
if (dlpMatch) {
|
|
9089
|
-
const isDupe = result.dlpFindings.some(
|
|
9090
|
-
(f) => f.patternName === dlpMatch.patternName && f.redactedSample === dlpMatch.redactedSample && f.project === projLabel
|
|
9091
|
-
);
|
|
9092
|
-
if (!isDupe) {
|
|
9093
|
-
result.dlpFindings.push({
|
|
9094
|
-
patternName: dlpMatch.patternName,
|
|
9095
|
-
redactedSample: dlpMatch.redactedSample,
|
|
9096
|
-
toolName: "user-prompt",
|
|
9097
|
-
timestamp: entry.timestamp ?? "",
|
|
9098
|
-
project: projLabel,
|
|
9099
|
-
sessionId,
|
|
9100
|
-
agent: "claude"
|
|
9101
|
-
});
|
|
9102
|
-
}
|
|
9103
|
-
}
|
|
9104
|
-
}
|
|
9105
|
-
for (const block of content2) {
|
|
9106
|
-
if (block.type !== "tool_result") continue;
|
|
9107
|
-
const filePath = block.tool_use_id ? toolUseFilePaths.get(block.tool_use_id) : void 0;
|
|
9108
|
-
if (filePath) {
|
|
9109
|
-
const ext = import_path21.default.extname(filePath).toLowerCase();
|
|
9110
|
-
if (CODE_EXTENSIONS.has(ext)) continue;
|
|
9111
|
-
}
|
|
9112
|
-
const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
|
|
9113
|
-
if (!resultText) continue;
|
|
9114
|
-
if (isNode9SelfOutput(resultText)) continue;
|
|
9115
|
-
const dlpMatch = scanArgs({ text: resultText });
|
|
9116
|
-
if (dlpMatch) {
|
|
9117
|
-
if (looksLikeFixtureToken(dlpMatch.redactedSample)) continue;
|
|
9118
|
-
if (firstDlpTs === null) firstDlpTs = entry.timestamp ?? null;
|
|
9119
|
-
const isDupe = result.dlpFindings.some(
|
|
9120
|
-
(f) => f.patternName === dlpMatch.patternName && f.redactedSample === dlpMatch.redactedSample && f.project === projLabel
|
|
9121
|
-
);
|
|
9122
|
-
if (!isDupe) {
|
|
9123
|
-
result.dlpFindings.push({
|
|
9124
|
-
patternName: dlpMatch.patternName,
|
|
9125
|
-
redactedSample: dlpMatch.redactedSample,
|
|
9126
|
-
toolName: "tool-result",
|
|
9127
|
-
timestamp: entry.timestamp ?? "",
|
|
9128
|
-
project: projLabel,
|
|
9129
|
-
sessionId,
|
|
9130
|
-
agent: "claude"
|
|
9131
|
-
});
|
|
9132
|
-
}
|
|
9133
|
-
}
|
|
9134
|
-
}
|
|
9135
|
-
}
|
|
9136
|
-
continue;
|
|
9137
|
-
}
|
|
9138
|
-
const usage = entry.message?.usage;
|
|
9139
|
-
const model = entry.message?.model;
|
|
9140
|
-
if (usage && model) {
|
|
9141
|
-
const p = claudeModelPrice(model);
|
|
9142
|
-
if (p) {
|
|
9143
|
-
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;
|
|
9144
|
-
}
|
|
9145
|
-
}
|
|
9146
|
-
const content = entry.message?.content;
|
|
9147
|
-
if (!Array.isArray(content)) continue;
|
|
9148
|
-
for (const block of content) {
|
|
9149
|
-
if (block.type !== "tool_use") continue;
|
|
9150
|
-
result.totalToolCalls++;
|
|
9151
|
-
const toolName = block.name ?? "";
|
|
9152
|
-
const toolNameLower = toolName.toLowerCase();
|
|
9153
|
-
const input = block.input ?? {};
|
|
9154
|
-
if (block.id && typeof input.file_path === "string") {
|
|
9155
|
-
toolUseFilePaths.set(block.id, input.file_path);
|
|
9156
|
-
}
|
|
9157
|
-
sessionCalls.push({ toolName, input, timestamp: entry.timestamp ?? "" });
|
|
9158
|
-
if (toolNameLower === "bash" || toolNameLower === "execute_bash") {
|
|
9159
|
-
result.bashCalls++;
|
|
9160
|
-
}
|
|
9161
|
-
if (firstEditTs === null && (toolNameLower === "edit" || toolNameLower === "write" || toolNameLower === "write_file" || toolNameLower === "edit_file" || toolNameLower === "multiedit")) {
|
|
9162
|
-
firstEditTs = entry.timestamp ?? null;
|
|
9163
|
-
}
|
|
9164
|
-
const rawCmd = String(input.command ?? "").trimStart();
|
|
9165
|
-
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd))
|
|
9166
|
-
continue;
|
|
9167
|
-
const inputFilePath = typeof input.file_path === "string" ? input.file_path : "";
|
|
9168
|
-
const inputFileExt = inputFilePath ? import_path21.default.extname(inputFilePath).toLowerCase() : "";
|
|
9169
|
-
if (CODE_EXTENSIONS.has(inputFileExt)) continue;
|
|
9170
|
-
const dlpMatch = scanArgs(input);
|
|
9260
|
+
if (entry.timestamp) {
|
|
9261
|
+
if (!result.firstDate || entry.timestamp < result.firstDate)
|
|
9262
|
+
result.firstDate = entry.timestamp;
|
|
9263
|
+
if (!result.lastDate || entry.timestamp > result.lastDate) result.lastDate = entry.timestamp;
|
|
9264
|
+
}
|
|
9265
|
+
if (entry.type === "user") {
|
|
9266
|
+
const content2 = entry.message?.content;
|
|
9267
|
+
if (Array.isArray(content2)) {
|
|
9268
|
+
const text = content2.filter((b) => b.type === "text").map((b) => b["text"] ?? "").join("\n");
|
|
9269
|
+
if (text) {
|
|
9270
|
+
const dlpMatch = scanArgs({ text });
|
|
9171
9271
|
if (dlpMatch) {
|
|
9172
|
-
|
|
9173
|
-
|
|
9174
|
-
(
|
|
9175
|
-
);
|
|
9176
|
-
if (!isDupe) {
|
|
9272
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9273
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9274
|
+
dedup.dlpKeys.add(k);
|
|
9177
9275
|
result.dlpFindings.push({
|
|
9178
9276
|
patternName: dlpMatch.patternName,
|
|
9179
9277
|
redactedSample: dlpMatch.redactedSample,
|
|
9180
|
-
toolName,
|
|
9278
|
+
toolName: "user-prompt",
|
|
9181
9279
|
timestamp: entry.timestamp ?? "",
|
|
9182
9280
|
project: projLabel,
|
|
9183
9281
|
sessionId,
|
|
@@ -9185,85 +9283,234 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
|
|
|
9185
9283
|
});
|
|
9186
9284
|
}
|
|
9187
9285
|
}
|
|
9188
|
-
|
|
9189
|
-
|
|
9190
|
-
if (
|
|
9191
|
-
|
|
9192
|
-
|
|
9193
|
-
|
|
9194
|
-
|
|
9195
|
-
entry.timestamp ?? "",
|
|
9196
|
-
projLabel,
|
|
9197
|
-
sessionId,
|
|
9198
|
-
"claude",
|
|
9199
|
-
result
|
|
9200
|
-
);
|
|
9286
|
+
}
|
|
9287
|
+
for (const block of content2) {
|
|
9288
|
+
if (block.type !== "tool_result") continue;
|
|
9289
|
+
const filePath = block.tool_use_id ? toolUseFilePaths.get(block.tool_use_id) : void 0;
|
|
9290
|
+
if (filePath) {
|
|
9291
|
+
const ext = import_path21.default.extname(filePath).toLowerCase();
|
|
9292
|
+
if (CODE_EXTENSIONS.has(ext)) continue;
|
|
9201
9293
|
}
|
|
9202
|
-
|
|
9203
|
-
|
|
9204
|
-
|
|
9205
|
-
|
|
9206
|
-
|
|
9207
|
-
if (
|
|
9208
|
-
if (
|
|
9209
|
-
const
|
|
9210
|
-
|
|
9211
|
-
|
|
9212
|
-
|
|
9213
|
-
|
|
9214
|
-
|
|
9215
|
-
|
|
9216
|
-
toolName,
|
|
9217
|
-
input,
|
|
9294
|
+
const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
|
|
9295
|
+
if (!resultText) continue;
|
|
9296
|
+
if (isNode9SelfOutput(resultText)) continue;
|
|
9297
|
+
const dlpMatch = scanArgs({ text: resultText });
|
|
9298
|
+
if (dlpMatch) {
|
|
9299
|
+
if (looksLikeFixtureToken(dlpMatch.redactedSample)) continue;
|
|
9300
|
+
if (firstDlpTs === null) firstDlpTs = entry.timestamp ?? null;
|
|
9301
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9302
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9303
|
+
dedup.dlpKeys.add(k);
|
|
9304
|
+
result.dlpFindings.push({
|
|
9305
|
+
patternName: dlpMatch.patternName,
|
|
9306
|
+
redactedSample: dlpMatch.redactedSample,
|
|
9307
|
+
toolName: "tool-result",
|
|
9218
9308
|
timestamp: entry.timestamp ?? "",
|
|
9219
9309
|
project: projLabel,
|
|
9220
9310
|
sessionId,
|
|
9221
9311
|
agent: "claude"
|
|
9222
9312
|
});
|
|
9223
9313
|
}
|
|
9224
|
-
ruleMatched = true;
|
|
9225
|
-
break;
|
|
9226
|
-
}
|
|
9227
|
-
if (!ruleMatched && (toolNameLower === "bash" || toolNameLower === "execute_bash")) {
|
|
9228
|
-
const shellVerdict = detectDangerousShellExec(String(input.command ?? ""));
|
|
9229
|
-
if (shellVerdict) {
|
|
9230
|
-
const astRule = {
|
|
9231
|
-
name: `ast:bash-safe:${shellVerdict}-shell-exec-remote`,
|
|
9232
|
-
tool: "bash",
|
|
9233
|
-
conditions: [],
|
|
9234
|
-
verdict: shellVerdict,
|
|
9235
|
-
reason: `Shell execution of remote download detected by AST analysis (bash-safe)`
|
|
9236
|
-
};
|
|
9237
|
-
const inputPreview = preview(input, 120);
|
|
9238
|
-
const isDupe = result.findings.some(
|
|
9239
|
-
(f) => f.source.rule.name === astRule.name && preview(f.input, 120) === inputPreview && f.project === projLabel
|
|
9240
|
-
);
|
|
9241
|
-
if (!isDupe) {
|
|
9242
|
-
result.findings.push({
|
|
9243
|
-
source: {
|
|
9244
|
-
shieldName: "bash-safe",
|
|
9245
|
-
shieldLabel: "bash-safe (AST)",
|
|
9246
|
-
sourceType: "shield",
|
|
9247
|
-
rule: astRule
|
|
9248
|
-
},
|
|
9249
|
-
toolName,
|
|
9250
|
-
input,
|
|
9251
|
-
timestamp: entry.timestamp ?? "",
|
|
9252
|
-
project: projLabel,
|
|
9253
|
-
sessionId,
|
|
9254
|
-
agent: "claude"
|
|
9255
|
-
});
|
|
9256
|
-
}
|
|
9257
|
-
}
|
|
9258
9314
|
}
|
|
9259
9315
|
}
|
|
9260
9316
|
}
|
|
9261
|
-
|
|
9262
|
-
|
|
9263
|
-
|
|
9317
|
+
continue;
|
|
9318
|
+
}
|
|
9319
|
+
const usage = entry.message?.usage;
|
|
9320
|
+
const model = entry.message?.model;
|
|
9321
|
+
if (usage && model) {
|
|
9322
|
+
const p = claudeModelPrice(model);
|
|
9323
|
+
if (p) {
|
|
9324
|
+
result.totalCostUSD += (usage.input_tokens ?? 0) * p.i + (usage.output_tokens ?? 0) * p.o + (usage.cache_creation_input_tokens ?? 0) * p.cw + (usage.cache_read_input_tokens ?? 0) * p.cr;
|
|
9264
9325
|
}
|
|
9265
9326
|
}
|
|
9266
|
-
|
|
9327
|
+
const content = entry.message?.content;
|
|
9328
|
+
if (!Array.isArray(content)) continue;
|
|
9329
|
+
for (const block of content) {
|
|
9330
|
+
if (block.type !== "tool_use") continue;
|
|
9331
|
+
result.totalToolCalls++;
|
|
9332
|
+
const toolName = block.name ?? "";
|
|
9333
|
+
const toolNameLower = toolName.toLowerCase();
|
|
9334
|
+
const input = block.input ?? {};
|
|
9335
|
+
if (block.id && typeof input.file_path === "string") {
|
|
9336
|
+
toolUseFilePaths.set(block.id, input.file_path);
|
|
9337
|
+
}
|
|
9338
|
+
sessionCalls.push({ toolName, input, timestamp: entry.timestamp ?? "" });
|
|
9339
|
+
if (toolNameLower === "bash" || toolNameLower === "execute_bash") {
|
|
9340
|
+
result.bashCalls++;
|
|
9341
|
+
}
|
|
9342
|
+
if (firstEditTs === null && (toolNameLower === "edit" || toolNameLower === "write" || toolNameLower === "write_file" || toolNameLower === "edit_file" || toolNameLower === "multiedit")) {
|
|
9343
|
+
firstEditTs = entry.timestamp ?? null;
|
|
9344
|
+
}
|
|
9345
|
+
const rawCmd = String(input.command ?? "").trimStart();
|
|
9346
|
+
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd)) continue;
|
|
9347
|
+
const inputFilePath = typeof input.file_path === "string" ? input.file_path : "";
|
|
9348
|
+
const inputFileExt = inputFilePath ? import_path21.default.extname(inputFilePath).toLowerCase() : "";
|
|
9349
|
+
if (CODE_EXTENSIONS.has(inputFileExt)) continue;
|
|
9350
|
+
const dlpMatch = scanArgs(input);
|
|
9351
|
+
if (dlpMatch) {
|
|
9352
|
+
if (firstDlpTs === null) firstDlpTs = entry.timestamp ?? null;
|
|
9353
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9354
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9355
|
+
dedup.dlpKeys.add(k);
|
|
9356
|
+
result.dlpFindings.push({
|
|
9357
|
+
patternName: dlpMatch.patternName,
|
|
9358
|
+
redactedSample: dlpMatch.redactedSample,
|
|
9359
|
+
toolName,
|
|
9360
|
+
timestamp: entry.timestamp ?? "",
|
|
9361
|
+
project: projLabel,
|
|
9362
|
+
sessionId,
|
|
9363
|
+
agent: "claude"
|
|
9364
|
+
});
|
|
9365
|
+
}
|
|
9366
|
+
}
|
|
9367
|
+
let astFsMatched = false;
|
|
9368
|
+
const astRanForBash = toolNameLower === "bash" || toolNameLower === "execute_bash";
|
|
9369
|
+
if (astRanForBash) {
|
|
9370
|
+
astFsMatched = pushFsOpAstFinding(
|
|
9371
|
+
String(input.command ?? ""),
|
|
9372
|
+
toolName,
|
|
9373
|
+
input,
|
|
9374
|
+
entry.timestamp ?? "",
|
|
9375
|
+
projLabel,
|
|
9376
|
+
sessionId,
|
|
9377
|
+
"claude",
|
|
9378
|
+
result,
|
|
9379
|
+
dedup
|
|
9380
|
+
);
|
|
9381
|
+
}
|
|
9382
|
+
let ruleMatched = astFsMatched;
|
|
9383
|
+
for (const source of ruleSources) {
|
|
9384
|
+
const { rule } = source;
|
|
9385
|
+
if (rule.verdict === "allow") continue;
|
|
9386
|
+
if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
|
|
9387
|
+
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
9388
|
+
if (!evaluateSmartConditions(input, rule)) continue;
|
|
9389
|
+
const inputPreview = preview(input, 120);
|
|
9390
|
+
const k = findingKey(rule.name, inputPreview, projLabel);
|
|
9391
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9392
|
+
dedup.findingsKeys.add(k);
|
|
9393
|
+
result.findings.push({
|
|
9394
|
+
source,
|
|
9395
|
+
toolName,
|
|
9396
|
+
input,
|
|
9397
|
+
timestamp: entry.timestamp ?? "",
|
|
9398
|
+
project: projLabel,
|
|
9399
|
+
sessionId,
|
|
9400
|
+
agent: "claude"
|
|
9401
|
+
});
|
|
9402
|
+
}
|
|
9403
|
+
ruleMatched = true;
|
|
9404
|
+
break;
|
|
9405
|
+
}
|
|
9406
|
+
if (!ruleMatched && (toolNameLower === "bash" || toolNameLower === "execute_bash")) {
|
|
9407
|
+
const shellVerdict = detectDangerousShellExec(String(input.command ?? ""));
|
|
9408
|
+
if (shellVerdict) {
|
|
9409
|
+
const astRule = {
|
|
9410
|
+
name: `ast:bash-safe:${shellVerdict}-shell-exec-remote`,
|
|
9411
|
+
tool: "bash",
|
|
9412
|
+
conditions: [],
|
|
9413
|
+
verdict: shellVerdict,
|
|
9414
|
+
reason: `Shell execution of remote download detected by AST analysis (bash-safe)`
|
|
9415
|
+
};
|
|
9416
|
+
const inputPreview = preview(input, 120);
|
|
9417
|
+
const k = findingKey(astRule.name, inputPreview, projLabel);
|
|
9418
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9419
|
+
dedup.findingsKeys.add(k);
|
|
9420
|
+
result.findings.push({
|
|
9421
|
+
source: {
|
|
9422
|
+
shieldName: "bash-safe",
|
|
9423
|
+
shieldLabel: "bash-safe (AST)",
|
|
9424
|
+
sourceType: "shield",
|
|
9425
|
+
rule: astRule
|
|
9426
|
+
},
|
|
9427
|
+
toolName,
|
|
9428
|
+
input,
|
|
9429
|
+
timestamp: entry.timestamp ?? "",
|
|
9430
|
+
project: projLabel,
|
|
9431
|
+
sessionId,
|
|
9432
|
+
agent: "claude"
|
|
9433
|
+
});
|
|
9434
|
+
}
|
|
9435
|
+
}
|
|
9436
|
+
}
|
|
9437
|
+
}
|
|
9438
|
+
}
|
|
9439
|
+
result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "claude"));
|
|
9440
|
+
if (firstDlpTs !== null && (firstEditTs === null || firstDlpTs < firstEditTs)) {
|
|
9441
|
+
result.sessionsWithEarlySecrets++;
|
|
9442
|
+
}
|
|
9443
|
+
}
|
|
9444
|
+
function processClaudeProject(proj, projectsDir, ruleSources, startDate, result, dedup, onProgress, onLine) {
|
|
9445
|
+
const projPath = import_path21.default.join(projectsDir, proj);
|
|
9446
|
+
try {
|
|
9447
|
+
if (!import_fs19.default.statSync(projPath).isDirectory()) return;
|
|
9448
|
+
} catch {
|
|
9449
|
+
return;
|
|
9450
|
+
}
|
|
9451
|
+
const projLabel = stripTerminalEscapes(decodeURIComponent(proj).replace(import_os18.default.homedir(), "~")).slice(
|
|
9452
|
+
0,
|
|
9453
|
+
40
|
|
9454
|
+
);
|
|
9455
|
+
let files;
|
|
9456
|
+
try {
|
|
9457
|
+
files = import_fs19.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
9458
|
+
} catch {
|
|
9459
|
+
return;
|
|
9460
|
+
}
|
|
9461
|
+
for (const file of files) {
|
|
9462
|
+
processClaudeFile(
|
|
9463
|
+
file,
|
|
9464
|
+
projPath,
|
|
9465
|
+
projLabel,
|
|
9466
|
+
ruleSources,
|
|
9467
|
+
startDate,
|
|
9468
|
+
result,
|
|
9469
|
+
dedup,
|
|
9470
|
+
onProgress,
|
|
9471
|
+
onLine
|
|
9472
|
+
);
|
|
9473
|
+
}
|
|
9474
|
+
}
|
|
9475
|
+
function emptyClaudeScan() {
|
|
9476
|
+
return {
|
|
9477
|
+
filesScanned: 0,
|
|
9478
|
+
sessions: 0,
|
|
9479
|
+
totalToolCalls: 0,
|
|
9480
|
+
bashCalls: 0,
|
|
9481
|
+
findings: [],
|
|
9482
|
+
dlpFindings: [],
|
|
9483
|
+
loopFindings: [],
|
|
9484
|
+
totalCostUSD: 0,
|
|
9485
|
+
firstDate: null,
|
|
9486
|
+
lastDate: null,
|
|
9487
|
+
sessionsWithEarlySecrets: 0
|
|
9488
|
+
};
|
|
9489
|
+
}
|
|
9490
|
+
function scanClaudeHistory(startDate, onProgress, onLine) {
|
|
9491
|
+
const projectsDir = import_path21.default.join(import_os18.default.homedir(), ".claude", "projects");
|
|
9492
|
+
const result = emptyClaudeScan();
|
|
9493
|
+
if (!import_fs19.default.existsSync(projectsDir)) return result;
|
|
9494
|
+
let projDirs;
|
|
9495
|
+
try {
|
|
9496
|
+
projDirs = import_fs19.default.readdirSync(projectsDir);
|
|
9497
|
+
} catch {
|
|
9498
|
+
return result;
|
|
9499
|
+
}
|
|
9500
|
+
const ruleSources = buildRuleSources();
|
|
9501
|
+
const dedup = emptyScanDedup();
|
|
9502
|
+
for (const proj of projDirs) {
|
|
9503
|
+
processClaudeProject(
|
|
9504
|
+
proj,
|
|
9505
|
+
projectsDir,
|
|
9506
|
+
ruleSources,
|
|
9507
|
+
startDate,
|
|
9508
|
+
result,
|
|
9509
|
+
dedup,
|
|
9510
|
+
onProgress,
|
|
9511
|
+
onLine
|
|
9512
|
+
);
|
|
9513
|
+
}
|
|
9267
9514
|
return result;
|
|
9268
9515
|
}
|
|
9269
9516
|
function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
@@ -9281,6 +9528,7 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9281
9528
|
lastDate: null,
|
|
9282
9529
|
sessionsWithEarlySecrets: 0
|
|
9283
9530
|
};
|
|
9531
|
+
const dedup = emptyScanDedup();
|
|
9284
9532
|
if (!import_fs19.default.existsSync(tmpDir)) return result;
|
|
9285
9533
|
let slugDirs;
|
|
9286
9534
|
try {
|
|
@@ -9337,10 +9585,9 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9337
9585
|
if (text) {
|
|
9338
9586
|
const dlpMatch = scanArgs({ text });
|
|
9339
9587
|
if (dlpMatch) {
|
|
9340
|
-
const
|
|
9341
|
-
|
|
9342
|
-
|
|
9343
|
-
if (!isDupe) {
|
|
9588
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9589
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9590
|
+
dedup.dlpKeys.add(k);
|
|
9344
9591
|
result.dlpFindings.push({
|
|
9345
9592
|
patternName: dlpMatch.patternName,
|
|
9346
9593
|
redactedSample: dlpMatch.redactedSample,
|
|
@@ -9385,10 +9632,9 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9385
9632
|
continue;
|
|
9386
9633
|
const dlpMatch = scanArgs(input);
|
|
9387
9634
|
if (dlpMatch) {
|
|
9388
|
-
const
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
if (!isDupe) {
|
|
9635
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9636
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9637
|
+
dedup.dlpKeys.add(k);
|
|
9392
9638
|
result.dlpFindings.push({
|
|
9393
9639
|
patternName: dlpMatch.patternName,
|
|
9394
9640
|
redactedSample: dlpMatch.redactedSample,
|
|
@@ -9411,7 +9657,8 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9411
9657
|
projLabel,
|
|
9412
9658
|
sessionId,
|
|
9413
9659
|
"gemini",
|
|
9414
|
-
result
|
|
9660
|
+
result,
|
|
9661
|
+
dedup
|
|
9415
9662
|
);
|
|
9416
9663
|
}
|
|
9417
9664
|
let ruleMatched = astFsMatched;
|
|
@@ -9422,10 +9669,9 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9422
9669
|
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
9423
9670
|
if (!evaluateSmartConditions(input, rule)) continue;
|
|
9424
9671
|
const inputPreview = preview(input, 120);
|
|
9425
|
-
const
|
|
9426
|
-
|
|
9427
|
-
|
|
9428
|
-
if (!isDupe) {
|
|
9672
|
+
const k = findingKey(rule.name, inputPreview, projLabel);
|
|
9673
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9674
|
+
dedup.findingsKeys.add(k);
|
|
9429
9675
|
result.findings.push({
|
|
9430
9676
|
source,
|
|
9431
9677
|
toolName,
|
|
@@ -9453,10 +9699,9 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
9453
9699
|
reason: `Shell execution of remote download detected by AST analysis (bash-safe)`
|
|
9454
9700
|
};
|
|
9455
9701
|
const inputPreview = preview(input, 120);
|
|
9456
|
-
const
|
|
9457
|
-
|
|
9458
|
-
|
|
9459
|
-
if (!isDupe) {
|
|
9702
|
+
const k = findingKey(astRule.name, inputPreview, projLabel);
|
|
9703
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9704
|
+
dedup.findingsKeys.add(k);
|
|
9460
9705
|
result.findings.push({
|
|
9461
9706
|
source: {
|
|
9462
9707
|
shieldName: "bash-safe",
|
|
@@ -9496,6 +9741,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9496
9741
|
lastDate: null,
|
|
9497
9742
|
sessionsWithEarlySecrets: 0
|
|
9498
9743
|
};
|
|
9744
|
+
const dedup = emptyScanDedup();
|
|
9499
9745
|
if (!import_fs19.default.existsSync(sessionsBase)) return result;
|
|
9500
9746
|
const jsonlFiles = [];
|
|
9501
9747
|
try {
|
|
@@ -9577,10 +9823,9 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9577
9823
|
if (text) {
|
|
9578
9824
|
const dlpMatch2 = scanArgs({ text });
|
|
9579
9825
|
if (dlpMatch2) {
|
|
9580
|
-
const
|
|
9581
|
-
|
|
9582
|
-
|
|
9583
|
-
if (!isDupe) {
|
|
9826
|
+
const k = dlpKey(dlpMatch2.patternName, dlpMatch2.redactedSample, projLabel);
|
|
9827
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9828
|
+
dedup.dlpKeys.add(k);
|
|
9584
9829
|
result.dlpFindings.push({
|
|
9585
9830
|
patternName: dlpMatch2.patternName,
|
|
9586
9831
|
redactedSample: dlpMatch2.redactedSample,
|
|
@@ -9622,10 +9867,9 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9622
9867
|
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd)) continue;
|
|
9623
9868
|
const dlpMatch = scanArgs(input);
|
|
9624
9869
|
if (dlpMatch) {
|
|
9625
|
-
const
|
|
9626
|
-
|
|
9627
|
-
|
|
9628
|
-
if (!isDupe) {
|
|
9870
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
9871
|
+
if (!dedup.dlpKeys.has(k)) {
|
|
9872
|
+
dedup.dlpKeys.add(k);
|
|
9629
9873
|
result.dlpFindings.push({
|
|
9630
9874
|
patternName: dlpMatch.patternName,
|
|
9631
9875
|
redactedSample: dlpMatch.redactedSample,
|
|
@@ -9648,7 +9892,8 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9648
9892
|
projLabel,
|
|
9649
9893
|
sessionId,
|
|
9650
9894
|
"codex",
|
|
9651
|
-
result
|
|
9895
|
+
result,
|
|
9896
|
+
dedup
|
|
9652
9897
|
);
|
|
9653
9898
|
}
|
|
9654
9899
|
let ruleMatched = astFsMatched;
|
|
@@ -9660,10 +9905,9 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9660
9905
|
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
9661
9906
|
if (!evaluateSmartConditions(input, rule)) continue;
|
|
9662
9907
|
const inputPreview = preview(input, 120);
|
|
9663
|
-
const
|
|
9664
|
-
|
|
9665
|
-
|
|
9666
|
-
if (!isDupe) {
|
|
9908
|
+
const k = findingKey(rule.name, inputPreview, projLabel);
|
|
9909
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9910
|
+
dedup.findingsKeys.add(k);
|
|
9667
9911
|
result.findings.push({
|
|
9668
9912
|
source,
|
|
9669
9913
|
toolName,
|
|
@@ -9688,10 +9932,9 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
9688
9932
|
reason: `Shell execution of remote download detected by AST analysis (bash-safe)`
|
|
9689
9933
|
};
|
|
9690
9934
|
const inputPreview = preview(input, 120);
|
|
9691
|
-
const
|
|
9692
|
-
|
|
9693
|
-
|
|
9694
|
-
if (!isDupe) {
|
|
9935
|
+
const k = findingKey(astRule.name, inputPreview, projLabel);
|
|
9936
|
+
if (!dedup.findingsKeys.has(k)) {
|
|
9937
|
+
dedup.findingsKeys.add(k);
|
|
9695
9938
|
result.findings.push({
|
|
9696
9939
|
source: {
|
|
9697
9940
|
shieldName: "bash-safe",
|
|
@@ -9722,6 +9965,7 @@ function scanShellConfig() {
|
|
|
9722
9965
|
(f) => import_path21.default.join(home, f)
|
|
9723
9966
|
);
|
|
9724
9967
|
const findings = [];
|
|
9968
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9725
9969
|
for (const filePath of configFiles) {
|
|
9726
9970
|
if (!import_fs19.default.existsSync(filePath)) continue;
|
|
9727
9971
|
let lines;
|
|
@@ -9736,10 +9980,9 @@ function scanShellConfig() {
|
|
|
9736
9980
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
9737
9981
|
const dlpMatch = scanArgs({ text: trimmed });
|
|
9738
9982
|
if (!dlpMatch) continue;
|
|
9739
|
-
const
|
|
9740
|
-
|
|
9741
|
-
|
|
9742
|
-
if (!isDupe) {
|
|
9983
|
+
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, shortPath);
|
|
9984
|
+
if (!seen.has(k)) {
|
|
9985
|
+
seen.add(k);
|
|
9743
9986
|
findings.push({
|
|
9744
9987
|
patternName: dlpMatch.patternName,
|
|
9745
9988
|
redactedSample: dlpMatch.redactedSample,
|
|
@@ -9998,6 +10241,263 @@ function renderNarrativeScorecard(input) {
|
|
|
9998
10241
|
console.log(import_chalk5.default.dim("\u2192 github.com/node9-ai/node9-proxy"));
|
|
9999
10242
|
console.log("");
|
|
10000
10243
|
}
|
|
10244
|
+
function mkLine(...parts) {
|
|
10245
|
+
let rendered = "";
|
|
10246
|
+
let width = 0;
|
|
10247
|
+
for (const [text, fmt] of parts) {
|
|
10248
|
+
rendered += fmt ? fmt(text) : text;
|
|
10249
|
+
width += text.length;
|
|
10250
|
+
}
|
|
10251
|
+
return { rendered, width };
|
|
10252
|
+
}
|
|
10253
|
+
function shortRule(name, width) {
|
|
10254
|
+
const stripped = name.replace(/^shield:[^:]+:/, "");
|
|
10255
|
+
if (stripped.length <= width) return stripped.padEnd(width);
|
|
10256
|
+
return stripped.slice(0, width - 1) + "\u2026";
|
|
10257
|
+
}
|
|
10258
|
+
function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
|
|
10259
|
+
const { scan, summary, blast, blastExposures, blockedCount, reviewCount } = input;
|
|
10260
|
+
const topLines = [];
|
|
10261
|
+
if (scan.dlpFindings.length > 0) {
|
|
10262
|
+
const latest = scan.dlpFindings[0];
|
|
10263
|
+
const rel = relativeDate(latest.timestamp, now);
|
|
10264
|
+
const noun = `credential leak${scan.dlpFindings.length !== 1 ? "s" : ""}`;
|
|
10265
|
+
topLines.push(
|
|
10266
|
+
mkLine(
|
|
10267
|
+
["\u{1F6A8} ", import_chalk5.default.red],
|
|
10268
|
+
[`${scan.dlpFindings.length} ${noun} in tool input `, import_chalk5.default.bold],
|
|
10269
|
+
[`(latest: ${rel} ago, ${latest.patternName})`, import_chalk5.default.dim]
|
|
10270
|
+
)
|
|
10271
|
+
);
|
|
10272
|
+
}
|
|
10273
|
+
if (blockedCount > 0) {
|
|
10274
|
+
const topBlocked = topRulesByVerdict(summary.sections, "block", 2).map(
|
|
10275
|
+
(r) => r.count > 1 ? `${shortRule(r.name, 20).trimEnd()} \xD7${r.count}` : shortRule(r.name, 20).trimEnd()
|
|
10276
|
+
).join(", ");
|
|
10277
|
+
topLines.push(
|
|
10278
|
+
mkLine(
|
|
10279
|
+
["\u{1F6D1} ", import_chalk5.default.red],
|
|
10280
|
+
[`${blockedCount} ops node9 would have blocked `, import_chalk5.default.bold],
|
|
10281
|
+
[`(${topBlocked})`, import_chalk5.default.dim]
|
|
10282
|
+
)
|
|
10283
|
+
);
|
|
10284
|
+
}
|
|
10285
|
+
if (scan.loopFindings.length > 0) {
|
|
10286
|
+
const { wastePct } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
|
|
10287
|
+
const byTool = /* @__PURE__ */ new Map();
|
|
10288
|
+
for (const f of scan.loopFindings) {
|
|
10289
|
+
byTool.set(f.toolName, (byTool.get(f.toolName) ?? 0) + Math.max(0, f.count - 1));
|
|
10290
|
+
}
|
|
10291
|
+
const top = [...byTool.entries()].sort((a, b) => b[1] - a[1])[0];
|
|
10292
|
+
const wasteSuffix = wastePct > 0 ? `, ${wastePct}% wasted` : "";
|
|
10293
|
+
const detail = top ? `(${top[0]} dominates${wasteSuffix})` : "";
|
|
10294
|
+
topLines.push(
|
|
10295
|
+
mkLine(
|
|
10296
|
+
["\u{1F501} ", import_chalk5.default.yellow],
|
|
10297
|
+
[`${scan.loopFindings.length} agent loops detected `, import_chalk5.default.bold],
|
|
10298
|
+
[detail, import_chalk5.default.dim]
|
|
10299
|
+
)
|
|
10300
|
+
);
|
|
10301
|
+
}
|
|
10302
|
+
if (blastExposures > 0) {
|
|
10303
|
+
const exposed2 = Math.max(0, 100 - blast.score);
|
|
10304
|
+
const pjDiscount = PROTECTIVE_SHIELD_DISCOUNTS["project-jail"] ?? 0;
|
|
10305
|
+
const pjBonus = Math.round(exposed2 * pjDiscount);
|
|
10306
|
+
const cta = pjBonus > 0 ? ` \u2192 enable project-jail (+${pjBonus} pts)` : "";
|
|
10307
|
+
topLines.push(
|
|
10308
|
+
mkLine(
|
|
10309
|
+
["\u{1F52D} ", import_chalk5.default.red],
|
|
10310
|
+
[`${blastExposures} secrets reachable on disk`, import_chalk5.default.bold],
|
|
10311
|
+
[cta, import_chalk5.default.dim]
|
|
10312
|
+
)
|
|
10313
|
+
);
|
|
10314
|
+
}
|
|
10315
|
+
if (topLines.length > 0) {
|
|
10316
|
+
for (const ln of boxPanel("TOP FINDINGS", topLines)) console.log(" " + ln);
|
|
10317
|
+
console.log("");
|
|
10318
|
+
}
|
|
10319
|
+
if (summary.leaks.length > 0) {
|
|
10320
|
+
const leakLines = [];
|
|
10321
|
+
for (const leak of summary.leaks.slice(0, 5)) {
|
|
10322
|
+
const rel = relativeDate(leak.timestamp, now);
|
|
10323
|
+
leakLines.push(
|
|
10324
|
+
mkLine(
|
|
10325
|
+
[rel.padStart(4) + " ", import_chalk5.default.dim],
|
|
10326
|
+
[leak.patternName.padEnd(14), import_chalk5.default.red.bold],
|
|
10327
|
+
[" "],
|
|
10328
|
+
[leak.redactedSample.padEnd(20), import_chalk5.default.red],
|
|
10329
|
+
[" "],
|
|
10330
|
+
[`[${leak.toolName}]`.padEnd(15), import_chalk5.default.dim],
|
|
10331
|
+
[" "],
|
|
10332
|
+
[leak.agent, import_chalk5.default.dim]
|
|
10333
|
+
)
|
|
10334
|
+
);
|
|
10335
|
+
}
|
|
10336
|
+
const remaining = summary.leaks.length - 5;
|
|
10337
|
+
if (remaining > 0) {
|
|
10338
|
+
leakLines.push(mkLine([`\u2026 +${remaining} more`, import_chalk5.default.dim]));
|
|
10339
|
+
}
|
|
10340
|
+
const title = `LEAKS \xB7 ${summary.leaks.length} secret${summary.leaks.length !== 1 ? "s" : ""} in plain text`;
|
|
10341
|
+
for (const ln of boxPanel(title, leakLines)) console.log(" " + ln);
|
|
10342
|
+
console.log("");
|
|
10343
|
+
}
|
|
10344
|
+
if (blockedCount > 0) {
|
|
10345
|
+
const blockedLines = [];
|
|
10346
|
+
const ruleEntries = topRulesByVerdict(summary.sections, "block", 12);
|
|
10347
|
+
for (const r of ruleEntries) {
|
|
10348
|
+
const origin = originForRule(r.name, summary.sections);
|
|
10349
|
+
blockedLines.push(
|
|
10350
|
+
mkLine(
|
|
10351
|
+
["\u2717 ", import_chalk5.default.red],
|
|
10352
|
+
[shortRule(r.name, 24), import_chalk5.default.bold],
|
|
10353
|
+
[" \xD7" + String(r.count).padEnd(4), import_chalk5.default.bold],
|
|
10354
|
+
[" "],
|
|
10355
|
+
[origin, import_chalk5.default.dim]
|
|
10356
|
+
)
|
|
10357
|
+
);
|
|
10358
|
+
}
|
|
10359
|
+
const title = `BLOCKED \xB7 ${blockedCount} ops node9 would have stopped`;
|
|
10360
|
+
for (const ln of boxPanel(title, blockedLines)) console.log(" " + ln);
|
|
10361
|
+
console.log("");
|
|
10362
|
+
}
|
|
10363
|
+
if (reviewCount > 0) {
|
|
10364
|
+
const reviewLines = [];
|
|
10365
|
+
const ruleEntries = topRulesByVerdict(summary.sections, "review", 12);
|
|
10366
|
+
for (const r of ruleEntries) {
|
|
10367
|
+
const origin = originForRule(r.name, summary.sections);
|
|
10368
|
+
reviewLines.push(
|
|
10369
|
+
mkLine(
|
|
10370
|
+
["\u{1F441} ", import_chalk5.default.yellow],
|
|
10371
|
+
[shortRule(r.name, 24), import_chalk5.default.bold],
|
|
10372
|
+
[" \xD7" + String(r.count).padEnd(4), import_chalk5.default.bold],
|
|
10373
|
+
[" "],
|
|
10374
|
+
[origin, import_chalk5.default.dim]
|
|
10375
|
+
)
|
|
10376
|
+
);
|
|
10377
|
+
}
|
|
10378
|
+
const title = `REVIEW QUEUE \xB7 ${reviewCount} ops flagged for approval`;
|
|
10379
|
+
for (const ln of boxPanel(title, reviewLines)) console.log(" " + ln);
|
|
10380
|
+
console.log("");
|
|
10381
|
+
}
|
|
10382
|
+
if (scan.loopFindings.length > 0) {
|
|
10383
|
+
const { wastePct } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
|
|
10384
|
+
const byTool = /* @__PURE__ */ new Map();
|
|
10385
|
+
let totalRepeats = 0;
|
|
10386
|
+
for (const f of scan.loopFindings) {
|
|
10387
|
+
const repeats = Math.max(0, f.count - 1);
|
|
10388
|
+
byTool.set(f.toolName, (byTool.get(f.toolName) ?? 0) + repeats);
|
|
10389
|
+
totalRepeats += repeats;
|
|
10390
|
+
}
|
|
10391
|
+
const toolEntries = [...byTool.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
10392
|
+
const loopLines = [];
|
|
10393
|
+
for (const [tool, repeats] of toolEntries) {
|
|
10394
|
+
const pct = totalRepeats > 0 ? Math.round(repeats / totalRepeats * 100) : 0;
|
|
10395
|
+
loopLines.push(
|
|
10396
|
+
mkLine(
|
|
10397
|
+
[tool.padEnd(10), import_chalk5.default.bold],
|
|
10398
|
+
[`\xD7${num(repeats)} repeats`.padEnd(16)],
|
|
10399
|
+
[`(${pct}%)`, import_chalk5.default.dim]
|
|
10400
|
+
)
|
|
10401
|
+
);
|
|
10402
|
+
}
|
|
10403
|
+
const topStuck = [...scan.loopFindings].sort((a, b) => b.count - a.count).slice(0, 3);
|
|
10404
|
+
if (topStuck.length > 0) {
|
|
10405
|
+
loopLines.push(mkLine([""]));
|
|
10406
|
+
loopLines.push(mkLine(["Top stuck patterns:", import_chalk5.default.dim]));
|
|
10407
|
+
for (const f of topStuck) {
|
|
10408
|
+
const raw = f.commandPreview || f.toolName;
|
|
10409
|
+
const target = raw.length > 60 ? "\u2026" + raw.slice(raw.length - 59) : raw.padEnd(60);
|
|
10410
|
+
loopLines.push(mkLine([`\xD7${num(f.count).padEnd(4)} `, import_chalk5.default.bold], [target, import_chalk5.default.dim]));
|
|
10411
|
+
}
|
|
10412
|
+
}
|
|
10413
|
+
const wasteSuffix = wastePct > 0 ? ` \xB7 ${wastePct}% wasted` : "";
|
|
10414
|
+
const title = `AGENT LOOPS \xB7 ${scan.loopFindings.length} repeated patterns${wasteSuffix}`;
|
|
10415
|
+
for (const ln of boxPanel(title, loopLines)) console.log(" " + ln);
|
|
10416
|
+
console.log("");
|
|
10417
|
+
}
|
|
10418
|
+
if (blast.reachable.length > 0 || blast.envFindings.length > 0) {
|
|
10419
|
+
const blastLines = [];
|
|
10420
|
+
const DESC_W = 33;
|
|
10421
|
+
for (const r of blast.reachable.slice(0, 8)) {
|
|
10422
|
+
const trimmed = r.description.split(" \u2014 ")[0].split(/—|--/)[0].trim();
|
|
10423
|
+
const desc = trimmed.length > DESC_W ? trimmed.slice(0, DESC_W - 1) + "\u2026" : trimmed;
|
|
10424
|
+
blastLines.push(mkLine(["\u2717 ", import_chalk5.default.red], [r.label.padEnd(36)], [desc, import_chalk5.default.dim]));
|
|
10425
|
+
}
|
|
10426
|
+
for (const e of blast.envFindings.slice(0, 3)) {
|
|
10427
|
+
blastLines.push(
|
|
10428
|
+
mkLine(["\u26A0 ", import_chalk5.default.yellow], [`${e.key} `], [`(${e.patternName})`, import_chalk5.default.dim])
|
|
10429
|
+
);
|
|
10430
|
+
}
|
|
10431
|
+
const totalExposed = blast.reachable.length + blast.envFindings.length;
|
|
10432
|
+
if (totalExposed > 8) {
|
|
10433
|
+
blastLines.push(mkLine([`\u2026 +${totalExposed - 8} more`, import_chalk5.default.dim]));
|
|
10434
|
+
}
|
|
10435
|
+
const title = `BLAST RADIUS \xB7 ${totalExposed} path${totalExposed !== 1 ? "s" : ""} reachable right now`;
|
|
10436
|
+
for (const ln of boxPanel(title, blastLines)) console.log(" " + ln);
|
|
10437
|
+
console.log("");
|
|
10438
|
+
}
|
|
10439
|
+
const shieldImpacts = rollupByShield(summary.sections);
|
|
10440
|
+
const exposed = Math.max(0, 100 - blast.score);
|
|
10441
|
+
const shieldLines = [];
|
|
10442
|
+
const ranked = [...shieldImpacts].sort((a, b) => {
|
|
10443
|
+
const aDiscount = PROTECTIVE_SHIELD_DISCOUNTS[a.shieldName] ?? 0;
|
|
10444
|
+
const bDiscount = PROTECTIVE_SHIELD_DISCOUNTS[b.shieldName] ?? 0;
|
|
10445
|
+
if (aDiscount !== bDiscount) return bDiscount - aDiscount;
|
|
10446
|
+
return b.totalCatches - a.totalCatches;
|
|
10447
|
+
});
|
|
10448
|
+
for (const impact of ranked) {
|
|
10449
|
+
if (impact.totalCatches === 0) continue;
|
|
10450
|
+
const discount = PROTECTIVE_SHIELD_DISCOUNTS[impact.shieldName] ?? 0;
|
|
10451
|
+
const bonus = Math.round(exposed * discount);
|
|
10452
|
+
const icon = discount > 0 ? "\u{1F6E1} " : "\u2610 ";
|
|
10453
|
+
const wouldCatch = `would catch ${impact.totalCatches} op${impact.totalCatches !== 1 ? "s" : ""}`;
|
|
10454
|
+
const deltaSuffix = bonus > 0 ? ` \u2192 +${bonus} pts (${blast.score} \u2192 ${blast.score + bonus})` : "";
|
|
10455
|
+
shieldLines.push(
|
|
10456
|
+
mkLine(
|
|
10457
|
+
[icon, discount > 0 ? import_chalk5.default.cyan : import_chalk5.default.dim],
|
|
10458
|
+
[impact.shieldName.padEnd(14), import_chalk5.default.bold],
|
|
10459
|
+
[wouldCatch.padEnd(22), import_chalk5.default.dim],
|
|
10460
|
+
[deltaSuffix, bonus > 0 ? import_chalk5.default.green.bold : import_chalk5.default.dim]
|
|
10461
|
+
)
|
|
10462
|
+
);
|
|
10463
|
+
if (impact.topRuleLabels.length > 0) {
|
|
10464
|
+
const rules = impact.topRuleLabels.join(", ");
|
|
10465
|
+
shieldLines.push(mkLine([" ", import_chalk5.default.dim], [rules, import_chalk5.default.dim]));
|
|
10466
|
+
}
|
|
10467
|
+
}
|
|
10468
|
+
const hitShieldSet = new Set(
|
|
10469
|
+
shieldImpacts.filter((i) => i.totalCatches > 0).map((i) => i.shieldName)
|
|
10470
|
+
);
|
|
10471
|
+
const zeroHitBuiltins = Object.keys(SHIELDS).filter((name) => !hitShieldSet.has(name)).sort();
|
|
10472
|
+
if (zeroHitBuiltins.length > 0) {
|
|
10473
|
+
shieldLines.push(mkLine([""]));
|
|
10474
|
+
shieldLines.push(mkLine([zeroHitBuiltins.join(" \xB7 "), import_chalk5.default.dim]));
|
|
10475
|
+
shieldLines.push(mkLine([" no hits in your history \u2014 install proactively", import_chalk5.default.dim]));
|
|
10476
|
+
}
|
|
10477
|
+
const topRec = ranked.find(
|
|
10478
|
+
(r) => r.totalCatches > 0 && (PROTECTIVE_SHIELD_DISCOUNTS[r.shieldName] ?? 0) > 0
|
|
10479
|
+
);
|
|
10480
|
+
if (topRec) {
|
|
10481
|
+
const bonus = Math.round(exposed * (PROTECTIVE_SHIELD_DISCOUNTS[topRec.shieldName] ?? 0));
|
|
10482
|
+
const cta = `\u2192 node9 shield enable ${topRec.shieldName} (start here \u2014 +${bonus} pts)`;
|
|
10483
|
+
shieldLines.push(mkLine([""]));
|
|
10484
|
+
shieldLines.push(mkLine([cta, import_chalk5.default.cyan]));
|
|
10485
|
+
}
|
|
10486
|
+
if (shieldLines.length > 0) {
|
|
10487
|
+
const title = "SHIELDS \xB7 install node9 + enable these to catch what we found";
|
|
10488
|
+
for (const ln of boxPanel(title, shieldLines)) console.log(" " + ln);
|
|
10489
|
+
console.log("");
|
|
10490
|
+
}
|
|
10491
|
+
}
|
|
10492
|
+
function originForRule(ruleName, sections) {
|
|
10493
|
+
for (const section of sections) {
|
|
10494
|
+
if (section.rules.some((r) => r.name === ruleName)) {
|
|
10495
|
+
if (section.sourceType === "default") return "default";
|
|
10496
|
+
if (section.sourceType === "shield") return `needs shield:${section.shieldKey ?? section.id}`;
|
|
10497
|
+
}
|
|
10498
|
+
}
|
|
10499
|
+
return "";
|
|
10500
|
+
}
|
|
10001
10501
|
function registerScanCommand(program2) {
|
|
10002
10502
|
program2.command("scan").description("Forecast: scan agent history and show what node9 would catch if installed").option("--all", "Scan all history (default: last 90 days)").option("--days <n>", "Scan last N days of history", "90").option("--top <n>", "Max findings to show per rule (default: 5)", "5").option("--drill-down", "Show all findings with full commands and session IDs").option("--compact", "Compact one-screen scorecard \u2014 for screenshots and sharing").option("--narrative", "Severity-grouped report \u2014 for video / dramatic sharing").option(
|
|
10003
10503
|
"--json",
|
|
@@ -10229,7 +10729,7 @@ function registerScanCommand(program2) {
|
|
|
10229
10729
|
" " + import_chalk5.default.dim("AI spend ") + import_chalk5.default.bold(fmtCost(scan.totalCostUSD)) + (summary.loopWastedUSD > 0 ? import_chalk5.default.dim(" \xB7 wasted on loops ") + import_chalk5.default.yellow("~" + fmtCost(summary.loopWastedUSD)) : "")
|
|
10230
10730
|
);
|
|
10231
10731
|
}
|
|
10232
|
-
if (scan.dlpFindings.length > 0 && scan.sessionsWithEarlySecrets > 0) {
|
|
10732
|
+
if (drillDown && scan.dlpFindings.length > 0 && scan.sessionsWithEarlySecrets > 0) {
|
|
10233
10733
|
console.log(
|
|
10234
10734
|
" " + import_chalk5.default.dim(
|
|
10235
10735
|
`${scan.sessionsWithEarlySecrets} session${scan.sessionsWithEarlySecrets !== 1 ? "s" : ""} loaded secrets before first edit`
|
|
@@ -10237,6 +10737,26 @@ function registerScanCommand(program2) {
|
|
|
10237
10737
|
);
|
|
10238
10738
|
}
|
|
10239
10739
|
console.log("");
|
|
10740
|
+
if (!drillDown) {
|
|
10741
|
+
renderPanelScorecard({
|
|
10742
|
+
scan,
|
|
10743
|
+
summary,
|
|
10744
|
+
blast,
|
|
10745
|
+
blastExposures,
|
|
10746
|
+
blockedCount,
|
|
10747
|
+
reviewCount
|
|
10748
|
+
});
|
|
10749
|
+
const cta = isWired ? "\u2705 node9 is active" : "\u2192 install node9 to enable protection";
|
|
10750
|
+
console.log(" " + import_chalk5.default.green(cta));
|
|
10751
|
+
console.log(
|
|
10752
|
+
" " + import_chalk5.default.dim("\u2192 ") + import_chalk5.default.cyan("node9 monitor") + import_chalk5.default.dim(" live dashboard")
|
|
10753
|
+
);
|
|
10754
|
+
console.log(
|
|
10755
|
+
" " + import_chalk5.default.dim("\u2192 ") + import_chalk5.default.cyan("node9 scan --drill-down") + import_chalk5.default.dim(" full commands + session IDs")
|
|
10756
|
+
);
|
|
10757
|
+
console.log("");
|
|
10758
|
+
return;
|
|
10759
|
+
}
|
|
10240
10760
|
if (scan.dlpFindings.length > 0) {
|
|
10241
10761
|
console.log(" " + import_chalk5.default.dim("\u2500".repeat(70)));
|
|
10242
10762
|
console.log(
|
|
@@ -10425,7 +10945,7 @@ function registerScanCommand(program2) {
|
|
|
10425
10945
|
}
|
|
10426
10946
|
);
|
|
10427
10947
|
}
|
|
10428
|
-
var import_chalk5, import_fs19, import_path21, import_os18, CLAUDE_PRICING, GEMINI_PRICING, CODE_EXTENSIONS, SELF_OUTPUT_MARKERS, FIXTURE_TOKEN_PATTERNS, TERMINAL_ESCAPE_RE2, LOOP_TOOLS, LOOP_THRESHOLD, LOOP_TIMESPAN_THRESHOLD_MS, STUCK_TOOLS_MIN_WASTE, STUCK_TOOLS_LIMIT, RECURRING_SESSION_THRESHOLD, STALE_AGE_DAYS,
|
|
10948
|
+
var import_chalk5, import_fs19, import_path21, import_os18, CLAUDE_PRICING, GEMINI_PRICING, CODE_EXTENSIONS, SELF_OUTPUT_MARKERS, FIXTURE_TOKEN_PATTERNS, TERMINAL_ESCAPE_RE2, LOOP_TOOLS, LOOP_THRESHOLD, LOOP_TIMESPAN_THRESHOLD_MS, STUCK_TOOLS_MIN_WASTE, STUCK_TOOLS_LIMIT, RECURRING_SESSION_THRESHOLD, STALE_AGE_DAYS, classifyRuleSeverity2, narrativeRuleLabel2;
|
|
10429
10949
|
var init_scan = __esm({
|
|
10430
10950
|
"src/cli/commands/scan.ts"() {
|
|
10431
10951
|
"use strict";
|
|
@@ -10443,6 +10963,7 @@ var init_scan = __esm({
|
|
|
10443
10963
|
init_setup();
|
|
10444
10964
|
init_blast();
|
|
10445
10965
|
init_scan_derive();
|
|
10966
|
+
init_protection();
|
|
10446
10967
|
init_scan_json();
|
|
10447
10968
|
init_scan_history();
|
|
10448
10969
|
CLAUDE_PRICING = {
|
|
@@ -10525,9 +11046,6 @@ var init_scan = __esm({
|
|
|
10525
11046
|
STUCK_TOOLS_LIMIT = 3;
|
|
10526
11047
|
RECURRING_SESSION_THRESHOLD = 3;
|
|
10527
11048
|
STALE_AGE_DAYS = 30;
|
|
10528
|
-
DEFAULT_RULE_NAMES = new Set(
|
|
10529
|
-
DEFAULT_CONFIG.policy.smartRules.map((r) => r.name).filter(Boolean)
|
|
10530
|
-
);
|
|
10531
11049
|
classifyRuleSeverity2 = classifyRuleSeverity;
|
|
10532
11050
|
narrativeRuleLabel2 = narrativeRuleLabel;
|
|
10533
11051
|
}
|
|
@@ -11027,6 +11545,19 @@ data: ${JSON.stringify(data)}
|
|
|
11027
11545
|
}
|
|
11028
11546
|
});
|
|
11029
11547
|
}
|
|
11548
|
+
function broadcastForensic(finding) {
|
|
11549
|
+
const severity = CRITICAL_FORENSIC_CATEGORIES.has(finding.type) ? "critical" : "warning";
|
|
11550
|
+
const event = {
|
|
11551
|
+
type: "forensic",
|
|
11552
|
+
id: `fnd_${(0, import_crypto6.randomUUID)()}`,
|
|
11553
|
+
ts: Date.now(),
|
|
11554
|
+
sessionId: finding.sessionId,
|
|
11555
|
+
category: finding.type,
|
|
11556
|
+
severity
|
|
11557
|
+
};
|
|
11558
|
+
if (finding.patternName !== void 0) event.patternName = finding.patternName;
|
|
11559
|
+
broadcast("forensic", event);
|
|
11560
|
+
}
|
|
11030
11561
|
function abandonPending() {
|
|
11031
11562
|
setAbandonTimer(null);
|
|
11032
11563
|
pending.forEach((entry, id) => {
|
|
@@ -11210,7 +11741,7 @@ function bindActivitySocket() {
|
|
|
11210
11741
|
});
|
|
11211
11742
|
activitySocketServer = unixServer;
|
|
11212
11743
|
}
|
|
11213
|
-
var import_net2, import_fs21, import_path23, import_os19, import_crypto6, 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;
|
|
11744
|
+
var import_net2, import_fs21, import_path23, import_os19, import_crypto6, 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;
|
|
11214
11745
|
var init_state2 = __esm({
|
|
11215
11746
|
"src/daemon/state.ts"() {
|
|
11216
11747
|
"use strict";
|
|
@@ -11260,6 +11791,11 @@ var init_state2 = __esm({
|
|
|
11260
11791
|
INPUT_PRICE_PER_1M = 3;
|
|
11261
11792
|
OUTPUT_PRICE_PER_1M = 15;
|
|
11262
11793
|
BYTES_PER_TOKEN = 4;
|
|
11794
|
+
CRITICAL_FORENSIC_CATEGORIES = /* @__PURE__ */ new Set([
|
|
11795
|
+
"privilege-escalation",
|
|
11796
|
+
"destructive-op",
|
|
11797
|
+
"eval-of-remote"
|
|
11798
|
+
]);
|
|
11263
11799
|
WRITE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
11264
11800
|
"write",
|
|
11265
11801
|
"write_file",
|
|
@@ -11600,7 +12136,26 @@ function startCloudSync() {
|
|
|
11600
12136
|
const recurring = setInterval(() => void syncOnce(), intervalMs);
|
|
11601
12137
|
recurring.unref();
|
|
11602
12138
|
}
|
|
11603
|
-
|
|
12139
|
+
function startForensicBroadcast() {
|
|
12140
|
+
const tick = async () => {
|
|
12141
|
+
try {
|
|
12142
|
+
const findings = await tickForensicBroadcast(forensicBroadcastOffsets);
|
|
12143
|
+
for (const f of findings) broadcastForensic(f);
|
|
12144
|
+
} catch (err2) {
|
|
12145
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
12146
|
+
appendToLog(HOOK_DEBUG_LOG, {
|
|
12147
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12148
|
+
kind: "forensic-broadcast-error",
|
|
12149
|
+
error: msg
|
|
12150
|
+
});
|
|
12151
|
+
}
|
|
12152
|
+
};
|
|
12153
|
+
const initial = setTimeout(() => void tick(), FORENSIC_INITIAL_DELAY_MS);
|
|
12154
|
+
initial.unref();
|
|
12155
|
+
const recurring = setInterval(() => void tick(), FORENSIC_BROADCAST_INTERVAL_MS);
|
|
12156
|
+
recurring.unref();
|
|
12157
|
+
}
|
|
12158
|
+
var import_fs22, import_https2, import_os20, import_path24, FINDING_TO_SIGNAL3, rulesCacheFile, DEFAULT_API_URL, DEFAULT_INTERVAL_HOURS, MIN_INTERVAL_HOURS, FORENSIC_BROADCAST_INTERVAL_MS, FORENSIC_INITIAL_DELAY_MS, forensicBroadcastOffsets;
|
|
11604
12159
|
var init_sync = __esm({
|
|
11605
12160
|
"src/daemon/sync.ts"() {
|
|
11606
12161
|
"use strict";
|
|
@@ -11612,6 +12167,8 @@ var init_sync = __esm({
|
|
|
11612
12167
|
init_blast();
|
|
11613
12168
|
init_dist();
|
|
11614
12169
|
init_scan_watermark();
|
|
12170
|
+
init_state2();
|
|
12171
|
+
init_audit();
|
|
11615
12172
|
FINDING_TO_SIGNAL3 = {
|
|
11616
12173
|
dlp: "dlpFindings",
|
|
11617
12174
|
pii: "piiFindings",
|
|
@@ -11628,6 +12185,9 @@ var init_sync = __esm({
|
|
|
11628
12185
|
DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept/policies/sync";
|
|
11629
12186
|
DEFAULT_INTERVAL_HOURS = 5;
|
|
11630
12187
|
MIN_INTERVAL_HOURS = 1;
|
|
12188
|
+
FORENSIC_BROADCAST_INTERVAL_MS = 3e4;
|
|
12189
|
+
FORENSIC_INITIAL_DELAY_MS = 5e3;
|
|
12190
|
+
forensicBroadcastOffsets = /* @__PURE__ */ new Map();
|
|
11631
12191
|
}
|
|
11632
12192
|
});
|
|
11633
12193
|
|
|
@@ -11855,6 +12415,7 @@ var init_mcp_tools = __esm({
|
|
|
11855
12415
|
function startDaemon() {
|
|
11856
12416
|
startCostSync();
|
|
11857
12417
|
startCloudSync();
|
|
12418
|
+
startForensicBroadcast();
|
|
11858
12419
|
startDlpScanner();
|
|
11859
12420
|
loadInsightCounts();
|
|
11860
12421
|
const internalToken = (0, import_crypto7.randomUUID)();
|
|
@@ -13057,8 +13618,15 @@ var tail_exports = {};
|
|
|
13057
13618
|
__export(tail_exports, {
|
|
13058
13619
|
agentLabel: () => agentLabel,
|
|
13059
13620
|
sessionTag: () => sessionTag,
|
|
13621
|
+
shortenPathSummary: () => shortenPathSummary,
|
|
13060
13622
|
startTail: () => startTail
|
|
13061
13623
|
});
|
|
13624
|
+
function shortenPathSummary(s) {
|
|
13625
|
+
if (!s || !s.startsWith("/")) return s;
|
|
13626
|
+
const parts = s.split("/").filter(Boolean);
|
|
13627
|
+
if (parts.length <= 2) return s;
|
|
13628
|
+
return `\u2026/${parts.slice(-2).join("/")}`;
|
|
13629
|
+
}
|
|
13062
13630
|
function getIcon(tool) {
|
|
13063
13631
|
const t = tool.toLowerCase();
|
|
13064
13632
|
for (const [k, v] of Object.entries(ICONS)) {
|
|
@@ -13812,7 +14380,8 @@ async function startTail(options = {}) {
|
|
|
13812
14380
|
if (event === "snapshot") {
|
|
13813
14381
|
const time = new Date(data.ts).toLocaleTimeString([], { hour12: false });
|
|
13814
14382
|
const hash = data.hash ?? "";
|
|
13815
|
-
const
|
|
14383
|
+
const rawSummary = data.argsSummary ?? data.tool;
|
|
14384
|
+
const summary = shortenPathSummary(rawSummary);
|
|
13816
14385
|
const fileCount = data.fileCount ?? 0;
|
|
13817
14386
|
const files = fileCount > 0 ? import_chalk30.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
|
|
13818
14387
|
process.stdout.write(
|
|
@@ -16201,63 +16770,13 @@ function registerAuditCommand(program2) {
|
|
|
16201
16770
|
|
|
16202
16771
|
// src/cli/commands/report.ts
|
|
16203
16772
|
var import_chalk13 = __toESM(require("chalk"));
|
|
16773
|
+
|
|
16774
|
+
// src/cli/aggregate/report-audit.ts
|
|
16204
16775
|
var import_fs35 = __toESM(require("fs"));
|
|
16205
|
-
var import_path36 = __toESM(require("path"));
|
|
16206
16776
|
var import_os31 = __toESM(require("os"));
|
|
16207
|
-
|
|
16208
|
-
|
|
16209
|
-
|
|
16210
|
-
const totalBlocked = input.timedOut + input.hardBlocked + input.dlpBlocked + input.loopHits + input.userDenied;
|
|
16211
|
-
const blockRate = input.total > 0 ? totalBlocked / input.total : 0;
|
|
16212
|
-
const deltaPct = input.priorBlockRate === null ? null : Math.round((blockRate - input.priorBlockRate) * 100);
|
|
16213
|
-
return {
|
|
16214
|
-
schemaVersion: 1,
|
|
16215
|
-
generatedAt: input.generatedAt,
|
|
16216
|
-
period: input.period,
|
|
16217
|
-
range: { start: input.start.toISOString(), end: input.end.toISOString() },
|
|
16218
|
-
excludedTests: input.excludedTests,
|
|
16219
|
-
totals: {
|
|
16220
|
-
events: input.total,
|
|
16221
|
-
blocked: totalBlocked,
|
|
16222
|
-
blockRate,
|
|
16223
|
-
userApproved: input.userApproved,
|
|
16224
|
-
userDenied: input.userDenied,
|
|
16225
|
-
timedOut: input.timedOut,
|
|
16226
|
-
hardBlocked: input.hardBlocked,
|
|
16227
|
-
dlpBlocked: input.dlpBlocked,
|
|
16228
|
-
observeDlp: input.observeDlp,
|
|
16229
|
-
loopHits: input.loopHits,
|
|
16230
|
-
unackedDlp: input.unackedDlp
|
|
16231
|
-
},
|
|
16232
|
-
tests: {
|
|
16233
|
-
passes: input.testPasses,
|
|
16234
|
-
fails: input.testFails
|
|
16235
|
-
},
|
|
16236
|
-
cost: {
|
|
16237
|
-
totalUSD: input.cost.claudeUSD + input.cost.codexUSD,
|
|
16238
|
-
claudeUSD: input.cost.claudeUSD,
|
|
16239
|
-
codexUSD: input.cost.codexUSD,
|
|
16240
|
-
inputTokens: input.cost.inputTokens,
|
|
16241
|
-
outputTokens: input.cost.outputTokens,
|
|
16242
|
-
cacheWriteTokens: input.cost.cacheWriteTokens,
|
|
16243
|
-
cacheReadTokens: input.cost.cacheReadTokens,
|
|
16244
|
-
byDay: [...input.cost.byDay.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([day, usd]) => ({ day, usd })),
|
|
16245
|
-
byModel: [...input.cost.byModel.entries()].sort((a, b) => b[1] - a[1]).map(([model, usd]) => ({ model, usd }))
|
|
16246
|
-
},
|
|
16247
|
-
byTool: [...input.toolMap.entries()].sort((a, b) => b[1].calls - a[1].calls).map(([tool, v]) => ({ tool, calls: v.calls, blocked: v.blocked })),
|
|
16248
|
-
byBlock: [...input.blockMap.entries()].sort((a, b) => b[1] - a[1]).map(([rule, count]) => ({ rule, count })),
|
|
16249
|
-
byAgent: [...input.agentMap.entries()].sort((a, b) => b[1] - a[1]).map(([agent, calls]) => ({ agent, calls })),
|
|
16250
|
-
byMcp: [...input.mcpMap.entries()].sort((a, b) => b[1] - a[1]).map(([server, calls]) => ({ server, calls })),
|
|
16251
|
-
byDay: [...input.dailyMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([day, v]) => ({ day, calls: v.calls, blocked: v.blocked })),
|
|
16252
|
-
byHour: [...input.hourMap.entries()].sort((a, b) => a[0] - b[0]).map(([hour, calls]) => ({ hour, calls })),
|
|
16253
|
-
trend: {
|
|
16254
|
-
priorBlockRate: input.priorBlockRate,
|
|
16255
|
-
deltaPct
|
|
16256
|
-
}
|
|
16257
|
-
};
|
|
16258
|
-
}
|
|
16259
|
-
|
|
16260
|
-
// src/cli/commands/report.ts
|
|
16777
|
+
var import_path36 = __toESM(require("path"));
|
|
16778
|
+
init_costSync();
|
|
16779
|
+
init_litellm();
|
|
16261
16780
|
var TEST_COMMAND_RE3 = /(?:^|\s)(npm\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|yarn\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|vitest|jest|mocha|pytest|py\.test|cargo\s+test|go\s+test|bundle\s+exec\s+rspec|rspec|phpunit|dotnet\s+test)\b/i;
|
|
16262
16781
|
function buildTestTimestamps(allEntries) {
|
|
16263
16782
|
const testTs = /* @__PURE__ */ new Set();
|
|
@@ -16282,8 +16801,7 @@ function isTestEntry(entry, testTs) {
|
|
|
16282
16801
|
}
|
|
16283
16802
|
return false;
|
|
16284
16803
|
}
|
|
16285
|
-
function getDateRange(period) {
|
|
16286
|
-
const now = /* @__PURE__ */ new Date();
|
|
16804
|
+
function getDateRange(period, now) {
|
|
16287
16805
|
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
16288
16806
|
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
|
|
16289
16807
|
switch (period) {
|
|
@@ -16299,6 +16817,11 @@ function getDateRange(period) {
|
|
|
16299
16817
|
s.setDate(s.getDate() - 29);
|
|
16300
16818
|
return { start: s, end };
|
|
16301
16819
|
}
|
|
16820
|
+
case "90d": {
|
|
16821
|
+
const s = new Date(todayStart);
|
|
16822
|
+
s.setDate(s.getDate() - 89);
|
|
16823
|
+
return { start: s, end };
|
|
16824
|
+
}
|
|
16302
16825
|
case "month":
|
|
16303
16826
|
return { start: new Date(now.getFullYear(), now.getMonth(), 1), end };
|
|
16304
16827
|
}
|
|
@@ -16321,40 +16844,6 @@ function isAllow(decision) {
|
|
|
16321
16844
|
function isDlp(checkedBy) {
|
|
16322
16845
|
return !!checkedBy?.includes("dlp");
|
|
16323
16846
|
}
|
|
16324
|
-
var BLOCK_REASON_LABELS = {
|
|
16325
|
-
timeout: "Popup timeout",
|
|
16326
|
-
"smart-rule-block": "Smart rule",
|
|
16327
|
-
"observe-mode-dlp-would-block": "DLP (observe)",
|
|
16328
|
-
"persistent-deny": "Persistent deny",
|
|
16329
|
-
"local-decision": "User denied",
|
|
16330
|
-
"dlp-block": "DLP block",
|
|
16331
|
-
"loop-detected": "Loop detected"
|
|
16332
|
-
};
|
|
16333
|
-
function humanBlockReason(reason) {
|
|
16334
|
-
return BLOCK_REASON_LABELS[reason] ?? reason;
|
|
16335
|
-
}
|
|
16336
|
-
function barStr(value, max, width) {
|
|
16337
|
-
if (max === 0 || width <= 0) return "\u2591".repeat(width);
|
|
16338
|
-
const filled = Math.max(1, Math.round(value / max * width));
|
|
16339
|
-
return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
16340
|
-
}
|
|
16341
|
-
function colorBar(value, max, width) {
|
|
16342
|
-
const s = barStr(value, max, width);
|
|
16343
|
-
const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
|
|
16344
|
-
return import_chalk13.default.cyan(s.slice(0, filled)) + import_chalk13.default.dim(s.slice(filled));
|
|
16345
|
-
}
|
|
16346
|
-
function fmtDate(d) {
|
|
16347
|
-
const date = typeof d === "string" ? /* @__PURE__ */ new Date(d + "T12:00:00") : d;
|
|
16348
|
-
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
16349
|
-
}
|
|
16350
|
-
function num2(n) {
|
|
16351
|
-
return n.toLocaleString();
|
|
16352
|
-
}
|
|
16353
|
-
function fmtCost2(usd) {
|
|
16354
|
-
if (usd < 1e-3) return "< $0.001";
|
|
16355
|
-
if (usd < 1) return "$" + usd.toFixed(4);
|
|
16356
|
-
return "$" + usd.toFixed(2);
|
|
16357
|
-
}
|
|
16358
16847
|
var CLAUDE_PRICING2 = {
|
|
16359
16848
|
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
16360
16849
|
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
@@ -16374,90 +16863,160 @@ function claudeModelPrice2(model) {
|
|
|
16374
16863
|
}
|
|
16375
16864
|
return null;
|
|
16376
16865
|
}
|
|
16377
|
-
function
|
|
16378
|
-
|
|
16866
|
+
function emptyClaudeCostAccumulator() {
|
|
16867
|
+
return {
|
|
16379
16868
|
total: 0,
|
|
16380
|
-
byDay: /* @__PURE__ */ new Map(),
|
|
16381
|
-
byModel: /* @__PURE__ */ new Map(),
|
|
16382
16869
|
inputTokens: 0,
|
|
16383
16870
|
outputTokens: 0,
|
|
16384
16871
|
cacheWriteTokens: 0,
|
|
16385
|
-
cacheReadTokens: 0
|
|
16872
|
+
cacheReadTokens: 0,
|
|
16873
|
+
byDay: /* @__PURE__ */ new Map(),
|
|
16874
|
+
byModel: /* @__PURE__ */ new Map(),
|
|
16875
|
+
byProject: /* @__PURE__ */ new Map()
|
|
16876
|
+
};
|
|
16877
|
+
}
|
|
16878
|
+
function freezeClaudeCost(acc) {
|
|
16879
|
+
return {
|
|
16880
|
+
total: acc.total,
|
|
16881
|
+
byDay: acc.byDay,
|
|
16882
|
+
byModel: acc.byModel,
|
|
16883
|
+
byProject: acc.byProject,
|
|
16884
|
+
inputTokens: acc.inputTokens,
|
|
16885
|
+
outputTokens: acc.outputTokens,
|
|
16886
|
+
cacheWriteTokens: acc.cacheWriteTokens,
|
|
16887
|
+
cacheReadTokens: acc.cacheReadTokens
|
|
16386
16888
|
};
|
|
16387
|
-
|
|
16388
|
-
|
|
16889
|
+
}
|
|
16890
|
+
function processClaudeCostProject(proj, projectsDir, start, end, acc) {
|
|
16891
|
+
const projPath = import_path36.default.join(projectsDir, proj);
|
|
16892
|
+
let files;
|
|
16893
|
+
try {
|
|
16894
|
+
const stat = import_fs35.default.statSync(projPath);
|
|
16895
|
+
if (!stat.isDirectory()) return;
|
|
16896
|
+
files = import_fs35.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
16897
|
+
} catch {
|
|
16898
|
+
return;
|
|
16899
|
+
}
|
|
16900
|
+
const startMs = start.getTime();
|
|
16901
|
+
for (const file of files) {
|
|
16902
|
+
const filePath = import_path36.default.join(projPath, file);
|
|
16903
|
+
try {
|
|
16904
|
+
if (import_fs35.default.statSync(filePath).mtimeMs < startMs) continue;
|
|
16905
|
+
} catch {
|
|
16906
|
+
continue;
|
|
16907
|
+
}
|
|
16908
|
+
try {
|
|
16909
|
+
const raw = import_fs35.default.readFileSync(filePath, "utf-8");
|
|
16910
|
+
for (const line of raw.split("\n")) {
|
|
16911
|
+
if (!line.trim()) continue;
|
|
16912
|
+
let entry;
|
|
16913
|
+
try {
|
|
16914
|
+
entry = JSON.parse(line);
|
|
16915
|
+
} catch {
|
|
16916
|
+
continue;
|
|
16917
|
+
}
|
|
16918
|
+
if (entry.type !== "assistant") continue;
|
|
16919
|
+
if (!entry.timestamp) continue;
|
|
16920
|
+
const ts = new Date(entry.timestamp);
|
|
16921
|
+
if (ts < start || ts > end) continue;
|
|
16922
|
+
const usage = entry.message?.usage;
|
|
16923
|
+
const model = entry.message?.model;
|
|
16924
|
+
if (!usage || !model) continue;
|
|
16925
|
+
const p = claudeModelPrice2(model);
|
|
16926
|
+
if (!p) continue;
|
|
16927
|
+
const inp = usage.input_tokens ?? 0;
|
|
16928
|
+
const out = usage.output_tokens ?? 0;
|
|
16929
|
+
const cw = usage.cache_creation_input_tokens ?? 0;
|
|
16930
|
+
const cr = usage.cache_read_input_tokens ?? 0;
|
|
16931
|
+
const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
|
|
16932
|
+
acc.total += cost;
|
|
16933
|
+
acc.inputTokens += inp;
|
|
16934
|
+
acc.outputTokens += out;
|
|
16935
|
+
acc.cacheWriteTokens += cw;
|
|
16936
|
+
acc.cacheReadTokens += cr;
|
|
16937
|
+
const dateKey = entry.timestamp.slice(0, 10);
|
|
16938
|
+
acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
|
|
16939
|
+
const normModel = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
|
|
16940
|
+
acc.byModel.set(normModel, (acc.byModel.get(normModel) ?? 0) + cost);
|
|
16941
|
+
const projectKey = decodeProjectDirName(proj);
|
|
16942
|
+
const projectRollup = acc.byProject.get(projectKey) ?? {
|
|
16943
|
+
cost: 0,
|
|
16944
|
+
inputTokens: 0,
|
|
16945
|
+
outputTokens: 0
|
|
16946
|
+
};
|
|
16947
|
+
projectRollup.cost += cost;
|
|
16948
|
+
projectRollup.inputTokens += inp;
|
|
16949
|
+
projectRollup.outputTokens += out;
|
|
16950
|
+
acc.byProject.set(projectKey, projectRollup);
|
|
16951
|
+
}
|
|
16952
|
+
} catch {
|
|
16953
|
+
continue;
|
|
16954
|
+
}
|
|
16955
|
+
}
|
|
16956
|
+
}
|
|
16957
|
+
function loadClaudeCost(start, end, projectsDir) {
|
|
16958
|
+
const acc = emptyClaudeCostAccumulator();
|
|
16959
|
+
if (!import_fs35.default.existsSync(projectsDir)) return freezeClaudeCost(acc);
|
|
16389
16960
|
let dirs;
|
|
16390
16961
|
try {
|
|
16391
16962
|
dirs = import_fs35.default.readdirSync(projectsDir);
|
|
16392
16963
|
} catch {
|
|
16393
|
-
return
|
|
16964
|
+
return freezeClaudeCost(acc);
|
|
16394
16965
|
}
|
|
16395
|
-
let total = 0;
|
|
16396
|
-
let inputTokens = 0;
|
|
16397
|
-
let outputTokens = 0;
|
|
16398
|
-
let cacheWriteTokens = 0;
|
|
16399
|
-
let cacheReadTokens = 0;
|
|
16400
|
-
const byDay = /* @__PURE__ */ new Map();
|
|
16401
|
-
const byModel = /* @__PURE__ */ new Map();
|
|
16402
16966
|
for (const proj of dirs) {
|
|
16403
|
-
|
|
16404
|
-
|
|
16967
|
+
processClaudeCostProject(proj, projectsDir, start, end, acc);
|
|
16968
|
+
}
|
|
16969
|
+
return freezeClaudeCost(acc);
|
|
16970
|
+
}
|
|
16971
|
+
function processCodexCostFile(filePath, start, end, acc) {
|
|
16972
|
+
let lines;
|
|
16973
|
+
try {
|
|
16974
|
+
lines = import_fs35.default.readFileSync(filePath, "utf-8").split("\n");
|
|
16975
|
+
} catch {
|
|
16976
|
+
return;
|
|
16977
|
+
}
|
|
16978
|
+
let sessionStart2 = "";
|
|
16979
|
+
let lastTotalInput = 0;
|
|
16980
|
+
let lastTotalCached = 0;
|
|
16981
|
+
let lastTotalOutput = 0;
|
|
16982
|
+
let sessionToolCalls = 0;
|
|
16983
|
+
for (const line of lines) {
|
|
16984
|
+
if (!line.trim()) continue;
|
|
16985
|
+
let entry;
|
|
16405
16986
|
try {
|
|
16406
|
-
|
|
16407
|
-
if (!stat.isDirectory()) continue;
|
|
16408
|
-
files = import_fs35.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
16987
|
+
entry = JSON.parse(line);
|
|
16409
16988
|
} catch {
|
|
16410
16989
|
continue;
|
|
16411
16990
|
}
|
|
16412
|
-
|
|
16413
|
-
|
|
16414
|
-
|
|
16415
|
-
|
|
16416
|
-
|
|
16417
|
-
|
|
16418
|
-
|
|
16419
|
-
|
|
16420
|
-
|
|
16421
|
-
|
|
16422
|
-
|
|
16423
|
-
|
|
16424
|
-
|
|
16425
|
-
|
|
16426
|
-
if (ts < start || ts > end) continue;
|
|
16427
|
-
const usage = entry.message?.usage;
|
|
16428
|
-
const model = entry.message?.model;
|
|
16429
|
-
if (!usage || !model) continue;
|
|
16430
|
-
const p = claudeModelPrice2(model);
|
|
16431
|
-
if (!p) continue;
|
|
16432
|
-
const inp = usage.input_tokens ?? 0;
|
|
16433
|
-
const out = usage.output_tokens ?? 0;
|
|
16434
|
-
const cw = usage.cache_creation_input_tokens ?? 0;
|
|
16435
|
-
const cr = usage.cache_read_input_tokens ?? 0;
|
|
16436
|
-
const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
|
|
16437
|
-
total += cost;
|
|
16438
|
-
inputTokens += inp;
|
|
16439
|
-
outputTokens += out;
|
|
16440
|
-
cacheWriteTokens += cw;
|
|
16441
|
-
cacheReadTokens += cr;
|
|
16442
|
-
const dateKey = entry.timestamp.slice(0, 10);
|
|
16443
|
-
byDay.set(dateKey, (byDay.get(dateKey) ?? 0) + cost);
|
|
16444
|
-
const normModel = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
|
|
16445
|
-
byModel.set(normModel, (byModel.get(normModel) ?? 0) + cost);
|
|
16446
|
-
}
|
|
16447
|
-
} catch {
|
|
16448
|
-
continue;
|
|
16449
|
-
}
|
|
16991
|
+
const p = entry.payload ?? {};
|
|
16992
|
+
if (entry.type === "session_meta") {
|
|
16993
|
+
sessionStart2 = String(p["timestamp"] ?? "");
|
|
16994
|
+
continue;
|
|
16995
|
+
}
|
|
16996
|
+
if (entry.type === "event_msg" && p["type"] === "token_count") {
|
|
16997
|
+
const info = p["info"] ?? {};
|
|
16998
|
+
const usage = info["total_token_usage"] ?? {};
|
|
16999
|
+
lastTotalInput = usage["input_tokens"] ?? lastTotalInput;
|
|
17000
|
+
lastTotalCached = usage["cached_input_tokens"] ?? lastTotalCached;
|
|
17001
|
+
lastTotalOutput = usage["output_tokens"] ?? lastTotalOutput;
|
|
17002
|
+
}
|
|
17003
|
+
if (entry.type === "response_item" && p["type"] === "function_call") {
|
|
17004
|
+
sessionToolCalls++;
|
|
16450
17005
|
}
|
|
16451
17006
|
}
|
|
16452
|
-
|
|
17007
|
+
if (!sessionStart2) return;
|
|
17008
|
+
const ts = new Date(sessionStart2);
|
|
17009
|
+
if (ts < start || ts > end) return;
|
|
17010
|
+
const nonCached = Math.max(0, lastTotalInput - lastTotalCached);
|
|
17011
|
+
const cost = nonCached * 5e-6 + lastTotalCached * 25e-7 + lastTotalOutput * 15e-6;
|
|
17012
|
+
acc.total += cost;
|
|
17013
|
+
acc.toolCalls += sessionToolCalls;
|
|
17014
|
+
const dateKey = sessionStart2.slice(0, 10);
|
|
17015
|
+
acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
|
|
16453
17016
|
}
|
|
16454
|
-
function
|
|
16455
|
-
const sessionsBase = import_path36.default.join(import_os31.default.homedir(), ".codex", "sessions");
|
|
16456
|
-
const byDay = /* @__PURE__ */ new Map();
|
|
16457
|
-
let total = 0;
|
|
16458
|
-
let toolCalls = 0;
|
|
16459
|
-
if (!import_fs35.default.existsSync(sessionsBase)) return { total, byDay, toolCalls };
|
|
17017
|
+
function listCodexSessionFiles(sessionsBase) {
|
|
16460
17018
|
const jsonlFiles = [];
|
|
17019
|
+
if (!import_fs35.default.existsSync(sessionsBase)) return jsonlFiles;
|
|
16461
17020
|
try {
|
|
16462
17021
|
for (const year of import_fs35.default.readdirSync(sessionsBase)) {
|
|
16463
17022
|
const yearPath = import_path36.default.join(sessionsBase, year);
|
|
@@ -16487,495 +17046,742 @@ function loadCodexCost(start, end) {
|
|
|
16487
17046
|
}
|
|
16488
17047
|
}
|
|
16489
17048
|
} catch {
|
|
16490
|
-
return
|
|
17049
|
+
return [];
|
|
16491
17050
|
}
|
|
16492
|
-
|
|
16493
|
-
|
|
17051
|
+
return jsonlFiles;
|
|
17052
|
+
}
|
|
17053
|
+
function loadCodexCost(start, end, sessionsBase) {
|
|
17054
|
+
const acc = { total: 0, toolCalls: 0, byDay: /* @__PURE__ */ new Map() };
|
|
17055
|
+
const files = listCodexSessionFiles(sessionsBase);
|
|
17056
|
+
for (const filePath of files) {
|
|
17057
|
+
processCodexCostFile(filePath, start, end, acc);
|
|
17058
|
+
}
|
|
17059
|
+
return { total: acc.total, byDay: acc.byDay, toolCalls: acc.toolCalls };
|
|
17060
|
+
}
|
|
17061
|
+
var GEMINI_FALLBACK_MODELS = ["gemini-2.5-flash", "gemini-2.0-flash"];
|
|
17062
|
+
function geminiPriceFor(model) {
|
|
17063
|
+
let tuple = pricingFor(model);
|
|
17064
|
+
if (!tuple && /^gemini-/i.test(model)) {
|
|
17065
|
+
for (const proxy of GEMINI_FALLBACK_MODELS) {
|
|
17066
|
+
tuple = pricingFor(proxy);
|
|
17067
|
+
if (tuple) break;
|
|
17068
|
+
}
|
|
17069
|
+
}
|
|
17070
|
+
if (!tuple) return null;
|
|
17071
|
+
return { input: tuple[0], output: tuple[1], cacheRead: tuple[3] || tuple[0] };
|
|
17072
|
+
}
|
|
17073
|
+
function emptyGeminiAccumulator() {
|
|
17074
|
+
return {
|
|
17075
|
+
total: 0,
|
|
17076
|
+
inputTokens: 0,
|
|
17077
|
+
outputTokens: 0,
|
|
17078
|
+
cacheReadTokens: 0,
|
|
17079
|
+
byDay: /* @__PURE__ */ new Map(),
|
|
17080
|
+
byProject: /* @__PURE__ */ new Map()
|
|
17081
|
+
};
|
|
17082
|
+
}
|
|
17083
|
+
function freezeGeminiCost(acc) {
|
|
17084
|
+
return {
|
|
17085
|
+
total: acc.total,
|
|
17086
|
+
byDay: acc.byDay,
|
|
17087
|
+
byProject: acc.byProject,
|
|
17088
|
+
inputTokens: acc.inputTokens,
|
|
17089
|
+
outputTokens: acc.outputTokens,
|
|
17090
|
+
cacheReadTokens: acc.cacheReadTokens
|
|
17091
|
+
};
|
|
17092
|
+
}
|
|
17093
|
+
function processGeminiCostFile(filePath, projectKey, start, end, acc) {
|
|
17094
|
+
const startMs = start.getTime();
|
|
17095
|
+
try {
|
|
17096
|
+
if (import_fs35.default.statSync(filePath).mtimeMs < startMs) return;
|
|
17097
|
+
} catch {
|
|
17098
|
+
return;
|
|
17099
|
+
}
|
|
17100
|
+
let raw;
|
|
17101
|
+
try {
|
|
17102
|
+
raw = import_fs35.default.readFileSync(filePath, "utf-8");
|
|
17103
|
+
} catch {
|
|
17104
|
+
return;
|
|
17105
|
+
}
|
|
17106
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
17107
|
+
for (const line of raw.split("\n")) {
|
|
17108
|
+
if (!line.trim()) continue;
|
|
17109
|
+
let entry;
|
|
16494
17110
|
try {
|
|
16495
|
-
|
|
17111
|
+
entry = JSON.parse(line);
|
|
16496
17112
|
} catch {
|
|
16497
17113
|
continue;
|
|
16498
17114
|
}
|
|
16499
|
-
|
|
16500
|
-
|
|
16501
|
-
|
|
16502
|
-
|
|
16503
|
-
|
|
16504
|
-
for (const line of lines) {
|
|
16505
|
-
if (!line.trim()) continue;
|
|
16506
|
-
let entry;
|
|
16507
|
-
try {
|
|
16508
|
-
entry = JSON.parse(line);
|
|
16509
|
-
} catch {
|
|
16510
|
-
continue;
|
|
16511
|
-
}
|
|
16512
|
-
const p = entry.payload ?? {};
|
|
16513
|
-
if (entry.type === "session_meta") {
|
|
16514
|
-
sessionStart2 = String(p["timestamp"] ?? "");
|
|
16515
|
-
continue;
|
|
16516
|
-
}
|
|
16517
|
-
if (entry.type === "event_msg" && p["type"] === "token_count") {
|
|
16518
|
-
const info = p["info"] ?? {};
|
|
16519
|
-
const usage = info["total_token_usage"] ?? {};
|
|
16520
|
-
lastTotalInput = usage["input_tokens"] ?? lastTotalInput;
|
|
16521
|
-
lastTotalCached = usage["cached_input_tokens"] ?? lastTotalCached;
|
|
16522
|
-
lastTotalOutput = usage["output_tokens"] ?? lastTotalOutput;
|
|
16523
|
-
}
|
|
16524
|
-
if (entry.type === "response_item" && p["type"] === "function_call") {
|
|
16525
|
-
sessionToolCalls++;
|
|
16526
|
-
}
|
|
17115
|
+
if (entry.type !== "gemini") continue;
|
|
17116
|
+
if (!entry.tokens || !entry.model || !entry.timestamp) continue;
|
|
17117
|
+
if (entry.id) {
|
|
17118
|
+
if (seenIds.has(entry.id)) continue;
|
|
17119
|
+
seenIds.add(entry.id);
|
|
16527
17120
|
}
|
|
16528
|
-
|
|
16529
|
-
const ts = new Date(sessionStart2);
|
|
17121
|
+
const ts = new Date(entry.timestamp);
|
|
16530
17122
|
if (ts < start || ts > end) continue;
|
|
16531
|
-
const
|
|
16532
|
-
|
|
16533
|
-
|
|
16534
|
-
|
|
16535
|
-
const
|
|
16536
|
-
|
|
17123
|
+
const price = geminiPriceFor(entry.model);
|
|
17124
|
+
if (!price) continue;
|
|
17125
|
+
const inp = entry.tokens.input ?? 0;
|
|
17126
|
+
const out = entry.tokens.output ?? 0;
|
|
17127
|
+
const cached = Math.min(entry.tokens.cached ?? 0, inp);
|
|
17128
|
+
const fresh = Math.max(0, inp - cached);
|
|
17129
|
+
const cost = fresh * price.input + cached * price.cacheRead + out * price.output;
|
|
17130
|
+
acc.total += cost;
|
|
17131
|
+
acc.inputTokens += inp;
|
|
17132
|
+
acc.outputTokens += out;
|
|
17133
|
+
acc.cacheReadTokens += cached;
|
|
17134
|
+
const dateKey = entry.timestamp.slice(0, 10);
|
|
17135
|
+
acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
|
|
17136
|
+
const rollup = acc.byProject.get(projectKey) ?? {
|
|
17137
|
+
cost: 0,
|
|
17138
|
+
inputTokens: 0,
|
|
17139
|
+
outputTokens: 0
|
|
17140
|
+
};
|
|
17141
|
+
rollup.cost += cost;
|
|
17142
|
+
rollup.inputTokens += inp;
|
|
17143
|
+
rollup.outputTokens += out;
|
|
17144
|
+
acc.byProject.set(projectKey, rollup);
|
|
16537
17145
|
}
|
|
16538
|
-
|
|
17146
|
+
}
|
|
17147
|
+
function listGeminiSessionFiles(geminiTmpDir) {
|
|
17148
|
+
const out = [];
|
|
17149
|
+
let dirs;
|
|
17150
|
+
try {
|
|
17151
|
+
if (!import_fs35.default.statSync(geminiTmpDir).isDirectory()) return out;
|
|
17152
|
+
dirs = import_fs35.default.readdirSync(geminiTmpDir);
|
|
17153
|
+
} catch {
|
|
17154
|
+
return out;
|
|
17155
|
+
}
|
|
17156
|
+
for (const proj of dirs) {
|
|
17157
|
+
const chatsDir = import_path36.default.join(geminiTmpDir, proj, "chats");
|
|
17158
|
+
let files;
|
|
17159
|
+
try {
|
|
17160
|
+
if (!import_fs35.default.statSync(chatsDir).isDirectory()) continue;
|
|
17161
|
+
files = import_fs35.default.readdirSync(chatsDir);
|
|
17162
|
+
} catch {
|
|
17163
|
+
continue;
|
|
17164
|
+
}
|
|
17165
|
+
for (const f of files) {
|
|
17166
|
+
if (!f.endsWith(".jsonl")) continue;
|
|
17167
|
+
out.push({ projectKey: proj, file: import_path36.default.join(chatsDir, f) });
|
|
17168
|
+
}
|
|
17169
|
+
}
|
|
17170
|
+
return out;
|
|
17171
|
+
}
|
|
17172
|
+
function loadGeminiCost(start, end, geminiTmpDir) {
|
|
17173
|
+
const acc = emptyGeminiAccumulator();
|
|
17174
|
+
if (!import_fs35.default.existsSync(geminiTmpDir)) return freezeGeminiCost(acc);
|
|
17175
|
+
for (const { projectKey, file } of listGeminiSessionFiles(geminiTmpDir)) {
|
|
17176
|
+
processGeminiCostFile(file, projectKey, start, end, acc);
|
|
17177
|
+
}
|
|
17178
|
+
return freezeGeminiCost(acc);
|
|
17179
|
+
}
|
|
17180
|
+
function aggregateReportFromAudit(period, opts = {}) {
|
|
17181
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
17182
|
+
const auditLogPath = opts.auditLogPath ?? import_path36.default.join(import_os31.default.homedir(), ".node9", "audit.log");
|
|
17183
|
+
const claudeProjectsDir = opts.claudeProjectsDir ?? import_path36.default.join(import_os31.default.homedir(), ".claude", "projects");
|
|
17184
|
+
const codexSessionsDir = opts.codexSessionsDir ?? import_path36.default.join(import_os31.default.homedir(), ".codex", "sessions");
|
|
17185
|
+
const geminiTmpDir = opts.geminiTmpDir ?? import_path36.default.join(import_os31.default.homedir(), ".gemini", "tmp");
|
|
17186
|
+
const hasAuditFile = import_fs35.default.existsSync(auditLogPath);
|
|
17187
|
+
const allEntries = opts.preloadedAuditEntries ?? parseAuditLog(auditLogPath);
|
|
17188
|
+
const unackedDlp = allEntries.filter((e) => e.source === "response-dlp");
|
|
17189
|
+
const { start, end } = getDateRange(period, now);
|
|
17190
|
+
const responseDlpEntries = allEntries.filter((e) => {
|
|
17191
|
+
if (e.source !== "response-dlp") return false;
|
|
17192
|
+
const ts = new Date(e.ts);
|
|
17193
|
+
return ts >= start && ts <= end;
|
|
17194
|
+
}).map((e) => {
|
|
17195
|
+
const raw = e;
|
|
17196
|
+
return {
|
|
17197
|
+
ts: e.ts,
|
|
17198
|
+
dlpPattern: typeof raw.dlpPattern === "string" ? raw.dlpPattern : void 0,
|
|
17199
|
+
dlpSample: typeof raw.dlpSample === "string" ? raw.dlpSample : void 0
|
|
17200
|
+
};
|
|
17201
|
+
});
|
|
17202
|
+
const claudeCost = opts.preloadedClaudeCost ?? loadClaudeCost(start, end, claudeProjectsDir);
|
|
17203
|
+
const codexCost = opts.preloadedCodexCost ?? loadCodexCost(start, end, codexSessionsDir);
|
|
17204
|
+
const geminiCost = opts.preloadedGeminiCost ?? loadGeminiCost(start, end, geminiTmpDir);
|
|
17205
|
+
for (const [day, c] of codexCost.byDay) {
|
|
17206
|
+
claudeCost.byDay.set(day, (claudeCost.byDay.get(day) ?? 0) + c);
|
|
17207
|
+
}
|
|
17208
|
+
for (const [day, c] of geminiCost.byDay) {
|
|
17209
|
+
claudeCost.byDay.set(day, (claudeCost.byDay.get(day) ?? 0) + c);
|
|
17210
|
+
}
|
|
17211
|
+
for (const [geminiKey, gRollup] of geminiCost.byProject) {
|
|
17212
|
+
let mergedInto = null;
|
|
17213
|
+
for (const claudeKey of claudeCost.byProject.keys()) {
|
|
17214
|
+
const claudeBase = claudeKey.match(/[^/\\]+$/)?.[0] ?? claudeKey;
|
|
17215
|
+
if (claudeBase === geminiKey) {
|
|
17216
|
+
mergedInto = claudeKey;
|
|
17217
|
+
break;
|
|
17218
|
+
}
|
|
17219
|
+
}
|
|
17220
|
+
const targetKey = mergedInto ?? geminiKey;
|
|
17221
|
+
const existing = claudeCost.byProject.get(targetKey) ?? {
|
|
17222
|
+
cost: 0,
|
|
17223
|
+
inputTokens: 0,
|
|
17224
|
+
outputTokens: 0
|
|
17225
|
+
};
|
|
17226
|
+
existing.cost += gRollup.cost;
|
|
17227
|
+
existing.inputTokens += gRollup.inputTokens;
|
|
17228
|
+
existing.outputTokens += gRollup.outputTokens;
|
|
17229
|
+
claudeCost.byProject.set(targetKey, existing);
|
|
17230
|
+
}
|
|
17231
|
+
const periodMs = end.getTime() - start.getTime();
|
|
17232
|
+
const priorEnd = new Date(start.getTime() - 1);
|
|
17233
|
+
const priorStart = new Date(start.getTime() - periodMs);
|
|
17234
|
+
const priorEntries = allEntries.filter((e) => {
|
|
17235
|
+
if (e.source === "post-hook") return false;
|
|
17236
|
+
const ts = new Date(e.ts);
|
|
17237
|
+
return ts >= priorStart && ts <= priorEnd;
|
|
17238
|
+
});
|
|
17239
|
+
const priorBlocked = priorEntries.filter((e) => !isAllow(e.decision)).length;
|
|
17240
|
+
const priorBlockRate = priorEntries.length > 0 ? priorBlocked / priorEntries.length : null;
|
|
17241
|
+
const excludeTests = opts.excludeTests === true;
|
|
17242
|
+
const testTs = excludeTests ? buildTestTimestamps(allEntries) : /* @__PURE__ */ new Set();
|
|
17243
|
+
let excludedTests = 0;
|
|
17244
|
+
const entries = allEntries.filter((e) => {
|
|
17245
|
+
if (e.source === "post-hook") return false;
|
|
17246
|
+
if (e.source === "response-dlp") return false;
|
|
17247
|
+
const ts = new Date(e.ts);
|
|
17248
|
+
if (ts < start || ts > end) return false;
|
|
17249
|
+
if (excludeTests && isTestEntry(e, testTs)) {
|
|
17250
|
+
excludedTests++;
|
|
17251
|
+
return false;
|
|
17252
|
+
}
|
|
17253
|
+
return true;
|
|
17254
|
+
});
|
|
17255
|
+
let userApproved = 0;
|
|
17256
|
+
let userDenied = 0;
|
|
17257
|
+
let timedOut = 0;
|
|
17258
|
+
let hardBlocked = 0;
|
|
17259
|
+
let dlpBlocked = 0;
|
|
17260
|
+
let observeDlp = 0;
|
|
17261
|
+
let loopHits = 0;
|
|
17262
|
+
let testPasses = 0;
|
|
17263
|
+
let testFails = 0;
|
|
17264
|
+
const toolMap = /* @__PURE__ */ new Map();
|
|
17265
|
+
const blockMap = /* @__PURE__ */ new Map();
|
|
17266
|
+
const ruleMap = /* @__PURE__ */ new Map();
|
|
17267
|
+
const agentMap = /* @__PURE__ */ new Map();
|
|
17268
|
+
const mcpMap = /* @__PURE__ */ new Map();
|
|
17269
|
+
const dailyMap = /* @__PURE__ */ new Map();
|
|
17270
|
+
const hourMap = /* @__PURE__ */ new Map();
|
|
17271
|
+
for (const e of entries) {
|
|
17272
|
+
const allow = isAllow(e.decision);
|
|
17273
|
+
const dateKey = e.ts.slice(0, 10);
|
|
17274
|
+
const userInteracted = e.source === "daemon";
|
|
17275
|
+
if (userInteracted) {
|
|
17276
|
+
if (allow) userApproved++;
|
|
17277
|
+
else userDenied++;
|
|
17278
|
+
} else if (!allow) {
|
|
17279
|
+
if (e.checkedBy === "timeout") timedOut++;
|
|
17280
|
+
else if (e.checkedBy === "observe-mode-dlp-would-block") observeDlp++;
|
|
17281
|
+
else if (isDlp(e.checkedBy)) dlpBlocked++;
|
|
17282
|
+
else if (e.checkedBy !== "loop-detected") hardBlocked++;
|
|
17283
|
+
}
|
|
17284
|
+
if (e.checkedBy === "loop-detected") loopHits++;
|
|
17285
|
+
const t = toolMap.get(e.tool) ?? { calls: 0, blocked: 0 };
|
|
17286
|
+
t.calls++;
|
|
17287
|
+
if (!allow) t.blocked++;
|
|
17288
|
+
toolMap.set(e.tool, t);
|
|
17289
|
+
if (!allow && e.checkedBy) {
|
|
17290
|
+
blockMap.set(e.checkedBy, (blockMap.get(e.checkedBy) ?? 0) + 1);
|
|
17291
|
+
}
|
|
17292
|
+
if (!allow && e.ruleName) {
|
|
17293
|
+
ruleMap.set(e.ruleName, (ruleMap.get(e.ruleName) ?? 0) + 1);
|
|
17294
|
+
}
|
|
17295
|
+
if (e.agent) agentMap.set(e.agent, (agentMap.get(e.agent) ?? 0) + 1);
|
|
17296
|
+
if (e.mcpServer) mcpMap.set(e.mcpServer, (mcpMap.get(e.mcpServer) ?? 0) + 1);
|
|
17297
|
+
const hour = new Date(e.ts).getHours();
|
|
17298
|
+
hourMap.set(hour, (hourMap.get(hour) ?? 0) + 1);
|
|
17299
|
+
const d = dailyMap.get(dateKey) ?? { calls: 0, blocked: 0 };
|
|
17300
|
+
d.calls++;
|
|
17301
|
+
if (!allow) d.blocked++;
|
|
17302
|
+
dailyMap.set(dateKey, d);
|
|
17303
|
+
}
|
|
17304
|
+
for (const e of allEntries) {
|
|
17305
|
+
if (e.source !== "test-result") continue;
|
|
17306
|
+
const ts = new Date(e.ts);
|
|
17307
|
+
if (ts < start || ts > end) continue;
|
|
17308
|
+
if (e.testResult === "pass") testPasses++;
|
|
17309
|
+
else if (e.testResult === "fail") testFails++;
|
|
17310
|
+
}
|
|
17311
|
+
if (codexCost.toolCalls > 0) {
|
|
17312
|
+
agentMap.set("Codex", (agentMap.get("Codex") ?? 0) + codexCost.toolCalls);
|
|
17313
|
+
}
|
|
17314
|
+
const data = {
|
|
17315
|
+
period,
|
|
17316
|
+
start,
|
|
17317
|
+
end,
|
|
17318
|
+
excludedTests,
|
|
17319
|
+
total: entries.length,
|
|
17320
|
+
userApproved,
|
|
17321
|
+
userDenied,
|
|
17322
|
+
timedOut,
|
|
17323
|
+
hardBlocked,
|
|
17324
|
+
dlpBlocked,
|
|
17325
|
+
observeDlp,
|
|
17326
|
+
loopHits,
|
|
17327
|
+
testPasses,
|
|
17328
|
+
testFails,
|
|
17329
|
+
unackedDlp: unackedDlp.length,
|
|
17330
|
+
priorBlockRate,
|
|
17331
|
+
cost: {
|
|
17332
|
+
claudeUSD: claudeCost.total,
|
|
17333
|
+
codexUSD: codexCost.total,
|
|
17334
|
+
geminiUSD: geminiCost.total,
|
|
17335
|
+
inputTokens: claudeCost.inputTokens + geminiCost.inputTokens,
|
|
17336
|
+
outputTokens: claudeCost.outputTokens + geminiCost.outputTokens,
|
|
17337
|
+
cacheWriteTokens: claudeCost.cacheWriteTokens,
|
|
17338
|
+
cacheReadTokens: claudeCost.cacheReadTokens + geminiCost.cacheReadTokens,
|
|
17339
|
+
byDay: claudeCost.byDay,
|
|
17340
|
+
byModel: claudeCost.byModel,
|
|
17341
|
+
byProject: claudeCost.byProject
|
|
17342
|
+
},
|
|
17343
|
+
toolMap,
|
|
17344
|
+
blockMap,
|
|
17345
|
+
ruleMap,
|
|
17346
|
+
agentMap,
|
|
17347
|
+
mcpMap,
|
|
17348
|
+
dailyMap,
|
|
17349
|
+
hourMap,
|
|
17350
|
+
generatedAt: now.toISOString()
|
|
17351
|
+
};
|
|
17352
|
+
return { data, hasAuditFile, responseDlpEntries };
|
|
17353
|
+
}
|
|
17354
|
+
|
|
17355
|
+
// src/cli/render/report-json.ts
|
|
17356
|
+
function buildReportJson(input) {
|
|
17357
|
+
const totalBlocked = input.timedOut + input.hardBlocked + input.dlpBlocked + input.loopHits + input.userDenied;
|
|
17358
|
+
const blockRate = input.total > 0 ? totalBlocked / input.total : 0;
|
|
17359
|
+
const deltaPct = input.priorBlockRate === null ? null : Math.round((blockRate - input.priorBlockRate) * 100);
|
|
17360
|
+
return {
|
|
17361
|
+
schemaVersion: 1,
|
|
17362
|
+
generatedAt: input.generatedAt,
|
|
17363
|
+
period: input.period,
|
|
17364
|
+
range: { start: input.start.toISOString(), end: input.end.toISOString() },
|
|
17365
|
+
excludedTests: input.excludedTests,
|
|
17366
|
+
totals: {
|
|
17367
|
+
events: input.total,
|
|
17368
|
+
blocked: totalBlocked,
|
|
17369
|
+
blockRate,
|
|
17370
|
+
userApproved: input.userApproved,
|
|
17371
|
+
userDenied: input.userDenied,
|
|
17372
|
+
timedOut: input.timedOut,
|
|
17373
|
+
hardBlocked: input.hardBlocked,
|
|
17374
|
+
dlpBlocked: input.dlpBlocked,
|
|
17375
|
+
observeDlp: input.observeDlp,
|
|
17376
|
+
loopHits: input.loopHits,
|
|
17377
|
+
unackedDlp: input.unackedDlp
|
|
17378
|
+
},
|
|
17379
|
+
tests: {
|
|
17380
|
+
passes: input.testPasses,
|
|
17381
|
+
fails: input.testFails
|
|
17382
|
+
},
|
|
17383
|
+
cost: {
|
|
17384
|
+
totalUSD: input.cost.claudeUSD + input.cost.codexUSD + input.cost.geminiUSD,
|
|
17385
|
+
claudeUSD: input.cost.claudeUSD,
|
|
17386
|
+
codexUSD: input.cost.codexUSD,
|
|
17387
|
+
geminiUSD: input.cost.geminiUSD,
|
|
17388
|
+
inputTokens: input.cost.inputTokens,
|
|
17389
|
+
outputTokens: input.cost.outputTokens,
|
|
17390
|
+
cacheWriteTokens: input.cost.cacheWriteTokens,
|
|
17391
|
+
cacheReadTokens: input.cost.cacheReadTokens,
|
|
17392
|
+
byDay: [...input.cost.byDay.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([day, usd]) => ({ day, usd })),
|
|
17393
|
+
byModel: [...input.cost.byModel.entries()].sort((a, b) => b[1] - a[1]).map(([model, usd]) => ({ model, usd }))
|
|
17394
|
+
},
|
|
17395
|
+
byTool: [...input.toolMap.entries()].sort((a, b) => b[1].calls - a[1].calls).map(([tool, v]) => ({ tool, calls: v.calls, blocked: v.blocked })),
|
|
17396
|
+
byBlock: [...input.blockMap.entries()].sort((a, b) => b[1] - a[1]).map(([rule, count]) => ({ rule, count })),
|
|
17397
|
+
byAgent: [...input.agentMap.entries()].sort((a, b) => b[1] - a[1]).map(([agent, calls]) => ({ agent, calls })),
|
|
17398
|
+
byMcp: [...input.mcpMap.entries()].sort((a, b) => b[1] - a[1]).map(([server, calls]) => ({ server, calls })),
|
|
17399
|
+
byDay: [...input.dailyMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([day, v]) => ({ day, calls: v.calls, blocked: v.blocked })),
|
|
17400
|
+
byHour: [...input.hourMap.entries()].sort((a, b) => a[0] - b[0]).map(([hour, calls]) => ({ hour, calls })),
|
|
17401
|
+
trend: {
|
|
17402
|
+
priorBlockRate: input.priorBlockRate,
|
|
17403
|
+
deltaPct
|
|
17404
|
+
}
|
|
17405
|
+
};
|
|
17406
|
+
}
|
|
17407
|
+
|
|
17408
|
+
// src/cli/commands/report.ts
|
|
17409
|
+
var BLOCK_REASON_LABELS = {
|
|
17410
|
+
timeout: "Approval timeout",
|
|
17411
|
+
"smart-rule-block": "Smart rule",
|
|
17412
|
+
"observe-mode-dlp-would-block": "DLP (observe)",
|
|
17413
|
+
"persistent-deny": "Persistent deny",
|
|
17414
|
+
"local-decision": "User denied",
|
|
17415
|
+
"dlp-block": "DLP block",
|
|
17416
|
+
"loop-detected": "Loop detected"
|
|
17417
|
+
};
|
|
17418
|
+
function humanBlockReason(reason) {
|
|
17419
|
+
return BLOCK_REASON_LABELS[reason] ?? reason;
|
|
17420
|
+
}
|
|
17421
|
+
function barStr(value, max, width) {
|
|
17422
|
+
if (max === 0 || width <= 0) return "\u2591".repeat(width);
|
|
17423
|
+
const filled = Math.max(1, Math.round(value / max * width));
|
|
17424
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
17425
|
+
}
|
|
17426
|
+
function colorBar(value, max, width) {
|
|
17427
|
+
const s = barStr(value, max, width);
|
|
17428
|
+
const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
|
|
17429
|
+
return import_chalk13.default.cyan(s.slice(0, filled)) + import_chalk13.default.dim(s.slice(filled));
|
|
17430
|
+
}
|
|
17431
|
+
function fmtDate(d) {
|
|
17432
|
+
const date = typeof d === "string" ? /* @__PURE__ */ new Date(d + "T12:00:00") : d;
|
|
17433
|
+
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
17434
|
+
}
|
|
17435
|
+
function num2(n) {
|
|
17436
|
+
return n.toLocaleString();
|
|
17437
|
+
}
|
|
17438
|
+
function fmtCost2(usd) {
|
|
17439
|
+
if (usd < 1e-3) return "< $0.001";
|
|
17440
|
+
if (usd < 1) return "$" + usd.toFixed(4);
|
|
17441
|
+
return "$" + usd.toFixed(2);
|
|
16539
17442
|
}
|
|
16540
17443
|
function registerReportCommand(program2) {
|
|
16541
17444
|
program2.command("report").description("Activity and security report \u2014 what Claude did, what was blocked").option("--period <period>", "today | 7d | 30d | month", "7d").option("--no-tests", "exclude test runner calls (npm test, vitest, pytest\u2026) from stats").option("--json", "Emit machine-readable JSON to stdout (suppresses renderer)").action((options) => {
|
|
16542
|
-
const period = ["today", "7d", "30d", "month"].includes(
|
|
17445
|
+
const period = ["today", "7d", "30d", "90d", "month"].includes(
|
|
16543
17446
|
options.period
|
|
16544
17447
|
) ? options.period : "7d";
|
|
16545
|
-
const
|
|
16546
|
-
const
|
|
16547
|
-
|
|
16548
|
-
|
|
17448
|
+
const excludeTests = options.tests === false;
|
|
17449
|
+
const { data, hasAuditFile, responseDlpEntries } = aggregateReportFromAudit(period, {
|
|
17450
|
+
excludeTests
|
|
17451
|
+
});
|
|
17452
|
+
if (data.unackedDlp > 0 && !options.json) {
|
|
16549
17453
|
console.log("");
|
|
16550
17454
|
console.log(
|
|
16551
17455
|
import_chalk13.default.bgRed.white.bold(
|
|
16552
|
-
` \u26A0\uFE0F DLP ALERT: ${unackedDlp
|
|
17456
|
+
` \u26A0\uFE0F DLP ALERT: ${data.unackedDlp} secret${data.unackedDlp !== 1 ? "s" : ""} found in Claude response text `
|
|
16553
17457
|
) + " " + import_chalk13.default.yellow("\u2192 run: node9 dlp")
|
|
16554
17458
|
);
|
|
16555
17459
|
}
|
|
16556
|
-
if (
|
|
17460
|
+
if (!hasAuditFile && !options.json) {
|
|
16557
17461
|
console.log(
|
|
16558
17462
|
import_chalk13.default.yellow("\n No audit data found. Run node9 with Claude Code to generate entries.\n")
|
|
16559
17463
|
);
|
|
16560
17464
|
return;
|
|
16561
17465
|
}
|
|
16562
|
-
|
|
16563
|
-
|
|
16564
|
-
|
|
16565
|
-
|
|
16566
|
-
|
|
16567
|
-
|
|
16568
|
-
outputTokens: costOutputTokens,
|
|
16569
|
-
cacheWriteTokens: costCacheWrite,
|
|
16570
|
-
cacheReadTokens: costCacheRead
|
|
16571
|
-
} = loadClaudeCost(start, end);
|
|
16572
|
-
const {
|
|
16573
|
-
total: codexCostUSD,
|
|
16574
|
-
byDay: codexCostByDay,
|
|
16575
|
-
toolCalls: codexToolCalls
|
|
16576
|
-
} = loadCodexCost(start, end);
|
|
16577
|
-
const costUSD = claudeCostUSD + codexCostUSD;
|
|
16578
|
-
for (const [day, c] of codexCostByDay) {
|
|
16579
|
-
costByDay.set(day, (costByDay.get(day) ?? 0) + c);
|
|
16580
|
-
}
|
|
16581
|
-
const periodMs = end.getTime() - start.getTime();
|
|
16582
|
-
const priorEnd = new Date(start.getTime() - 1);
|
|
16583
|
-
const priorStart = new Date(start.getTime() - periodMs);
|
|
16584
|
-
const priorEntries = allEntries.filter((e) => {
|
|
16585
|
-
if (e.source === "post-hook") return false;
|
|
16586
|
-
const ts = new Date(e.ts);
|
|
16587
|
-
return ts >= priorStart && ts <= priorEnd;
|
|
16588
|
-
});
|
|
16589
|
-
const priorBlocked = priorEntries.filter((e) => !isAllow(e.decision)).length;
|
|
16590
|
-
const priorBlockRate = priorEntries.length > 0 ? priorBlocked / priorEntries.length : null;
|
|
16591
|
-
const excludeTests = options.tests === false;
|
|
16592
|
-
const testTs = excludeTests ? buildTestTimestamps(allEntries) : /* @__PURE__ */ new Set();
|
|
16593
|
-
let filteredTestCount = 0;
|
|
16594
|
-
const entries = allEntries.filter((e) => {
|
|
16595
|
-
if (e.source === "post-hook") return false;
|
|
16596
|
-
if (e.source === "response-dlp") return false;
|
|
16597
|
-
const ts = new Date(e.ts);
|
|
16598
|
-
if (ts < start || ts > end) return false;
|
|
16599
|
-
if (excludeTests && isTestEntry(e, testTs)) {
|
|
16600
|
-
filteredTestCount++;
|
|
16601
|
-
return false;
|
|
16602
|
-
}
|
|
16603
|
-
return true;
|
|
16604
|
-
});
|
|
16605
|
-
if (entries.length === 0 && !options.json) {
|
|
17466
|
+
if (options.json) {
|
|
17467
|
+
const envelope = buildReportJson(data);
|
|
17468
|
+
process.stdout.write(JSON.stringify(envelope, null, 2) + "\n");
|
|
17469
|
+
return;
|
|
17470
|
+
}
|
|
17471
|
+
if (data.total === 0) {
|
|
16606
17472
|
console.log(import_chalk13.default.yellow(`
|
|
16607
17473
|
No activity for period "${period}".
|
|
16608
17474
|
`));
|
|
16609
17475
|
return;
|
|
16610
17476
|
}
|
|
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
|
-
|
|
16696
|
-
|
|
16697
|
-
|
|
16698
|
-
|
|
16699
|
-
|
|
16700
|
-
|
|
16701
|
-
|
|
16702
|
-
|
|
16703
|
-
|
|
16704
|
-
|
|
16705
|
-
|
|
16706
|
-
|
|
16707
|
-
|
|
16708
|
-
|
|
16709
|
-
|
|
16710
|
-
|
|
16711
|
-
|
|
16712
|
-
|
|
16713
|
-
|
|
16714
|
-
|
|
16715
|
-
|
|
16716
|
-
|
|
16717
|
-
|
|
16718
|
-
|
|
16719
|
-
|
|
16720
|
-
|
|
16721
|
-
|
|
16722
|
-
|
|
16723
|
-
}
|
|
17477
|
+
renderTerminalReport(data, responseDlpEntries, excludeTests);
|
|
17478
|
+
});
|
|
17479
|
+
}
|
|
17480
|
+
function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
17481
|
+
const {
|
|
17482
|
+
period,
|
|
17483
|
+
start,
|
|
17484
|
+
end,
|
|
17485
|
+
total,
|
|
17486
|
+
excludedTests,
|
|
17487
|
+
userApproved,
|
|
17488
|
+
userDenied,
|
|
17489
|
+
timedOut,
|
|
17490
|
+
hardBlocked,
|
|
17491
|
+
dlpBlocked,
|
|
17492
|
+
observeDlp,
|
|
17493
|
+
loopHits,
|
|
17494
|
+
testPasses,
|
|
17495
|
+
testFails,
|
|
17496
|
+
priorBlockRate,
|
|
17497
|
+
cost: {
|
|
17498
|
+
claudeUSD,
|
|
17499
|
+
codexUSD,
|
|
17500
|
+
geminiUSD,
|
|
17501
|
+
inputTokens: costInputTokens,
|
|
17502
|
+
outputTokens: costOutputTokens,
|
|
17503
|
+
cacheWriteTokens: costCacheWrite,
|
|
17504
|
+
cacheReadTokens: costCacheRead,
|
|
17505
|
+
byDay: costByDay,
|
|
17506
|
+
byModel: costByModel
|
|
17507
|
+
},
|
|
17508
|
+
toolMap,
|
|
17509
|
+
blockMap,
|
|
17510
|
+
agentMap,
|
|
17511
|
+
mcpMap,
|
|
17512
|
+
dailyMap,
|
|
17513
|
+
hourMap
|
|
17514
|
+
} = data;
|
|
17515
|
+
const costUSD = claudeUSD + codexUSD + geminiUSD;
|
|
17516
|
+
const topTools = [...toolMap.entries()].sort((a, b) => b[1].calls - a[1].calls).slice(0, 8);
|
|
17517
|
+
const topBlocks = [...blockMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
|
|
17518
|
+
const dailyList = [...dailyMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).slice(-14);
|
|
17519
|
+
const maxTool = Math.max(...topTools.map(([, v]) => v.calls), 1);
|
|
17520
|
+
const maxBlock = Math.max(...topBlocks.map(([, v]) => v), 1);
|
|
17521
|
+
const maxDaily = Math.max(...dailyList.map(([, v]) => v.calls), 1);
|
|
17522
|
+
const W = Math.min(process.stdout.columns || 80, 100);
|
|
17523
|
+
const INNER = W - 4;
|
|
17524
|
+
const COL = Math.floor(INNER / 2) - 1;
|
|
17525
|
+
const LABEL = 24;
|
|
17526
|
+
const BAR = Math.max(6, Math.min(14, COL - LABEL - 8));
|
|
17527
|
+
const TOOL_COUNT_W = Math.max(...topTools.map(([, v]) => num2(v.calls).length), 1);
|
|
17528
|
+
const BLOCK_COUNT_W = Math.max(...topBlocks.map(([, v]) => num2(v).length), 1);
|
|
17529
|
+
const line = import_chalk13.default.dim("\u2500".repeat(W - 2));
|
|
17530
|
+
const periodLabel = {
|
|
17531
|
+
today: "Today",
|
|
17532
|
+
"7d": "Last 7 Days",
|
|
17533
|
+
"30d": "Last 30 Days",
|
|
17534
|
+
"90d": "Last 90 Days",
|
|
17535
|
+
month: "This Month"
|
|
17536
|
+
};
|
|
17537
|
+
console.log("");
|
|
17538
|
+
console.log(
|
|
17539
|
+
" " + import_chalk13.default.bold.cyan("\u{1F6E1} node9 Report") + import_chalk13.default.dim(" \xB7 ") + import_chalk13.default.white(periodLabel[period]) + import_chalk13.default.dim(` ${fmtDate(start)} \u2013 ${fmtDate(end)}`) + import_chalk13.default.dim(` ${num2(total)} events`) + (excludeTests ? import_chalk13.default.dim(` \u2013tests (\u2013${excludedTests})`) : "")
|
|
17540
|
+
);
|
|
17541
|
+
console.log(" " + line);
|
|
17542
|
+
const totalBlocked = timedOut + hardBlocked + dlpBlocked + loopHits + userDenied;
|
|
17543
|
+
const currentRate = total > 0 ? totalBlocked / total : 0;
|
|
17544
|
+
const trendLabel = (() => {
|
|
17545
|
+
if (priorBlockRate === null) return "";
|
|
17546
|
+
const delta = Math.round((currentRate - priorBlockRate) * 100);
|
|
17547
|
+
if (delta === 0) return "";
|
|
17548
|
+
return " " + (delta > 0 ? import_chalk13.default.red(`\u25B2${delta}%`) : import_chalk13.default.green(`\u25BC${Math.abs(delta)}%`)) + import_chalk13.default.dim(" vs prior");
|
|
17549
|
+
})();
|
|
17550
|
+
const reads = toolMap.get("Read")?.calls ?? 0;
|
|
17551
|
+
const edits = (toolMap.get("Edit")?.calls ?? 0) + (toolMap.get("Write")?.calls ?? 0);
|
|
17552
|
+
const ratioLabel = reads > 0 ? import_chalk13.default.dim(`edit/read ${(edits / reads).toFixed(1)}`) : import_chalk13.default.dim("edit/read \u2013");
|
|
17553
|
+
const testLabel = testPasses + testFails > 0 ? import_chalk13.default.dim("tests ") + import_chalk13.default.green(`${testPasses}\u2713`) + (testFails > 0 ? " " + import_chalk13.default.red(`${testFails}\u2717`) : "") : import_chalk13.default.dim("tests \u2013");
|
|
17554
|
+
console.log("");
|
|
17555
|
+
console.log(" " + import_chalk13.default.bold("Protection Summary"));
|
|
17556
|
+
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
17557
|
+
console.log(
|
|
17558
|
+
" " + import_chalk13.default.dim("Intercepted") + " " + import_chalk13.default.white(num2(total)) + import_chalk13.default.dim(" tool calls")
|
|
17559
|
+
);
|
|
17560
|
+
console.log("");
|
|
17561
|
+
const COL1 = 18;
|
|
17562
|
+
const summaryRow = (icon, label, count, note, colorFn = (s) => s) => {
|
|
17563
|
+
const countStr = colorFn(num2(count));
|
|
17564
|
+
const noteStr = note ? import_chalk13.default.dim(" " + note) : "";
|
|
17565
|
+
console.log(" " + icon + " " + import_chalk13.default.white(label.padEnd(COL1)) + countStr + noteStr);
|
|
17566
|
+
};
|
|
17567
|
+
summaryRow(
|
|
17568
|
+
userApproved > 0 ? import_chalk13.default.green("\u2705") : import_chalk13.default.dim("\u2705"),
|
|
17569
|
+
"User approved",
|
|
17570
|
+
userApproved,
|
|
17571
|
+
userApproved === 0 ? "no popups this period" : void 0,
|
|
17572
|
+
userApproved > 0 ? (s) => import_chalk13.default.green(s) : (s) => import_chalk13.default.dim(s)
|
|
17573
|
+
);
|
|
17574
|
+
summaryRow(
|
|
17575
|
+
userDenied > 0 ? import_chalk13.default.red("\u{1F6AB}") : import_chalk13.default.dim("\u{1F6AB}"),
|
|
17576
|
+
"User denied",
|
|
17577
|
+
userDenied,
|
|
17578
|
+
void 0,
|
|
17579
|
+
userDenied > 0 ? (s) => import_chalk13.default.red(s) : (s) => import_chalk13.default.dim(s)
|
|
17580
|
+
);
|
|
17581
|
+
summaryRow(
|
|
17582
|
+
timedOut > 0 ? import_chalk13.default.yellow("\u23F1") : import_chalk13.default.dim("\u23F1"),
|
|
17583
|
+
"Timed out",
|
|
17584
|
+
timedOut,
|
|
17585
|
+
timedOut > 0 ? "no approval response" : void 0,
|
|
17586
|
+
timedOut > 0 ? (s) => import_chalk13.default.yellow(s) : (s) => import_chalk13.default.dim(s)
|
|
17587
|
+
);
|
|
17588
|
+
summaryRow(
|
|
17589
|
+
hardBlocked > 0 ? import_chalk13.default.red("\u{1F6D1}") : import_chalk13.default.dim("\u{1F6D1}"),
|
|
17590
|
+
"Auto-blocked",
|
|
17591
|
+
hardBlocked,
|
|
17592
|
+
void 0,
|
|
17593
|
+
hardBlocked > 0 ? (s) => import_chalk13.default.red(s) : (s) => import_chalk13.default.dim(s)
|
|
17594
|
+
);
|
|
17595
|
+
summaryRow(
|
|
17596
|
+
dlpBlocked > 0 ? import_chalk13.default.yellow("\u{1F6A8}") : import_chalk13.default.dim("\u{1F6A8}"),
|
|
17597
|
+
"DLP blocked",
|
|
17598
|
+
dlpBlocked,
|
|
17599
|
+
void 0,
|
|
17600
|
+
dlpBlocked > 0 ? (s) => import_chalk13.default.yellow(s) : (s) => import_chalk13.default.dim(s)
|
|
17601
|
+
);
|
|
17602
|
+
summaryRow(
|
|
17603
|
+
observeDlp > 0 ? import_chalk13.default.blue("\u{1F441}") : import_chalk13.default.dim("\u{1F441}"),
|
|
17604
|
+
"DLP (observe)",
|
|
17605
|
+
observeDlp,
|
|
17606
|
+
observeDlp > 0 ? "would-block in strict mode" : void 0,
|
|
17607
|
+
observeDlp > 0 ? (s) => import_chalk13.default.blue(s) : (s) => import_chalk13.default.dim(s)
|
|
17608
|
+
);
|
|
17609
|
+
summaryRow(
|
|
17610
|
+
loopHits > 0 ? import_chalk13.default.yellow("\u{1F504}") : import_chalk13.default.dim("\u{1F504}"),
|
|
17611
|
+
"Loops detected",
|
|
17612
|
+
loopHits,
|
|
17613
|
+
void 0,
|
|
17614
|
+
loopHits > 0 ? (s) => import_chalk13.default.yellow(s) : (s) => import_chalk13.default.dim(s)
|
|
17615
|
+
);
|
|
17616
|
+
if (trendLabel || ratioLabel || testPasses + testFails > 0) {
|
|
16724
17617
|
console.log("");
|
|
16725
|
-
console.log(
|
|
16726
|
-
|
|
16727
|
-
|
|
16728
|
-
|
|
16729
|
-
|
|
16730
|
-
|
|
16731
|
-
|
|
16732
|
-
|
|
16733
|
-
|
|
16734
|
-
|
|
16735
|
-
|
|
16736
|
-
|
|
16737
|
-
|
|
16738
|
-
|
|
16739
|
-
|
|
16740
|
-
|
|
17618
|
+
console.log(" " + ratioLabel + " " + testLabel + trendLabel);
|
|
17619
|
+
}
|
|
17620
|
+
console.log("");
|
|
17621
|
+
const toolHeaderRaw = "Top Tools";
|
|
17622
|
+
const blockHeaderRaw = "Top Blocks";
|
|
17623
|
+
console.log(
|
|
17624
|
+
" " + import_chalk13.default.bold(toolHeaderRaw) + " ".repeat(COL - toolHeaderRaw.length) + " " + import_chalk13.default.bold(blockHeaderRaw)
|
|
17625
|
+
);
|
|
17626
|
+
console.log(" " + import_chalk13.default.dim("\u2500".repeat(COL)) + " " + import_chalk13.default.dim("\u2500".repeat(COL)));
|
|
17627
|
+
const rows = Math.max(topTools.length, topBlocks.length, 1);
|
|
17628
|
+
for (let i = 0; i < rows; i++) {
|
|
17629
|
+
let leftStyled = " ".repeat(COL);
|
|
17630
|
+
if (i < topTools.length) {
|
|
17631
|
+
const [tool, { calls }] = topTools[i];
|
|
17632
|
+
const label = tool.length > LABEL - 1 ? tool.slice(0, LABEL - 2) + "\u2026" : tool;
|
|
17633
|
+
const countStr = num2(calls).padStart(TOOL_COUNT_W);
|
|
17634
|
+
const b = colorBar(calls, maxTool, BAR);
|
|
17635
|
+
const rawLen = LABEL + BAR + 1 + TOOL_COUNT_W;
|
|
17636
|
+
const pad = Math.max(0, COL - rawLen);
|
|
17637
|
+
leftStyled = import_chalk13.default.white(label.padEnd(LABEL)) + b + " " + import_chalk13.default.white(countStr) + " ".repeat(pad);
|
|
17638
|
+
}
|
|
17639
|
+
let rightStyled = "";
|
|
17640
|
+
if (i < topBlocks.length) {
|
|
17641
|
+
const [reason, count] = topBlocks[i];
|
|
17642
|
+
const readable = humanBlockReason(reason);
|
|
17643
|
+
const label = readable.length > LABEL - 1 ? readable.slice(0, LABEL - 2) + "\u2026" : readable;
|
|
17644
|
+
const countStr = num2(count).padStart(BLOCK_COUNT_W);
|
|
17645
|
+
const b = colorBar(count, maxBlock, BAR);
|
|
17646
|
+
rightStyled = import_chalk13.default.white(label.padEnd(LABEL)) + b + " " + import_chalk13.default.red(countStr);
|
|
17647
|
+
}
|
|
17648
|
+
console.log(" " + leftStyled + " " + rightStyled);
|
|
17649
|
+
}
|
|
17650
|
+
if (topBlocks.length === 0) {
|
|
17651
|
+
console.log(" " + " ".repeat(COL) + " " + import_chalk13.default.dim("nothing blocked \u2713"));
|
|
17652
|
+
}
|
|
17653
|
+
if (agentMap.size >= 1) {
|
|
16741
17654
|
console.log("");
|
|
16742
|
-
console.log(" " + import_chalk13.default.bold("
|
|
17655
|
+
console.log(" " + import_chalk13.default.bold("Agents"));
|
|
16743
17656
|
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
16744
|
-
|
|
16745
|
-
|
|
16746
|
-
|
|
16747
|
-
|
|
16748
|
-
|
|
16749
|
-
const summaryRow = (icon, label, count, note, colorFn = (s) => s) => {
|
|
16750
|
-
const countStr = colorFn(num2(count));
|
|
16751
|
-
const noteStr = note ? import_chalk13.default.dim(" " + note) : "";
|
|
16752
|
-
console.log(" " + icon + " " + import_chalk13.default.white(label.padEnd(COL1)) + countStr + noteStr);
|
|
16753
|
-
};
|
|
16754
|
-
summaryRow(
|
|
16755
|
-
userApproved > 0 ? import_chalk13.default.green("\u2705") : import_chalk13.default.dim("\u2705"),
|
|
16756
|
-
"User approved",
|
|
16757
|
-
userApproved,
|
|
16758
|
-
userApproved === 0 ? "no popups this period" : void 0,
|
|
16759
|
-
userApproved > 0 ? (s) => import_chalk13.default.green(s) : (s) => import_chalk13.default.dim(s)
|
|
16760
|
-
);
|
|
16761
|
-
summaryRow(
|
|
16762
|
-
userDenied > 0 ? import_chalk13.default.red("\u{1F6AB}") : import_chalk13.default.dim("\u{1F6AB}"),
|
|
16763
|
-
"User denied",
|
|
16764
|
-
userDenied,
|
|
16765
|
-
void 0,
|
|
16766
|
-
userDenied > 0 ? (s) => import_chalk13.default.red(s) : (s) => import_chalk13.default.dim(s)
|
|
16767
|
-
);
|
|
16768
|
-
summaryRow(
|
|
16769
|
-
timedOut > 0 ? import_chalk13.default.yellow("\u23F1") : import_chalk13.default.dim("\u23F1"),
|
|
16770
|
-
"Timed out",
|
|
16771
|
-
timedOut,
|
|
16772
|
-
timedOut > 0 ? "no approval response" : void 0,
|
|
16773
|
-
timedOut > 0 ? (s) => import_chalk13.default.yellow(s) : (s) => import_chalk13.default.dim(s)
|
|
16774
|
-
);
|
|
16775
|
-
summaryRow(
|
|
16776
|
-
hardBlocked > 0 ? import_chalk13.default.red("\u{1F6D1}") : import_chalk13.default.dim("\u{1F6D1}"),
|
|
16777
|
-
"Auto-blocked",
|
|
16778
|
-
hardBlocked,
|
|
16779
|
-
void 0,
|
|
16780
|
-
hardBlocked > 0 ? (s) => import_chalk13.default.red(s) : (s) => import_chalk13.default.dim(s)
|
|
16781
|
-
);
|
|
16782
|
-
summaryRow(
|
|
16783
|
-
dlpBlocked > 0 ? import_chalk13.default.yellow("\u{1F6A8}") : import_chalk13.default.dim("\u{1F6A8}"),
|
|
16784
|
-
"DLP blocked",
|
|
16785
|
-
dlpBlocked,
|
|
16786
|
-
void 0,
|
|
16787
|
-
dlpBlocked > 0 ? (s) => import_chalk13.default.yellow(s) : (s) => import_chalk13.default.dim(s)
|
|
16788
|
-
);
|
|
16789
|
-
summaryRow(
|
|
16790
|
-
observeDlp > 0 ? import_chalk13.default.blue("\u{1F441}") : import_chalk13.default.dim("\u{1F441}"),
|
|
16791
|
-
"DLP (observe)",
|
|
16792
|
-
observeDlp,
|
|
16793
|
-
observeDlp > 0 ? "would-block in strict mode" : void 0,
|
|
16794
|
-
observeDlp > 0 ? (s) => import_chalk13.default.blue(s) : (s) => import_chalk13.default.dim(s)
|
|
16795
|
-
);
|
|
16796
|
-
summaryRow(
|
|
16797
|
-
loopHits > 0 ? import_chalk13.default.yellow("\u{1F504}") : import_chalk13.default.dim("\u{1F504}"),
|
|
16798
|
-
"Loops detected",
|
|
16799
|
-
loopHits,
|
|
16800
|
-
void 0,
|
|
16801
|
-
loopHits > 0 ? (s) => import_chalk13.default.yellow(s) : (s) => import_chalk13.default.dim(s)
|
|
16802
|
-
);
|
|
16803
|
-
if (trendLabel || ratioLabel || testPasses + testFails > 0) {
|
|
16804
|
-
console.log("");
|
|
16805
|
-
console.log(" " + ratioLabel + " " + testLabel + trendLabel);
|
|
17657
|
+
const maxAgent = Math.max(...agentMap.values(), 1);
|
|
17658
|
+
for (const [agent, count] of [...agentMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
17659
|
+
const label = agent.slice(0, LABEL - 1);
|
|
17660
|
+
const b = colorBar(count, maxAgent, BAR);
|
|
17661
|
+
console.log(" " + import_chalk13.default.white(label.padEnd(LABEL)) + b + " " + import_chalk13.default.white(num2(count)));
|
|
16806
17662
|
}
|
|
17663
|
+
}
|
|
17664
|
+
if (mcpMap.size > 0) {
|
|
16807
17665
|
console.log("");
|
|
16808
|
-
|
|
16809
|
-
|
|
16810
|
-
|
|
16811
|
-
|
|
16812
|
-
|
|
16813
|
-
|
|
16814
|
-
|
|
16815
|
-
|
|
16816
|
-
|
|
16817
|
-
|
|
16818
|
-
|
|
16819
|
-
|
|
16820
|
-
|
|
16821
|
-
|
|
16822
|
-
|
|
16823
|
-
|
|
16824
|
-
|
|
16825
|
-
|
|
16826
|
-
|
|
16827
|
-
|
|
16828
|
-
|
|
16829
|
-
|
|
16830
|
-
|
|
16831
|
-
|
|
16832
|
-
|
|
16833
|
-
|
|
16834
|
-
|
|
16835
|
-
|
|
16836
|
-
|
|
16837
|
-
|
|
16838
|
-
|
|
16839
|
-
|
|
16840
|
-
|
|
16841
|
-
|
|
16842
|
-
|
|
16843
|
-
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
16844
|
-
const maxAgent = Math.max(...agentMap.values(), 1);
|
|
16845
|
-
for (const [agent, count] of [...agentMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
16846
|
-
const label = agent.slice(0, LABEL - 1);
|
|
16847
|
-
const b = colorBar(count, maxAgent, BAR);
|
|
16848
|
-
console.log(" " + import_chalk13.default.white(label.padEnd(LABEL)) + b + " " + import_chalk13.default.white(num2(count)));
|
|
16849
|
-
}
|
|
16850
|
-
}
|
|
16851
|
-
if (mcpMap.size > 0) {
|
|
16852
|
-
console.log("");
|
|
16853
|
-
console.log(" " + import_chalk13.default.bold("MCP Servers"));
|
|
16854
|
-
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
16855
|
-
const maxMcp = Math.max(...mcpMap.values(), 1);
|
|
16856
|
-
for (const [server, count] of [...mcpMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
16857
|
-
const label = server.slice(0, LABEL - 1).padEnd(LABEL);
|
|
16858
|
-
const b = colorBar(count, maxMcp, BAR);
|
|
16859
|
-
console.log(" " + import_chalk13.default.white(label) + b + " " + import_chalk13.default.white(num2(count)));
|
|
16860
|
-
}
|
|
16861
|
-
}
|
|
16862
|
-
if (hourMap.size > 0) {
|
|
16863
|
-
const BLOCKS = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
16864
|
-
const maxHour = Math.max(...hourMap.values(), 1);
|
|
16865
|
-
const bar = Array.from({ length: 24 }, (_, h) => {
|
|
16866
|
-
const v = hourMap.get(h) ?? 0;
|
|
16867
|
-
return BLOCKS[Math.round(v / maxHour * 8)];
|
|
16868
|
-
}).join("");
|
|
16869
|
-
console.log("");
|
|
16870
|
-
console.log(" " + import_chalk13.default.bold("Hour of Day") + import_chalk13.default.dim(" (local, 0h \u2013 23h)"));
|
|
16871
|
-
console.log(" " + import_chalk13.default.cyan(bar));
|
|
16872
|
-
console.log(" " + import_chalk13.default.dim("0h" + " ".repeat(10) + "12h" + " ".repeat(7) + "23h"));
|
|
16873
|
-
}
|
|
16874
|
-
if (dailyList.length > 1) {
|
|
16875
|
-
console.log("");
|
|
16876
|
-
console.log(" " + import_chalk13.default.bold("Daily Activity"));
|
|
16877
|
-
console.log(" " + import_chalk13.default.dim("\u2500".repeat(W - 2)));
|
|
16878
|
-
const DAY_BAR = Math.max(8, Math.min(30, W - 36));
|
|
16879
|
-
for (const [dateKey, { calls, blocked: db }] of dailyList) {
|
|
16880
|
-
const label = fmtDate(dateKey).padEnd(10);
|
|
16881
|
-
const b = colorBar(calls, maxDaily, DAY_BAR);
|
|
16882
|
-
const dayCost = costByDay.get(dateKey);
|
|
16883
|
-
const costNote = dayCost ? import_chalk13.default.magenta(` ${fmtCost2(dayCost)}`) : "";
|
|
16884
|
-
const blockNote = db > 0 ? import_chalk13.default.red(` ${db} blocked`) : "";
|
|
16885
|
-
console.log(
|
|
16886
|
-
" " + import_chalk13.default.dim(label) + " " + b + " " + import_chalk13.default.white(num2(calls)) + blockNote + costNote
|
|
16887
|
-
);
|
|
16888
|
-
}
|
|
16889
|
-
}
|
|
16890
|
-
const totalTokens = costInputTokens + costOutputTokens + costCacheWrite + costCacheRead;
|
|
16891
|
-
if (totalTokens > 0) {
|
|
16892
|
-
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
16893
|
-
console.log("");
|
|
16894
|
-
console.log(" " + import_chalk13.default.bold("Tokens") + " " + import_chalk13.default.dim(`${num2(totalTokens)} total`));
|
|
16895
|
-
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
16896
|
-
const TOK_BAR = Math.max(6, Math.min(20, W - 30));
|
|
16897
|
-
const TOK_LABEL = 14;
|
|
16898
|
-
const maxNonCache = Math.max(costInputTokens, costOutputTokens, costCacheWrite, 1);
|
|
16899
|
-
const nonCacheRows = [
|
|
16900
|
-
["Input", costInputTokens, import_chalk13.default.cyan(num2(costInputTokens))],
|
|
16901
|
-
["Output", costOutputTokens, import_chalk13.default.white(num2(costOutputTokens))],
|
|
16902
|
-
["Cache write", costCacheWrite, import_chalk13.default.yellow(num2(costCacheWrite))]
|
|
16903
|
-
];
|
|
16904
|
-
for (const [label, count, colored] of nonCacheRows) {
|
|
16905
|
-
if (count === 0) continue;
|
|
16906
|
-
const b = colorBar(count, maxNonCache, TOK_BAR);
|
|
16907
|
-
console.log(" " + import_chalk13.default.white(label.padEnd(TOK_LABEL)) + b + " " + colored);
|
|
16908
|
-
}
|
|
16909
|
-
if (costCacheRead > 0) {
|
|
16910
|
-
const cacheBar = colorBar(costCacheRead, costCacheRead, TOK_BAR);
|
|
16911
|
-
const pct = cacheHitPct > 0 ? import_chalk13.default.dim(` ${cacheHitPct}% hit rate`) : "";
|
|
16912
|
-
console.log(
|
|
16913
|
-
" " + import_chalk13.default.white("Cache read".padEnd(TOK_LABEL)) + cacheBar + " " + import_chalk13.default.green(num2(costCacheRead)) + pct
|
|
16914
|
-
);
|
|
16915
|
-
}
|
|
16916
|
-
}
|
|
16917
|
-
if (costUSD > 0) {
|
|
16918
|
-
const periodDays = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 864e5));
|
|
16919
|
-
const avgPerDay = costUSD / periodDays;
|
|
16920
|
-
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
16921
|
-
const costHeaderRight = [
|
|
16922
|
-
import_chalk13.default.yellow(fmtCost2(costUSD)),
|
|
16923
|
-
import_chalk13.default.dim(`avg ${fmtCost2(avgPerDay)}/day`),
|
|
16924
|
-
cacheHitPct > 0 ? import_chalk13.default.dim(`${cacheHitPct}% cache hit`) : null
|
|
16925
|
-
].filter(Boolean).join(import_chalk13.default.dim(" \xB7 "));
|
|
16926
|
-
console.log("");
|
|
16927
|
-
console.log(" " + import_chalk13.default.bold("Cost") + " " + costHeaderRight);
|
|
16928
|
-
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
16929
|
-
if (codexCostUSD > 0)
|
|
16930
|
-
costByModel.set(
|
|
16931
|
-
"codex (openai)",
|
|
16932
|
-
(costByModel.get("codex (openai)") ?? 0) + codexCostUSD
|
|
16933
|
-
);
|
|
16934
|
-
const modelList = [...costByModel.entries()].sort((a, b) => b[1] - a[1]);
|
|
16935
|
-
const maxModelCost = Math.max(...modelList.map(([, v]) => v), 1e-9);
|
|
16936
|
-
const MODEL_LABEL = 22;
|
|
16937
|
-
const MODEL_BAR = Math.max(6, Math.min(20, W - MODEL_LABEL - 12));
|
|
16938
|
-
for (const [model, cost] of modelList) {
|
|
16939
|
-
const label = model.length > MODEL_LABEL - 1 ? model.slice(0, MODEL_LABEL - 2) + "\u2026" : model;
|
|
16940
|
-
const b = colorBar(cost, maxModelCost, MODEL_BAR);
|
|
16941
|
-
console.log(
|
|
16942
|
-
" " + import_chalk13.default.white(label.padEnd(MODEL_LABEL)) + b + " " + import_chalk13.default.yellow(fmtCost2(cost))
|
|
16943
|
-
);
|
|
16944
|
-
}
|
|
17666
|
+
console.log(" " + import_chalk13.default.bold("MCP Servers"));
|
|
17667
|
+
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
17668
|
+
const maxMcp = Math.max(...mcpMap.values(), 1);
|
|
17669
|
+
for (const [server, count] of [...mcpMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
17670
|
+
const label = server.slice(0, LABEL - 1).padEnd(LABEL);
|
|
17671
|
+
const b = colorBar(count, maxMcp, BAR);
|
|
17672
|
+
console.log(" " + import_chalk13.default.white(label) + b + " " + import_chalk13.default.white(num2(count)));
|
|
17673
|
+
}
|
|
17674
|
+
}
|
|
17675
|
+
if (hourMap.size > 0) {
|
|
17676
|
+
const BLOCKS = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
17677
|
+
const maxHour = Math.max(...hourMap.values(), 1);
|
|
17678
|
+
const bar = Array.from({ length: 24 }, (_, h) => {
|
|
17679
|
+
const v = hourMap.get(h) ?? 0;
|
|
17680
|
+
return BLOCKS[Math.round(v / maxHour * 8)];
|
|
17681
|
+
}).join("");
|
|
17682
|
+
console.log("");
|
|
17683
|
+
console.log(" " + import_chalk13.default.bold("Hour of Day") + import_chalk13.default.dim(" (local, 0h \u2013 23h)"));
|
|
17684
|
+
console.log(" " + import_chalk13.default.cyan(bar));
|
|
17685
|
+
console.log(" " + import_chalk13.default.dim("0h" + " ".repeat(10) + "12h" + " ".repeat(7) + "23h"));
|
|
17686
|
+
}
|
|
17687
|
+
if (dailyList.length > 1) {
|
|
17688
|
+
console.log("");
|
|
17689
|
+
console.log(" " + import_chalk13.default.bold("Daily Activity"));
|
|
17690
|
+
console.log(" " + import_chalk13.default.dim("\u2500".repeat(W - 2)));
|
|
17691
|
+
const DAY_BAR = Math.max(8, Math.min(30, W - 36));
|
|
17692
|
+
for (const [dateKey, { calls, blocked: db }] of dailyList) {
|
|
17693
|
+
const label = fmtDate(dateKey).padEnd(10);
|
|
17694
|
+
const b = colorBar(calls, maxDaily, DAY_BAR);
|
|
17695
|
+
const dayCost = costByDay.get(dateKey);
|
|
17696
|
+
const costNote = dayCost ? import_chalk13.default.magenta(` ${fmtCost2(dayCost)}`) : "";
|
|
17697
|
+
const blockNote = db > 0 ? import_chalk13.default.red(` ${db} blocked`) : "";
|
|
17698
|
+
console.log(
|
|
17699
|
+
" " + import_chalk13.default.dim(label) + " " + b + " " + import_chalk13.default.white(num2(calls)) + blockNote + costNote
|
|
17700
|
+
);
|
|
16945
17701
|
}
|
|
16946
|
-
|
|
16947
|
-
|
|
16948
|
-
|
|
16949
|
-
|
|
16950
|
-
|
|
16951
|
-
|
|
16952
|
-
|
|
17702
|
+
}
|
|
17703
|
+
const totalTokens = costInputTokens + costOutputTokens + costCacheWrite + costCacheRead;
|
|
17704
|
+
if (totalTokens > 0) {
|
|
17705
|
+
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
17706
|
+
console.log("");
|
|
17707
|
+
console.log(" " + import_chalk13.default.bold("Tokens") + " " + import_chalk13.default.dim(`${num2(totalTokens)} total`));
|
|
17708
|
+
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
17709
|
+
const TOK_BAR = Math.max(6, Math.min(20, W - 30));
|
|
17710
|
+
const TOK_LABEL = 14;
|
|
17711
|
+
const maxNonCache = Math.max(costInputTokens, costOutputTokens, costCacheWrite, 1);
|
|
17712
|
+
const nonCacheRows = [
|
|
17713
|
+
["Input", costInputTokens, import_chalk13.default.cyan(num2(costInputTokens))],
|
|
17714
|
+
["Output", costOutputTokens, import_chalk13.default.white(num2(costOutputTokens))],
|
|
17715
|
+
["Cache write", costCacheWrite, import_chalk13.default.yellow(num2(costCacheWrite))]
|
|
17716
|
+
];
|
|
17717
|
+
for (const [label, count, colored] of nonCacheRows) {
|
|
17718
|
+
if (count === 0) continue;
|
|
17719
|
+
const b = colorBar(count, maxNonCache, TOK_BAR);
|
|
17720
|
+
console.log(" " + import_chalk13.default.white(label.padEnd(TOK_LABEL)) + b + " " + colored);
|
|
17721
|
+
}
|
|
17722
|
+
if (costCacheRead > 0) {
|
|
17723
|
+
const cacheBar = colorBar(costCacheRead, costCacheRead, TOK_BAR);
|
|
17724
|
+
const pct = cacheHitPct > 0 ? import_chalk13.default.dim(` ${cacheHitPct}% hit rate`) : "";
|
|
16953
17725
|
console.log(
|
|
16954
|
-
" " + import_chalk13.default.
|
|
16955
|
-
`${responseDlpEntries.length} secret${responseDlpEntries.length !== 1 ? "s" : ""} found in Claude response text`
|
|
16956
|
-
)
|
|
17726
|
+
" " + import_chalk13.default.white("Cache read".padEnd(TOK_LABEL)) + cacheBar + " " + import_chalk13.default.green(num2(costCacheRead)) + pct
|
|
16957
17727
|
);
|
|
16958
|
-
|
|
17728
|
+
}
|
|
17729
|
+
}
|
|
17730
|
+
if (costUSD > 0) {
|
|
17731
|
+
const periodDays = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 864e5));
|
|
17732
|
+
const avgPerDay = costUSD / periodDays;
|
|
17733
|
+
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
17734
|
+
const costHeaderRight = [
|
|
17735
|
+
import_chalk13.default.yellow(fmtCost2(costUSD)),
|
|
17736
|
+
import_chalk13.default.dim(`avg ${fmtCost2(avgPerDay)}/day`),
|
|
17737
|
+
cacheHitPct > 0 ? import_chalk13.default.dim(`${cacheHitPct}% cache hit`) : null
|
|
17738
|
+
].filter(Boolean).join(import_chalk13.default.dim(" \xB7 "));
|
|
17739
|
+
console.log("");
|
|
17740
|
+
console.log(" " + import_chalk13.default.bold("Cost") + " " + costHeaderRight);
|
|
17741
|
+
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
17742
|
+
if (codexUSD > 0)
|
|
17743
|
+
costByModel.set("codex (openai)", (costByModel.get("codex (openai)") ?? 0) + codexUSD);
|
|
17744
|
+
if (geminiUSD > 0)
|
|
17745
|
+
costByModel.set("gemini (google)", (costByModel.get("gemini (google)") ?? 0) + geminiUSD);
|
|
17746
|
+
const modelList = [...costByModel.entries()].sort((a, b) => b[1] - a[1]);
|
|
17747
|
+
const maxModelCost = Math.max(...modelList.map(([, v]) => v), 1e-9);
|
|
17748
|
+
const MODEL_LABEL = 22;
|
|
17749
|
+
const MODEL_BAR = Math.max(6, Math.min(20, W - MODEL_LABEL - 12));
|
|
17750
|
+
for (const [model, cost] of modelList) {
|
|
17751
|
+
const label = model.length > MODEL_LABEL - 1 ? model.slice(0, MODEL_LABEL - 2) + "\u2026" : model;
|
|
17752
|
+
const b = colorBar(cost, maxModelCost, MODEL_BAR);
|
|
16959
17753
|
console.log(
|
|
16960
|
-
" " + import_chalk13.default.
|
|
17754
|
+
" " + import_chalk13.default.white(label.padEnd(MODEL_LABEL)) + b + " " + import_chalk13.default.yellow(fmtCost2(cost))
|
|
16961
17755
|
);
|
|
16962
|
-
console.log(" " + import_chalk13.default.yellow("Rotate affected keys immediately."));
|
|
16963
|
-
for (const e of responseDlpEntries.slice(0, 5)) {
|
|
16964
|
-
const ts = import_chalk13.default.dim(fmtDate(e.ts) + " ");
|
|
16965
|
-
const pattern = import_chalk13.default.red(e.dlpPattern ?? "DLP");
|
|
16966
|
-
const sample = import_chalk13.default.gray(e.dlpSample ?? "");
|
|
16967
|
-
console.log(` ${ts}${pattern} ${sample}`);
|
|
16968
|
-
}
|
|
16969
|
-
if (responseDlpEntries.length > 5) {
|
|
16970
|
-
console.log(import_chalk13.default.dim(` \u2026 and ${responseDlpEntries.length - 5} more`));
|
|
16971
|
-
}
|
|
16972
17756
|
}
|
|
17757
|
+
}
|
|
17758
|
+
if (responseDlpEntries.length > 0) {
|
|
16973
17759
|
console.log("");
|
|
16974
17760
|
console.log(
|
|
16975
|
-
" " + import_chalk13.default.
|
|
17761
|
+
" " + import_chalk13.default.red.bold("\u26A0\uFE0F Response DLP") + import_chalk13.default.dim(" \xB7 ") + import_chalk13.default.red(
|
|
17762
|
+
`${responseDlpEntries.length} secret${responseDlpEntries.length !== 1 ? "s" : ""} found in Claude response text`
|
|
17763
|
+
)
|
|
16976
17764
|
);
|
|
16977
|
-
console.log("");
|
|
16978
|
-
|
|
17765
|
+
console.log(" " + import_chalk13.default.dim("\u2500".repeat(Math.min(60, W - 4))));
|
|
17766
|
+
console.log(
|
|
17767
|
+
" " + import_chalk13.default.yellow("These were NOT blocked \u2014 Claude included them in response prose.")
|
|
17768
|
+
);
|
|
17769
|
+
console.log(" " + import_chalk13.default.yellow("Rotate affected keys immediately."));
|
|
17770
|
+
for (const e of responseDlpEntries.slice(0, 5)) {
|
|
17771
|
+
const ts = import_chalk13.default.dim(fmtDate(e.ts) + " ");
|
|
17772
|
+
const pattern = import_chalk13.default.red(e.dlpPattern ?? "DLP");
|
|
17773
|
+
const sample = import_chalk13.default.gray(e.dlpSample ?? "");
|
|
17774
|
+
console.log(` ${ts}${pattern} ${sample}`);
|
|
17775
|
+
}
|
|
17776
|
+
if (responseDlpEntries.length > 5) {
|
|
17777
|
+
console.log(import_chalk13.default.dim(` \u2026 and ${responseDlpEntries.length - 5} more`));
|
|
17778
|
+
}
|
|
17779
|
+
}
|
|
17780
|
+
console.log("");
|
|
17781
|
+
console.log(
|
|
17782
|
+
" " + import_chalk13.default.dim("node9 audit --deny") + import_chalk13.default.dim(" \xB7 ") + import_chalk13.default.dim("node9 report --period today|7d|30d|month --no-tests")
|
|
17783
|
+
);
|
|
17784
|
+
console.log("");
|
|
16979
17785
|
}
|
|
16980
17786
|
|
|
16981
17787
|
// src/cli/commands/daemon-cmd.ts
|
|
@@ -20785,6 +21591,17 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
20785
21591
|
process.exit(1);
|
|
20786
21592
|
}
|
|
20787
21593
|
});
|
|
21594
|
+
program.command("monitor").description("Live interactive dashboard \u2014 activity feed, approvals, security signals").action(async () => {
|
|
21595
|
+
try {
|
|
21596
|
+
const dashboardPath = import_path49.default.join(__dirname, "dashboard.mjs");
|
|
21597
|
+
const dynamicImport = new Function("id", "return import(id)");
|
|
21598
|
+
const mod = await dynamicImport(`file://${dashboardPath}`);
|
|
21599
|
+
await mod.startMonitor();
|
|
21600
|
+
} catch (err2) {
|
|
21601
|
+
console.error(import_chalk31.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
21602
|
+
process.exit(1);
|
|
21603
|
+
}
|
|
21604
|
+
});
|
|
20788
21605
|
registerWatchCommand(program);
|
|
20789
21606
|
registerMcpGatewayCommand(program);
|
|
20790
21607
|
registerMcpServerCommand(program);
|