@node9/proxy 2.12.0 → 2.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1146,9 +1146,7 @@ function unwrapCommandHead(words) {
1146
1146
  while (i < words.length) {
1147
1147
  const head = (words[i] ?? "").toLowerCase().split("/").pop() ?? "";
1148
1148
  if (head === "find") {
1149
- const x = words.findIndex(
1150
- (w, k) => k > i && (w === "-exec" || w === "-execdir" || w === "-ok")
1151
- );
1149
+ const x = words.findIndex((w, k) => k > i && w !== null && FIND_EXEC_FLAGS.has(w));
1152
1150
  if (x < 0) break;
1153
1151
  i = x + 1;
1154
1152
  continue;
@@ -1170,7 +1168,11 @@ function unwrapCommandHead(words) {
1170
1168
  if (t.startsWith("-")) {
1171
1169
  i++;
1172
1170
  const nxt = words[i];
1173
- if (nxt != null && !nxt.startsWith("-") && !INLINE_INTERPRETER.test(nxt.split("/").pop() ?? "") && !COMMAND_WRAPPERS.has(nxt.toLowerCase()) && !RUNNER_WRAPPERS.has(nxt.toLowerCase()))
1171
+ if (nxt != null && !nxt.startsWith("-") && !INLINE_INTERPRETER.test(nxt.split("/").pop() ?? "") && !COMMAND_WRAPPERS.has(nxt.toLowerCase()) && !RUNNER_WRAPPERS.has(nxt.toLowerCase()) && // A reader is a command, never a flag's operand: `env - cat X`,
1172
+ // `stdbuf -o0 cat X`, `ionice -c3 cat X`. Without this the head was
1173
+ // swallowed and the jail needed a looser fallback whose cost was a
1174
+ // false positive on `sudo echo cat X`.
1175
+ !FS_READ_TOOLS.has(nxt.split("/").pop()?.toLowerCase() ?? ""))
1174
1176
  i++;
1175
1177
  continue;
1176
1178
  }
@@ -1291,34 +1293,18 @@ function isProtectedHomePath(rawPath) {
1291
1293
  }
1292
1294
  function extractLiteralArgs(callExpr) {
1293
1295
  const args = callExpr.Args || [];
1294
- if (args.length === 0) return { name: "", flags: [], paths: [] };
1295
- const litFromWord = (w) => {
1296
- const parts = w?.Parts || [];
1297
- let s = "";
1298
- for (const p of parts) {
1299
- const t = syntax.NodeType(p);
1300
- if (t === "Lit") s += (p.Value ?? "").replace(/\\(.)/g, "$1");
1301
- else if (t === "SglQuoted") s += p.Value ?? "";
1302
- else if (t === "DblQuoted") {
1303
- const inner = p.Parts || [];
1304
- if (!inner.every((ip) => syntax.NodeType(ip) === "Lit")) return null;
1305
- s += inner.map((ip) => ip.Value ?? "").join("");
1306
- } else {
1307
- return null;
1308
- }
1309
- }
1310
- return s;
1311
- };
1312
- const name = (litFromWord(args[0]) || "").toLowerCase();
1296
+ if (args.length === 0) return { name: "", flags: [], paths: [], words: [] };
1297
+ const words = args.map((a) => resolveWordLiteral(a));
1298
+ const name = (words[0] ?? "").toLowerCase();
1313
1299
  const flags = [];
1314
1300
  const paths = [];
1315
- for (let i = 1; i < args.length; i++) {
1316
- const v = litFromWord(args[i]);
1301
+ for (let i = 1; i < words.length; i++) {
1302
+ const v = words[i];
1317
1303
  if (v === null) continue;
1318
1304
  if (v.startsWith("-")) flags.push(v);
1319
1305
  else paths.push(v);
1320
1306
  }
1321
- return { name, flags, paths };
1307
+ return { name, flags, paths, words };
1322
1308
  }
1323
1309
  function resolveWordLiteral(w) {
1324
1310
  const parts = w?.Parts || [];
@@ -1576,16 +1562,21 @@ function isRmCreatedInCommandCleanup(command) {
1576
1562
  }
1577
1563
  return sawRm && ok2;
1578
1564
  }
1579
- function analyzeFsOperationImpl(command) {
1565
+ function analyzeFsOperationImpl(command, depth = 0) {
1580
1566
  const f = parseShared(command);
1581
1567
  if (f === PARSE_FAIL) return null;
1582
1568
  let result = null;
1583
1569
  try {
1584
1570
  syntax.Walk(f, (node) => {
1585
- if (!node || result) return false;
1571
+ if (!node || result?.verdict === "block") return false;
1586
1572
  const n = node;
1587
- if (syntax.NodeType(n) !== "CallExpr") return true;
1588
- const { name, flags, paths } = extractLiteralArgs(n);
1573
+ const nodeType = syntax.NodeType(n);
1574
+ if (nodeType === "Stmt") {
1575
+ result = stricter(result, jailedRedirectRead(n));
1576
+ return result?.verdict !== "block";
1577
+ }
1578
+ if (nodeType !== "CallExpr") return true;
1579
+ const { name, flags, paths, words } = extractLiteralArgs(n);
1589
1580
  if (!name) return true;
1590
1581
  if (name === "rm") {
1591
1582
  const flagStr = flags.join("").toLowerCase();
@@ -1614,19 +1605,22 @@ function analyzeFsOperationImpl(command) {
1614
1605
  }
1615
1606
  }
1616
1607
  }
1617
- if (FS_READ_TOOLS.has(name)) {
1618
- for (const p of paths) {
1619
- for (const sp of SENSITIVE_PATH_RULES) {
1620
- if (sp.match(p)) {
1621
- result = {
1622
- ruleName: sp.rule,
1623
- verdict: sp.verdict ?? "block",
1624
- reason: sp.reason,
1625
- path: p
1626
- };
1627
- return false;
1628
- }
1608
+ if (depth < 1) {
1609
+ const payload = literalShellPayload(words, name);
1610
+ if (payload !== null) {
1611
+ const inner = analyzeFsOperationImpl(payload, depth + 1);
1612
+ if (inner) {
1613
+ result = inner;
1614
+ return false;
1629
1615
  }
1616
+ return true;
1617
+ }
1618
+ }
1619
+ const readPaths = FS_READ_TOOLS.has(name) ? paths : wrappedReadPaths(words, name);
1620
+ if (readPaths) {
1621
+ for (const p of readPaths) {
1622
+ result = stricter(result, matchSensitivePath2(p));
1623
+ if (result?.verdict === "block") return false;
1630
1624
  }
1631
1625
  }
1632
1626
  return true;
@@ -1636,6 +1630,55 @@ function analyzeFsOperationImpl(command) {
1636
1630
  return null;
1637
1631
  }
1638
1632
  }
1633
+ function stricter(a, b) {
1634
+ if (!a) return b;
1635
+ if (!b) return a;
1636
+ return b.verdict === "block" && a.verdict !== "block" ? b : a;
1637
+ }
1638
+ function matchSensitivePath2(p) {
1639
+ for (const sp of SENSITIVE_PATH_RULES) {
1640
+ if (sp.match(p))
1641
+ return { ruleName: sp.rule, verdict: sp.verdict ?? "block", reason: sp.reason, path: p };
1642
+ }
1643
+ return null;
1644
+ }
1645
+ function wrappedReadPaths(words, name) {
1646
+ if (name === "find") {
1647
+ const k = words.findIndex((w) => w !== null && FIND_EXEC_FLAGS.has(w));
1648
+ if (k < 1 || !isReaderWord(words[k + 1] ?? null)) return null;
1649
+ const firstPredicate = words.findIndex((w, i) => i > 0 && w !== null && w.startsWith("-"));
1650
+ return positionalAfter(words, 1, firstPredicate > 0 ? firstPredicate : k);
1651
+ }
1652
+ if (!COMMAND_WRAPPERS.has(name) && !RUNNER_WRAPPERS.has(name)) return null;
1653
+ const h = unwrapCommandHead(words);
1654
+ return h > 0 && isReaderWord(words[h] ?? null) ? positionalAfter(words, h + 1) : null;
1655
+ }
1656
+ function literalShellPayload(words, name) {
1657
+ const h = COMMAND_WRAPPERS.has(name) || RUNNER_WRAPPERS.has(name) ? unwrapCommandHead(words) : 0;
1658
+ const head = (words[h] ?? "").split("/").pop()?.toLowerCase() ?? "";
1659
+ if (head === "eval") {
1660
+ const rest = words.slice(h + 1);
1661
+ if (rest.length === 0 || rest.some((w) => w === null)) return null;
1662
+ return rest.join(" ");
1663
+ }
1664
+ if (SHELL_INTERPRETERS.has(head)) {
1665
+ const c = words.findIndex((w, i) => i > h && w !== null && isInlineCodeFlag(head, w));
1666
+ if (c < 0) return null;
1667
+ return words[c + 1] ?? null;
1668
+ }
1669
+ return null;
1670
+ }
1671
+ function jailedRedirectRead(stmt) {
1672
+ const redirs = stmt.Redirs || [];
1673
+ for (const r of redirs) {
1674
+ if (!r || !REDIR_FILE_IN_OPS.has(r.Op)) continue;
1675
+ const p = resolveWordLiteral(r.Word);
1676
+ if (p === null || p === "") continue;
1677
+ const hit = matchSensitivePath2(p);
1678
+ if (hit) return hit;
1679
+ }
1680
+ return null;
1681
+ }
1639
1682
  function analyzeShellCommand(command) {
1640
1683
  const actions = [];
1641
1684
  const paths = [];
@@ -1771,8 +1814,8 @@ function splitOnPipe(cmd) {
1771
1814
  if (current.trim()) segments2.push(current.trim());
1772
1815
  return segments2.filter(Boolean);
1773
1816
  }
1774
- function positionalTokens(segment) {
1775
- return segment.split(/\s+/).slice(1).filter((t) => !t.startsWith("-") && !t.startsWith("@") && t.length > 0);
1817
+ function positionalTokens(tokens) {
1818
+ return tokens.slice(1).filter((t) => !t.startsWith("-") && !t.startsWith("@") && t.length > 0);
1776
1819
  }
1777
1820
  function analyzePipeChain(command) {
1778
1821
  const segments2 = splitOnPipe(command);
@@ -1795,8 +1838,10 @@ function analyzePipeChain(command) {
1795
1838
  for (const segment of segments2) {
1796
1839
  const tokens = segment.split(/\s+/).filter(Boolean);
1797
1840
  if (tokens.length === 0) continue;
1798
- const binary = tokens[0].toLowerCase();
1799
- const args = positionalTokens(segment);
1841
+ const h = unwrapCommandHead(tokens);
1842
+ const head = h < tokens.length ? h : 0;
1843
+ const binary = tokens[head].toLowerCase();
1844
+ const args = positionalTokens(tokens.slice(head));
1800
1845
  if (SOURCE_COMMANDS.has(binary)) {
1801
1846
  sourceFiles.push(...args);
1802
1847
  if (args.some(isSensitivePath)) hasSensitiveSource = true;
@@ -3276,7 +3321,7 @@ function* stringValues(obj, depth = 0) {
3276
3321
  }
3277
3322
  for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
3278
3323
  }
3279
- var import_safe_regex2, import_crypto3, import_mvdan_sh, import_picomatch, import_safe_regex22, import_safe_regex23, import_crypto4, IBAN_LENGTH, B58, B58_INDEX, XPRV_VERSIONS, MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, 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, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, INTERP_LEADING_TARGET, NET_BINARIES, VALUE_FLAGS, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_HEREDOC_OPS, DEFAULT_EGRESS_ALLOWLIST, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, DESTINATION_ARGS, VERDICT_RANK, 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, DESTRUCTIVE_OP_RE, SENSITIVE_PATH_RE, FILE_TOOLS, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC16_RE, PII_CC15_RE, PII_IBAN_RE, REALTIME_PII_PATTERNS, MAX_PII_SCAN_BYTES, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH2, MAX_JSON_PARSE, URL_DEPTH, B64_DEPTH, MIN_SEGMENT, SEPARATORS, stripSeparators, looksText, CANARY_DECODERS, VIEWS, LONG_OUTPUT_THRESHOLD_BYTES, CANONICAL_EXTRACTOR_VERSION, DEDUPE_PREVIEW_LEN, TERMINAL_ESCAPE_RE, ENGINE_VERSION;
3324
+ var import_safe_regex2, import_crypto3, import_mvdan_sh, import_picomatch, import_safe_regex22, import_safe_regex23, import_crypto4, IBAN_LENGTH, B58, B58_INDEX, XPRV_VERSIONS, MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, 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, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, INTERP_LEADING_TARGET, NET_BINARIES, VALUE_FLAGS, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, isReaderWord, positionalAfter, DEFAULT_EGRESS_ALLOWLIST, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, DESTINATION_ARGS, VERDICT_RANK, 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, DESTRUCTIVE_OP_RE, SENSITIVE_PATH_RE, FILE_TOOLS, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC16_RE, PII_CC15_RE, PII_IBAN_RE, REALTIME_PII_PATTERNS, MAX_PII_SCAN_BYTES, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH2, MAX_JSON_PARSE, URL_DEPTH, B64_DEPTH, MIN_SEGMENT, SEPARATORS, stripSeparators, looksText, CANARY_DECODERS, VIEWS, LONG_OUTPUT_THRESHOLD_BYTES, CANONICAL_EXTRACTOR_VERSION, DEDUPE_PREVIEW_LEN, TERMINAL_ESCAPE_RE, ENGINE_VERSION;
3280
3325
  var init_dist = __esm({
3281
3326
  "packages/policy-engine/dist/index.mjs"() {
3282
3327
  "use strict";
@@ -3901,13 +3946,32 @@ var init_dist = __esm({
3901
3946
  })
3902
3947
  );
3903
3948
  SENSITIVE_PATH_PATTERNS = [
3904
- /[/\\]\.ssh[/\\]/i,
3905
- /[/\\]\.aws[/\\]/i,
3949
+ /[/\\]\.ssh([/\\]|$)/i,
3950
+ /[/\\]\.aws([/\\]|$)/i,
3906
3951
  /[/\\]\.config[/\\]gcloud[/\\]/i,
3907
3952
  /[/\\]\.azure[/\\]/i,
3908
3953
  /[/\\]\.kube[/\\]config$/i,
3909
- /[/\\]\.env($|\.)/i,
3910
- // .env, .env.local, .env.production — not .envoy
3954
+ // ⚠️ ONE SEMANTIC, FOUR COPIES. This is the AST tier's `.env` rule verbatim
3955
+ // (shell/index.ts SENSITIVE_PATH_RULES), whose reasoning is documented there:
3956
+ // structural suffix chain rather than a hand-written list, `example|sample|
3957
+ // template` exempt because a fixture stays a fixture whatever follows, and
3958
+ // `.test` anchored because `test` names an ENVIRONMENT -- `.env.test` is the
3959
+ // committed template, `.env.test.local` is gitignored and holds real values.
3960
+ //
3961
+ // It was previously `[/\\]\.env($|\.)` with NO exemptions, so `Read .env.example`
3962
+ // blocked while `cat .env.example` allowed: the same file, opposite verdicts,
3963
+ // decided only by which tool asked. See src/__tests__/jail-both-doors.test.ts,
3964
+ // which is the contract that now holds these copies in step, and stage 5 of
3965
+ // doc/credential-jail-architecture.md, which replaces them with one generated
3966
+ // source.
3967
+ // ⚠️ The `.local` branch comes FIRST and takes no exemption. A fixture stays a
3968
+ // fixture whatever follows it -- `.env.example.md` is documentation -- but
3969
+ // `.env.example.local` is gitignored by the `.env*.local` convention and holds
3970
+ // real values, exactly the reasoning that anchors `(?!\.test$)` rather than
3971
+ // using `\b`. Without this branch the fixture exemption also bought a two-step
3972
+ // bypass: `cp .env .env.sample`, then read the copy.
3973
+ /[/\\]\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i,
3974
+ // .env + any suffix chain; fixtures exempt unless .local
3911
3975
  /[/\\]\.git-credentials$/i,
3912
3976
  /[/\\]\.npmrc$/i,
3913
3977
  /[/\\]\.docker[/\\]config\.json$/i,
@@ -3989,6 +4053,10 @@ var init_dist = __esm({
3989
4053
  "od",
3990
4054
  "xxd",
3991
4055
  "hexdump",
4056
+ // Emits the file's bytes, re-encoded, so it is a read by the set's own test
4057
+ // ("does it emit file contents"). Absent until 2026-09-10, which is why
4058
+ // `base64 ~/.ssh/id_rsa` printed a private key with no verdict.
4059
+ "base64",
3992
4060
  "strings",
3993
4061
  "sort",
3994
4062
  "uniq",
@@ -3997,7 +4065,11 @@ var init_dist = __esm({
3997
4065
  "dd"
3998
4066
  ]);
3999
4067
  FS_OP_PRESCREEN_RE = new RegExp(
4000
- `(?:^|[\\s|;&(\`\\n])(?:rm|${[...FS_READ_TOOLS].map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b`
4068
+ // A quote is a separator too: `eval "cat X"` and `sh -c 'cat X'` put the
4069
+ // reader right after `"` / `'`, and without these two characters the
4070
+ // prescreen rejected every string-wrapped read before the parser ran.
4071
+ // Found 2026-09-11 by instrumenting the walk -- no CallExpr was ever visited.
4072
+ `(?:^|[\\s|;&("'\`\\n])(?:rm|${[...FS_READ_TOOLS].map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b|(?<!<)<(?!<)`
4001
4073
  );
4002
4074
  HOME_CACHE_ALLOWLIST = [
4003
4075
  ".cache",
@@ -4018,12 +4090,12 @@ var init_dist = __esm({
4018
4090
  {
4019
4091
  rule: "shield:project-jail:block-read-ssh",
4020
4092
  reason: "Reading SSH private keys is blocked by project-jail shield",
4021
- match: (p) => /(^|[\\/])\.ssh[\\/]/i.test(p)
4093
+ match: (p) => /([\\/]\.ssh[\\/]|^\.ssh[\\/]|^(?:[~/]|[A-Za-z]:).*[\\/]\.ssh$)/i.test(p)
4022
4094
  },
4023
4095
  {
4024
4096
  rule: "shield:project-jail:block-read-aws",
4025
4097
  reason: "Reading AWS credentials is blocked by project-jail shield",
4026
- match: (p) => /(^|[\\/])\.aws[\\/]/i.test(p)
4098
+ match: (p) => /([\\/]\.aws[\\/]|^\.aws[\\/]|^(?:[~/]|[A-Za-z]:).*[\\/]\.aws$)/i.test(p)
4027
4099
  },
4028
4100
  {
4029
4101
  // Mirrors the JSON shield's `.env` pattern (project-jail.json's
@@ -4067,7 +4139,9 @@ var init_dist = __esm({
4067
4139
  // symmetry — silently exempts every `.env.test.*` file.
4068
4140
  //
4069
4141
  // shields.test.ts:983-995 is the canonical contract; keep both in step.
4070
- match: (p) => /(?:^|[\\/])\.env(?![\w-])(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$/i.test(p)
4142
+ match: (p) => /(?:^|[\\/])\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i.test(
4143
+ p
4144
+ )
4071
4145
  },
4072
4146
  {
4073
4147
  // verdict: 'review' (not 'block') is a deliberate design choice
@@ -4178,8 +4252,18 @@ var init_dist = __esm({
4178
4252
  _redirStdinOps = null;
4179
4253
  _listOps = null;
4180
4254
  WRAPPER_TAKES_TARGET = /* @__PURE__ */ new Set(["chroot"]);
4255
+ FIND_EXEC_FLAGS = /* @__PURE__ */ new Set(["-exec", "-execdir", "-ok", "-okdir"]);
4181
4256
  INTERP_LEADING_TARGET = /* @__PURE__ */ new Set(["su"]);
4182
- NET_BINARIES = /* @__PURE__ */ new Set(["curl", "wget", "scp", "ssh", "nc", "ncat", "netcat"]);
4257
+ NET_BINARIES = /* @__PURE__ */ new Set([
4258
+ "curl",
4259
+ "wget",
4260
+ "scp",
4261
+ "ssh",
4262
+ "nc",
4263
+ "ncat",
4264
+ "netcat",
4265
+ "rsync"
4266
+ ]);
4183
4267
  VALUE_FLAGS = {
4184
4268
  curl: /* @__PURE__ */ new Set([
4185
4269
  "-d",
@@ -4268,10 +4352,15 @@ var init_dist = __esm({
4268
4352
  fsOpCache = /* @__PURE__ */ new Map();
4269
4353
  stripDotSlash = (p) => p.replace(/^\.\//, "");
4270
4354
  REDIR_TRUNCATE_OPS = /* @__PURE__ */ new Set([deriveRedirOp(">_f")]);
4355
+ REDIR_FILE_IN_OPS = new Set(
4356
+ [deriveRedirOp("cat < f"), deriveRedirOp("cat <> f")].filter((op) => op >= 0)
4357
+ );
4271
4358
  REDIR_HEREDOC_OPS = /* @__PURE__ */ new Set([
4272
4359
  deriveRedirOp("cat <<X\nX"),
4273
4360
  deriveRedirOp("cat <<-X\nX")
4274
4361
  ]);
4362
+ isReaderWord = (w) => w !== null && FS_READ_TOOLS.has(w.split("/").pop()?.toLowerCase() ?? "");
4363
+ positionalAfter = (words, from, to = words.length) => words.slice(from, to).filter((w) => w !== null && !w.startsWith("-"));
4275
4364
  DEFAULT_EGRESS_ALLOWLIST = [
4276
4365
  // node9's own control plane (api, app, dev-api, staging and the apex).
4277
4366
  // Without it, turning egress on asks the user to approve node9 itself.
@@ -4295,21 +4384,7 @@ var init_dist = __esm({
4295
4384
  "deb.debian.org",
4296
4385
  "*.ubuntu.com"
4297
4386
  ];
4298
- SOURCE_COMMANDS = /* @__PURE__ */ new Set([
4299
- "cat",
4300
- "head",
4301
- "tail",
4302
- "grep",
4303
- "awk",
4304
- "sed",
4305
- "cut",
4306
- "sort",
4307
- "tee",
4308
- "less",
4309
- "more",
4310
- "strings",
4311
- "xxd"
4312
- ]);
4387
+ SOURCE_COMMANDS = /* @__PURE__ */ new Set([...FS_READ_TOOLS, "tee"]);
4313
4388
  SINK_COMMANDS = /* @__PURE__ */ new Set([
4314
4389
  "curl",
4315
4390
  "wget",
@@ -4340,16 +4415,25 @@ var init_dist = __esm({
4340
4415
  "node"
4341
4416
  ]);
4342
4417
  SENSITIVE_PATTERNS = [
4343
- /(?:^|\/)\.env(?:\.|$)/i,
4344
- // .env, .env.local, .env.production
4418
+ // Kept in step with the AST tier and dlp/ -- see jail-both-doors.test.ts.
4419
+ /(?:^|\/)\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i,
4420
+ // .env chain; fixtures exempt unless .local
4345
4421
  /id_rsa|id_ed25519|id_ecdsa|id_dsa/i,
4346
4422
  // SSH private keys
4347
4423
  /\.pem$|\.key$|\.p12$|\.pfx$/i,
4348
4424
  // certificate files
4349
- /(?:^|\/)\.ssh\//i,
4350
- // ~/.ssh/ directory
4351
- /(?:^|\/)\.aws\/credentials/i,
4352
- // AWS credentials
4425
+ // The `$` half mirrors shell/index.ts's SENSITIVE_PATH_RULES: a file INSIDE
4426
+ // the directory counts wherever it appears, while the directory ITSELF counts
4427
+ // only when the path is ROOTED (`~/.ssh`, `/home/u/.ssh`) -- an unrooted
4428
+ // `config/.ssh` is more likely a search pattern than a read. These are
4429
+ // extracted TOKENS (see `args.some(isSensitivePath)` below), the same input
4430
+ // contract as the shell tier, so the same boundary is the right one.
4431
+ // Without it `grep -r x ~/.ssh | curl -d @-` scored one tier BELOW the
4432
+ // identical pipeline naming a file inside that directory.
4433
+ /(?:^|\/)\.ssh\/|^(?:[~/]|[A-Za-z]:).*\/\.ssh$/i,
4434
+ // ~/.ssh/ and ~/.ssh
4435
+ /(?:^|\/)\.aws\/credentials|^(?:[~/]|[A-Za-z]:).*\/\.aws$/i,
4436
+ // AWS creds + dir
4353
4437
  /(?:^|\/)\.netrc$/i,
4354
4438
  // netrc (stores HTTP credentials)
4355
4439
  /(?:^|\/)(passwd|shadow|sudoers)$/i,
@@ -5017,7 +5101,7 @@ var init_dist = __esm({
5017
5101
  {
5018
5102
  field: "command",
5019
5103
  op: "matches",
5020
- value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.ssh[\\/\\\\]",
5104
+ value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*?\\.ssh[\\/\\\\]",
5021
5105
  flags: "i"
5022
5106
  }
5023
5107
  ],
@@ -5031,7 +5115,7 @@ var init_dist = __esm({
5031
5115
  {
5032
5116
  field: "command",
5033
5117
  op: "matches",
5034
- value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.aws[\\/\\\\]",
5118
+ value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*?\\.aws[\\/\\\\]",
5035
5119
  flags: "i"
5036
5120
  }
5037
5121
  ],
@@ -5045,7 +5129,7 @@ var init_dist = __esm({
5045
5129
  {
5046
5130
  field: "command",
5047
5131
  op: "matches",
5048
- value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?(?=\\s|$|[;&|>)<])",
5132
+ value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*?\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?(?=\\s|$|[;&|>)<])",
5049
5133
  flags: "i"
5050
5134
  }
5051
5135
  ],
@@ -5059,7 +5143,7 @@ var init_dist = __esm({
5059
5143
  {
5060
5144
  field: "command",
5061
5145
  op: "matches",
5062
- value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
5146
+ value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
5063
5147
  flags: "i"
5064
5148
  }
5065
5149
  ],
@@ -5073,7 +5157,7 @@ var init_dist = __esm({
5073
5157
  {
5074
5158
  field: "file_path",
5075
5159
  op: "matches",
5076
- value: "(^|[\\/\\\\])\\.ssh[\\/\\\\]",
5160
+ value: "([\\/\\\\]\\.ssh([\\/\\\\]|$)|^\\.ssh[\\/\\\\])",
5077
5161
  flags: "i"
5078
5162
  }
5079
5163
  ],
@@ -5087,7 +5171,7 @@ var init_dist = __esm({
5087
5171
  {
5088
5172
  field: "file_path",
5089
5173
  op: "matches",
5090
- value: "(^|[\\/\\\\])\\.aws[\\/\\\\]",
5174
+ value: "([\\/\\\\]\\.aws([\\/\\\\]|$)|^\\.aws[\\/\\\\])",
5091
5175
  flags: "i"
5092
5176
  }
5093
5177
  ],
@@ -5101,7 +5185,7 @@ var init_dist = __esm({
5101
5185
  {
5102
5186
  field: "file_path",
5103
5187
  op: "matches",
5104
- value: "(^|[\\/\\\\])\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?$",
5188
+ value: "(^|[\\/\\\\])\\.env(?![\\w-])(?:[\\w.-]*\\.local$|(?!\\.(example|sample|template)\\b)(?!\\.test$)[\\w.-]*$)",
5105
5189
  flags: "i"
5106
5190
  }
5107
5191
  ],
@@ -5244,7 +5328,7 @@ var init_dist = __esm({
5244
5328
  };
5245
5329
  LOOP_THRESHOLD_FOR_WASTE = 3;
5246
5330
  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;
5247
- 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;
5331
+ SENSITIVE_PATH_RE = /[\\/]\.aws(?:[\\/]|$)|^\.aws[\\/]|[\\/]\.ssh(?:[\\/]|$)|^\.ssh[\\/]|(?:^|[\\/])\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)|\.config\/gcloud\/credentials\.db\b|\.docker\/config\.json\b|\.netrc\b|\.npmrc\b|\.node9\/credentials\.json\b/i;
5248
5332
  FILE_TOOLS = /* @__PURE__ */ new Set([
5249
5333
  "read",
5250
5334
  "read_file",
@@ -5321,7 +5405,7 @@ var init_dist = __esm({
5321
5405
  { view: "separators-stripped", decoder: "separators", stripped: true }
5322
5406
  ];
5323
5407
  LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
5324
- CANONICAL_EXTRACTOR_VERSION = "canonical-v10";
5408
+ CANONICAL_EXTRACTOR_VERSION = "canonical-v13";
5325
5409
  DEDUPE_PREVIEW_LEN = 120;
5326
5410
  TERMINAL_ESCAPE_RE = // eslint-disable-next-line no-control-regex
5327
5411
  /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
@@ -8640,7 +8724,7 @@ function isNetworkTool(toolName, args) {
8640
8724
  if (t === "bash" || t === "shell" || t === "run_shell_command" || t === "terminal.execute") {
8641
8725
  const a = args;
8642
8726
  const cmd = typeof a?.command === "string" ? a.command : typeof a?.cmd === "string" ? a.cmd : "";
8643
- return /\b(curl|wget|scp|rsync|nc|ncat|netcat|ssh)\b/.test(cmd);
8727
+ return NETWORK_COMMAND_RE.test(cmd);
8644
8728
  }
8645
8729
  return false;
8646
8730
  }
@@ -9515,7 +9599,7 @@ function canaryRecordById(id) {
9515
9599
  return null;
9516
9600
  }
9517
9601
  }
9518
- var import_crypto7, WRITE_TOOLS;
9602
+ var import_crypto7, WRITE_TOOLS, NETWORK_COMMAND_RE;
9519
9603
  var init_orchestrator = __esm({
9520
9604
  "src/auth/orchestrator.ts"() {
9521
9605
  "use strict";
@@ -9546,6 +9630,7 @@ var init_orchestrator = __esm({
9546
9630
  "notebook_edit",
9547
9631
  "notebookedit"
9548
9632
  ]);
9633
+ NETWORK_COMMAND_RE = new RegExp(`(?<![.\\w-])(${[...NET_BINARIES].join("|")})\\b`);
9549
9634
  }
9550
9635
  });
9551
9636
 
@@ -12609,9 +12694,10 @@ async function ensurePricingLoaded() {
12609
12694
  memCacheAt = Date.now();
12610
12695
  lookupCache.clear();
12611
12696
  }
12612
- function pricingFor(model) {
12697
+ function pricingFor(model, options = {}) {
12613
12698
  const norm = normalizeModel(model);
12614
- const cached = lookupCache.get(norm);
12699
+ const lookupKey = options.exact ? `exact:${norm}` : norm;
12700
+ const cached = lookupCache.get(lookupKey);
12615
12701
  if (cached !== void 0) return cached;
12616
12702
  if (memCache === null && !diskChecked) {
12617
12703
  diskChecked = true;
@@ -12631,6 +12717,7 @@ function pricingFor(model) {
12631
12717
  resolved = exact;
12632
12718
  break;
12633
12719
  }
12720
+ if (options.exact) continue;
12634
12721
  let best = null;
12635
12722
  for (const key of Object.keys(source)) {
12636
12723
  if (norm.startsWith(key.toLowerCase()) && (best === null || key.length > best.length)) {
@@ -12642,7 +12729,7 @@ function pricingFor(model) {
12642
12729
  break;
12643
12730
  }
12644
12731
  }
12645
- lookupCache.set(norm, resolved);
12732
+ lookupCache.set(lookupKey, resolved);
12646
12733
  return resolved;
12647
12734
  }
12648
12735
  var import_fs18, import_path20, import_os17, LITELLM_URL, BUNDLED_PRICING, CACHE_FILE, TTL_MS, memCache, memCacheAt, diskChecked, lookupCache;
@@ -12679,6 +12766,18 @@ var init_litellm = __esm({
12679
12766
  "gpt-5": [125e-8, 1e-5, 0, 125e-9],
12680
12767
  "gpt-5-codex": [125e-8, 1e-5, 0, 125e-9],
12681
12768
  "gpt-5-mini": [25e-8, 2e-6, 0, 25e-9],
12769
+ // Codex offline rates checked against official OpenAI model pages, 2026-09-11.
12770
+ "gpt-5.1-codex": [125e-8, 1e-5, 0, 125e-9],
12771
+ "gpt-5.1-codex-max": [125e-8, 1e-5, 0, 125e-9],
12772
+ "gpt-5.1-codex-mini": [25e-8, 2e-6, 0, 25e-9],
12773
+ "gpt-5.2-codex": [175e-8, 14e-6, 0, 175e-9],
12774
+ "gpt-5.3-codex": [175e-8, 14e-6, 0, 175e-9],
12775
+ "gpt-5.4": [25e-7, 15e-6, 0, 25e-8],
12776
+ "gpt-5.4-mini": [75e-8, 45e-7, 0, 75e-9],
12777
+ "gpt-5.5": [5e-6, 3e-5, 0, 5e-7],
12778
+ "gpt-5.6-sol": [4e-6, 2e-5, 5e-6, 4e-7],
12779
+ "gpt-5.6-terra": [2e-6, 12e-6, 25e-7, 2e-7],
12780
+ "gpt-6-astra": [1e-5, 5e-5, 125e-7, 1e-6],
12682
12781
  o3: [2e-6, 8e-6, 0, 5e-7],
12683
12782
  "o4-mini": [11e-7, 44e-7, 0, 275e-9],
12684
12783
  // Google. Values copied from the live LiteLLM table (verified 2026-06-14)
@@ -12847,103 +12946,237 @@ var init_cost_gemini = __esm({
12847
12946
 
12848
12947
  // src/cost-codex.ts
12849
12948
  function codexSessionsDir() {
12850
- return import_path22.default.join(import_os19.default.homedir(), ".codex", "sessions");
12949
+ return import_path22.default.join(process.env.CODEX_HOME?.trim() || import_path22.default.join(import_os19.default.homedir(), ".codex"), "sessions");
12851
12950
  }
12852
12951
  function codexPriceFor(model) {
12853
- return pricingFor(model) ?? CODEX_FALLBACK;
12952
+ return pricingFor(codexModel(model), { exact: true }) ?? CODEX_FALLBACK;
12854
12953
  }
12855
- function codexSessionCost(model, tokens) {
12856
- const nonCached = Math.max(0, tokens.input - tokens.cached);
12857
- const [pin, pout, , pcr] = codexPriceFor(model || "gpt-5");
12858
- return nonCached * pin + tokens.cached * pcr + tokens.output * pout;
12954
+ function codexModel(model) {
12955
+ return normalizeModel(model.replace(/^openai\//i, "").replace(/-\d{4}-\d{2}-\d{2}$/, ""));
12859
12956
  }
12860
- function listCodexSessionFiles(base) {
12861
- const out = [];
12862
- for (const y of safeReaddir2(base)) {
12863
- const yp = import_path22.default.join(base, y);
12864
- if (!isDir2(yp)) continue;
12865
- for (const m of safeReaddir2(yp)) {
12866
- const mp = import_path22.default.join(yp, m);
12867
- if (!isDir2(mp)) continue;
12868
- for (const d of safeReaddir2(mp)) {
12869
- const dp = import_path22.default.join(mp, d);
12870
- if (!isDir2(dp)) continue;
12871
- for (const f of safeReaddir2(dp)) {
12872
- if (f.endsWith(".jsonl")) out.push(import_path22.default.join(dp, f));
12873
- }
12957
+ function addTokens(previous, delta) {
12958
+ return {
12959
+ input: (previous?.input ?? 0) + delta.input,
12960
+ cached: (previous?.cached ?? 0) + delta.cached,
12961
+ output: (previous?.output ?? 0) + delta.output,
12962
+ cacheWrite: (previous?.cacheWrite ?? 0) + delta.cacheWrite
12963
+ };
12964
+ }
12965
+ function codexSessionCost(model, tokens, request2) {
12966
+ const input = tokenNumber(tokens.input);
12967
+ const cached = Math.min(input, tokenNumber(tokens.cached));
12968
+ const written = Math.min(input - cached, tokenNumber(tokens.cacheWrite));
12969
+ const [pin, pout, pcw, pcr] = codexPriceFor(model || "gpt-5");
12970
+ const longContext = request2 && request2.inputTokens > 272e3 && ["gpt-5.4", "gpt-5.5", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-6-astra"].includes(
12971
+ codexModel(model)
12972
+ );
12973
+ const inputMultiplier = longContext ? 2 : 1;
12974
+ const outputMultiplier = longContext ? 1.5 : 1;
12975
+ const tierMultiplier = request2?.serviceTier === "flex" ? 0.5 : codexModel(model) === "gpt-6-astra" && (request2?.serviceTier === "fast" || request2?.serviceTier === "priority") ? 2 : 1;
12976
+ return (((input - cached - written) * pin + cached * pcr + written * (pcw || pin)) * inputMultiplier + tokenNumber(tokens.output) * pout * outputMultiplier) * tierMultiplier;
12977
+ }
12978
+ function listCodexSessionFiles(base = codexSessionsDir()) {
12979
+ const files = [];
12980
+ const walk = (dir) => {
12981
+ try {
12982
+ for (const entry of import_fs20.default.readdirSync(dir, { withFileTypes: true })) {
12983
+ const file = import_path22.default.join(dir, entry.name);
12984
+ if (entry.isDirectory()) walk(file);
12985
+ else if (entry.isFile() && entry.name.endsWith(".jsonl")) files.push(file);
12874
12986
  }
12987
+ } catch {
12988
+ }
12989
+ };
12990
+ walk(base);
12991
+ if (import_path22.default.basename(base) === "sessions") walk(import_path22.default.join(import_path22.default.dirname(base), "archived_sessions"));
12992
+ const sessions = /* @__PURE__ */ new Map();
12993
+ for (const file of files.sort()) {
12994
+ try {
12995
+ const stat = import_fs20.default.statSync(file);
12996
+ let id = "";
12997
+ try {
12998
+ const first = JSON.parse(import_fs20.default.readFileSync(file, "utf8").split("\n", 1)[0]);
12999
+ if (first?.type === "session_meta" && typeof first.payload?.id === "string")
13000
+ id = first.payload.id;
13001
+ } catch {
13002
+ }
13003
+ const key = id ? `session:${id}` : `file:${file}`;
13004
+ const prior = sessions.get(key);
13005
+ if (!prior || stat.mtimeMs > prior.mtime || stat.mtimeMs === prior.mtime && stat.size > prior.size) {
13006
+ sessions.set(key, { file, mtime: stat.mtimeMs, size: stat.size });
13007
+ }
13008
+ } catch {
12875
13009
  }
12876
13010
  }
12877
- return out;
13011
+ return [...sessions.values()].map((s) => s.file);
12878
13012
  }
12879
- function safeReaddir2(dir) {
12880
- try {
12881
- return import_fs20.default.readdirSync(dir);
12882
- } catch {
12883
- return [];
12884
- }
13013
+ function record(value) {
13014
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
12885
13015
  }
12886
- function isDir2(p) {
12887
- try {
12888
- return import_fs20.default.statSync(p).isDirectory();
12889
- } catch {
12890
- return false;
13016
+ function tokenNumber(value) {
13017
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
13018
+ }
13019
+ function usage(value, fallback) {
13020
+ const u = record(value);
13021
+ if (!["input_tokens", "output_tokens"].some((k) => typeof u[k] === "number")) return null;
13022
+ for (const key of [
13023
+ "input_tokens",
13024
+ "cached_input_tokens",
13025
+ "cache_read_input_tokens",
13026
+ "output_tokens",
13027
+ "cache_write_input_tokens"
13028
+ ]) {
13029
+ if (u[key] !== void 0 && (typeof u[key] !== "number" || !Number.isFinite(u[key]) || u[key] < 0))
13030
+ return null;
12891
13031
  }
13032
+ return {
13033
+ input: tokenNumber(u.input_tokens ?? fallback?.input),
13034
+ cached: tokenNumber(u.cached_input_tokens ?? u.cache_read_input_tokens ?? fallback?.cached),
13035
+ output: tokenNumber(u.output_tokens ?? fallback?.output),
13036
+ cacheWrite: tokenNumber(u.cache_write_input_tokens ?? fallback?.cacheWrite)
13037
+ };
12892
13038
  }
12893
- function parseCodexSession(lines) {
12894
- let sessionStart = "";
12895
- let runId = "";
12896
- let cwd = "";
12897
- let model = "";
12898
- let input = 0;
12899
- let cached = 0;
12900
- let output = 0;
12901
- let sawUsage = false;
13039
+ function timestamp(value) {
13040
+ return typeof value === "string" && Number.isFinite(Date.parse(value)) ? new Date(value).toISOString() : "";
13041
+ }
13042
+ function parseCodexUsage(lines) {
13043
+ const result = {
13044
+ events: [],
13045
+ sessionStart: "",
13046
+ runId: "",
13047
+ workingDir: "",
13048
+ legacyModels: []
13049
+ };
13050
+ let model = "gpt-5";
13051
+ let serviceTier;
13052
+ let previous = null;
13053
+ const legacyModels = /* @__PURE__ */ new Set();
13054
+ const seenStandalone = /* @__PURE__ */ new Set();
12902
13055
  for (const raw of lines) {
12903
- if (!raw.trim()) continue;
12904
13056
  let entry;
12905
13057
  try {
12906
- entry = JSON.parse(raw);
13058
+ entry = record(JSON.parse(raw));
12907
13059
  } catch {
12908
13060
  continue;
12909
13061
  }
12910
- const p = entry.payload ?? {};
13062
+ const p = record(entry.payload);
12911
13063
  if (entry.type === "session_meta") {
12912
- if (!sessionStart && typeof p["timestamp"] === "string") sessionStart = p["timestamp"];
12913
- if (!runId && typeof p["id"] === "string") runId = p["id"];
12914
- if (!cwd && typeof p["cwd"] === "string") cwd = p["cwd"];
13064
+ result.sessionStart ||= timestamp(p.timestamp ?? entry.timestamp);
13065
+ if (!result.runId && typeof p.id === "string") result.runId = p.id;
13066
+ if (!result.workingDir && typeof p.cwd === "string") result.workingDir = p.cwd;
12915
13067
  continue;
12916
13068
  }
12917
13069
  if (entry.type === "turn_context") {
12918
- if (typeof p["model"] === "string") model = p["model"];
12919
- if (!cwd && typeof p["cwd"] === "string") cwd = p["cwd"];
13070
+ if (typeof p.model === "string" && p.model) {
13071
+ model = p.model;
13072
+ legacyModels.add(normalizeModel(model));
13073
+ }
13074
+ if (!result.workingDir && typeof p.cwd === "string") result.workingDir = p.cwd;
13075
+ serviceTier = typeof p.service_tier === "string" ? p.service_tier : void 0;
12920
13076
  continue;
12921
13077
  }
12922
- if (entry.type === "event_msg" && p["type"] === "token_count") {
12923
- const info = p["info"] ?? {};
12924
- const usage = info["total_token_usage"] ?? {};
12925
- if (typeof usage["input_tokens"] === "number") input = usage["input_tokens"];
12926
- if (typeof usage["cached_input_tokens"] === "number") cached = usage["cached_input_tokens"];
12927
- if (typeof usage["output_tokens"] === "number") output = usage["output_tokens"];
12928
- sawUsage = true;
13078
+ if (entry.type !== "event_msg" || p.type !== "token_count") continue;
13079
+ const info = record(p.info);
13080
+ const total = usage(info.total_token_usage, previous);
13081
+ const last = usage(info.last_token_usage);
13082
+ if (!total && !last) continue;
13083
+ const eventModel = [info.model, info.model_name, p.model].find(
13084
+ (v) => typeof v === "string" && v
13085
+ );
13086
+ if (typeof eventModel === "string") model = eventModel;
13087
+ let delta;
13088
+ if (total) {
13089
+ if (previous && Object.keys(total).every(
13090
+ (k) => total[k] === previous[k]
13091
+ ))
13092
+ continue;
13093
+ const reset = previous && (total.input < previous.input || total.output < previous.output);
13094
+ delta = reset ? last ?? total : {
13095
+ input: Math.max(0, total.input - (previous?.input ?? 0)),
13096
+ cached: Math.max(0, total.cached - (previous?.cached ?? 0)),
13097
+ output: Math.max(0, total.output - (previous?.output ?? 0)),
13098
+ cacheWrite: Math.max(0, total.cacheWrite - (previous?.cacheWrite ?? 0))
13099
+ };
13100
+ previous = total;
13101
+ } else {
13102
+ delta = last;
13103
+ const key = JSON.stringify([entry.timestamp, model, delta]);
13104
+ if (entry.timestamp && seenStandalone.has(key)) continue;
13105
+ if (entry.timestamp) seenStandalone.add(key);
13106
+ previous = addTokens(previous, delta);
13107
+ }
13108
+ if (delta.input === 0 && delta.output === 0) continue;
13109
+ const ts = timestamp(entry.timestamp) || result.sessionStart;
13110
+ if (!ts) continue;
13111
+ const cached = Math.min(delta.input, delta.cached);
13112
+ const written = Math.min(delta.input - cached, delta.cacheWrite);
13113
+ result.events.push({
13114
+ timestamp: ts,
13115
+ date: ts.slice(0, 10),
13116
+ model: normalizeModel(model),
13117
+ workingDir: result.workingDir,
13118
+ runId: result.runId,
13119
+ costUSD: codexSessionCost(model, delta, {
13120
+ inputTokens: last?.input ?? delta.input,
13121
+ serviceTier: typeof info.service_tier === "string" ? info.service_tier : serviceTier
13122
+ }),
13123
+ inputTokens: delta.input - cached - written,
13124
+ outputTokens: delta.output,
13125
+ cacheReadTokens: cached,
13126
+ cacheWriteTokens: written
13127
+ });
13128
+ }
13129
+ result.legacyModels = [...legacyModels.size ? legacyModels : ["gpt-5"]];
13130
+ return result;
13131
+ }
13132
+ function codexUsageInWindow(usage2, start, end) {
13133
+ return usage2.events.filter(
13134
+ (e) => (!start || Date.parse(e.timestamp) >= start.getTime()) && (!end || Date.parse(e.timestamp) <= end.getTime())
13135
+ );
13136
+ }
13137
+ function parseCodexSession(lines) {
13138
+ const parsed = parseCodexUsage(lines);
13139
+ if (!parsed.events.length) return [];
13140
+ const rows = /* @__PURE__ */ new Map();
13141
+ if (parsed.sessionStart) {
13142
+ for (const model of parsed.legacyModels) {
13143
+ rows.set(`${parsed.sessionStart.slice(0, 10)}::${model}`, {
13144
+ date: parsed.sessionStart.slice(0, 10),
13145
+ model,
13146
+ workingDir: parsed.workingDir,
13147
+ runId: parsed.runId,
13148
+ costUSD: 0,
13149
+ inputTokens: 0,
13150
+ outputTokens: 0,
13151
+ cacheReadTokens: 0,
13152
+ cacheWriteTokens: 0
13153
+ });
12929
13154
  }
12930
13155
  }
12931
- if (!sessionStart || !sawUsage) return null;
12932
- const nonCached = Math.max(0, input - cached);
12933
- if (nonCached === 0 && output === 0 && cached === 0) return null;
12934
- const norm = normalizeModel(model || "gpt-5");
12935
- const costUSD = codexSessionCost(model, { input, cached, output });
12936
- return {
12937
- date: sessionStart.slice(0, 10),
12938
- model: norm,
12939
- workingDir: cwd,
12940
- runId,
12941
- costUSD,
12942
- inputTokens: nonCached,
12943
- outputTokens: output,
12944
- cacheReadTokens: cached,
12945
- cacheWriteTokens: 0
12946
- };
13156
+ for (const event of parsed.events) {
13157
+ const e = {
13158
+ date: event.date,
13159
+ model: event.model,
13160
+ workingDir: event.workingDir,
13161
+ runId: event.runId,
13162
+ costUSD: event.costUSD,
13163
+ inputTokens: event.inputTokens,
13164
+ outputTokens: event.outputTokens,
13165
+ cacheReadTokens: event.cacheReadTokens,
13166
+ cacheWriteTokens: event.cacheWriteTokens
13167
+ };
13168
+ const key = `${e.date}::${e.model}`;
13169
+ const prev = rows.get(key);
13170
+ if (!prev) rows.set(key, { ...e });
13171
+ else {
13172
+ prev.costUSD += e.costUSD;
13173
+ prev.inputTokens += e.inputTokens;
13174
+ prev.outputTokens += e.outputTokens;
13175
+ prev.cacheReadTokens += e.cacheReadTokens;
13176
+ prev.cacheWriteTokens += e.cacheWriteTokens;
13177
+ }
13178
+ }
13179
+ return [...rows.values()];
12947
13180
  }
12948
13181
  var import_fs20, import_os19, import_path22, CODEX_FALLBACK, codexSource;
12949
13182
  var init_cost_codex = __esm({
@@ -12956,43 +13189,17 @@ var init_cost_codex = __esm({
12956
13189
  CODEX_FALLBACK = [5e-6, 15e-6, 0, 25e-7];
12957
13190
  codexSource = {
12958
13191
  id: "codex",
12959
- available() {
12960
- try {
12961
- return import_fs20.default.existsSync(codexSessionsDir());
12962
- } catch {
12963
- return false;
12964
- }
12965
- },
13192
+ available: () => import_fs20.default.existsSync(codexSessionsDir()) || import_fs20.default.existsSync(import_path22.default.join(import_path22.default.dirname(codexSessionsDir()), "archived_sessions")),
12966
13193
  collect(sinceMs) {
12967
- const base = codexSessionsDir();
12968
- const combined = /* @__PURE__ */ new Map();
12969
- for (const file of listCodexSessionFiles(base)) {
13194
+ const entries = [];
13195
+ for (const file of listCodexSessionFiles()) {
12970
13196
  try {
12971
13197
  if (sinceMs !== void 0 && import_fs20.default.statSync(file).mtimeMs < sinceMs) continue;
13198
+ entries.push(...parseCodexSession(import_fs20.default.readFileSync(file, "utf8").split("\n")));
12972
13199
  } catch {
12973
- continue;
12974
- }
12975
- let content;
12976
- try {
12977
- content = import_fs20.default.readFileSync(file, "utf8");
12978
- } catch {
12979
- continue;
12980
- }
12981
- const e = parseCodexSession(content.split("\n"));
12982
- if (!e) continue;
12983
- const key = `${e.date}::${e.model}::${e.workingDir ?? ""}::${e.runId ?? ""}`;
12984
- const prev = combined.get(key);
12985
- if (prev) {
12986
- prev.costUSD += e.costUSD;
12987
- prev.inputTokens += e.inputTokens;
12988
- prev.outputTokens += e.outputTokens;
12989
- prev.cacheReadTokens += e.cacheReadTokens;
12990
- prev.cacheWriteTokens += e.cacheWriteTokens;
12991
- } else {
12992
- combined.set(key, { ...e });
12993
13200
  }
12994
13201
  }
12995
- return [...combined.values()];
13202
+ return entries;
12996
13203
  }
12997
13204
  };
12998
13205
  }
@@ -13002,7 +13209,7 @@ var init_cost_codex = __esm({
13002
13209
  function copilotSessionsDir() {
13003
13210
  return import_path23.default.join(import_os20.default.homedir(), ".copilot", "session-state");
13004
13211
  }
13005
- function safeReaddir3(dir) {
13212
+ function safeReaddir2(dir) {
13006
13213
  try {
13007
13214
  return import_fs21.default.readdirSync(dir);
13008
13215
  } catch {
@@ -13091,7 +13298,7 @@ var init_cost_copilot = __esm({
13091
13298
  collect(sinceMs) {
13092
13299
  const base = copilotSessionsDir();
13093
13300
  const combined = /* @__PURE__ */ new Map();
13094
- for (const sid of safeReaddir3(base)) {
13301
+ for (const sid of safeReaddir2(base)) {
13095
13302
  const file = import_path23.default.join(base, sid, "events.jsonl");
13096
13303
  try {
13097
13304
  if (sinceMs !== void 0 && import_fs21.default.statSync(file).mtimeMs < sinceMs) continue;
@@ -13705,8 +13912,8 @@ function originForRule(ruleName, sections, enabled) {
13705
13912
  }
13706
13913
  return "";
13707
13914
  }
13708
- function relativeDate(timestamp, now = /* @__PURE__ */ new Date()) {
13709
- const t = new Date(timestamp).getTime();
13915
+ function relativeDate(timestamp2, now = /* @__PURE__ */ new Date()) {
13916
+ const t = new Date(timestamp2).getTime();
13710
13917
  if (Number.isNaN(t)) return "?";
13711
13918
  const days = Math.floor((now.getTime() - t) / 864e5);
13712
13919
  if (days < 1) return "today";
@@ -13817,7 +14024,7 @@ function readPreviousScan(opts = {}) {
13817
14024
  return null;
13818
14025
  }
13819
14026
  }
13820
- function appendScanHistory(record, opts = {}) {
14027
+ function appendScanHistory(record2, opts = {}) {
13821
14028
  const filePath = opts.path ?? defaultHistoryPath();
13822
14029
  const cap = opts.cap ?? SCAN_HISTORY_CAP;
13823
14030
  try {
@@ -13832,7 +14039,7 @@ function appendScanHistory(record, opts = {}) {
13832
14039
  } catch {
13833
14040
  }
13834
14041
  }
13835
- history.push(record);
14042
+ history.push(record2);
13836
14043
  if (history.length > cap) {
13837
14044
  history = history.slice(history.length - cap);
13838
14045
  }
@@ -13893,17 +14100,17 @@ function parseJSONLFile(filePath, fallbackWorkingDir) {
13893
14100
  if (row["type"] !== "assistant") continue;
13894
14101
  const msg = row["message"];
13895
14102
  if (!msg?.["usage"] || typeof msg["model"] !== "string") continue;
13896
- const usage = msg["usage"];
14103
+ const usage2 = msg["usage"];
13897
14104
  const model = msg["model"];
13898
- const timestamp = row["timestamp"];
13899
- if (typeof timestamp !== "string" || timestamp.length < 10) continue;
13900
- const date = timestamp.slice(0, 10);
14105
+ const timestamp2 = row["timestamp"];
14106
+ if (typeof timestamp2 !== "string" || timestamp2.length < 10) continue;
14107
+ const date = timestamp2.slice(0, 10);
13901
14108
  const p = pricingFor(model);
13902
14109
  if (!p) continue;
13903
- const inp = Number(usage["input_tokens"] ?? 0);
13904
- const out = Number(usage["output_tokens"] ?? 0);
13905
- const cw = Number(usage["cache_creation_input_tokens"] ?? 0);
13906
- const cr = Number(usage["cache_read_input_tokens"] ?? 0);
14110
+ const inp = Number(usage2["input_tokens"] ?? 0);
14111
+ const out = Number(usage2["output_tokens"] ?? 0);
14112
+ const cw = Number(usage2["cache_creation_input_tokens"] ?? 0);
14113
+ const cr = Number(usage2["cache_read_input_tokens"] ?? 0);
13907
14114
  const cost = inp * p[0] + out * p[1] + cw * p[2] + cr * p[3];
13908
14115
  const rowCwd = typeof row["cwd"] === "string" ? row["cwd"] : null;
13909
14116
  const workingDir = rowCwd && rowCwd.startsWith("/") ? rowCwd : fallbackWorkingDir;
@@ -14931,7 +15138,7 @@ function safeCanaryScanValues() {
14931
15138
  return [];
14932
15139
  }
14933
15140
  }
14934
- function recordCanaries(scanned, toolName, timestamp, projLabel, sessionId, agent, result, dedup, values) {
15141
+ function recordCanaries(scanned, toolName, timestamp2, projLabel, sessionId, agent, result, dedup, values) {
14935
15142
  if (values.length === 0) return [];
14936
15143
  let pool = [...values];
14937
15144
  const matched = [];
@@ -14945,8 +15152,8 @@ function recordCanaries(scanned, toolName, timestamp, projLabel, sessionId, agen
14945
15152
  const existing = dedup.canaryIndex.get(key);
14946
15153
  if (existing) {
14947
15154
  existing.count++;
14948
- if (timestamp && (!existing.timestamp || timestamp < existing.timestamp)) {
14949
- existing.timestamp = timestamp;
15155
+ if (timestamp2 && (!existing.timestamp || timestamp2 < existing.timestamp)) {
15156
+ existing.timestamp = timestamp2;
14950
15157
  }
14951
15158
  continue;
14952
15159
  }
@@ -14959,7 +15166,7 @@ function recordCanaries(scanned, toolName, timestamp, projLabel, sessionId, agen
14959
15166
  view: hit.view,
14960
15167
  retired: hit.retired,
14961
15168
  toolName,
14962
- timestamp,
15169
+ timestamp: timestamp2,
14963
15170
  project: projLabel,
14964
15171
  sessionId,
14965
15172
  agent,
@@ -14991,7 +15198,7 @@ function scrubDecoys(subject, values) {
14991
15198
  };
14992
15199
  return walk(subject, 0);
14993
15200
  }
14994
- function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sessionId, agent, result, dedup) {
15201
+ function pushFsOpAstFinding(command, toolName, input, timestamp2, projLabel, sessionId, agent, result, dedup) {
14995
15202
  const fsVerdict = analyzeFsOperation(command);
14996
15203
  if (!fsVerdict) return false;
14997
15204
  const synthRule = {
@@ -15021,7 +15228,7 @@ function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sess
15021
15228
  source: synthSource,
15022
15229
  toolName,
15023
15230
  input,
15024
- timestamp,
15231
+ timestamp: timestamp2,
15025
15232
  project: projLabel,
15026
15233
  sessionId,
15027
15234
  agent
@@ -15029,9 +15236,9 @@ function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sess
15029
15236
  }
15030
15237
  return true;
15031
15238
  }
15032
- function isStaleFinding(timestamp, now = Date.now()) {
15033
- if (!timestamp) return false;
15034
- const t = Date.parse(timestamp);
15239
+ function isStaleFinding(timestamp2, now = Date.now()) {
15240
+ if (!timestamp2) return false;
15241
+ const t = Date.parse(timestamp2);
15035
15242
  if (Number.isNaN(t)) return false;
15036
15243
  const ageDays = (now - t) / 864e5;
15037
15244
  return ageDays > STALE_AGE_DAYS;
@@ -15179,37 +15386,7 @@ function countScanFiles() {
15179
15386
  } catch {
15180
15387
  }
15181
15388
  }
15182
- const codexDir = import_path29.default.join(import_os26.default.homedir(), ".codex", "sessions");
15183
- if (import_fs27.default.existsSync(codexDir)) {
15184
- try {
15185
- for (const year of import_fs27.default.readdirSync(codexDir)) {
15186
- const yp = import_path29.default.join(codexDir, year);
15187
- try {
15188
- if (!import_fs27.default.statSync(yp).isDirectory()) continue;
15189
- for (const month of import_fs27.default.readdirSync(yp)) {
15190
- const mp = import_path29.default.join(yp, month);
15191
- try {
15192
- if (!import_fs27.default.statSync(mp).isDirectory()) continue;
15193
- for (const day of import_fs27.default.readdirSync(mp)) {
15194
- const dp = import_path29.default.join(mp, day);
15195
- try {
15196
- if (!import_fs27.default.statSync(dp).isDirectory()) continue;
15197
- total += listSessionFiles(dp).length;
15198
- } catch {
15199
- continue;
15200
- }
15201
- }
15202
- } catch {
15203
- continue;
15204
- }
15205
- }
15206
- } catch {
15207
- continue;
15208
- }
15209
- }
15210
- } catch {
15211
- }
15212
- }
15389
+ total += listCodexSessionFiles().length;
15213
15390
  return total;
15214
15391
  }
15215
15392
  function renderProgressBar(done, total, lines) {
@@ -15332,12 +15509,12 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
15332
15509
  }
15333
15510
  continue;
15334
15511
  }
15335
- const usage = entry.message?.usage;
15512
+ const usage2 = entry.message?.usage;
15336
15513
  const model = entry.message?.model;
15337
- if (usage && model) {
15514
+ if (usage2 && model) {
15338
15515
  const p = claudeModelPrice(model);
15339
15516
  if (p) {
15340
- const rowCost = (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;
15517
+ const rowCost = (usage2.input_tokens ?? 0) * p.i + (usage2.output_tokens ?? 0) * p.o + (usage2.cache_creation_input_tokens ?? 0) * p.cw + (usage2.cache_read_input_tokens ?? 0) * p.cr;
15341
15518
  result.totalCostUSD += rowCost;
15342
15519
  session.costUSD += rowCost;
15343
15520
  }
@@ -15871,15 +16048,15 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
15871
16048
  } catch {
15872
16049
  continue;
15873
16050
  }
15874
- const timestamp = step.created_at ?? "";
15875
- if (startDate && timestamp && new Date(timestamp) < startDate) continue;
16051
+ const timestamp2 = step.created_at ?? "";
16052
+ if (startDate && timestamp2 && new Date(timestamp2) < startDate) continue;
15876
16053
  if (step.type === "USER_INPUT") {
15877
16054
  const text = typeof step.content === "string" ? step.content : "";
15878
16055
  if (text) {
15879
16056
  const decoysHere5 = recordCanaries(
15880
16057
  { text },
15881
16058
  "user-prompt",
15882
- timestamp,
16059
+ timestamp2,
15883
16060
  projLabel,
15884
16061
  sessionId,
15885
16062
  "antigravity",
@@ -15896,7 +16073,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
15896
16073
  patternName: dlpMatch.patternName,
15897
16074
  redactedSample: dlpMatch.redactedSample,
15898
16075
  toolName: "user-prompt",
15899
- timestamp,
16076
+ timestamp: timestamp2,
15900
16077
  project: projLabel,
15901
16078
  sessionId,
15902
16079
  agent: "antigravity"
@@ -15907,16 +16084,16 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
15907
16084
  continue;
15908
16085
  }
15909
16086
  if (!Array.isArray(step.tool_calls) || step.tool_calls.length === 0) continue;
15910
- if (timestamp) {
15911
- if (!result.firstDate || timestamp < result.firstDate) result.firstDate = timestamp;
15912
- if (!result.lastDate || timestamp > result.lastDate) result.lastDate = timestamp;
16087
+ if (timestamp2) {
16088
+ if (!result.firstDate || timestamp2 < result.firstDate) result.firstDate = timestamp2;
16089
+ if (!result.lastDate || timestamp2 > result.lastDate) result.lastDate = timestamp2;
15913
16090
  }
15914
16091
  for (const tc of step.tool_calls) {
15915
16092
  result.totalToolCalls++;
15916
16093
  const toolName = tc.name ?? "";
15917
16094
  const toolNameLower = toolName.toLowerCase();
15918
16095
  const input = canonicalToolInput(toolName, tc.args ?? {});
15919
- sessionCalls.push({ toolName, input, timestamp });
16096
+ sessionCalls.push({ toolName, input, timestamp: timestamp2 });
15920
16097
  const isShellTool = toolNameLower === "run_command";
15921
16098
  if (isShellTool) {
15922
16099
  result.bashCalls++;
@@ -15931,7 +16108,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
15931
16108
  const decoysHere6 = recordCanaries(
15932
16109
  input,
15933
16110
  toolName,
15934
- timestamp,
16111
+ timestamp2,
15935
16112
  projLabel,
15936
16113
  sessionId,
15937
16114
  "antigravity",
@@ -15948,7 +16125,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
15948
16125
  patternName: dlpMatch.patternName,
15949
16126
  redactedSample: dlpMatch.redactedSample,
15950
16127
  toolName,
15951
- timestamp,
16128
+ timestamp: timestamp2,
15952
16129
  project: projLabel,
15953
16130
  sessionId,
15954
16131
  agent: "antigravity"
@@ -15961,7 +16138,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
15961
16138
  String(input.command ?? ""),
15962
16139
  toolName,
15963
16140
  input,
15964
- timestamp,
16141
+ timestamp2,
15965
16142
  projLabel,
15966
16143
  sessionId,
15967
16144
  "antigravity",
@@ -15985,7 +16162,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
15985
16162
  source,
15986
16163
  toolName,
15987
16164
  input,
15988
- timestamp,
16165
+ timestamp: timestamp2,
15989
16166
  project: projLabel,
15990
16167
  sessionId,
15991
16168
  agent: "antigravity"
@@ -16017,7 +16194,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
16017
16194
  },
16018
16195
  toolName,
16019
16196
  input,
16020
- timestamp,
16197
+ timestamp: timestamp2,
16021
16198
  project: projLabel,
16022
16199
  sessionId,
16023
16200
  agent: "antigravity"
@@ -16087,7 +16264,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16087
16264
  } catch {
16088
16265
  continue;
16089
16266
  }
16090
- const timestamp = ev.timestamp ?? "";
16267
+ const timestamp2 = ev.timestamp ?? "";
16091
16268
  if (ev.type === "session.start") {
16092
16269
  const cwd = ev.data?.context?.cwd;
16093
16270
  if (typeof cwd === "string" && cwd) {
@@ -16095,14 +16272,14 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16095
16272
  }
16096
16273
  continue;
16097
16274
  }
16098
- if (startDate && timestamp && new Date(timestamp) < startDate) continue;
16275
+ if (startDate && timestamp2 && new Date(timestamp2) < startDate) continue;
16099
16276
  if (ev.type === "user.message") {
16100
16277
  const text = ev.data?.content ?? ev.data?.text ?? "";
16101
16278
  if (typeof text === "string" && text) {
16102
16279
  const decoysHere7 = recordCanaries(
16103
16280
  { text },
16104
16281
  "user-prompt",
16105
- timestamp,
16282
+ timestamp2,
16106
16283
  projLabel,
16107
16284
  sessionId,
16108
16285
  "copilot",
@@ -16119,7 +16296,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16119
16296
  patternName: dlpMatch2.patternName,
16120
16297
  redactedSample: dlpMatch2.redactedSample,
16121
16298
  toolName: "user-prompt",
16122
- timestamp,
16299
+ timestamp: timestamp2,
16123
16300
  project: projLabel,
16124
16301
  sessionId,
16125
16302
  agent: "copilot"
@@ -16134,19 +16311,19 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16134
16311
  const toolNameLower = toolName.toLowerCase();
16135
16312
  const input = ev.data?.arguments ?? {};
16136
16313
  result.totalToolCalls++;
16137
- sessionCalls.push({ toolName, input, timestamp });
16314
+ sessionCalls.push({ toolName, input, timestamp: timestamp2 });
16138
16315
  const isShellTool = isShellShapedTool(toolNameLower, toolInspectionMap);
16139
16316
  if (isShellTool) result.bashCalls++;
16140
- if (timestamp) {
16141
- if (!result.firstDate || timestamp < result.firstDate) result.firstDate = timestamp;
16142
- if (!result.lastDate || timestamp > result.lastDate) result.lastDate = timestamp;
16317
+ if (timestamp2) {
16318
+ if (!result.firstDate || timestamp2 < result.firstDate) result.firstDate = timestamp2;
16319
+ if (!result.lastDate || timestamp2 > result.lastDate) result.lastDate = timestamp2;
16143
16320
  }
16144
16321
  const rawCmd = String(input.command ?? "").trimStart();
16145
16322
  if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd)) continue;
16146
16323
  const decoysHere8 = recordCanaries(
16147
16324
  input,
16148
16325
  toolName,
16149
- timestamp,
16326
+ timestamp2,
16150
16327
  projLabel,
16151
16328
  sessionId,
16152
16329
  "copilot",
@@ -16163,7 +16340,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16163
16340
  patternName: dlpMatch.patternName,
16164
16341
  redactedSample: dlpMatch.redactedSample,
16165
16342
  toolName,
16166
- timestamp,
16343
+ timestamp: timestamp2,
16167
16344
  project: projLabel,
16168
16345
  sessionId,
16169
16346
  agent: "copilot"
@@ -16176,7 +16353,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16176
16353
  String(input.command ?? ""),
16177
16354
  toolName,
16178
16355
  input,
16179
- timestamp,
16356
+ timestamp2,
16180
16357
  projLabel,
16181
16358
  sessionId,
16182
16359
  "copilot",
@@ -16199,7 +16376,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16199
16376
  source,
16200
16377
  toolName,
16201
16378
  input,
16202
- timestamp,
16379
+ timestamp: timestamp2,
16203
16380
  project: projLabel,
16204
16381
  sessionId,
16205
16382
  agent: "copilot"
@@ -16231,7 +16408,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16231
16408
  },
16232
16409
  toolName,
16233
16410
  input,
16234
- timestamp,
16411
+ timestamp: timestamp2,
16235
16412
  project: projLabel,
16236
16413
  sessionId,
16237
16414
  agent: "copilot"
@@ -16246,7 +16423,6 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16246
16423
  }
16247
16424
  function scanCodexHistory(startDate, onProgress, onLine) {
16248
16425
  const canaryVals = safeCanaryScanValues();
16249
- const sessionsBase = import_path29.default.join(import_os26.default.homedir(), ".codex", "sessions");
16250
16426
  const result = {
16251
16427
  filesScanned: 0,
16252
16428
  sessions: 0,
@@ -16263,39 +16439,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
16263
16439
  perSession: []
16264
16440
  };
16265
16441
  const dedup = emptyScanDedup();
16266
- if (!import_fs27.default.existsSync(sessionsBase)) return result;
16267
- const jsonlFiles = [];
16268
- try {
16269
- for (const year of import_fs27.default.readdirSync(sessionsBase)) {
16270
- const yearPath = import_path29.default.join(sessionsBase, year);
16271
- try {
16272
- if (!import_fs27.default.statSync(yearPath).isDirectory()) continue;
16273
- } catch {
16274
- continue;
16275
- }
16276
- for (const month of import_fs27.default.readdirSync(yearPath)) {
16277
- const monthPath = import_path29.default.join(yearPath, month);
16278
- try {
16279
- if (!import_fs27.default.statSync(monthPath).isDirectory()) continue;
16280
- } catch {
16281
- continue;
16282
- }
16283
- for (const day of import_fs27.default.readdirSync(monthPath)) {
16284
- const dayPath = import_path29.default.join(monthPath, day);
16285
- try {
16286
- if (!import_fs27.default.statSync(dayPath).isDirectory()) continue;
16287
- } catch {
16288
- continue;
16289
- }
16290
- for (const file of import_fs27.default.readdirSync(dayPath)) {
16291
- if (file.endsWith(".jsonl")) jsonlFiles.push(import_path29.default.join(dayPath, file));
16292
- }
16293
- }
16294
- }
16295
- }
16296
- } catch {
16297
- return result;
16298
- }
16442
+ const jsonlFiles = listCodexSessionFiles();
16299
16443
  const ruleSources = buildRuleSources();
16300
16444
  for (const filePath of jsonlFiles) {
16301
16445
  result.filesScanned++;
@@ -16311,10 +16455,6 @@ function scanCodexHistory(startDate, onProgress, onLine) {
16311
16455
  let projLabel = "";
16312
16456
  result.sessions++;
16313
16457
  const sessionCalls = [];
16314
- let lastTotalInput = 0;
16315
- let lastTotalCached = 0;
16316
- let lastTotalOutput = 0;
16317
- let model = "";
16318
16458
  for (const line of lines) {
16319
16459
  if (!line.trim()) continue;
16320
16460
  onLine?.();
@@ -16332,18 +16472,6 @@ function scanCodexHistory(startDate, onProgress, onLine) {
16332
16472
  projLabel = stripTerminalEscapes(cwd.replace(import_os26.default.homedir(), "~")).slice(0, 40);
16333
16473
  continue;
16334
16474
  }
16335
- if (entry.type === "turn_context" && typeof payload["model"] === "string") {
16336
- model = payload["model"];
16337
- continue;
16338
- }
16339
- if (entry.type === "event_msg" && payload["type"] === "token_count") {
16340
- const info = payload["info"];
16341
- const usage = info?.["total_token_usage"] ?? {};
16342
- lastTotalInput = usage["input_tokens"] ?? lastTotalInput;
16343
- lastTotalCached = usage["cached_input_tokens"] ?? lastTotalCached;
16344
- lastTotalOutput = usage["output_tokens"] ?? lastTotalOutput;
16345
- continue;
16346
- }
16347
16475
  if (entry.type === "event_msg" && payload["type"] === "user_message") {
16348
16476
  const text = String(payload["message"] ?? "");
16349
16477
  if (text) {
@@ -16500,13 +16628,8 @@ function scanCodexHistory(startDate, onProgress, onLine) {
16500
16628
  }
16501
16629
  }
16502
16630
  }
16503
- const withinWindow = !startDate || startTime !== "" && new Date(startTime) >= startDate;
16504
- if (withinWindow) {
16505
- result.totalCostUSD += codexSessionCost(model, {
16506
- input: lastTotalInput,
16507
- cached: lastTotalCached,
16508
- output: lastTotalOutput
16509
- });
16631
+ for (const event of codexUsageInWindow(parseCodexUsage(lines), startDate)) {
16632
+ result.totalCostUSD += event.costUSD;
16510
16633
  }
16511
16634
  result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "codex"));
16512
16635
  }
@@ -17943,13 +18066,13 @@ var init_taint_store = __esm({
17943
18066
  */
17944
18067
  check(filePath) {
17945
18068
  const resolved = this._resolve(filePath);
17946
- const record = this.records.get(resolved);
17947
- if (!record) return null;
17948
- if (Date.now() > record.expiresAt) {
18069
+ const record2 = this.records.get(resolved);
18070
+ if (!record2) return null;
18071
+ if (Date.now() > record2.expiresAt) {
17949
18072
  this.records.delete(resolved);
17950
18073
  return null;
17951
18074
  }
17952
- return record;
18075
+ return record2;
17953
18076
  }
17954
18077
  /**
17955
18078
  * Propagate taint from sourcePath to destPath (e.g. cp, mv).
@@ -17970,8 +18093,8 @@ var init_taint_store = __esm({
17970
18093
  /** Remove all expired records. Called periodically by the daemon. */
17971
18094
  prune() {
17972
18095
  const now = Date.now();
17973
- for (const [key, record] of this.records) {
17974
- if (now > record.expiresAt) this.records.delete(key);
18096
+ for (const [key, record2] of this.records) {
18097
+ if (now > record2.expiresAt) this.records.delete(key);
17975
18098
  }
17976
18099
  }
17977
18100
  /** Return all non-expired taint records (for audit/debug). */
@@ -18010,13 +18133,13 @@ var init_taint_store = __esm({
18010
18133
  * Expired records are pruned on access. */
18011
18134
  check(sessionId) {
18012
18135
  if (!sessionId) return null;
18013
- const record = this.records.get(sessionId);
18014
- if (!record) return null;
18015
- if (Date.now() > record.expiresAt) {
18136
+ const record2 = this.records.get(sessionId);
18137
+ if (!record2) return null;
18138
+ if (Date.now() > record2.expiresAt) {
18016
18139
  this.records.delete(sessionId);
18017
18140
  return null;
18018
18141
  }
18019
- return record;
18142
+ return record2;
18020
18143
  }
18021
18144
  /** Clear a session's taint (e.g. the user resolved it). Returns true if a
18022
18145
  * record was actually removed (false if the session wasn't tainted). */
@@ -18031,8 +18154,8 @@ var init_taint_store = __esm({
18031
18154
  /** Remove all expired records. Called periodically by the daemon. */
18032
18155
  prune() {
18033
18156
  const now = Date.now();
18034
- for (const [key, record] of this.records) {
18035
- if (now > record.expiresAt) this.records.delete(key);
18157
+ for (const [key, record2] of this.records) {
18158
+ if (now > record2.expiresAt) this.records.delete(key);
18036
18159
  }
18037
18160
  }
18038
18161
  /** Remove all records. Used by tests to reset state between runs. */
@@ -20607,13 +20730,36 @@ function isPolicyStale(nowMs = Date.now(), health) {
20607
20730
  if (Number.isNaN(last)) return false;
20608
20731
  return nowMs - last > stalenessThresholdMs(effectiveSyncIntervalMs());
20609
20732
  }
20610
- function fetchCloudPolicy(apiKey, apiUrl, ifNoneMatch) {
20611
- const parsed = new URL(apiUrl);
20733
+ function safeNode9Version() {
20734
+ let dir = __dirname;
20735
+ for (let up = 0; up < 5; up++) {
20736
+ try {
20737
+ const pkg = JSON.parse(import_fs38.default.readFileSync(import_path37.default.join(dir, "package.json"), "utf-8"));
20738
+ if (pkg.name === "@node9/proxy" || pkg.name === "node9-ai") {
20739
+ return pkg.version;
20740
+ }
20741
+ } catch {
20742
+ }
20743
+ const parent = import_path37.default.dirname(dir);
20744
+ if (parent === dir) break;
20745
+ dir = parent;
20746
+ }
20747
+ return void 0;
20748
+ }
20749
+ function buildPolicyPullHeaders(apiKey, ifNoneMatch, proxyVersion) {
20612
20750
  const headers = {
20613
20751
  Authorization: `Bearer ${apiKey}`,
20614
20752
  "Content-Type": "application/json"
20615
20753
  };
20616
20754
  if (ifNoneMatch) headers["If-None-Match"] = `"${ifNoneMatch}"`;
20755
+ if (proxyVersion && proxyVersion !== "unknown") {
20756
+ headers["X-Node9-Version"] = proxyVersion;
20757
+ }
20758
+ return headers;
20759
+ }
20760
+ function fetchCloudPolicy(apiKey, apiUrl, ifNoneMatch) {
20761
+ const parsed = new URL(apiUrl);
20762
+ const headers = buildPolicyPullHeaders(apiKey, ifNoneMatch, safeNode9Version());
20617
20763
  return new Promise((resolve2, reject) => {
20618
20764
  const req = import_https4.default.request(
20619
20765
  {
@@ -22742,10 +22888,10 @@ data: ${JSON.stringify(item.data)}
22742
22888
  return res.end(JSON.stringify({ error: "all paths must be strings" }));
22743
22889
  }
22744
22890
  for (const p of body.paths) {
22745
- const record = taintStore.check(p);
22746
- if (record) {
22891
+ const record2 = taintStore.check(p);
22892
+ if (record2) {
22747
22893
  res.writeHead(200, { "Content-Type": "application/json" });
22748
- return res.end(JSON.stringify({ tainted: true, record }));
22894
+ return res.end(JSON.stringify({ tainted: true, record: record2 }));
22749
22895
  }
22750
22896
  }
22751
22897
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -22794,9 +22940,9 @@ data: ${JSON.stringify(item.data)}
22794
22940
  res.writeHead(400, { "Content-Type": "application/json" });
22795
22941
  return res.end(JSON.stringify({ error: "sessionId must be a string" }));
22796
22942
  }
22797
- const record = sessionTaintStore.check(body.sessionId);
22943
+ const record2 = sessionTaintStore.check(body.sessionId);
22798
22944
  res.writeHead(200, { "Content-Type": "application/json" });
22799
- return res.end(JSON.stringify(record ? { tainted: true, record } : { tainted: false }));
22945
+ return res.end(JSON.stringify(record2 ? { tainted: true, record: record2 } : { tainted: false }));
22800
22946
  } catch {
22801
22947
  res.writeHead(400).end();
22802
22948
  return;
@@ -28750,8 +28896,8 @@ var require_util2 = __commonJS({
28750
28896
  request2.headersList.append("origin", serializedOrigin, true);
28751
28897
  }
28752
28898
  }
28753
- function coarsenTime(timestamp, crossOriginIsolatedCapability) {
28754
- return timestamp;
28899
+ function coarsenTime(timestamp2, crossOriginIsolatedCapability) {
28900
+ return timestamp2;
28755
28901
  }
28756
28902
  function clampAndCoarsenConnectionTimingInfo(connectionTimingInfo, defaultStartTime, crossOriginIsolatedCapability) {
28757
28903
  if (!connectionTimingInfo?.startTime || connectionTimingInfo.startTime < defaultStartTime) {
@@ -38832,20 +38978,20 @@ var require_dns = __commonJS({
38832
38978
  return ip;
38833
38979
  }
38834
38980
  setRecords(origin, addresses) {
38835
- const timestamp = Date.now();
38981
+ const timestamp2 = Date.now();
38836
38982
  const records = { records: { 4: null, 6: null } };
38837
38983
  let minTTL = this.#maxTTL;
38838
- for (const record of addresses) {
38839
- record.timestamp = timestamp;
38840
- if (typeof record.ttl === "number") {
38841
- record.ttl = Math.min(record.ttl, this.#maxTTL);
38842
- minTTL = Math.min(minTTL, record.ttl);
38984
+ for (const record2 of addresses) {
38985
+ record2.timestamp = timestamp2;
38986
+ if (typeof record2.ttl === "number") {
38987
+ record2.ttl = Math.min(record2.ttl, this.#maxTTL);
38988
+ minTTL = Math.min(minTTL, record2.ttl);
38843
38989
  } else {
38844
- record.ttl = this.#maxTTL;
38990
+ record2.ttl = this.#maxTTL;
38845
38991
  }
38846
- const familyRecords = records.records[record.family] ?? { ips: [] };
38847
- familyRecords.ips.push(record);
38848
- records.records[record.family] = familyRecords;
38992
+ const familyRecords = records.records[record2.family] ?? { ips: [] };
38993
+ familyRecords.ips.push(record2);
38994
+ records.records[record2.family] = familyRecords;
38849
38995
  }
38850
38996
  this.storage.set(origin.hostname, records, { ttl: minTTL });
38851
38997
  }
@@ -53509,15 +53655,15 @@ var import_fs55 = __toESM(require("fs"));
53509
53655
  var import_path53 = __toESM(require("path"));
53510
53656
  init_decision();
53511
53657
  var import_os49 = __toESM(require("os"));
53512
- function formatRelativeTime(timestamp) {
53513
- const diff = Date.now() - new Date(timestamp).getTime();
53658
+ function formatRelativeTime(timestamp2) {
53659
+ const diff = Date.now() - new Date(timestamp2).getTime();
53514
53660
  const sec = Math.floor(diff / 1e3);
53515
53661
  if (sec < 60) return `${sec}s ago`;
53516
53662
  const min = Math.floor(sec / 60);
53517
53663
  if (min < 60) return `${min}m ago`;
53518
53664
  const hrs = Math.floor(min / 60);
53519
53665
  if (hrs < 24) return `${hrs}h ago`;
53520
- return new Date(timestamp).toLocaleDateString();
53666
+ return new Date(timestamp2).toLocaleDateString();
53521
53667
  }
53522
53668
  function registerAuditCommand(program2) {
53523
53669
  program2.command("audit").description("View local execution audit log").option("--tail <n>", "Number of entries to show", "20").option("--tool <pattern>", "Filter by tool name (substring match)").option("--deny", "Show only denied actions").option("--json", "Output raw JSON").action((options) => {
@@ -53763,15 +53909,15 @@ function processClaudeCostProject(proj, projectsDir, start, end, acc) {
53763
53909
  if (!entry.timestamp) continue;
53764
53910
  const ts = new Date(entry.timestamp);
53765
53911
  if (ts < start || ts > end) continue;
53766
- const usage = entry.message?.usage;
53912
+ const usage2 = entry.message?.usage;
53767
53913
  const model = entry.message?.model;
53768
- if (!usage || !model) continue;
53914
+ if (!usage2 || !model) continue;
53769
53915
  const p = claudeModelPrice2(model);
53770
53916
  if (!p) continue;
53771
- const inp = usage.input_tokens ?? 0;
53772
- const out = usage.output_tokens ?? 0;
53773
- const cw = usage.cache_creation_input_tokens ?? 0;
53774
- const cr = usage.cache_read_input_tokens ?? 0;
53917
+ const inp = usage2.input_tokens ?? 0;
53918
+ const out = usage2.output_tokens ?? 0;
53919
+ const cw = usage2.cache_creation_input_tokens ?? 0;
53920
+ const cr = usage2.cache_read_input_tokens ?? 0;
53775
53921
  const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
53776
53922
  acc.total += cost;
53777
53923
  acc.inputTokens += inp;
@@ -53819,90 +53965,21 @@ function processCodexCostFile(filePath, start, end, acc) {
53819
53965
  } catch {
53820
53966
  return;
53821
53967
  }
53822
- let sessionStart = "";
53823
- let model = "";
53824
- let lastTotalInput = 0;
53825
- let lastTotalCached = 0;
53826
- let lastTotalOutput = 0;
53827
- let sessionToolCalls = 0;
53968
+ const parsed = parseCodexUsage(lines);
53969
+ for (const event of codexUsageInWindow(parsed, start, end)) {
53970
+ acc.total += event.costUSD;
53971
+ acc.byDay.set(event.date, (acc.byDay.get(event.date) ?? 0) + event.costUSD);
53972
+ acc.byModel.set(event.model, (acc.byModel.get(event.model) ?? 0) + event.costUSD);
53973
+ }
53828
53974
  for (const line of lines) {
53829
- if (!line.trim()) continue;
53830
- let entry;
53831
53975
  try {
53832
- entry = JSON.parse(line);
53976
+ const entry = JSON.parse(line);
53977
+ if (entry?.type !== "response_item" || entry.payload?.type !== "function_call") continue;
53978
+ const ts = new Date(entry.timestamp ?? parsed.sessionStart);
53979
+ if (ts >= start && ts <= end) acc.toolCalls++;
53833
53980
  } catch {
53834
- continue;
53835
- }
53836
- const p = entry.payload ?? {};
53837
- if (entry.type === "session_meta") {
53838
- sessionStart = String(p["timestamp"] ?? "");
53839
- continue;
53840
- }
53841
- if (entry.type === "turn_context" && typeof p["model"] === "string") {
53842
- model = p["model"];
53843
- continue;
53844
- }
53845
- if (entry.type === "event_msg" && p["type"] === "token_count") {
53846
- const info = p["info"] ?? {};
53847
- const usage = info["total_token_usage"] ?? {};
53848
- lastTotalInput = usage["input_tokens"] ?? lastTotalInput;
53849
- lastTotalCached = usage["cached_input_tokens"] ?? lastTotalCached;
53850
- lastTotalOutput = usage["output_tokens"] ?? lastTotalOutput;
53851
- }
53852
- if (entry.type === "response_item" && p["type"] === "function_call") {
53853
- sessionToolCalls++;
53854
53981
  }
53855
53982
  }
53856
- if (!sessionStart) return;
53857
- const ts = new Date(sessionStart);
53858
- if (ts < start || ts > end) return;
53859
- const cost = codexSessionCost(model, {
53860
- input: lastTotalInput,
53861
- cached: lastTotalCached,
53862
- output: lastTotalOutput
53863
- });
53864
- acc.total += cost;
53865
- acc.toolCalls += sessionToolCalls;
53866
- const dateKey = sessionStart.slice(0, 10);
53867
- acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
53868
- const normModel = normalizeModel(model || "gpt-5");
53869
- acc.byModel.set(normModel, (acc.byModel.get(normModel) ?? 0) + cost);
53870
- }
53871
- function listCodexSessionFiles2(sessionsBase) {
53872
- const jsonlFiles = [];
53873
- if (!import_fs56.default.existsSync(sessionsBase)) return jsonlFiles;
53874
- try {
53875
- for (const year of import_fs56.default.readdirSync(sessionsBase)) {
53876
- const yearPath = import_path54.default.join(sessionsBase, year);
53877
- try {
53878
- if (!import_fs56.default.statSync(yearPath).isDirectory()) continue;
53879
- } catch {
53880
- continue;
53881
- }
53882
- for (const month of import_fs56.default.readdirSync(yearPath)) {
53883
- const monthPath = import_path54.default.join(yearPath, month);
53884
- try {
53885
- if (!import_fs56.default.statSync(monthPath).isDirectory()) continue;
53886
- } catch {
53887
- continue;
53888
- }
53889
- for (const day of import_fs56.default.readdirSync(monthPath)) {
53890
- const dayPath = import_path54.default.join(monthPath, day);
53891
- try {
53892
- if (!import_fs56.default.statSync(dayPath).isDirectory()) continue;
53893
- } catch {
53894
- continue;
53895
- }
53896
- for (const file of import_fs56.default.readdirSync(dayPath)) {
53897
- if (file.endsWith(".jsonl")) jsonlFiles.push(import_path54.default.join(dayPath, file));
53898
- }
53899
- }
53900
- }
53901
- }
53902
- } catch {
53903
- return [];
53904
- }
53905
- return jsonlFiles;
53906
53983
  }
53907
53984
  function mergeByModel(...maps) {
53908
53985
  const out = /* @__PURE__ */ new Map();
@@ -53918,7 +53995,7 @@ function loadCodexCost(start, end, sessionsBase) {
53918
53995
  byDay: /* @__PURE__ */ new Map(),
53919
53996
  byModel: /* @__PURE__ */ new Map()
53920
53997
  };
53921
- const files = listCodexSessionFiles2(sessionsBase);
53998
+ const files = listCodexSessionFiles(sessionsBase);
53922
53999
  for (const filePath of files) {
53923
54000
  processCodexCostFile(filePath, start, end, acc);
53924
54001
  }
@@ -54058,7 +54135,7 @@ function aggregateReportFromAudit(period, opts = {}) {
54058
54135
  const now = opts.now ?? /* @__PURE__ */ new Date();
54059
54136
  const auditLogPath = opts.auditLogPath ?? import_path54.default.join(import_os50.default.homedir(), ".node9", "audit.log");
54060
54137
  const claudeProjectsDir = opts.claudeProjectsDir ?? import_path54.default.join(import_os50.default.homedir(), ".claude", "projects");
54061
- const codexSessionsDir2 = opts.codexSessionsDir ?? import_path54.default.join(import_os50.default.homedir(), ".codex", "sessions");
54138
+ const codexSessionsDir2 = opts.codexSessionsDir ?? codexSessionsDir();
54062
54139
  const geminiTmpDir2 = opts.geminiTmpDir ?? import_path54.default.join(import_os50.default.homedir(), ".gemini", "tmp");
54063
54140
  const hasAuditFile = import_fs56.default.existsSync(auditLogPath);
54064
54141
  const allEntries = opts.preloadedAuditEntries ?? parseAuditLog(auditLogPath);
@@ -60257,12 +60334,12 @@ function parseSessionLines(lines) {
60257
60334
  continue;
60258
60335
  }
60259
60336
  if (entry.type !== "assistant") continue;
60260
- const usage = entry.message?.usage;
60337
+ const usage2 = entry.message?.usage;
60261
60338
  const model = entry.message?.model;
60262
- if (usage && model) {
60339
+ if (usage2 && model) {
60263
60340
  const p = modelPrice(model);
60264
60341
  if (p) {
60265
- costUSD += (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;
60342
+ costUSD += (usage2.input_tokens ?? 0) * p.i + (usage2.output_tokens ?? 0) * p.o + (usage2.cache_creation_input_tokens ?? 0) * p.cw + (usage2.cache_read_input_tokens ?? 0) * p.cr;
60266
60343
  }
60267
60344
  }
60268
60345
  const content = entry.message?.content;
@@ -60442,46 +60519,13 @@ function buildGeminiSessions(days, allAuditEntries) {
60442
60519
  return summaries;
60443
60520
  }
60444
60521
  function buildCodexSessions(days, allAuditEntries) {
60445
- const sessionsBase = import_path65.default.join(import_os57.default.homedir(), ".codex", "sessions");
60446
- if (!import_fs70.default.existsSync(sessionsBase)) return [];
60447
60522
  const cutoff = days !== null ? (() => {
60448
60523
  const d = /* @__PURE__ */ new Date();
60449
60524
  d.setDate(d.getDate() - days);
60450
60525
  d.setHours(0, 0, 0, 0);
60451
60526
  return d;
60452
60527
  })() : null;
60453
- const jsonlFiles = [];
60454
- try {
60455
- for (const year of import_fs70.default.readdirSync(sessionsBase)) {
60456
- const yearPath = import_path65.default.join(sessionsBase, year);
60457
- try {
60458
- if (!import_fs70.default.statSync(yearPath).isDirectory()) continue;
60459
- } catch {
60460
- continue;
60461
- }
60462
- for (const month of import_fs70.default.readdirSync(yearPath)) {
60463
- const monthPath = import_path65.default.join(yearPath, month);
60464
- try {
60465
- if (!import_fs70.default.statSync(monthPath).isDirectory()) continue;
60466
- } catch {
60467
- continue;
60468
- }
60469
- for (const day of import_fs70.default.readdirSync(monthPath)) {
60470
- const dayPath = import_path65.default.join(monthPath, day);
60471
- try {
60472
- if (!import_fs70.default.statSync(dayPath).isDirectory()) continue;
60473
- } catch {
60474
- continue;
60475
- }
60476
- for (const file of import_fs70.default.readdirSync(dayPath)) {
60477
- if (file.endsWith(".jsonl")) jsonlFiles.push(import_path65.default.join(dayPath, file));
60478
- }
60479
- }
60480
- }
60481
- }
60482
- } catch {
60483
- return [];
60484
- }
60528
+ const jsonlFiles = listCodexSessionFiles();
60485
60529
  const summaries = [];
60486
60530
  for (const filePath of jsonlFiles) {
60487
60531
  let lines;
@@ -60496,10 +60540,6 @@ function buildCodexSessions(days, allAuditEntries) {
60496
60540
  let firstPrompt = "";
60497
60541
  const toolCalls = [];
60498
60542
  let lastToolTs = "";
60499
- let lastTotalInput = 0;
60500
- let lastTotalCached = 0;
60501
- let lastTotalOutput = 0;
60502
- let model = "";
60503
60543
  for (const line of lines) {
60504
60544
  if (!line.trim()) continue;
60505
60545
  let entry;
@@ -60515,22 +60555,10 @@ function buildCodexSessions(days, allAuditEntries) {
60515
60555
  cwd = String(p["cwd"] ?? "");
60516
60556
  continue;
60517
60557
  }
60518
- if (entry.type === "turn_context" && typeof p["model"] === "string") {
60519
- model = p["model"];
60520
- continue;
60521
- }
60522
60558
  if (entry.type === "event_msg" && p["type"] === "user_message" && !firstPrompt) {
60523
60559
  firstPrompt = String(p["message"] ?? "");
60524
60560
  continue;
60525
60561
  }
60526
- if (entry.type === "event_msg" && p["type"] === "token_count") {
60527
- const info = p["info"] ?? {};
60528
- const usage = info["total_token_usage"] ?? {};
60529
- lastTotalInput = usage["input_tokens"] ?? lastTotalInput;
60530
- lastTotalCached = usage["cached_input_tokens"] ?? lastTotalCached;
60531
- lastTotalOutput = usage["output_tokens"] ?? lastTotalOutput;
60532
- continue;
60533
- }
60534
60562
  if (entry.type === "response_item" && p["type"] === "function_call") {
60535
60563
  const tool = String(p["name"] ?? "");
60536
60564
  let input = {};
@@ -60544,12 +60572,13 @@ function buildCodexSessions(days, allAuditEntries) {
60544
60572
  }
60545
60573
  }
60546
60574
  if (!sessionId || !startTime) continue;
60547
- if (cutoff && new Date(startTime) < cutoff) continue;
60548
- const costUSD = codexSessionCost(model, {
60549
- input: lastTotalInput,
60550
- cached: lastTotalCached,
60551
- output: lastTotalOutput
60552
- });
60575
+ const parsedUsage = parseCodexUsage(lines);
60576
+ const usageEvents = codexUsageInWindow(parsedUsage, cutoff);
60577
+ if (cutoff && new Date(startTime) < cutoff && usageEvents.length === 0 && !toolCalls.some((call) => new Date(call.timestamp) >= cutoff))
60578
+ continue;
60579
+ const costUSD = usageEvents.reduce((sum, event) => sum + event.costUSD, 0);
60580
+ const lastUsageTs = parsedUsage.events.at(-1)?.timestamp ?? "";
60581
+ if (lastUsageTs > lastToolTs) lastToolTs = lastUsageTs;
60553
60582
  const windowEnd = new Date(
60554
60583
  Math.max(new Date(startTime).getTime(), lastToolTs ? new Date(lastToolTs).getTime() : 0) + 5 * 60 * 1e3
60555
60584
  ).toISOString();