devrage 0.5.3 → 0.5.5

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
@@ -569,11 +569,19 @@ function codexAdapter() {
569
569
  }
570
570
  },
571
571
  async *usage(options) {
572
+ const seenUsage = /* @__PURE__ */ new Set();
572
573
  for await (const file of discoverCodexSessionFiles(CODEX_SESSIONS_DIR)) {
573
- yield* parseCodexUsageJsonl(file.filePath, {
574
+ for await (const record of parseCodexUsageJsonl(file.filePath, {
574
575
  session: file.session,
575
576
  since: options?.since
576
- });
577
+ })) {
578
+ const key = codexUsageRecordKey(record);
579
+ if (seenUsage.has(key)) {
580
+ continue;
581
+ }
582
+ seenUsage.add(key);
583
+ yield record;
584
+ }
577
585
  }
578
586
  }
579
587
  };
@@ -591,10 +599,15 @@ async function* discoverCodexSessionFiles(dir) {
591
599
  if (entryStat.isDirectory()) {
592
600
  yield* discoverCodexSessionFiles(fullPath);
593
601
  } else if (entry.endsWith(".jsonl")) {
594
- yield { filePath: fullPath, session: entry.replace(".jsonl", "") };
602
+ yield { filePath: fullPath, session: sessionFromRolloutFileName(entry) };
595
603
  }
596
604
  }
597
605
  }
606
+ function sessionFromRolloutFileName(fileName) {
607
+ return fileName.match(
608
+ /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i
609
+ )?.[1] ?? fileName.replace(".jsonl", "");
610
+ }
598
611
  async function* parseCodexJsonl(filePath, context) {
599
612
  const rl = createInterface2({
600
613
  input: createReadStream2(filePath, { encoding: "utf-8" }),
@@ -655,6 +668,8 @@ async function* parseCodexUsageJsonl(filePath, context) {
655
668
  let model;
656
669
  let previousTotal = null;
657
670
  let previousUsageSignature = null;
671
+ let session = context.session;
672
+ let sawSessionMeta = false;
658
673
  for await (const line of rl) {
659
674
  if (!line.trim()) {
660
675
  continue;
@@ -662,6 +677,14 @@ async function* parseCodexUsageJsonl(filePath, context) {
662
677
  try {
663
678
  const entry = JSON.parse(line);
664
679
  const payload = asRecord3(entry["payload"]);
680
+ if (entry["type"] === "session_meta") {
681
+ const metaSession = stringValue2(payload?.["id"]) ?? stringValue2(entry["id"]);
682
+ if (metaSession && !sawSessionMeta) {
683
+ session = metaSession;
684
+ sawSessionMeta = true;
685
+ }
686
+ continue;
687
+ }
665
688
  if (entry["type"] === "turn_context") {
666
689
  model = stringValue2(payload?.["model"]) ?? model;
667
690
  continue;
@@ -710,7 +733,7 @@ async function* parseCodexUsageJsonl(filePath, context) {
710
733
  provider: "openai",
711
734
  model,
712
735
  timestamp,
713
- session: context.session,
736
+ session,
714
737
  inputTokens: Math.max(usage2.inputTokens - usage2.cachedInputTokens, 0),
715
738
  outputTokens: Math.max(usage2.outputTokens - reasoningTokens, 0),
716
739
  reasoningTokens,
@@ -721,6 +744,19 @@ async function* parseCodexUsageJsonl(filePath, context) {
721
744
  }
722
745
  }
723
746
  }
747
+ function codexUsageRecordKey(record) {
748
+ return JSON.stringify([
749
+ record.session ?? "",
750
+ record.timestamp ?? "",
751
+ record.provider ?? "",
752
+ record.model ?? "",
753
+ record.inputTokens,
754
+ record.outputTokens,
755
+ record.reasoningTokens,
756
+ record.cacheReadTokens,
757
+ record.cacheWriteTokens
758
+ ]);
759
+ }
724
760
  function parseCodexTokenUsage(value) {
725
761
  const usage2 = asRecord3(value);
726
762
  if (!usage2) {
@@ -2430,30 +2466,49 @@ var SPINNER_MESSAGES = [
2430
2466
  "Auditing your language",
2431
2467
  "Tabulating regrets"
2432
2468
  ];
2469
+ var COST_SPINNER_MESSAGES = [
2470
+ "Loading price catalog",
2471
+ "Reading local usage",
2472
+ "Scanning transcript stores",
2473
+ "Crunching token counts",
2474
+ "Still working through local history"
2475
+ ];
2433
2476
  var DAY_MS = 24 * 60 * 60 * 1e3;
2434
2477
  function createSpinner(messages = SPINNER_MESSAGES) {
2435
2478
  let messageIdx = 0;
2436
2479
  let dotCount = 0;
2437
2480
  let timer = null;
2481
+ let messageOverride = null;
2482
+ function render() {
2483
+ dotCount = (dotCount + 1) % 4;
2484
+ const msg = messageOverride ?? messages[messageIdx % messages.length];
2485
+ const dots = ".".repeat(dotCount || 1);
2486
+ process.stdout.write(`\r ${c.dim}${msg}${dots}${c.reset} `);
2487
+ }
2438
2488
  return {
2439
- start() {
2489
+ start(message) {
2440
2490
  messageIdx = Math.floor(Math.random() * messages.length);
2491
+ messageOverride = message ?? null;
2492
+ render();
2441
2493
  timer = setInterval(() => {
2442
- dotCount = (dotCount + 1) % 4;
2443
- const msg = messages[messageIdx % messages.length];
2444
- const dots = ".".repeat(dotCount || 1);
2445
- process.stdout.write(`\r ${c.dim}${msg}${dots}${c.reset} `);
2494
+ render();
2446
2495
  }, 300);
2447
2496
  },
2448
- update() {
2449
- messageIdx++;
2497
+ update(message) {
2498
+ if (message) {
2499
+ messageOverride = message;
2500
+ } else {
2501
+ messageOverride = null;
2502
+ messageIdx++;
2503
+ }
2504
+ render();
2450
2505
  },
2451
2506
  stop() {
2452
2507
  if (timer) {
2453
2508
  clearInterval(timer);
2454
2509
  timer = null;
2455
2510
  }
2456
- process.stdout.write("\r" + " ".repeat(60) + "\r");
2511
+ process.stdout.write("\r" + " ".repeat(80) + "\r");
2457
2512
  }
2458
2513
  };
2459
2514
  }
@@ -2636,23 +2691,38 @@ async function cost(args) {
2636
2691
  const options = parseCostArgs(args);
2637
2692
  const adapters = options.agent ? [createAdapter(options.agent)] : allAdapters();
2638
2693
  const costByAgent = {};
2639
- const pricing = await loadPricingCatalog({ refresh: options.refreshPrices });
2640
- for (const adapter of adapters) {
2641
- if (!adapter.usage) {
2642
- continue;
2643
- }
2644
- const summary = await summarizeUsage(adapter.usage({ since: options.since }), pricing);
2645
- if (summary.requests > 0) {
2646
- costByAgent[adapter.name] = summary;
2694
+ const spinner = createSpinner(COST_SPINNER_MESSAGES);
2695
+ let totals = null;
2696
+ spinner.start("Loading price catalog");
2697
+ try {
2698
+ const pricing = await loadPricingCatalog({ refresh: options.refreshPrices });
2699
+ for (const adapter of adapters) {
2700
+ if (!adapter.usage) {
2701
+ continue;
2702
+ }
2703
+ spinner.update(`Reading ${adapter.name} usage`);
2704
+ const summary = await summarizeUsage(adapter.usage({ since: options.since }), pricing);
2705
+ if (summary.requests > 0) {
2706
+ costByAgent[adapter.name] = summary;
2707
+ }
2647
2708
  }
2709
+ totals = getCostTotals(costByAgent);
2710
+ } finally {
2711
+ spinner.stop();
2648
2712
  }
2649
- const totals = getCostTotals(costByAgent);
2650
- console.log("");
2651
- if (totals.entries.length === 0) {
2713
+ if (!totals || totals.entries.length === 0) {
2714
+ console.log("");
2652
2715
  printCostCommandUnavailable(options);
2653
2716
  return;
2654
2717
  }
2655
- const reportUrl = await writeCostHtmlReport(totals, options);
2718
+ spinner.start("Writing cost report");
2719
+ let reportUrl;
2720
+ try {
2721
+ reportUrl = await writeCostHtmlReport(totals, options);
2722
+ } finally {
2723
+ spinner.stop();
2724
+ }
2725
+ console.log("");
2656
2726
  printCostCommand(totals, options, reportUrl);
2657
2727
  }
2658
2728
  function printCostCommand(totals, options, reportUrl) {
@@ -3175,7 +3245,7 @@ async function main() {
3175
3245
  process.exit(0);
3176
3246
  }
3177
3247
  if (command === "--version") {
3178
- console.log("0.5.3");
3248
+ console.log("0.5.5");
3179
3249
  process.exit(0);
3180
3250
  }
3181
3251
  const parsed = parseCommand(args);