@node9/proxy 2.13.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.mjs CHANGED
@@ -1157,9 +1157,7 @@ function unwrapCommandHead(words) {
1157
1157
  while (i < words.length) {
1158
1158
  const head = (words[i] ?? "").toLowerCase().split("/").pop() ?? "";
1159
1159
  if (head === "find") {
1160
- const x = words.findIndex(
1161
- (w, k) => k > i && (w === "-exec" || w === "-execdir" || w === "-ok")
1162
- );
1160
+ const x = words.findIndex((w, k) => k > i && w !== null && FIND_EXEC_FLAGS.has(w));
1163
1161
  if (x < 0) break;
1164
1162
  i = x + 1;
1165
1163
  continue;
@@ -1181,7 +1179,11 @@ function unwrapCommandHead(words) {
1181
1179
  if (t.startsWith("-")) {
1182
1180
  i++;
1183
1181
  const nxt = words[i];
1184
- if (nxt != null && !nxt.startsWith("-") && !INLINE_INTERPRETER.test(nxt.split("/").pop() ?? "") && !COMMAND_WRAPPERS.has(nxt.toLowerCase()) && !RUNNER_WRAPPERS.has(nxt.toLowerCase()))
1182
+ 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`,
1183
+ // `stdbuf -o0 cat X`, `ionice -c3 cat X`. Without this the head was
1184
+ // swallowed and the jail needed a looser fallback whose cost was a
1185
+ // false positive on `sudo echo cat X`.
1186
+ !FS_READ_TOOLS.has(nxt.split("/").pop()?.toLowerCase() ?? ""))
1185
1187
  i++;
1186
1188
  continue;
1187
1189
  }
@@ -1302,34 +1304,18 @@ function isProtectedHomePath(rawPath) {
1302
1304
  }
1303
1305
  function extractLiteralArgs(callExpr) {
1304
1306
  const args = callExpr.Args || [];
1305
- if (args.length === 0) return { name: "", flags: [], paths: [] };
1306
- const litFromWord = (w) => {
1307
- const parts = w?.Parts || [];
1308
- let s = "";
1309
- for (const p of parts) {
1310
- const t = syntax.NodeType(p);
1311
- if (t === "Lit") s += (p.Value ?? "").replace(/\\(.)/g, "$1");
1312
- else if (t === "SglQuoted") s += p.Value ?? "";
1313
- else if (t === "DblQuoted") {
1314
- const inner = p.Parts || [];
1315
- if (!inner.every((ip) => syntax.NodeType(ip) === "Lit")) return null;
1316
- s += inner.map((ip) => ip.Value ?? "").join("");
1317
- } else {
1318
- return null;
1319
- }
1320
- }
1321
- return s;
1322
- };
1323
- const name = (litFromWord(args[0]) || "").toLowerCase();
1307
+ if (args.length === 0) return { name: "", flags: [], paths: [], words: [] };
1308
+ const words = args.map((a) => resolveWordLiteral(a));
1309
+ const name = (words[0] ?? "").toLowerCase();
1324
1310
  const flags = [];
1325
1311
  const paths = [];
1326
- for (let i = 1; i < args.length; i++) {
1327
- const v = litFromWord(args[i]);
1312
+ for (let i = 1; i < words.length; i++) {
1313
+ const v = words[i];
1328
1314
  if (v === null) continue;
1329
1315
  if (v.startsWith("-")) flags.push(v);
1330
1316
  else paths.push(v);
1331
1317
  }
1332
- return { name, flags, paths };
1318
+ return { name, flags, paths, words };
1333
1319
  }
1334
1320
  function resolveWordLiteral(w) {
1335
1321
  const parts = w?.Parts || [];
@@ -1587,16 +1573,21 @@ function isRmCreatedInCommandCleanup(command) {
1587
1573
  }
1588
1574
  return sawRm && ok2;
1589
1575
  }
1590
- function analyzeFsOperationImpl(command) {
1576
+ function analyzeFsOperationImpl(command, depth = 0) {
1591
1577
  const f = parseShared(command);
1592
1578
  if (f === PARSE_FAIL) return null;
1593
1579
  let result = null;
1594
1580
  try {
1595
1581
  syntax.Walk(f, (node) => {
1596
- if (!node || result) return false;
1582
+ if (!node || result?.verdict === "block") return false;
1597
1583
  const n = node;
1598
- if (syntax.NodeType(n) !== "CallExpr") return true;
1599
- const { name, flags, paths } = extractLiteralArgs(n);
1584
+ const nodeType = syntax.NodeType(n);
1585
+ if (nodeType === "Stmt") {
1586
+ result = stricter(result, jailedRedirectRead(n));
1587
+ return result?.verdict !== "block";
1588
+ }
1589
+ if (nodeType !== "CallExpr") return true;
1590
+ const { name, flags, paths, words } = extractLiteralArgs(n);
1600
1591
  if (!name) return true;
1601
1592
  if (name === "rm") {
1602
1593
  const flagStr = flags.join("").toLowerCase();
@@ -1625,19 +1616,22 @@ function analyzeFsOperationImpl(command) {
1625
1616
  }
1626
1617
  }
1627
1618
  }
1628
- if (FS_READ_TOOLS.has(name)) {
1629
- for (const p of paths) {
1630
- for (const sp of SENSITIVE_PATH_RULES) {
1631
- if (sp.match(p)) {
1632
- result = {
1633
- ruleName: sp.rule,
1634
- verdict: sp.verdict ?? "block",
1635
- reason: sp.reason,
1636
- path: p
1637
- };
1638
- return false;
1639
- }
1619
+ if (depth < 1) {
1620
+ const payload = literalShellPayload(words, name);
1621
+ if (payload !== null) {
1622
+ const inner = analyzeFsOperationImpl(payload, depth + 1);
1623
+ if (inner) {
1624
+ result = inner;
1625
+ return false;
1640
1626
  }
1627
+ return true;
1628
+ }
1629
+ }
1630
+ const readPaths = FS_READ_TOOLS.has(name) ? paths : wrappedReadPaths(words, name);
1631
+ if (readPaths) {
1632
+ for (const p of readPaths) {
1633
+ result = stricter(result, matchSensitivePath2(p));
1634
+ if (result?.verdict === "block") return false;
1641
1635
  }
1642
1636
  }
1643
1637
  return true;
@@ -1647,6 +1641,55 @@ function analyzeFsOperationImpl(command) {
1647
1641
  return null;
1648
1642
  }
1649
1643
  }
1644
+ function stricter(a, b) {
1645
+ if (!a) return b;
1646
+ if (!b) return a;
1647
+ return b.verdict === "block" && a.verdict !== "block" ? b : a;
1648
+ }
1649
+ function matchSensitivePath2(p) {
1650
+ for (const sp of SENSITIVE_PATH_RULES) {
1651
+ if (sp.match(p))
1652
+ return { ruleName: sp.rule, verdict: sp.verdict ?? "block", reason: sp.reason, path: p };
1653
+ }
1654
+ return null;
1655
+ }
1656
+ function wrappedReadPaths(words, name) {
1657
+ if (name === "find") {
1658
+ const k = words.findIndex((w) => w !== null && FIND_EXEC_FLAGS.has(w));
1659
+ if (k < 1 || !isReaderWord(words[k + 1] ?? null)) return null;
1660
+ const firstPredicate = words.findIndex((w, i) => i > 0 && w !== null && w.startsWith("-"));
1661
+ return positionalAfter(words, 1, firstPredicate > 0 ? firstPredicate : k);
1662
+ }
1663
+ if (!COMMAND_WRAPPERS.has(name) && !RUNNER_WRAPPERS.has(name)) return null;
1664
+ const h = unwrapCommandHead(words);
1665
+ return h > 0 && isReaderWord(words[h] ?? null) ? positionalAfter(words, h + 1) : null;
1666
+ }
1667
+ function literalShellPayload(words, name) {
1668
+ const h = COMMAND_WRAPPERS.has(name) || RUNNER_WRAPPERS.has(name) ? unwrapCommandHead(words) : 0;
1669
+ const head = (words[h] ?? "").split("/").pop()?.toLowerCase() ?? "";
1670
+ if (head === "eval") {
1671
+ const rest = words.slice(h + 1);
1672
+ if (rest.length === 0 || rest.some((w) => w === null)) return null;
1673
+ return rest.join(" ");
1674
+ }
1675
+ if (SHELL_INTERPRETERS.has(head)) {
1676
+ const c = words.findIndex((w, i) => i > h && w !== null && isInlineCodeFlag(head, w));
1677
+ if (c < 0) return null;
1678
+ return words[c + 1] ?? null;
1679
+ }
1680
+ return null;
1681
+ }
1682
+ function jailedRedirectRead(stmt) {
1683
+ const redirs = stmt.Redirs || [];
1684
+ for (const r of redirs) {
1685
+ if (!r || !REDIR_FILE_IN_OPS.has(r.Op)) continue;
1686
+ const p = resolveWordLiteral(r.Word);
1687
+ if (p === null || p === "") continue;
1688
+ const hit = matchSensitivePath2(p);
1689
+ if (hit) return hit;
1690
+ }
1691
+ return null;
1692
+ }
1650
1693
  function analyzeShellCommand(command) {
1651
1694
  const actions = [];
1652
1695
  const paths = [];
@@ -1782,8 +1825,8 @@ function splitOnPipe(cmd) {
1782
1825
  if (current.trim()) segments2.push(current.trim());
1783
1826
  return segments2.filter(Boolean);
1784
1827
  }
1785
- function positionalTokens(segment) {
1786
- return segment.split(/\s+/).slice(1).filter((t) => !t.startsWith("-") && !t.startsWith("@") && t.length > 0);
1828
+ function positionalTokens(tokens) {
1829
+ return tokens.slice(1).filter((t) => !t.startsWith("-") && !t.startsWith("@") && t.length > 0);
1787
1830
  }
1788
1831
  function analyzePipeChain(command) {
1789
1832
  const segments2 = splitOnPipe(command);
@@ -1806,8 +1849,10 @@ function analyzePipeChain(command) {
1806
1849
  for (const segment of segments2) {
1807
1850
  const tokens = segment.split(/\s+/).filter(Boolean);
1808
1851
  if (tokens.length === 0) continue;
1809
- const binary = tokens[0].toLowerCase();
1810
- const args = positionalTokens(segment);
1852
+ const h = unwrapCommandHead(tokens);
1853
+ const head = h < tokens.length ? h : 0;
1854
+ const binary = tokens[head].toLowerCase();
1855
+ const args = positionalTokens(tokens.slice(head));
1811
1856
  if (SOURCE_COMMANDS.has(binary)) {
1812
1857
  sourceFiles.push(...args);
1813
1858
  if (args.some(isSensitivePath)) hasSensitiveSource = true;
@@ -3287,7 +3332,7 @@ function* stringValues(obj, depth = 0) {
3287
3332
  }
3288
3333
  for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
3289
3334
  }
3290
- var 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;
3335
+ var 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;
3291
3336
  var init_dist = __esm({
3292
3337
  "packages/policy-engine/dist/index.mjs"() {
3293
3338
  "use strict";
@@ -3905,13 +3950,32 @@ var init_dist = __esm({
3905
3950
  })
3906
3951
  );
3907
3952
  SENSITIVE_PATH_PATTERNS = [
3908
- /[/\\]\.ssh[/\\]/i,
3909
- /[/\\]\.aws[/\\]/i,
3953
+ /[/\\]\.ssh([/\\]|$)/i,
3954
+ /[/\\]\.aws([/\\]|$)/i,
3910
3955
  /[/\\]\.config[/\\]gcloud[/\\]/i,
3911
3956
  /[/\\]\.azure[/\\]/i,
3912
3957
  /[/\\]\.kube[/\\]config$/i,
3913
- /[/\\]\.env($|\.)/i,
3914
- // .env, .env.local, .env.production — not .envoy
3958
+ // ⚠️ ONE SEMANTIC, FOUR COPIES. This is the AST tier's `.env` rule verbatim
3959
+ // (shell/index.ts SENSITIVE_PATH_RULES), whose reasoning is documented there:
3960
+ // structural suffix chain rather than a hand-written list, `example|sample|
3961
+ // template` exempt because a fixture stays a fixture whatever follows, and
3962
+ // `.test` anchored because `test` names an ENVIRONMENT -- `.env.test` is the
3963
+ // committed template, `.env.test.local` is gitignored and holds real values.
3964
+ //
3965
+ // It was previously `[/\\]\.env($|\.)` with NO exemptions, so `Read .env.example`
3966
+ // blocked while `cat .env.example` allowed: the same file, opposite verdicts,
3967
+ // decided only by which tool asked. See src/__tests__/jail-both-doors.test.ts,
3968
+ // which is the contract that now holds these copies in step, and stage 5 of
3969
+ // doc/credential-jail-architecture.md, which replaces them with one generated
3970
+ // source.
3971
+ // ⚠️ The `.local` branch comes FIRST and takes no exemption. A fixture stays a
3972
+ // fixture whatever follows it -- `.env.example.md` is documentation -- but
3973
+ // `.env.example.local` is gitignored by the `.env*.local` convention and holds
3974
+ // real values, exactly the reasoning that anchors `(?!\.test$)` rather than
3975
+ // using `\b`. Without this branch the fixture exemption also bought a two-step
3976
+ // bypass: `cp .env .env.sample`, then read the copy.
3977
+ /[/\\]\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i,
3978
+ // .env + any suffix chain; fixtures exempt unless .local
3915
3979
  /[/\\]\.git-credentials$/i,
3916
3980
  /[/\\]\.npmrc$/i,
3917
3981
  /[/\\]\.docker[/\\]config\.json$/i,
@@ -3993,6 +4057,10 @@ var init_dist = __esm({
3993
4057
  "od",
3994
4058
  "xxd",
3995
4059
  "hexdump",
4060
+ // Emits the file's bytes, re-encoded, so it is a read by the set's own test
4061
+ // ("does it emit file contents"). Absent until 2026-09-10, which is why
4062
+ // `base64 ~/.ssh/id_rsa` printed a private key with no verdict.
4063
+ "base64",
3996
4064
  "strings",
3997
4065
  "sort",
3998
4066
  "uniq",
@@ -4001,7 +4069,11 @@ var init_dist = __esm({
4001
4069
  "dd"
4002
4070
  ]);
4003
4071
  FS_OP_PRESCREEN_RE = new RegExp(
4004
- `(?:^|[\\s|;&(\`\\n])(?:rm|${[...FS_READ_TOOLS].map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b`
4072
+ // A quote is a separator too: `eval "cat X"` and `sh -c 'cat X'` put the
4073
+ // reader right after `"` / `'`, and without these two characters the
4074
+ // prescreen rejected every string-wrapped read before the parser ran.
4075
+ // Found 2026-09-11 by instrumenting the walk -- no CallExpr was ever visited.
4076
+ `(?:^|[\\s|;&("'\`\\n])(?:rm|${[...FS_READ_TOOLS].map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b|(?<!<)<(?!<)`
4005
4077
  );
4006
4078
  HOME_CACHE_ALLOWLIST = [
4007
4079
  ".cache",
@@ -4022,12 +4094,12 @@ var init_dist = __esm({
4022
4094
  {
4023
4095
  rule: "shield:project-jail:block-read-ssh",
4024
4096
  reason: "Reading SSH private keys is blocked by project-jail shield",
4025
- match: (p) => /(^|[\\/])\.ssh[\\/]/i.test(p)
4097
+ match: (p) => /([\\/]\.ssh[\\/]|^\.ssh[\\/]|^(?:[~/]|[A-Za-z]:).*[\\/]\.ssh$)/i.test(p)
4026
4098
  },
4027
4099
  {
4028
4100
  rule: "shield:project-jail:block-read-aws",
4029
4101
  reason: "Reading AWS credentials is blocked by project-jail shield",
4030
- match: (p) => /(^|[\\/])\.aws[\\/]/i.test(p)
4102
+ match: (p) => /([\\/]\.aws[\\/]|^\.aws[\\/]|^(?:[~/]|[A-Za-z]:).*[\\/]\.aws$)/i.test(p)
4031
4103
  },
4032
4104
  {
4033
4105
  // Mirrors the JSON shield's `.env` pattern (project-jail.json's
@@ -4071,7 +4143,9 @@ var init_dist = __esm({
4071
4143
  // symmetry — silently exempts every `.env.test.*` file.
4072
4144
  //
4073
4145
  // shields.test.ts:983-995 is the canonical contract; keep both in step.
4074
- match: (p) => /(?:^|[\\/])\.env(?![\w-])(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$/i.test(p)
4146
+ match: (p) => /(?:^|[\\/])\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i.test(
4147
+ p
4148
+ )
4075
4149
  },
4076
4150
  {
4077
4151
  // verdict: 'review' (not 'block') is a deliberate design choice
@@ -4182,8 +4256,18 @@ var init_dist = __esm({
4182
4256
  _redirStdinOps = null;
4183
4257
  _listOps = null;
4184
4258
  WRAPPER_TAKES_TARGET = /* @__PURE__ */ new Set(["chroot"]);
4259
+ FIND_EXEC_FLAGS = /* @__PURE__ */ new Set(["-exec", "-execdir", "-ok", "-okdir"]);
4185
4260
  INTERP_LEADING_TARGET = /* @__PURE__ */ new Set(["su"]);
4186
- NET_BINARIES = /* @__PURE__ */ new Set(["curl", "wget", "scp", "ssh", "nc", "ncat", "netcat"]);
4261
+ NET_BINARIES = /* @__PURE__ */ new Set([
4262
+ "curl",
4263
+ "wget",
4264
+ "scp",
4265
+ "ssh",
4266
+ "nc",
4267
+ "ncat",
4268
+ "netcat",
4269
+ "rsync"
4270
+ ]);
4187
4271
  VALUE_FLAGS = {
4188
4272
  curl: /* @__PURE__ */ new Set([
4189
4273
  "-d",
@@ -4272,10 +4356,15 @@ var init_dist = __esm({
4272
4356
  fsOpCache = /* @__PURE__ */ new Map();
4273
4357
  stripDotSlash = (p) => p.replace(/^\.\//, "");
4274
4358
  REDIR_TRUNCATE_OPS = /* @__PURE__ */ new Set([deriveRedirOp(">_f")]);
4359
+ REDIR_FILE_IN_OPS = new Set(
4360
+ [deriveRedirOp("cat < f"), deriveRedirOp("cat <> f")].filter((op) => op >= 0)
4361
+ );
4275
4362
  REDIR_HEREDOC_OPS = /* @__PURE__ */ new Set([
4276
4363
  deriveRedirOp("cat <<X\nX"),
4277
4364
  deriveRedirOp("cat <<-X\nX")
4278
4365
  ]);
4366
+ isReaderWord = (w) => w !== null && FS_READ_TOOLS.has(w.split("/").pop()?.toLowerCase() ?? "");
4367
+ positionalAfter = (words, from, to = words.length) => words.slice(from, to).filter((w) => w !== null && !w.startsWith("-"));
4279
4368
  DEFAULT_EGRESS_ALLOWLIST = [
4280
4369
  // node9's own control plane (api, app, dev-api, staging and the apex).
4281
4370
  // Without it, turning egress on asks the user to approve node9 itself.
@@ -4299,21 +4388,7 @@ var init_dist = __esm({
4299
4388
  "deb.debian.org",
4300
4389
  "*.ubuntu.com"
4301
4390
  ];
4302
- SOURCE_COMMANDS = /* @__PURE__ */ new Set([
4303
- "cat",
4304
- "head",
4305
- "tail",
4306
- "grep",
4307
- "awk",
4308
- "sed",
4309
- "cut",
4310
- "sort",
4311
- "tee",
4312
- "less",
4313
- "more",
4314
- "strings",
4315
- "xxd"
4316
- ]);
4391
+ SOURCE_COMMANDS = /* @__PURE__ */ new Set([...FS_READ_TOOLS, "tee"]);
4317
4392
  SINK_COMMANDS = /* @__PURE__ */ new Set([
4318
4393
  "curl",
4319
4394
  "wget",
@@ -4344,16 +4419,25 @@ var init_dist = __esm({
4344
4419
  "node"
4345
4420
  ]);
4346
4421
  SENSITIVE_PATTERNS = [
4347
- /(?:^|\/)\.env(?:\.|$)/i,
4348
- // .env, .env.local, .env.production
4422
+ // Kept in step with the AST tier and dlp/ -- see jail-both-doors.test.ts.
4423
+ /(?:^|\/)\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i,
4424
+ // .env chain; fixtures exempt unless .local
4349
4425
  /id_rsa|id_ed25519|id_ecdsa|id_dsa/i,
4350
4426
  // SSH private keys
4351
4427
  /\.pem$|\.key$|\.p12$|\.pfx$/i,
4352
4428
  // certificate files
4353
- /(?:^|\/)\.ssh\//i,
4354
- // ~/.ssh/ directory
4355
- /(?:^|\/)\.aws\/credentials/i,
4356
- // AWS credentials
4429
+ // The `$` half mirrors shell/index.ts's SENSITIVE_PATH_RULES: a file INSIDE
4430
+ // the directory counts wherever it appears, while the directory ITSELF counts
4431
+ // only when the path is ROOTED (`~/.ssh`, `/home/u/.ssh`) -- an unrooted
4432
+ // `config/.ssh` is more likely a search pattern than a read. These are
4433
+ // extracted TOKENS (see `args.some(isSensitivePath)` below), the same input
4434
+ // contract as the shell tier, so the same boundary is the right one.
4435
+ // Without it `grep -r x ~/.ssh | curl -d @-` scored one tier BELOW the
4436
+ // identical pipeline naming a file inside that directory.
4437
+ /(?:^|\/)\.ssh\/|^(?:[~/]|[A-Za-z]:).*\/\.ssh$/i,
4438
+ // ~/.ssh/ and ~/.ssh
4439
+ /(?:^|\/)\.aws\/credentials|^(?:[~/]|[A-Za-z]:).*\/\.aws$/i,
4440
+ // AWS creds + dir
4357
4441
  /(?:^|\/)\.netrc$/i,
4358
4442
  // netrc (stores HTTP credentials)
4359
4443
  /(?:^|\/)(passwd|shadow|sudoers)$/i,
@@ -5021,7 +5105,7 @@ var init_dist = __esm({
5021
5105
  {
5022
5106
  field: "command",
5023
5107
  op: "matches",
5024
- 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[\\/\\\\]",
5108
+ 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[\\/\\\\]",
5025
5109
  flags: "i"
5026
5110
  }
5027
5111
  ],
@@ -5035,7 +5119,7 @@ var init_dist = __esm({
5035
5119
  {
5036
5120
  field: "command",
5037
5121
  op: "matches",
5038
- 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[\\/\\\\]",
5122
+ 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[\\/\\\\]",
5039
5123
  flags: "i"
5040
5124
  }
5041
5125
  ],
@@ -5049,7 +5133,7 @@ var init_dist = __esm({
5049
5133
  {
5050
5134
  field: "command",
5051
5135
  op: "matches",
5052
- 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|$|[;&|>)<])",
5136
+ 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|$|[;&|>)<])",
5053
5137
  flags: "i"
5054
5138
  }
5055
5139
  ],
@@ -5063,7 +5147,7 @@ var init_dist = __esm({
5063
5147
  {
5064
5148
  field: "command",
5065
5149
  op: "matches",
5066
- 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)",
5150
+ 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)",
5067
5151
  flags: "i"
5068
5152
  }
5069
5153
  ],
@@ -5077,7 +5161,7 @@ var init_dist = __esm({
5077
5161
  {
5078
5162
  field: "file_path",
5079
5163
  op: "matches",
5080
- value: "(^|[\\/\\\\])\\.ssh[\\/\\\\]",
5164
+ value: "([\\/\\\\]\\.ssh([\\/\\\\]|$)|^\\.ssh[\\/\\\\])",
5081
5165
  flags: "i"
5082
5166
  }
5083
5167
  ],
@@ -5091,7 +5175,7 @@ var init_dist = __esm({
5091
5175
  {
5092
5176
  field: "file_path",
5093
5177
  op: "matches",
5094
- value: "(^|[\\/\\\\])\\.aws[\\/\\\\]",
5178
+ value: "([\\/\\\\]\\.aws([\\/\\\\]|$)|^\\.aws[\\/\\\\])",
5095
5179
  flags: "i"
5096
5180
  }
5097
5181
  ],
@@ -5105,7 +5189,7 @@ var init_dist = __esm({
5105
5189
  {
5106
5190
  field: "file_path",
5107
5191
  op: "matches",
5108
- value: "(^|[\\/\\\\])\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?$",
5192
+ value: "(^|[\\/\\\\])\\.env(?![\\w-])(?:[\\w.-]*\\.local$|(?!\\.(example|sample|template)\\b)(?!\\.test$)[\\w.-]*$)",
5109
5193
  flags: "i"
5110
5194
  }
5111
5195
  ],
@@ -5248,7 +5332,7 @@ var init_dist = __esm({
5248
5332
  };
5249
5333
  LOOP_THRESHOLD_FOR_WASTE = 3;
5250
5334
  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;
5251
- 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;
5335
+ 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;
5252
5336
  FILE_TOOLS = /* @__PURE__ */ new Set([
5253
5337
  "read",
5254
5338
  "read_file",
@@ -5325,7 +5409,7 @@ var init_dist = __esm({
5325
5409
  { view: "separators-stripped", decoder: "separators", stripped: true }
5326
5410
  ];
5327
5411
  LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
5328
- CANONICAL_EXTRACTOR_VERSION = "canonical-v10";
5412
+ CANONICAL_EXTRACTOR_VERSION = "canonical-v13";
5329
5413
  DEDUPE_PREVIEW_LEN = 120;
5330
5414
  TERMINAL_ESCAPE_RE = // eslint-disable-next-line no-control-regex
5331
5415
  /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
@@ -8643,7 +8727,7 @@ function isNetworkTool(toolName, args) {
8643
8727
  if (t === "bash" || t === "shell" || t === "run_shell_command" || t === "terminal.execute") {
8644
8728
  const a = args;
8645
8729
  const cmd = typeof a?.command === "string" ? a.command : typeof a?.cmd === "string" ? a.cmd : "";
8646
- return /\b(curl|wget|scp|rsync|nc|ncat|netcat|ssh)\b/.test(cmd);
8730
+ return NETWORK_COMMAND_RE.test(cmd);
8647
8731
  }
8648
8732
  return false;
8649
8733
  }
@@ -9518,7 +9602,7 @@ function canaryRecordById(id) {
9518
9602
  return null;
9519
9603
  }
9520
9604
  }
9521
- var WRITE_TOOLS;
9605
+ var WRITE_TOOLS, NETWORK_COMMAND_RE;
9522
9606
  var init_orchestrator = __esm({
9523
9607
  "src/auth/orchestrator.ts"() {
9524
9608
  "use strict";
@@ -9548,6 +9632,7 @@ var init_orchestrator = __esm({
9548
9632
  "notebook_edit",
9549
9633
  "notebookedit"
9550
9634
  ]);
9635
+ NETWORK_COMMAND_RE = new RegExp(`(?<![.\\w-])(${[...NET_BINARIES].join("|")})\\b`);
9551
9636
  }
9552
9637
  });
9553
9638
 
@@ -12613,9 +12698,10 @@ async function ensurePricingLoaded() {
12613
12698
  memCacheAt = Date.now();
12614
12699
  lookupCache.clear();
12615
12700
  }
12616
- function pricingFor(model) {
12701
+ function pricingFor(model, options = {}) {
12617
12702
  const norm = normalizeModel(model);
12618
- const cached = lookupCache.get(norm);
12703
+ const lookupKey = options.exact ? `exact:${norm}` : norm;
12704
+ const cached = lookupCache.get(lookupKey);
12619
12705
  if (cached !== void 0) return cached;
12620
12706
  if (memCache === null && !diskChecked) {
12621
12707
  diskChecked = true;
@@ -12635,6 +12721,7 @@ function pricingFor(model) {
12635
12721
  resolved = exact;
12636
12722
  break;
12637
12723
  }
12724
+ if (options.exact) continue;
12638
12725
  let best = null;
12639
12726
  for (const key of Object.keys(source)) {
12640
12727
  if (norm.startsWith(key.toLowerCase()) && (best === null || key.length > best.length)) {
@@ -12646,7 +12733,7 @@ function pricingFor(model) {
12646
12733
  break;
12647
12734
  }
12648
12735
  }
12649
- lookupCache.set(norm, resolved);
12736
+ lookupCache.set(lookupKey, resolved);
12650
12737
  return resolved;
12651
12738
  }
12652
12739
  var LITELLM_URL, BUNDLED_PRICING, CACHE_FILE, TTL_MS, memCache, memCacheAt, diskChecked, lookupCache;
@@ -12680,6 +12767,18 @@ var init_litellm = __esm({
12680
12767
  "gpt-5": [125e-8, 1e-5, 0, 125e-9],
12681
12768
  "gpt-5-codex": [125e-8, 1e-5, 0, 125e-9],
12682
12769
  "gpt-5-mini": [25e-8, 2e-6, 0, 25e-9],
12770
+ // Codex offline rates checked against official OpenAI model pages, 2026-09-11.
12771
+ "gpt-5.1-codex": [125e-8, 1e-5, 0, 125e-9],
12772
+ "gpt-5.1-codex-max": [125e-8, 1e-5, 0, 125e-9],
12773
+ "gpt-5.1-codex-mini": [25e-8, 2e-6, 0, 25e-9],
12774
+ "gpt-5.2-codex": [175e-8, 14e-6, 0, 175e-9],
12775
+ "gpt-5.3-codex": [175e-8, 14e-6, 0, 175e-9],
12776
+ "gpt-5.4": [25e-7, 15e-6, 0, 25e-8],
12777
+ "gpt-5.4-mini": [75e-8, 45e-7, 0, 75e-9],
12778
+ "gpt-5.5": [5e-6, 3e-5, 0, 5e-7],
12779
+ "gpt-5.6-sol": [4e-6, 2e-5, 5e-6, 4e-7],
12780
+ "gpt-5.6-terra": [2e-6, 12e-6, 25e-7, 2e-7],
12781
+ "gpt-6-astra": [1e-5, 5e-5, 125e-7, 1e-6],
12683
12782
  o3: [2e-6, 8e-6, 0, 5e-7],
12684
12783
  "o4-mini": [11e-7, 44e-7, 0, 275e-9],
12685
12784
  // Google. Values copied from the live LiteLLM table (verified 2026-06-14)
@@ -12851,103 +12950,237 @@ import fs21 from "fs";
12851
12950
  import os20 from "os";
12852
12951
  import path23 from "path";
12853
12952
  function codexSessionsDir() {
12854
- return path23.join(os20.homedir(), ".codex", "sessions");
12953
+ return path23.join(process.env.CODEX_HOME?.trim() || path23.join(os20.homedir(), ".codex"), "sessions");
12855
12954
  }
12856
12955
  function codexPriceFor(model) {
12857
- return pricingFor(model) ?? CODEX_FALLBACK;
12956
+ return pricingFor(codexModel(model), { exact: true }) ?? CODEX_FALLBACK;
12858
12957
  }
12859
- function codexSessionCost(model, tokens) {
12860
- const nonCached = Math.max(0, tokens.input - tokens.cached);
12861
- const [pin, pout, , pcr] = codexPriceFor(model || "gpt-5");
12862
- return nonCached * pin + tokens.cached * pcr + tokens.output * pout;
12958
+ function codexModel(model) {
12959
+ return normalizeModel(model.replace(/^openai\//i, "").replace(/-\d{4}-\d{2}-\d{2}$/, ""));
12863
12960
  }
12864
- function listCodexSessionFiles(base) {
12865
- const out = [];
12866
- for (const y of safeReaddir2(base)) {
12867
- const yp = path23.join(base, y);
12868
- if (!isDir2(yp)) continue;
12869
- for (const m of safeReaddir2(yp)) {
12870
- const mp = path23.join(yp, m);
12871
- if (!isDir2(mp)) continue;
12872
- for (const d of safeReaddir2(mp)) {
12873
- const dp = path23.join(mp, d);
12874
- if (!isDir2(dp)) continue;
12875
- for (const f of safeReaddir2(dp)) {
12876
- if (f.endsWith(".jsonl")) out.push(path23.join(dp, f));
12877
- }
12961
+ function addTokens(previous, delta) {
12962
+ return {
12963
+ input: (previous?.input ?? 0) + delta.input,
12964
+ cached: (previous?.cached ?? 0) + delta.cached,
12965
+ output: (previous?.output ?? 0) + delta.output,
12966
+ cacheWrite: (previous?.cacheWrite ?? 0) + delta.cacheWrite
12967
+ };
12968
+ }
12969
+ function codexSessionCost(model, tokens, request2) {
12970
+ const input = tokenNumber(tokens.input);
12971
+ const cached = Math.min(input, tokenNumber(tokens.cached));
12972
+ const written = Math.min(input - cached, tokenNumber(tokens.cacheWrite));
12973
+ const [pin, pout, pcw, pcr] = codexPriceFor(model || "gpt-5");
12974
+ const longContext = request2 && request2.inputTokens > 272e3 && ["gpt-5.4", "gpt-5.5", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-6-astra"].includes(
12975
+ codexModel(model)
12976
+ );
12977
+ const inputMultiplier = longContext ? 2 : 1;
12978
+ const outputMultiplier = longContext ? 1.5 : 1;
12979
+ const tierMultiplier = request2?.serviceTier === "flex" ? 0.5 : codexModel(model) === "gpt-6-astra" && (request2?.serviceTier === "fast" || request2?.serviceTier === "priority") ? 2 : 1;
12980
+ return (((input - cached - written) * pin + cached * pcr + written * (pcw || pin)) * inputMultiplier + tokenNumber(tokens.output) * pout * outputMultiplier) * tierMultiplier;
12981
+ }
12982
+ function listCodexSessionFiles(base = codexSessionsDir()) {
12983
+ const files = [];
12984
+ const walk = (dir) => {
12985
+ try {
12986
+ for (const entry of fs21.readdirSync(dir, { withFileTypes: true })) {
12987
+ const file = path23.join(dir, entry.name);
12988
+ if (entry.isDirectory()) walk(file);
12989
+ else if (entry.isFile() && entry.name.endsWith(".jsonl")) files.push(file);
12878
12990
  }
12991
+ } catch {
12992
+ }
12993
+ };
12994
+ walk(base);
12995
+ if (path23.basename(base) === "sessions") walk(path23.join(path23.dirname(base), "archived_sessions"));
12996
+ const sessions = /* @__PURE__ */ new Map();
12997
+ for (const file of files.sort()) {
12998
+ try {
12999
+ const stat = fs21.statSync(file);
13000
+ let id = "";
13001
+ try {
13002
+ const first = JSON.parse(fs21.readFileSync(file, "utf8").split("\n", 1)[0]);
13003
+ if (first?.type === "session_meta" && typeof first.payload?.id === "string")
13004
+ id = first.payload.id;
13005
+ } catch {
13006
+ }
13007
+ const key = id ? `session:${id}` : `file:${file}`;
13008
+ const prior = sessions.get(key);
13009
+ if (!prior || stat.mtimeMs > prior.mtime || stat.mtimeMs === prior.mtime && stat.size > prior.size) {
13010
+ sessions.set(key, { file, mtime: stat.mtimeMs, size: stat.size });
13011
+ }
13012
+ } catch {
12879
13013
  }
12880
13014
  }
12881
- return out;
13015
+ return [...sessions.values()].map((s) => s.file);
12882
13016
  }
12883
- function safeReaddir2(dir) {
12884
- try {
12885
- return fs21.readdirSync(dir);
12886
- } catch {
12887
- return [];
12888
- }
13017
+ function record(value) {
13018
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
12889
13019
  }
12890
- function isDir2(p) {
12891
- try {
12892
- return fs21.statSync(p).isDirectory();
12893
- } catch {
12894
- return false;
13020
+ function tokenNumber(value) {
13021
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
13022
+ }
13023
+ function usage(value, fallback) {
13024
+ const u = record(value);
13025
+ if (!["input_tokens", "output_tokens"].some((k) => typeof u[k] === "number")) return null;
13026
+ for (const key of [
13027
+ "input_tokens",
13028
+ "cached_input_tokens",
13029
+ "cache_read_input_tokens",
13030
+ "output_tokens",
13031
+ "cache_write_input_tokens"
13032
+ ]) {
13033
+ if (u[key] !== void 0 && (typeof u[key] !== "number" || !Number.isFinite(u[key]) || u[key] < 0))
13034
+ return null;
12895
13035
  }
13036
+ return {
13037
+ input: tokenNumber(u.input_tokens ?? fallback?.input),
13038
+ cached: tokenNumber(u.cached_input_tokens ?? u.cache_read_input_tokens ?? fallback?.cached),
13039
+ output: tokenNumber(u.output_tokens ?? fallback?.output),
13040
+ cacheWrite: tokenNumber(u.cache_write_input_tokens ?? fallback?.cacheWrite)
13041
+ };
12896
13042
  }
12897
- function parseCodexSession(lines) {
12898
- let sessionStart = "";
12899
- let runId = "";
12900
- let cwd = "";
12901
- let model = "";
12902
- let input = 0;
12903
- let cached = 0;
12904
- let output = 0;
12905
- let sawUsage = false;
13043
+ function timestamp(value) {
13044
+ return typeof value === "string" && Number.isFinite(Date.parse(value)) ? new Date(value).toISOString() : "";
13045
+ }
13046
+ function parseCodexUsage(lines) {
13047
+ const result = {
13048
+ events: [],
13049
+ sessionStart: "",
13050
+ runId: "",
13051
+ workingDir: "",
13052
+ legacyModels: []
13053
+ };
13054
+ let model = "gpt-5";
13055
+ let serviceTier;
13056
+ let previous = null;
13057
+ const legacyModels = /* @__PURE__ */ new Set();
13058
+ const seenStandalone = /* @__PURE__ */ new Set();
12906
13059
  for (const raw of lines) {
12907
- if (!raw.trim()) continue;
12908
13060
  let entry;
12909
13061
  try {
12910
- entry = JSON.parse(raw);
13062
+ entry = record(JSON.parse(raw));
12911
13063
  } catch {
12912
13064
  continue;
12913
13065
  }
12914
- const p = entry.payload ?? {};
13066
+ const p = record(entry.payload);
12915
13067
  if (entry.type === "session_meta") {
12916
- if (!sessionStart && typeof p["timestamp"] === "string") sessionStart = p["timestamp"];
12917
- if (!runId && typeof p["id"] === "string") runId = p["id"];
12918
- if (!cwd && typeof p["cwd"] === "string") cwd = p["cwd"];
13068
+ result.sessionStart ||= timestamp(p.timestamp ?? entry.timestamp);
13069
+ if (!result.runId && typeof p.id === "string") result.runId = p.id;
13070
+ if (!result.workingDir && typeof p.cwd === "string") result.workingDir = p.cwd;
12919
13071
  continue;
12920
13072
  }
12921
13073
  if (entry.type === "turn_context") {
12922
- if (typeof p["model"] === "string") model = p["model"];
12923
- if (!cwd && typeof p["cwd"] === "string") cwd = p["cwd"];
13074
+ if (typeof p.model === "string" && p.model) {
13075
+ model = p.model;
13076
+ legacyModels.add(normalizeModel(model));
13077
+ }
13078
+ if (!result.workingDir && typeof p.cwd === "string") result.workingDir = p.cwd;
13079
+ serviceTier = typeof p.service_tier === "string" ? p.service_tier : void 0;
12924
13080
  continue;
12925
13081
  }
12926
- if (entry.type === "event_msg" && p["type"] === "token_count") {
12927
- const info = p["info"] ?? {};
12928
- const usage = info["total_token_usage"] ?? {};
12929
- if (typeof usage["input_tokens"] === "number") input = usage["input_tokens"];
12930
- if (typeof usage["cached_input_tokens"] === "number") cached = usage["cached_input_tokens"];
12931
- if (typeof usage["output_tokens"] === "number") output = usage["output_tokens"];
12932
- sawUsage = true;
13082
+ if (entry.type !== "event_msg" || p.type !== "token_count") continue;
13083
+ const info = record(p.info);
13084
+ const total = usage(info.total_token_usage, previous);
13085
+ const last = usage(info.last_token_usage);
13086
+ if (!total && !last) continue;
13087
+ const eventModel = [info.model, info.model_name, p.model].find(
13088
+ (v) => typeof v === "string" && v
13089
+ );
13090
+ if (typeof eventModel === "string") model = eventModel;
13091
+ let delta;
13092
+ if (total) {
13093
+ if (previous && Object.keys(total).every(
13094
+ (k) => total[k] === previous[k]
13095
+ ))
13096
+ continue;
13097
+ const reset = previous && (total.input < previous.input || total.output < previous.output);
13098
+ delta = reset ? last ?? total : {
13099
+ input: Math.max(0, total.input - (previous?.input ?? 0)),
13100
+ cached: Math.max(0, total.cached - (previous?.cached ?? 0)),
13101
+ output: Math.max(0, total.output - (previous?.output ?? 0)),
13102
+ cacheWrite: Math.max(0, total.cacheWrite - (previous?.cacheWrite ?? 0))
13103
+ };
13104
+ previous = total;
13105
+ } else {
13106
+ delta = last;
13107
+ const key = JSON.stringify([entry.timestamp, model, delta]);
13108
+ if (entry.timestamp && seenStandalone.has(key)) continue;
13109
+ if (entry.timestamp) seenStandalone.add(key);
13110
+ previous = addTokens(previous, delta);
13111
+ }
13112
+ if (delta.input === 0 && delta.output === 0) continue;
13113
+ const ts = timestamp(entry.timestamp) || result.sessionStart;
13114
+ if (!ts) continue;
13115
+ const cached = Math.min(delta.input, delta.cached);
13116
+ const written = Math.min(delta.input - cached, delta.cacheWrite);
13117
+ result.events.push({
13118
+ timestamp: ts,
13119
+ date: ts.slice(0, 10),
13120
+ model: normalizeModel(model),
13121
+ workingDir: result.workingDir,
13122
+ runId: result.runId,
13123
+ costUSD: codexSessionCost(model, delta, {
13124
+ inputTokens: last?.input ?? delta.input,
13125
+ serviceTier: typeof info.service_tier === "string" ? info.service_tier : serviceTier
13126
+ }),
13127
+ inputTokens: delta.input - cached - written,
13128
+ outputTokens: delta.output,
13129
+ cacheReadTokens: cached,
13130
+ cacheWriteTokens: written
13131
+ });
13132
+ }
13133
+ result.legacyModels = [...legacyModels.size ? legacyModels : ["gpt-5"]];
13134
+ return result;
13135
+ }
13136
+ function codexUsageInWindow(usage2, start, end) {
13137
+ return usage2.events.filter(
13138
+ (e) => (!start || Date.parse(e.timestamp) >= start.getTime()) && (!end || Date.parse(e.timestamp) <= end.getTime())
13139
+ );
13140
+ }
13141
+ function parseCodexSession(lines) {
13142
+ const parsed = parseCodexUsage(lines);
13143
+ if (!parsed.events.length) return [];
13144
+ const rows = /* @__PURE__ */ new Map();
13145
+ if (parsed.sessionStart) {
13146
+ for (const model of parsed.legacyModels) {
13147
+ rows.set(`${parsed.sessionStart.slice(0, 10)}::${model}`, {
13148
+ date: parsed.sessionStart.slice(0, 10),
13149
+ model,
13150
+ workingDir: parsed.workingDir,
13151
+ runId: parsed.runId,
13152
+ costUSD: 0,
13153
+ inputTokens: 0,
13154
+ outputTokens: 0,
13155
+ cacheReadTokens: 0,
13156
+ cacheWriteTokens: 0
13157
+ });
12933
13158
  }
12934
13159
  }
12935
- if (!sessionStart || !sawUsage) return null;
12936
- const nonCached = Math.max(0, input - cached);
12937
- if (nonCached === 0 && output === 0 && cached === 0) return null;
12938
- const norm = normalizeModel(model || "gpt-5");
12939
- const costUSD = codexSessionCost(model, { input, cached, output });
12940
- return {
12941
- date: sessionStart.slice(0, 10),
12942
- model: norm,
12943
- workingDir: cwd,
12944
- runId,
12945
- costUSD,
12946
- inputTokens: nonCached,
12947
- outputTokens: output,
12948
- cacheReadTokens: cached,
12949
- cacheWriteTokens: 0
12950
- };
13160
+ for (const event of parsed.events) {
13161
+ const e = {
13162
+ date: event.date,
13163
+ model: event.model,
13164
+ workingDir: event.workingDir,
13165
+ runId: event.runId,
13166
+ costUSD: event.costUSD,
13167
+ inputTokens: event.inputTokens,
13168
+ outputTokens: event.outputTokens,
13169
+ cacheReadTokens: event.cacheReadTokens,
13170
+ cacheWriteTokens: event.cacheWriteTokens
13171
+ };
13172
+ const key = `${e.date}::${e.model}`;
13173
+ const prev = rows.get(key);
13174
+ if (!prev) rows.set(key, { ...e });
13175
+ else {
13176
+ prev.costUSD += e.costUSD;
13177
+ prev.inputTokens += e.inputTokens;
13178
+ prev.outputTokens += e.outputTokens;
13179
+ prev.cacheReadTokens += e.cacheReadTokens;
13180
+ prev.cacheWriteTokens += e.cacheWriteTokens;
13181
+ }
13182
+ }
13183
+ return [...rows.values()];
12951
13184
  }
12952
13185
  var CODEX_FALLBACK, codexSource;
12953
13186
  var init_cost_codex = __esm({
@@ -12957,43 +13190,17 @@ var init_cost_codex = __esm({
12957
13190
  CODEX_FALLBACK = [5e-6, 15e-6, 0, 25e-7];
12958
13191
  codexSource = {
12959
13192
  id: "codex",
12960
- available() {
12961
- try {
12962
- return fs21.existsSync(codexSessionsDir());
12963
- } catch {
12964
- return false;
12965
- }
12966
- },
13193
+ available: () => fs21.existsSync(codexSessionsDir()) || fs21.existsSync(path23.join(path23.dirname(codexSessionsDir()), "archived_sessions")),
12967
13194
  collect(sinceMs) {
12968
- const base = codexSessionsDir();
12969
- const combined = /* @__PURE__ */ new Map();
12970
- for (const file of listCodexSessionFiles(base)) {
13195
+ const entries = [];
13196
+ for (const file of listCodexSessionFiles()) {
12971
13197
  try {
12972
13198
  if (sinceMs !== void 0 && fs21.statSync(file).mtimeMs < sinceMs) continue;
13199
+ entries.push(...parseCodexSession(fs21.readFileSync(file, "utf8").split("\n")));
12973
13200
  } catch {
12974
- continue;
12975
- }
12976
- let content;
12977
- try {
12978
- content = fs21.readFileSync(file, "utf8");
12979
- } catch {
12980
- continue;
12981
- }
12982
- const e = parseCodexSession(content.split("\n"));
12983
- if (!e) continue;
12984
- const key = `${e.date}::${e.model}::${e.workingDir ?? ""}::${e.runId ?? ""}`;
12985
- const prev = combined.get(key);
12986
- if (prev) {
12987
- prev.costUSD += e.costUSD;
12988
- prev.inputTokens += e.inputTokens;
12989
- prev.outputTokens += e.outputTokens;
12990
- prev.cacheReadTokens += e.cacheReadTokens;
12991
- prev.cacheWriteTokens += e.cacheWriteTokens;
12992
- } else {
12993
- combined.set(key, { ...e });
12994
13201
  }
12995
13202
  }
12996
- return [...combined.values()];
13203
+ return entries;
12997
13204
  }
12998
13205
  };
12999
13206
  }
@@ -13006,7 +13213,7 @@ import path24 from "path";
13006
13213
  function copilotSessionsDir() {
13007
13214
  return path24.join(os21.homedir(), ".copilot", "session-state");
13008
13215
  }
13009
- function safeReaddir3(dir) {
13216
+ function safeReaddir2(dir) {
13010
13217
  try {
13011
13218
  return fs22.readdirSync(dir);
13012
13219
  } catch {
@@ -13092,7 +13299,7 @@ var init_cost_copilot = __esm({
13092
13299
  collect(sinceMs) {
13093
13300
  const base = copilotSessionsDir();
13094
13301
  const combined = /* @__PURE__ */ new Map();
13095
- for (const sid of safeReaddir3(base)) {
13302
+ for (const sid of safeReaddir2(base)) {
13096
13303
  const file = path24.join(base, sid, "events.jsonl");
13097
13304
  try {
13098
13305
  if (sinceMs !== void 0 && fs22.statSync(file).mtimeMs < sinceMs) continue;
@@ -13707,8 +13914,8 @@ function originForRule(ruleName, sections, enabled) {
13707
13914
  }
13708
13915
  return "";
13709
13916
  }
13710
- function relativeDate(timestamp, now = /* @__PURE__ */ new Date()) {
13711
- const t = new Date(timestamp).getTime();
13917
+ function relativeDate(timestamp2, now = /* @__PURE__ */ new Date()) {
13918
+ const t = new Date(timestamp2).getTime();
13712
13919
  if (Number.isNaN(t)) return "?";
13713
13920
  const days = Math.floor((now.getTime() - t) / 864e5);
13714
13921
  if (days < 1) return "today";
@@ -13819,7 +14026,7 @@ function readPreviousScan(opts = {}) {
13819
14026
  return null;
13820
14027
  }
13821
14028
  }
13822
- function appendScanHistory(record, opts = {}) {
14029
+ function appendScanHistory(record2, opts = {}) {
13823
14030
  const filePath = opts.path ?? defaultHistoryPath();
13824
14031
  const cap = opts.cap ?? SCAN_HISTORY_CAP;
13825
14032
  try {
@@ -13834,7 +14041,7 @@ function appendScanHistory(record, opts = {}) {
13834
14041
  } catch {
13835
14042
  }
13836
14043
  }
13837
- history.push(record);
14044
+ history.push(record2);
13838
14045
  if (history.length > cap) {
13839
14046
  history = history.slice(history.length - cap);
13840
14047
  }
@@ -13895,17 +14102,17 @@ function parseJSONLFile(filePath, fallbackWorkingDir) {
13895
14102
  if (row["type"] !== "assistant") continue;
13896
14103
  const msg = row["message"];
13897
14104
  if (!msg?.["usage"] || typeof msg["model"] !== "string") continue;
13898
- const usage = msg["usage"];
14105
+ const usage2 = msg["usage"];
13899
14106
  const model = msg["model"];
13900
- const timestamp = row["timestamp"];
13901
- if (typeof timestamp !== "string" || timestamp.length < 10) continue;
13902
- const date = timestamp.slice(0, 10);
14107
+ const timestamp2 = row["timestamp"];
14108
+ if (typeof timestamp2 !== "string" || timestamp2.length < 10) continue;
14109
+ const date = timestamp2.slice(0, 10);
13903
14110
  const p = pricingFor(model);
13904
14111
  if (!p) continue;
13905
- const inp = Number(usage["input_tokens"] ?? 0);
13906
- const out = Number(usage["output_tokens"] ?? 0);
13907
- const cw = Number(usage["cache_creation_input_tokens"] ?? 0);
13908
- const cr = Number(usage["cache_read_input_tokens"] ?? 0);
14112
+ const inp = Number(usage2["input_tokens"] ?? 0);
14113
+ const out = Number(usage2["output_tokens"] ?? 0);
14114
+ const cw = Number(usage2["cache_creation_input_tokens"] ?? 0);
14115
+ const cr = Number(usage2["cache_read_input_tokens"] ?? 0);
13909
14116
  const cost = inp * p[0] + out * p[1] + cw * p[2] + cr * p[3];
13910
14117
  const rowCwd = typeof row["cwd"] === "string" ? row["cwd"] : null;
13911
14118
  const workingDir = rowCwd && rowCwd.startsWith("/") ? rowCwd : fallbackWorkingDir;
@@ -14935,7 +15142,7 @@ function safeCanaryScanValues() {
14935
15142
  return [];
14936
15143
  }
14937
15144
  }
14938
- function recordCanaries(scanned, toolName, timestamp, projLabel, sessionId, agent, result, dedup, values) {
15145
+ function recordCanaries(scanned, toolName, timestamp2, projLabel, sessionId, agent, result, dedup, values) {
14939
15146
  if (values.length === 0) return [];
14940
15147
  let pool = [...values];
14941
15148
  const matched = [];
@@ -14949,8 +15156,8 @@ function recordCanaries(scanned, toolName, timestamp, projLabel, sessionId, agen
14949
15156
  const existing = dedup.canaryIndex.get(key);
14950
15157
  if (existing) {
14951
15158
  existing.count++;
14952
- if (timestamp && (!existing.timestamp || timestamp < existing.timestamp)) {
14953
- existing.timestamp = timestamp;
15159
+ if (timestamp2 && (!existing.timestamp || timestamp2 < existing.timestamp)) {
15160
+ existing.timestamp = timestamp2;
14954
15161
  }
14955
15162
  continue;
14956
15163
  }
@@ -14963,7 +15170,7 @@ function recordCanaries(scanned, toolName, timestamp, projLabel, sessionId, agen
14963
15170
  view: hit.view,
14964
15171
  retired: hit.retired,
14965
15172
  toolName,
14966
- timestamp,
15173
+ timestamp: timestamp2,
14967
15174
  project: projLabel,
14968
15175
  sessionId,
14969
15176
  agent,
@@ -14995,7 +15202,7 @@ function scrubDecoys(subject, values) {
14995
15202
  };
14996
15203
  return walk(subject, 0);
14997
15204
  }
14998
- function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sessionId, agent, result, dedup) {
15205
+ function pushFsOpAstFinding(command, toolName, input, timestamp2, projLabel, sessionId, agent, result, dedup) {
14999
15206
  const fsVerdict = analyzeFsOperation(command);
15000
15207
  if (!fsVerdict) return false;
15001
15208
  const synthRule = {
@@ -15025,7 +15232,7 @@ function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sess
15025
15232
  source: synthSource,
15026
15233
  toolName,
15027
15234
  input,
15028
- timestamp,
15235
+ timestamp: timestamp2,
15029
15236
  project: projLabel,
15030
15237
  sessionId,
15031
15238
  agent
@@ -15033,9 +15240,9 @@ function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sess
15033
15240
  }
15034
15241
  return true;
15035
15242
  }
15036
- function isStaleFinding(timestamp, now = Date.now()) {
15037
- if (!timestamp) return false;
15038
- const t = Date.parse(timestamp);
15243
+ function isStaleFinding(timestamp2, now = Date.now()) {
15244
+ if (!timestamp2) return false;
15245
+ const t = Date.parse(timestamp2);
15039
15246
  if (Number.isNaN(t)) return false;
15040
15247
  const ageDays = (now - t) / 864e5;
15041
15248
  return ageDays > STALE_AGE_DAYS;
@@ -15183,37 +15390,7 @@ function countScanFiles() {
15183
15390
  } catch {
15184
15391
  }
15185
15392
  }
15186
- const codexDir = path31.join(os27.homedir(), ".codex", "sessions");
15187
- if (fs29.existsSync(codexDir)) {
15188
- try {
15189
- for (const year of fs29.readdirSync(codexDir)) {
15190
- const yp = path31.join(codexDir, year);
15191
- try {
15192
- if (!fs29.statSync(yp).isDirectory()) continue;
15193
- for (const month of fs29.readdirSync(yp)) {
15194
- const mp = path31.join(yp, month);
15195
- try {
15196
- if (!fs29.statSync(mp).isDirectory()) continue;
15197
- for (const day of fs29.readdirSync(mp)) {
15198
- const dp = path31.join(mp, day);
15199
- try {
15200
- if (!fs29.statSync(dp).isDirectory()) continue;
15201
- total += listSessionFiles(dp).length;
15202
- } catch {
15203
- continue;
15204
- }
15205
- }
15206
- } catch {
15207
- continue;
15208
- }
15209
- }
15210
- } catch {
15211
- continue;
15212
- }
15213
- }
15214
- } catch {
15215
- }
15216
- }
15393
+ total += listCodexSessionFiles().length;
15217
15394
  return total;
15218
15395
  }
15219
15396
  function renderProgressBar(done, total, lines) {
@@ -15336,12 +15513,12 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
15336
15513
  }
15337
15514
  continue;
15338
15515
  }
15339
- const usage = entry.message?.usage;
15516
+ const usage2 = entry.message?.usage;
15340
15517
  const model = entry.message?.model;
15341
- if (usage && model) {
15518
+ if (usage2 && model) {
15342
15519
  const p = claudeModelPrice(model);
15343
15520
  if (p) {
15344
- 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;
15521
+ 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;
15345
15522
  result.totalCostUSD += rowCost;
15346
15523
  session.costUSD += rowCost;
15347
15524
  }
@@ -15875,15 +16052,15 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
15875
16052
  } catch {
15876
16053
  continue;
15877
16054
  }
15878
- const timestamp = step.created_at ?? "";
15879
- if (startDate && timestamp && new Date(timestamp) < startDate) continue;
16055
+ const timestamp2 = step.created_at ?? "";
16056
+ if (startDate && timestamp2 && new Date(timestamp2) < startDate) continue;
15880
16057
  if (step.type === "USER_INPUT") {
15881
16058
  const text = typeof step.content === "string" ? step.content : "";
15882
16059
  if (text) {
15883
16060
  const decoysHere5 = recordCanaries(
15884
16061
  { text },
15885
16062
  "user-prompt",
15886
- timestamp,
16063
+ timestamp2,
15887
16064
  projLabel,
15888
16065
  sessionId,
15889
16066
  "antigravity",
@@ -15900,7 +16077,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
15900
16077
  patternName: dlpMatch.patternName,
15901
16078
  redactedSample: dlpMatch.redactedSample,
15902
16079
  toolName: "user-prompt",
15903
- timestamp,
16080
+ timestamp: timestamp2,
15904
16081
  project: projLabel,
15905
16082
  sessionId,
15906
16083
  agent: "antigravity"
@@ -15911,16 +16088,16 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
15911
16088
  continue;
15912
16089
  }
15913
16090
  if (!Array.isArray(step.tool_calls) || step.tool_calls.length === 0) continue;
15914
- if (timestamp) {
15915
- if (!result.firstDate || timestamp < result.firstDate) result.firstDate = timestamp;
15916
- if (!result.lastDate || timestamp > result.lastDate) result.lastDate = timestamp;
16091
+ if (timestamp2) {
16092
+ if (!result.firstDate || timestamp2 < result.firstDate) result.firstDate = timestamp2;
16093
+ if (!result.lastDate || timestamp2 > result.lastDate) result.lastDate = timestamp2;
15917
16094
  }
15918
16095
  for (const tc of step.tool_calls) {
15919
16096
  result.totalToolCalls++;
15920
16097
  const toolName = tc.name ?? "";
15921
16098
  const toolNameLower = toolName.toLowerCase();
15922
16099
  const input = canonicalToolInput(toolName, tc.args ?? {});
15923
- sessionCalls.push({ toolName, input, timestamp });
16100
+ sessionCalls.push({ toolName, input, timestamp: timestamp2 });
15924
16101
  const isShellTool = toolNameLower === "run_command";
15925
16102
  if (isShellTool) {
15926
16103
  result.bashCalls++;
@@ -15935,7 +16112,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
15935
16112
  const decoysHere6 = recordCanaries(
15936
16113
  input,
15937
16114
  toolName,
15938
- timestamp,
16115
+ timestamp2,
15939
16116
  projLabel,
15940
16117
  sessionId,
15941
16118
  "antigravity",
@@ -15952,7 +16129,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
15952
16129
  patternName: dlpMatch.patternName,
15953
16130
  redactedSample: dlpMatch.redactedSample,
15954
16131
  toolName,
15955
- timestamp,
16132
+ timestamp: timestamp2,
15956
16133
  project: projLabel,
15957
16134
  sessionId,
15958
16135
  agent: "antigravity"
@@ -15965,7 +16142,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
15965
16142
  String(input.command ?? ""),
15966
16143
  toolName,
15967
16144
  input,
15968
- timestamp,
16145
+ timestamp2,
15969
16146
  projLabel,
15970
16147
  sessionId,
15971
16148
  "antigravity",
@@ -15989,7 +16166,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
15989
16166
  source,
15990
16167
  toolName,
15991
16168
  input,
15992
- timestamp,
16169
+ timestamp: timestamp2,
15993
16170
  project: projLabel,
15994
16171
  sessionId,
15995
16172
  agent: "antigravity"
@@ -16021,7 +16198,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
16021
16198
  },
16022
16199
  toolName,
16023
16200
  input,
16024
- timestamp,
16201
+ timestamp: timestamp2,
16025
16202
  project: projLabel,
16026
16203
  sessionId,
16027
16204
  agent: "antigravity"
@@ -16091,7 +16268,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16091
16268
  } catch {
16092
16269
  continue;
16093
16270
  }
16094
- const timestamp = ev.timestamp ?? "";
16271
+ const timestamp2 = ev.timestamp ?? "";
16095
16272
  if (ev.type === "session.start") {
16096
16273
  const cwd = ev.data?.context?.cwd;
16097
16274
  if (typeof cwd === "string" && cwd) {
@@ -16099,14 +16276,14 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16099
16276
  }
16100
16277
  continue;
16101
16278
  }
16102
- if (startDate && timestamp && new Date(timestamp) < startDate) continue;
16279
+ if (startDate && timestamp2 && new Date(timestamp2) < startDate) continue;
16103
16280
  if (ev.type === "user.message") {
16104
16281
  const text = ev.data?.content ?? ev.data?.text ?? "";
16105
16282
  if (typeof text === "string" && text) {
16106
16283
  const decoysHere7 = recordCanaries(
16107
16284
  { text },
16108
16285
  "user-prompt",
16109
- timestamp,
16286
+ timestamp2,
16110
16287
  projLabel,
16111
16288
  sessionId,
16112
16289
  "copilot",
@@ -16123,7 +16300,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16123
16300
  patternName: dlpMatch2.patternName,
16124
16301
  redactedSample: dlpMatch2.redactedSample,
16125
16302
  toolName: "user-prompt",
16126
- timestamp,
16303
+ timestamp: timestamp2,
16127
16304
  project: projLabel,
16128
16305
  sessionId,
16129
16306
  agent: "copilot"
@@ -16138,19 +16315,19 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16138
16315
  const toolNameLower = toolName.toLowerCase();
16139
16316
  const input = ev.data?.arguments ?? {};
16140
16317
  result.totalToolCalls++;
16141
- sessionCalls.push({ toolName, input, timestamp });
16318
+ sessionCalls.push({ toolName, input, timestamp: timestamp2 });
16142
16319
  const isShellTool = isShellShapedTool(toolNameLower, toolInspectionMap);
16143
16320
  if (isShellTool) result.bashCalls++;
16144
- if (timestamp) {
16145
- if (!result.firstDate || timestamp < result.firstDate) result.firstDate = timestamp;
16146
- if (!result.lastDate || timestamp > result.lastDate) result.lastDate = timestamp;
16321
+ if (timestamp2) {
16322
+ if (!result.firstDate || timestamp2 < result.firstDate) result.firstDate = timestamp2;
16323
+ if (!result.lastDate || timestamp2 > result.lastDate) result.lastDate = timestamp2;
16147
16324
  }
16148
16325
  const rawCmd = String(input.command ?? "").trimStart();
16149
16326
  if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd)) continue;
16150
16327
  const decoysHere8 = recordCanaries(
16151
16328
  input,
16152
16329
  toolName,
16153
- timestamp,
16330
+ timestamp2,
16154
16331
  projLabel,
16155
16332
  sessionId,
16156
16333
  "copilot",
@@ -16167,7 +16344,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16167
16344
  patternName: dlpMatch.patternName,
16168
16345
  redactedSample: dlpMatch.redactedSample,
16169
16346
  toolName,
16170
- timestamp,
16347
+ timestamp: timestamp2,
16171
16348
  project: projLabel,
16172
16349
  sessionId,
16173
16350
  agent: "copilot"
@@ -16180,7 +16357,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16180
16357
  String(input.command ?? ""),
16181
16358
  toolName,
16182
16359
  input,
16183
- timestamp,
16360
+ timestamp2,
16184
16361
  projLabel,
16185
16362
  sessionId,
16186
16363
  "copilot",
@@ -16203,7 +16380,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16203
16380
  source,
16204
16381
  toolName,
16205
16382
  input,
16206
- timestamp,
16383
+ timestamp: timestamp2,
16207
16384
  project: projLabel,
16208
16385
  sessionId,
16209
16386
  agent: "copilot"
@@ -16235,7 +16412,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16235
16412
  },
16236
16413
  toolName,
16237
16414
  input,
16238
- timestamp,
16415
+ timestamp: timestamp2,
16239
16416
  project: projLabel,
16240
16417
  sessionId,
16241
16418
  agent: "copilot"
@@ -16250,7 +16427,6 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
16250
16427
  }
16251
16428
  function scanCodexHistory(startDate, onProgress, onLine) {
16252
16429
  const canaryVals = safeCanaryScanValues();
16253
- const sessionsBase = path31.join(os27.homedir(), ".codex", "sessions");
16254
16430
  const result = {
16255
16431
  filesScanned: 0,
16256
16432
  sessions: 0,
@@ -16267,39 +16443,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
16267
16443
  perSession: []
16268
16444
  };
16269
16445
  const dedup = emptyScanDedup();
16270
- if (!fs29.existsSync(sessionsBase)) return result;
16271
- const jsonlFiles = [];
16272
- try {
16273
- for (const year of fs29.readdirSync(sessionsBase)) {
16274
- const yearPath = path31.join(sessionsBase, year);
16275
- try {
16276
- if (!fs29.statSync(yearPath).isDirectory()) continue;
16277
- } catch {
16278
- continue;
16279
- }
16280
- for (const month of fs29.readdirSync(yearPath)) {
16281
- const monthPath = path31.join(yearPath, month);
16282
- try {
16283
- if (!fs29.statSync(monthPath).isDirectory()) continue;
16284
- } catch {
16285
- continue;
16286
- }
16287
- for (const day of fs29.readdirSync(monthPath)) {
16288
- const dayPath = path31.join(monthPath, day);
16289
- try {
16290
- if (!fs29.statSync(dayPath).isDirectory()) continue;
16291
- } catch {
16292
- continue;
16293
- }
16294
- for (const file of fs29.readdirSync(dayPath)) {
16295
- if (file.endsWith(".jsonl")) jsonlFiles.push(path31.join(dayPath, file));
16296
- }
16297
- }
16298
- }
16299
- }
16300
- } catch {
16301
- return result;
16302
- }
16446
+ const jsonlFiles = listCodexSessionFiles();
16303
16447
  const ruleSources = buildRuleSources();
16304
16448
  for (const filePath of jsonlFiles) {
16305
16449
  result.filesScanned++;
@@ -16315,10 +16459,6 @@ function scanCodexHistory(startDate, onProgress, onLine) {
16315
16459
  let projLabel = "";
16316
16460
  result.sessions++;
16317
16461
  const sessionCalls = [];
16318
- let lastTotalInput = 0;
16319
- let lastTotalCached = 0;
16320
- let lastTotalOutput = 0;
16321
- let model = "";
16322
16462
  for (const line of lines) {
16323
16463
  if (!line.trim()) continue;
16324
16464
  onLine?.();
@@ -16336,18 +16476,6 @@ function scanCodexHistory(startDate, onProgress, onLine) {
16336
16476
  projLabel = stripTerminalEscapes(cwd.replace(os27.homedir(), "~")).slice(0, 40);
16337
16477
  continue;
16338
16478
  }
16339
- if (entry.type === "turn_context" && typeof payload["model"] === "string") {
16340
- model = payload["model"];
16341
- continue;
16342
- }
16343
- if (entry.type === "event_msg" && payload["type"] === "token_count") {
16344
- const info = payload["info"];
16345
- const usage = info?.["total_token_usage"] ?? {};
16346
- lastTotalInput = usage["input_tokens"] ?? lastTotalInput;
16347
- lastTotalCached = usage["cached_input_tokens"] ?? lastTotalCached;
16348
- lastTotalOutput = usage["output_tokens"] ?? lastTotalOutput;
16349
- continue;
16350
- }
16351
16479
  if (entry.type === "event_msg" && payload["type"] === "user_message") {
16352
16480
  const text = String(payload["message"] ?? "");
16353
16481
  if (text) {
@@ -16504,13 +16632,8 @@ function scanCodexHistory(startDate, onProgress, onLine) {
16504
16632
  }
16505
16633
  }
16506
16634
  }
16507
- const withinWindow = !startDate || startTime !== "" && new Date(startTime) >= startDate;
16508
- if (withinWindow) {
16509
- result.totalCostUSD += codexSessionCost(model, {
16510
- input: lastTotalInput,
16511
- cached: lastTotalCached,
16512
- output: lastTotalOutput
16513
- });
16635
+ for (const event of codexUsageInWindow(parseCodexUsage(lines), startDate)) {
16636
+ result.totalCostUSD += event.costUSD;
16514
16637
  }
16515
16638
  result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "codex"));
16516
16639
  }
@@ -17942,13 +18065,13 @@ var init_taint_store = __esm({
17942
18065
  */
17943
18066
  check(filePath) {
17944
18067
  const resolved = this._resolve(filePath);
17945
- const record = this.records.get(resolved);
17946
- if (!record) return null;
17947
- if (Date.now() > record.expiresAt) {
18068
+ const record2 = this.records.get(resolved);
18069
+ if (!record2) return null;
18070
+ if (Date.now() > record2.expiresAt) {
17948
18071
  this.records.delete(resolved);
17949
18072
  return null;
17950
18073
  }
17951
- return record;
18074
+ return record2;
17952
18075
  }
17953
18076
  /**
17954
18077
  * Propagate taint from sourcePath to destPath (e.g. cp, mv).
@@ -17969,8 +18092,8 @@ var init_taint_store = __esm({
17969
18092
  /** Remove all expired records. Called periodically by the daemon. */
17970
18093
  prune() {
17971
18094
  const now = Date.now();
17972
- for (const [key, record] of this.records) {
17973
- if (now > record.expiresAt) this.records.delete(key);
18095
+ for (const [key, record2] of this.records) {
18096
+ if (now > record2.expiresAt) this.records.delete(key);
17974
18097
  }
17975
18098
  }
17976
18099
  /** Return all non-expired taint records (for audit/debug). */
@@ -18009,13 +18132,13 @@ var init_taint_store = __esm({
18009
18132
  * Expired records are pruned on access. */
18010
18133
  check(sessionId) {
18011
18134
  if (!sessionId) return null;
18012
- const record = this.records.get(sessionId);
18013
- if (!record) return null;
18014
- if (Date.now() > record.expiresAt) {
18135
+ const record2 = this.records.get(sessionId);
18136
+ if (!record2) return null;
18137
+ if (Date.now() > record2.expiresAt) {
18015
18138
  this.records.delete(sessionId);
18016
18139
  return null;
18017
18140
  }
18018
- return record;
18141
+ return record2;
18019
18142
  }
18020
18143
  /** Clear a session's taint (e.g. the user resolved it). Returns true if a
18021
18144
  * record was actually removed (false if the session wasn't tainted). */
@@ -18030,8 +18153,8 @@ var init_taint_store = __esm({
18030
18153
  /** Remove all expired records. Called periodically by the daemon. */
18031
18154
  prune() {
18032
18155
  const now = Date.now();
18033
- for (const [key, record] of this.records) {
18034
- if (now > record.expiresAt) this.records.delete(key);
18156
+ for (const [key, record2] of this.records) {
18157
+ if (now > record2.expiresAt) this.records.delete(key);
18035
18158
  }
18036
18159
  }
18037
18160
  /** Remove all records. Used by tests to reset state between runs. */
@@ -22765,10 +22888,10 @@ data: ${JSON.stringify(item.data)}
22765
22888
  return res.end(JSON.stringify({ error: "all paths must be strings" }));
22766
22889
  }
22767
22890
  for (const p of body.paths) {
22768
- const record = taintStore.check(p);
22769
- if (record) {
22891
+ const record2 = taintStore.check(p);
22892
+ if (record2) {
22770
22893
  res.writeHead(200, { "Content-Type": "application/json" });
22771
- return res.end(JSON.stringify({ tainted: true, record }));
22894
+ return res.end(JSON.stringify({ tainted: true, record: record2 }));
22772
22895
  }
22773
22896
  }
22774
22897
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -22817,9 +22940,9 @@ data: ${JSON.stringify(item.data)}
22817
22940
  res.writeHead(400, { "Content-Type": "application/json" });
22818
22941
  return res.end(JSON.stringify({ error: "sessionId must be a string" }));
22819
22942
  }
22820
- const record = sessionTaintStore.check(body.sessionId);
22943
+ const record2 = sessionTaintStore.check(body.sessionId);
22821
22944
  res.writeHead(200, { "Content-Type": "application/json" });
22822
- return res.end(JSON.stringify(record ? { tainted: true, record } : { tainted: false }));
22945
+ return res.end(JSON.stringify(record2 ? { tainted: true, record: record2 } : { tainted: false }));
22823
22946
  } catch {
22824
22947
  res.writeHead(400).end();
22825
22948
  return;
@@ -28765,8 +28888,8 @@ var require_util2 = __commonJS({
28765
28888
  request2.headersList.append("origin", serializedOrigin, true);
28766
28889
  }
28767
28890
  }
28768
- function coarsenTime(timestamp, crossOriginIsolatedCapability) {
28769
- return timestamp;
28891
+ function coarsenTime(timestamp2, crossOriginIsolatedCapability) {
28892
+ return timestamp2;
28770
28893
  }
28771
28894
  function clampAndCoarsenConnectionTimingInfo(connectionTimingInfo, defaultStartTime, crossOriginIsolatedCapability) {
28772
28895
  if (!connectionTimingInfo?.startTime || connectionTimingInfo.startTime < defaultStartTime) {
@@ -38847,20 +38970,20 @@ var require_dns = __commonJS({
38847
38970
  return ip;
38848
38971
  }
38849
38972
  setRecords(origin, addresses) {
38850
- const timestamp = Date.now();
38973
+ const timestamp2 = Date.now();
38851
38974
  const records = { records: { 4: null, 6: null } };
38852
38975
  let minTTL = this.#maxTTL;
38853
- for (const record of addresses) {
38854
- record.timestamp = timestamp;
38855
- if (typeof record.ttl === "number") {
38856
- record.ttl = Math.min(record.ttl, this.#maxTTL);
38857
- minTTL = Math.min(minTTL, record.ttl);
38976
+ for (const record2 of addresses) {
38977
+ record2.timestamp = timestamp2;
38978
+ if (typeof record2.ttl === "number") {
38979
+ record2.ttl = Math.min(record2.ttl, this.#maxTTL);
38980
+ minTTL = Math.min(minTTL, record2.ttl);
38858
38981
  } else {
38859
- record.ttl = this.#maxTTL;
38982
+ record2.ttl = this.#maxTTL;
38860
38983
  }
38861
- const familyRecords = records.records[record.family] ?? { ips: [] };
38862
- familyRecords.ips.push(record);
38863
- records.records[record.family] = familyRecords;
38984
+ const familyRecords = records.records[record2.family] ?? { ips: [] };
38985
+ familyRecords.ips.push(record2);
38986
+ records.records[record2.family] = familyRecords;
38864
38987
  }
38865
38988
  this.storage.set(origin.hostname, records, { ttl: minTTL });
38866
38989
  }
@@ -53524,15 +53647,15 @@ import chalk16 from "chalk";
53524
53647
  import fs59 from "fs";
53525
53648
  import path57 from "path";
53526
53649
  import os53 from "os";
53527
- function formatRelativeTime(timestamp) {
53528
- const diff = Date.now() - new Date(timestamp).getTime();
53650
+ function formatRelativeTime(timestamp2) {
53651
+ const diff = Date.now() - new Date(timestamp2).getTime();
53529
53652
  const sec = Math.floor(diff / 1e3);
53530
53653
  if (sec < 60) return `${sec}s ago`;
53531
53654
  const min = Math.floor(sec / 60);
53532
53655
  if (min < 60) return `${min}m ago`;
53533
53656
  const hrs = Math.floor(min / 60);
53534
53657
  if (hrs < 24) return `${hrs}h ago`;
53535
- return new Date(timestamp).toLocaleDateString();
53658
+ return new Date(timestamp2).toLocaleDateString();
53536
53659
  }
53537
53660
  function registerAuditCommand(program2) {
53538
53661
  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) => {
@@ -53778,15 +53901,15 @@ function processClaudeCostProject(proj, projectsDir, start, end, acc) {
53778
53901
  if (!entry.timestamp) continue;
53779
53902
  const ts = new Date(entry.timestamp);
53780
53903
  if (ts < start || ts > end) continue;
53781
- const usage = entry.message?.usage;
53904
+ const usage2 = entry.message?.usage;
53782
53905
  const model = entry.message?.model;
53783
- if (!usage || !model) continue;
53906
+ if (!usage2 || !model) continue;
53784
53907
  const p = claudeModelPrice2(model);
53785
53908
  if (!p) continue;
53786
- const inp = usage.input_tokens ?? 0;
53787
- const out = usage.output_tokens ?? 0;
53788
- const cw = usage.cache_creation_input_tokens ?? 0;
53789
- const cr = usage.cache_read_input_tokens ?? 0;
53909
+ const inp = usage2.input_tokens ?? 0;
53910
+ const out = usage2.output_tokens ?? 0;
53911
+ const cw = usage2.cache_creation_input_tokens ?? 0;
53912
+ const cr = usage2.cache_read_input_tokens ?? 0;
53790
53913
  const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
53791
53914
  acc.total += cost;
53792
53915
  acc.inputTokens += inp;
@@ -53834,90 +53957,21 @@ function processCodexCostFile(filePath, start, end, acc) {
53834
53957
  } catch {
53835
53958
  return;
53836
53959
  }
53837
- let sessionStart = "";
53838
- let model = "";
53839
- let lastTotalInput = 0;
53840
- let lastTotalCached = 0;
53841
- let lastTotalOutput = 0;
53842
- let sessionToolCalls = 0;
53960
+ const parsed = parseCodexUsage(lines);
53961
+ for (const event of codexUsageInWindow(parsed, start, end)) {
53962
+ acc.total += event.costUSD;
53963
+ acc.byDay.set(event.date, (acc.byDay.get(event.date) ?? 0) + event.costUSD);
53964
+ acc.byModel.set(event.model, (acc.byModel.get(event.model) ?? 0) + event.costUSD);
53965
+ }
53843
53966
  for (const line of lines) {
53844
- if (!line.trim()) continue;
53845
- let entry;
53846
53967
  try {
53847
- entry = JSON.parse(line);
53968
+ const entry = JSON.parse(line);
53969
+ if (entry?.type !== "response_item" || entry.payload?.type !== "function_call") continue;
53970
+ const ts = new Date(entry.timestamp ?? parsed.sessionStart);
53971
+ if (ts >= start && ts <= end) acc.toolCalls++;
53848
53972
  } catch {
53849
- continue;
53850
- }
53851
- const p = entry.payload ?? {};
53852
- if (entry.type === "session_meta") {
53853
- sessionStart = String(p["timestamp"] ?? "");
53854
- continue;
53855
- }
53856
- if (entry.type === "turn_context" && typeof p["model"] === "string") {
53857
- model = p["model"];
53858
- continue;
53859
- }
53860
- if (entry.type === "event_msg" && p["type"] === "token_count") {
53861
- const info = p["info"] ?? {};
53862
- const usage = info["total_token_usage"] ?? {};
53863
- lastTotalInput = usage["input_tokens"] ?? lastTotalInput;
53864
- lastTotalCached = usage["cached_input_tokens"] ?? lastTotalCached;
53865
- lastTotalOutput = usage["output_tokens"] ?? lastTotalOutput;
53866
- }
53867
- if (entry.type === "response_item" && p["type"] === "function_call") {
53868
- sessionToolCalls++;
53869
53973
  }
53870
53974
  }
53871
- if (!sessionStart) return;
53872
- const ts = new Date(sessionStart);
53873
- if (ts < start || ts > end) return;
53874
- const cost = codexSessionCost(model, {
53875
- input: lastTotalInput,
53876
- cached: lastTotalCached,
53877
- output: lastTotalOutput
53878
- });
53879
- acc.total += cost;
53880
- acc.toolCalls += sessionToolCalls;
53881
- const dateKey = sessionStart.slice(0, 10);
53882
- acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
53883
- const normModel = normalizeModel(model || "gpt-5");
53884
- acc.byModel.set(normModel, (acc.byModel.get(normModel) ?? 0) + cost);
53885
- }
53886
- function listCodexSessionFiles2(sessionsBase) {
53887
- const jsonlFiles = [];
53888
- if (!fs60.existsSync(sessionsBase)) return jsonlFiles;
53889
- try {
53890
- for (const year of fs60.readdirSync(sessionsBase)) {
53891
- const yearPath = path58.join(sessionsBase, year);
53892
- try {
53893
- if (!fs60.statSync(yearPath).isDirectory()) continue;
53894
- } catch {
53895
- continue;
53896
- }
53897
- for (const month of fs60.readdirSync(yearPath)) {
53898
- const monthPath = path58.join(yearPath, month);
53899
- try {
53900
- if (!fs60.statSync(monthPath).isDirectory()) continue;
53901
- } catch {
53902
- continue;
53903
- }
53904
- for (const day of fs60.readdirSync(monthPath)) {
53905
- const dayPath = path58.join(monthPath, day);
53906
- try {
53907
- if (!fs60.statSync(dayPath).isDirectory()) continue;
53908
- } catch {
53909
- continue;
53910
- }
53911
- for (const file of fs60.readdirSync(dayPath)) {
53912
- if (file.endsWith(".jsonl")) jsonlFiles.push(path58.join(dayPath, file));
53913
- }
53914
- }
53915
- }
53916
- }
53917
- } catch {
53918
- return [];
53919
- }
53920
- return jsonlFiles;
53921
53975
  }
53922
53976
  function mergeByModel(...maps) {
53923
53977
  const out = /* @__PURE__ */ new Map();
@@ -53933,7 +53987,7 @@ function loadCodexCost(start, end, sessionsBase) {
53933
53987
  byDay: /* @__PURE__ */ new Map(),
53934
53988
  byModel: /* @__PURE__ */ new Map()
53935
53989
  };
53936
- const files = listCodexSessionFiles2(sessionsBase);
53990
+ const files = listCodexSessionFiles(sessionsBase);
53937
53991
  for (const filePath of files) {
53938
53992
  processCodexCostFile(filePath, start, end, acc);
53939
53993
  }
@@ -54073,7 +54127,7 @@ function aggregateReportFromAudit(period, opts = {}) {
54073
54127
  const now = opts.now ?? /* @__PURE__ */ new Date();
54074
54128
  const auditLogPath = opts.auditLogPath ?? path58.join(os54.homedir(), ".node9", "audit.log");
54075
54129
  const claudeProjectsDir = opts.claudeProjectsDir ?? path58.join(os54.homedir(), ".claude", "projects");
54076
- const codexSessionsDir2 = opts.codexSessionsDir ?? path58.join(os54.homedir(), ".codex", "sessions");
54130
+ const codexSessionsDir2 = opts.codexSessionsDir ?? codexSessionsDir();
54077
54131
  const geminiTmpDir2 = opts.geminiTmpDir ?? path58.join(os54.homedir(), ".gemini", "tmp");
54078
54132
  const hasAuditFile = fs60.existsSync(auditLogPath);
54079
54133
  const allEntries = opts.preloadedAuditEntries ?? parseAuditLog(auditLogPath);
@@ -60272,12 +60326,12 @@ function parseSessionLines(lines) {
60272
60326
  continue;
60273
60327
  }
60274
60328
  if (entry.type !== "assistant") continue;
60275
- const usage = entry.message?.usage;
60329
+ const usage2 = entry.message?.usage;
60276
60330
  const model = entry.message?.model;
60277
- if (usage && model) {
60331
+ if (usage2 && model) {
60278
60332
  const p = modelPrice(model);
60279
60333
  if (p) {
60280
- 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;
60334
+ 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;
60281
60335
  }
60282
60336
  }
60283
60337
  const content = entry.message?.content;
@@ -60457,46 +60511,13 @@ function buildGeminiSessions(days, allAuditEntries) {
60457
60511
  return summaries;
60458
60512
  }
60459
60513
  function buildCodexSessions(days, allAuditEntries) {
60460
- const sessionsBase = path69.join(os61.homedir(), ".codex", "sessions");
60461
- if (!fs74.existsSync(sessionsBase)) return [];
60462
60514
  const cutoff = days !== null ? (() => {
60463
60515
  const d = /* @__PURE__ */ new Date();
60464
60516
  d.setDate(d.getDate() - days);
60465
60517
  d.setHours(0, 0, 0, 0);
60466
60518
  return d;
60467
60519
  })() : null;
60468
- const jsonlFiles = [];
60469
- try {
60470
- for (const year of fs74.readdirSync(sessionsBase)) {
60471
- const yearPath = path69.join(sessionsBase, year);
60472
- try {
60473
- if (!fs74.statSync(yearPath).isDirectory()) continue;
60474
- } catch {
60475
- continue;
60476
- }
60477
- for (const month of fs74.readdirSync(yearPath)) {
60478
- const monthPath = path69.join(yearPath, month);
60479
- try {
60480
- if (!fs74.statSync(monthPath).isDirectory()) continue;
60481
- } catch {
60482
- continue;
60483
- }
60484
- for (const day of fs74.readdirSync(monthPath)) {
60485
- const dayPath = path69.join(monthPath, day);
60486
- try {
60487
- if (!fs74.statSync(dayPath).isDirectory()) continue;
60488
- } catch {
60489
- continue;
60490
- }
60491
- for (const file of fs74.readdirSync(dayPath)) {
60492
- if (file.endsWith(".jsonl")) jsonlFiles.push(path69.join(dayPath, file));
60493
- }
60494
- }
60495
- }
60496
- }
60497
- } catch {
60498
- return [];
60499
- }
60520
+ const jsonlFiles = listCodexSessionFiles();
60500
60521
  const summaries = [];
60501
60522
  for (const filePath of jsonlFiles) {
60502
60523
  let lines;
@@ -60511,10 +60532,6 @@ function buildCodexSessions(days, allAuditEntries) {
60511
60532
  let firstPrompt = "";
60512
60533
  const toolCalls = [];
60513
60534
  let lastToolTs = "";
60514
- let lastTotalInput = 0;
60515
- let lastTotalCached = 0;
60516
- let lastTotalOutput = 0;
60517
- let model = "";
60518
60535
  for (const line of lines) {
60519
60536
  if (!line.trim()) continue;
60520
60537
  let entry;
@@ -60530,22 +60547,10 @@ function buildCodexSessions(days, allAuditEntries) {
60530
60547
  cwd = String(p["cwd"] ?? "");
60531
60548
  continue;
60532
60549
  }
60533
- if (entry.type === "turn_context" && typeof p["model"] === "string") {
60534
- model = p["model"];
60535
- continue;
60536
- }
60537
60550
  if (entry.type === "event_msg" && p["type"] === "user_message" && !firstPrompt) {
60538
60551
  firstPrompt = String(p["message"] ?? "");
60539
60552
  continue;
60540
60553
  }
60541
- if (entry.type === "event_msg" && p["type"] === "token_count") {
60542
- const info = p["info"] ?? {};
60543
- const usage = info["total_token_usage"] ?? {};
60544
- lastTotalInput = usage["input_tokens"] ?? lastTotalInput;
60545
- lastTotalCached = usage["cached_input_tokens"] ?? lastTotalCached;
60546
- lastTotalOutput = usage["output_tokens"] ?? lastTotalOutput;
60547
- continue;
60548
- }
60549
60554
  if (entry.type === "response_item" && p["type"] === "function_call") {
60550
60555
  const tool = String(p["name"] ?? "");
60551
60556
  let input = {};
@@ -60559,12 +60564,13 @@ function buildCodexSessions(days, allAuditEntries) {
60559
60564
  }
60560
60565
  }
60561
60566
  if (!sessionId || !startTime) continue;
60562
- if (cutoff && new Date(startTime) < cutoff) continue;
60563
- const costUSD = codexSessionCost(model, {
60564
- input: lastTotalInput,
60565
- cached: lastTotalCached,
60566
- output: lastTotalOutput
60567
- });
60567
+ const parsedUsage = parseCodexUsage(lines);
60568
+ const usageEvents = codexUsageInWindow(parsedUsage, cutoff);
60569
+ if (cutoff && new Date(startTime) < cutoff && usageEvents.length === 0 && !toolCalls.some((call) => new Date(call.timestamp) >= cutoff))
60570
+ continue;
60571
+ const costUSD = usageEvents.reduce((sum, event) => sum + event.costUSD, 0);
60572
+ const lastUsageTs = parsedUsage.events.at(-1)?.timestamp ?? "";
60573
+ if (lastUsageTs > lastToolTs) lastToolTs = lastUsageTs;
60568
60574
  const windowEnd = new Date(
60569
60575
  Math.max(new Date(startTime).getTime(), lastToolTs ? new Date(lastToolTs).getTime() : 0) + 5 * 60 * 1e3
60570
60576
  ).toISOString();