tokenleak 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +39 -6
  2. package/package.json +1 -1
  3. package/tokenleak +1092 -195
package/tokenleak CHANGED
@@ -1657,6 +1657,179 @@ var init_focus = __esm(() => {
1657
1657
  };
1658
1658
  });
1659
1659
 
1660
+ // packages/core/dist/aggregation/replay.js
1661
+ function parseIsoTime2(value) {
1662
+ const parsed = Date.parse(value);
1663
+ return Number.isFinite(parsed) ? parsed : null;
1664
+ }
1665
+ function truncateToMinute(iso) {
1666
+ const date = new Date(iso);
1667
+ date.setSeconds(0, 0);
1668
+ return date.toISOString();
1669
+ }
1670
+ function labelFlowBlock(durationMs, eventCount) {
1671
+ if (durationMs >= DEEP_FLOW_DURATION_MS || eventCount >= DEEP_FLOW_EVENT_COUNT) {
1672
+ return "Deep Flow";
1673
+ }
1674
+ if (durationMs <= QUICK_LOOKUP_DURATION_MS && eventCount <= QUICK_LOOKUP_EVENT_COUNT) {
1675
+ return "Quick Lookup";
1676
+ }
1677
+ return "Moderate Session";
1678
+ }
1679
+ function computeDominantModel(events) {
1680
+ const byModel = new Map;
1681
+ for (const event of events) {
1682
+ byModel.set(event.model, (byModel.get(event.model) ?? 0) + event.totalTokens);
1683
+ }
1684
+ let best = "";
1685
+ let bestTokens = -1;
1686
+ for (const [model, tokens] of byModel) {
1687
+ if (tokens > bestTokens || tokens === bestTokens && model < best) {
1688
+ best = model;
1689
+ bestTokens = tokens;
1690
+ }
1691
+ }
1692
+ return best;
1693
+ }
1694
+ function countModelSwitches(events) {
1695
+ let switches = 0;
1696
+ for (let i = 1;i < events.length; i++) {
1697
+ if (events[i].model !== events[i - 1].model) {
1698
+ switches++;
1699
+ }
1700
+ }
1701
+ return switches;
1702
+ }
1703
+ function computeCacheHitRateTrend(events) {
1704
+ return events.map((event) => {
1705
+ const denominator = event.inputTokens + event.cacheReadTokens;
1706
+ return denominator > 0 ? event.cacheReadTokens / denominator : 0;
1707
+ });
1708
+ }
1709
+ function clusterIntoFlowBlocks(sortedEvents) {
1710
+ if (sortedEvents.length === 0) {
1711
+ return [];
1712
+ }
1713
+ const blocks = [];
1714
+ let currentEvents = [sortedEvents[0]];
1715
+ for (let i = 1;i < sortedEvents.length; i++) {
1716
+ const prevTime = parseIsoTime2(sortedEvents[i - 1].timestamp);
1717
+ const currTime = parseIsoTime2(sortedEvents[i].timestamp);
1718
+ if (prevTime !== null && currTime !== null && currTime - prevTime >= FLOW_BLOCK_GAP_MS) {
1719
+ blocks.push(buildFlowBlock(currentEvents, blocks.length));
1720
+ currentEvents = [];
1721
+ }
1722
+ currentEvents.push(sortedEvents[i]);
1723
+ }
1724
+ blocks.push(buildFlowBlock(currentEvents, blocks.length));
1725
+ return blocks;
1726
+ }
1727
+ function buildFlowBlock(events, blockIndex) {
1728
+ const startTime = parseIsoTime2(events[0].timestamp) ?? 0;
1729
+ const endTime = parseIsoTime2(events[events.length - 1].timestamp) ?? 0;
1730
+ const durationMs = endTime - startTime;
1731
+ let inputTokens = 0;
1732
+ let outputTokens = 0;
1733
+ let cacheReadTokens = 0;
1734
+ let cacheWriteTokens = 0;
1735
+ let totalTokens = 0;
1736
+ let cost = 0;
1737
+ for (const event of events) {
1738
+ inputTokens += event.inputTokens;
1739
+ outputTokens += event.outputTokens;
1740
+ cacheReadTokens += event.cacheReadTokens;
1741
+ cacheWriteTokens += event.cacheWriteTokens;
1742
+ totalTokens += event.totalTokens;
1743
+ cost += event.cost;
1744
+ }
1745
+ return {
1746
+ blockIndex,
1747
+ label: labelFlowBlock(durationMs, events.length),
1748
+ start: events[0].timestamp,
1749
+ end: events[events.length - 1].timestamp,
1750
+ durationMs,
1751
+ eventCount: events.length,
1752
+ inputTokens,
1753
+ outputTokens,
1754
+ cacheReadTokens,
1755
+ cacheWriteTokens,
1756
+ totalTokens,
1757
+ cost,
1758
+ dominantModel: computeDominantModel(events),
1759
+ events,
1760
+ modelSwitches: countModelSwitches(events),
1761
+ cacheHitRateTrend: computeCacheHitRateTrend(events)
1762
+ };
1763
+ }
1764
+ function buildTokenVelocity(sortedEvents) {
1765
+ const buckets = new Map;
1766
+ for (const event of sortedEvents) {
1767
+ const minute = truncateToMinute(event.timestamp);
1768
+ buckets.set(minute, (buckets.get(minute) ?? 0) + event.totalTokens);
1769
+ }
1770
+ return [...buckets.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([minute, tokens]) => ({
1771
+ minute,
1772
+ tokensPerMinute: tokens / (VELOCITY_BUCKET_MS / 1000 / 60)
1773
+ }));
1774
+ }
1775
+ function buildDaySummary(sortedEvents, flowBlocks, tokenVelocity) {
1776
+ const sessionIds = new Set;
1777
+ for (const event of sortedEvents) {
1778
+ if (event.sessionId) {
1779
+ sessionIds.add(event.sessionId);
1780
+ }
1781
+ }
1782
+ let flowTimeMs = 0;
1783
+ for (const block of flowBlocks) {
1784
+ flowTimeMs += block.durationMs;
1785
+ }
1786
+ let thinkTimeMs = 0;
1787
+ for (let i = 1;i < flowBlocks.length; i++) {
1788
+ const prevEnd = parseIsoTime2(flowBlocks[i - 1].end);
1789
+ const currStart = parseIsoTime2(flowBlocks[i].start);
1790
+ if (prevEnd !== null && currStart !== null) {
1791
+ thinkTimeMs += currStart - prevEnd;
1792
+ }
1793
+ }
1794
+ const totalTimeMs = flowTimeMs + thinkTimeMs;
1795
+ const flowThinkRatio = totalTimeMs > 0 ? flowTimeMs / totalTimeMs : 0;
1796
+ let peakMinute = null;
1797
+ for (const point of tokenVelocity) {
1798
+ if (peakMinute === null || point.tokensPerMinute > peakMinute.tokensPerMinute) {
1799
+ peakMinute = point;
1800
+ }
1801
+ }
1802
+ return {
1803
+ totalSessions: sessionIds.size,
1804
+ totalEvents: sortedEvents.length,
1805
+ flowTimeMs,
1806
+ thinkTimeMs,
1807
+ flowThinkRatio,
1808
+ peakMinute
1809
+ };
1810
+ }
1811
+ function buildReplayReport(providers, targetDate) {
1812
+ const allEvents = providers.flatMap((provider) => provider.events ?? []);
1813
+ const dayEvents = allEvents.filter((event) => event.date === targetDate).sort((a, b) => a.timestamp.localeCompare(b.timestamp));
1814
+ const flowBlocks = clusterIntoFlowBlocks(dayEvents);
1815
+ const tokenVelocity = buildTokenVelocity(dayEvents);
1816
+ const summary = buildDaySummary(dayEvents, flowBlocks, tokenVelocity);
1817
+ return {
1818
+ date: targetDate,
1819
+ events: dayEvents,
1820
+ flowBlocks,
1821
+ tokenVelocity,
1822
+ summary
1823
+ };
1824
+ }
1825
+ var FLOW_BLOCK_GAP_MS, DEEP_FLOW_DURATION_MS, DEEP_FLOW_EVENT_COUNT = 5, QUICK_LOOKUP_DURATION_MS, QUICK_LOOKUP_EVENT_COUNT = 2, VELOCITY_BUCKET_MS;
1826
+ var init_replay = __esm(() => {
1827
+ FLOW_BLOCK_GAP_MS = 15 * 60 * 1000;
1828
+ DEEP_FLOW_DURATION_MS = 45 * 60 * 1000;
1829
+ QUICK_LOOKUP_DURATION_MS = 10 * 60 * 1000;
1830
+ VELOCITY_BUCKET_MS = 60 * 1000;
1831
+ });
1832
+
1660
1833
  // packages/core/dist/aggregation/index.js
1661
1834
  var init_aggregation = __esm(() => {
1662
1835
  init_streaks();
@@ -1668,6 +1841,7 @@ var init_aggregation = __esm(() => {
1668
1841
  init_more();
1669
1842
  init_explain();
1670
1843
  init_focus();
1844
+ init_replay();
1671
1845
  init_analytics();
1672
1846
  });
1673
1847
 
@@ -1989,7 +2163,7 @@ var init_advisor2 = __esm(() => {
1989
2163
  });
1990
2164
 
1991
2165
  // packages/core/dist/index.js
1992
- var VERSION = "2.0.0";
2166
+ var VERSION = "2.1.0";
1993
2167
  var init_dist = __esm(() => {
1994
2168
  init_constants();
1995
2169
  init_aggregation();
@@ -2006,12 +2180,165 @@ var init_normalizer = __esm(() => {
2006
2180
  DATE_SUFFIX_PATTERN = /-\d{8}$/;
2007
2181
  });
2008
2182
 
2183
+ // packages/registry/dist/models/litellm.js
2184
+ function parseLiteLLMData(raw) {
2185
+ const result = {};
2186
+ for (const [key, value] of Object.entries(raw)) {
2187
+ if (value === null || typeof value !== "object")
2188
+ continue;
2189
+ const entry = value;
2190
+ const inputPerToken = entry.input_cost_per_token;
2191
+ const outputPerToken = entry.output_cost_per_token;
2192
+ if (typeof inputPerToken !== "number" || inputPerToken <= 0)
2193
+ continue;
2194
+ if (typeof outputPerToken !== "number")
2195
+ continue;
2196
+ const pricing = {
2197
+ input: inputPerToken * PER_TOKEN_TO_PER_MILLION,
2198
+ output: outputPerToken * PER_TOKEN_TO_PER_MILLION,
2199
+ cacheRead: typeof entry.cache_read_input_token_cost === "number" ? entry.cache_read_input_token_cost * PER_TOKEN_TO_PER_MILLION : 0,
2200
+ cacheWrite: typeof entry.cache_creation_input_token_cost === "number" ? entry.cache_creation_input_token_cost * PER_TOKEN_TO_PER_MILLION : 0
2201
+ };
2202
+ result[key] = pricing;
2203
+ const slashIndex = key.indexOf("/");
2204
+ const unprefixed = slashIndex !== -1 ? key.slice(slashIndex + 1) : key;
2205
+ result[unprefixed] = pricing;
2206
+ const normalized = normalizeModelName2(unprefixed);
2207
+ result[normalized] = pricing;
2208
+ }
2209
+ return result;
2210
+ }
2211
+ async function fetchLiteLLMPricing() {
2212
+ const controller = new AbortController;
2213
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
2214
+ try {
2215
+ const response = await fetch(LITELLM_URL, { signal: controller.signal });
2216
+ if (!response.ok) {
2217
+ throw new Error(`LiteLLM fetch failed: HTTP ${response.status}`);
2218
+ }
2219
+ const raw = await response.json();
2220
+ return parseLiteLLMData(raw);
2221
+ } finally {
2222
+ clearTimeout(timeout);
2223
+ }
2224
+ }
2225
+ var LITELLM_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json", FETCH_TIMEOUT_MS = 1e4, PER_TOKEN_TO_PER_MILLION = 1e6;
2226
+ var init_litellm = __esm(() => {
2227
+ init_normalizer();
2228
+ });
2229
+
2230
+ // packages/registry/dist/models/pricing-cache.js
2231
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
2232
+ import { homedir, tmpdir } from "os";
2233
+ import { join } from "path";
2234
+ function defaultCacheDir() {
2235
+ try {
2236
+ const home = homedir();
2237
+ return join(home, ".cache", "tokenleak");
2238
+ } catch {
2239
+ return join(tmpdir(), "tokenleak-cache");
2240
+ }
2241
+ }
2242
+ function cachePath(cacheDir) {
2243
+ return join(cacheDir ?? defaultCacheDir(), CACHE_FILENAME);
2244
+ }
2245
+ function isCacheValid(envelope) {
2246
+ return envelope.version === CACHE_VERSION && Date.now() - envelope.fetchedAt < CACHE_TTL_MS;
2247
+ }
2248
+ function readEnvelope(cacheDir) {
2249
+ const path = cachePath(cacheDir);
2250
+ try {
2251
+ if (!existsSync(path))
2252
+ return null;
2253
+ const raw = readFileSync(path, "utf-8");
2254
+ const parsed = JSON.parse(raw);
2255
+ if (typeof parsed !== "object" || parsed === null || typeof parsed.version !== "number" || typeof parsed.fetchedAt !== "number" || typeof parsed.data !== "object") {
2256
+ return null;
2257
+ }
2258
+ return parsed;
2259
+ } catch {
2260
+ return null;
2261
+ }
2262
+ }
2263
+ function readPricingCache(cacheDir) {
2264
+ const envelope = readEnvelope(cacheDir);
2265
+ if (envelope && isCacheValid(envelope))
2266
+ return envelope;
2267
+ return null;
2268
+ }
2269
+ function readStalePricingCache(cacheDir) {
2270
+ const envelope = readEnvelope(cacheDir);
2271
+ return envelope?.data ?? null;
2272
+ }
2273
+ function writePricingCache(data, cacheDir) {
2274
+ const dir = cacheDir ?? defaultCacheDir();
2275
+ const path = join(dir, CACHE_FILENAME);
2276
+ const tmpPath = `${path}.tmp`;
2277
+ try {
2278
+ mkdirSync(dir, { recursive: true });
2279
+ const envelope = {
2280
+ version: CACHE_VERSION,
2281
+ fetchedAt: Date.now(),
2282
+ data
2283
+ };
2284
+ writeFileSync(tmpPath, JSON.stringify(envelope), "utf-8");
2285
+ renameSync(tmpPath, path);
2286
+ } catch {
2287
+ try {
2288
+ rmSync(tmpPath, { force: true });
2289
+ } catch {}
2290
+ }
2291
+ }
2292
+ var CACHE_FILENAME = "pricing.json", CACHE_TTL_MS = 3600000, CACHE_VERSION = 1;
2293
+ var init_pricing_cache = () => {};
2294
+
2295
+ // packages/registry/dist/models/pricing-resolver.js
2296
+ async function initPricing() {
2297
+ if (initialized)
2298
+ return;
2299
+ try {
2300
+ const cached = readPricingCache();
2301
+ if (cached) {
2302
+ remotePricing = cached.data;
2303
+ initialized = true;
2304
+ return;
2305
+ }
2306
+ } catch {}
2307
+ try {
2308
+ const data = await fetchLiteLLMPricing();
2309
+ remotePricing = data;
2310
+ try {
2311
+ writePricingCache(data);
2312
+ } catch {}
2313
+ initialized = true;
2314
+ return;
2315
+ } catch {}
2316
+ try {
2317
+ const stale = readStalePricingCache();
2318
+ if (stale) {
2319
+ remotePricing = stale;
2320
+ initialized = true;
2321
+ return;
2322
+ }
2323
+ } catch {}
2324
+ initialized = true;
2325
+ }
2326
+ function getRemotePricing(model) {
2327
+ return remotePricing?.[model];
2328
+ }
2329
+ var remotePricing = null, initialized = false;
2330
+ var init_pricing_resolver = __esm(() => {
2331
+ init_litellm();
2332
+ init_pricing_cache();
2333
+ });
2334
+
2009
2335
  // packages/registry/dist/models/pricing.js
2010
2336
  function getModelPricing(model) {
2011
- return MODEL_PRICING[model];
2337
+ return getRemotePricing(model) ?? MODEL_PRICING[model];
2012
2338
  }
2013
2339
  var TOKENS_PER_MILLION3 = 1e6, MODEL_PRICING;
2014
2340
  var init_pricing = __esm(() => {
2341
+ init_pricing_resolver();
2015
2342
  MODEL_PRICING = {
2016
2343
  "claude-3-haiku": {
2017
2344
  input: 0.25,
@@ -2217,6 +2544,7 @@ var init_models = __esm(() => {
2217
2544
  init_normalizer();
2218
2545
  init_pricing();
2219
2546
  init_cost();
2547
+ init_pricing_resolver();
2220
2548
  });
2221
2549
 
2222
2550
  // packages/registry/dist/registry.js
@@ -2313,24 +2641,24 @@ function isInRange(date, range) {
2313
2641
  }
2314
2642
 
2315
2643
  // packages/registry/dist/providers/claude-code.js
2316
- import { existsSync, readdirSync, statSync } from "fs";
2317
- import { dirname as dirname2, join, relative as relative2, sep } from "path";
2318
- import { homedir } from "os";
2644
+ import { existsSync as existsSync2, readdirSync, statSync } from "fs";
2645
+ import { dirname as dirname2, join as join2, relative as relative2, sep } from "path";
2646
+ import { homedir as homedir2 } from "os";
2319
2647
  function resolveBaseDir(baseDir) {
2320
2648
  if (baseDir) {
2321
2649
  return baseDir;
2322
2650
  }
2323
2651
  const configDir = process.env["CLAUDE_CONFIG_DIR"];
2324
- return join(configDir && configDir.length > 0 ? configDir : DEFAULT_CONFIG_DIR, "projects");
2652
+ return join2(configDir && configDir.length > 0 ? configDir : DEFAULT_CONFIG_DIR, "projects");
2325
2653
  }
2326
2654
  function collectJsonlFiles(dir) {
2327
2655
  const results = [];
2328
- if (!existsSync(dir)) {
2656
+ if (!existsSync2(dir)) {
2329
2657
  return results;
2330
2658
  }
2331
2659
  const entries = readdirSync(dir);
2332
2660
  for (const entry of entries) {
2333
- const fullPath = join(dir, entry);
2661
+ const fullPath = join2(dir, entry);
2334
2662
  const stat = statSync(fullPath);
2335
2663
  if (stat.isDirectory()) {
2336
2664
  results.push(...collectJsonlFiles(fullPath));
@@ -2467,7 +2795,7 @@ class ClaudeCodeProvider {
2467
2795
  }
2468
2796
  async isAvailable() {
2469
2797
  try {
2470
- return existsSync(this.baseDir);
2798
+ return existsSync2(this.baseDir);
2471
2799
  } catch {
2472
2800
  return false;
2473
2801
  }
@@ -2537,7 +2865,7 @@ var init_claude_code = __esm(() => {
2537
2865
  init_jsonl_splitter();
2538
2866
  init_normalizer();
2539
2867
  init_cost();
2540
- DEFAULT_CONFIG_DIR = join(homedir(), ".claude");
2868
+ DEFAULT_CONFIG_DIR = join2(homedir2(), ".claude");
2541
2869
  CLAUDE_CODE_COLORS = {
2542
2870
  primary: "#ff6b35",
2543
2871
  secondary: "#ffa366",
@@ -2546,9 +2874,9 @@ var init_claude_code = __esm(() => {
2546
2874
  });
2547
2875
 
2548
2876
  // packages/registry/dist/providers/codex.js
2549
- import { existsSync as existsSync2, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
2550
- import { dirname as dirname3, join as join2, relative as relative3, sep as sep2 } from "path";
2551
- import { homedir as homedir2 } from "os";
2877
+ import { existsSync as existsSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
2878
+ import { dirname as dirname3, join as join3, relative as relative3, sep as sep2 } from "path";
2879
+ import { homedir as homedir3 } from "os";
2552
2880
  function parseResponseEvent(record) {
2553
2881
  if (typeof record !== "object" || record === null || !("type" in record)) {
2554
2882
  return null;
@@ -2593,12 +2921,12 @@ function toCachePricing2(pricing) {
2593
2921
  };
2594
2922
  }
2595
2923
  function collectJsonlFiles2(dir) {
2596
- if (!existsSync2(dir)) {
2924
+ if (!existsSync3(dir)) {
2597
2925
  return [];
2598
2926
  }
2599
2927
  const files = [];
2600
2928
  for (const entry of readdirSync2(dir)) {
2601
- const fullPath = join2(dir, entry);
2929
+ const fullPath = join3(dir, entry);
2602
2930
  const stats = statSync2(fullPath);
2603
2931
  if (stats.isDirectory()) {
2604
2932
  files.push(...collectJsonlFiles2(fullPath));
@@ -2758,7 +3086,7 @@ class CodexProvider {
2758
3086
  }
2759
3087
  async isAvailable() {
2760
3088
  try {
2761
- return existsSync2(this.sessionsDir);
3089
+ return existsSync3(this.sessionsDir);
2762
3090
  } catch {
2763
3091
  return false;
2764
3092
  }
@@ -2879,16 +3207,16 @@ var init_codex = __esm(() => {
2879
3207
  secondary: "#4ade80",
2880
3208
  gradient: ["#10a37f", "#4ade80"]
2881
3209
  };
2882
- DEFAULT_SESSIONS_DIR = join2(process.env["CODEX_HOME"] ?? join2(homedir2(), ".codex"), "sessions");
3210
+ DEFAULT_SESSIONS_DIR = join3(process.env["CODEX_HOME"] ?? join3(homedir3(), ".codex"), "sessions");
2883
3211
  DASHED_DATE_SUFFIX = /-(\d{4})-(\d{2})-(\d{2})$/;
2884
3212
  });
2885
3213
 
2886
3214
  // packages/registry/dist/providers/cursor.js
2887
- import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync, statSync as statSync3 } from "fs";
2888
- import { join as join3 } from "path";
2889
- import { homedir as homedir3 } from "os";
3215
+ import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync2, statSync as statSync3 } from "fs";
3216
+ import { join as join4 } from "path";
3217
+ import { homedir as homedir4 } from "os";
2890
3218
  function resolveCacheDir(baseDir) {
2891
- return baseDir ?? join3(process.env["TOKENLEAK_CURSOR_DIR"] ?? join3(homedir3(), ".config", "tokenleak"), "cursor-cache");
3219
+ return baseDir ?? join4(process.env["TOKENLEAK_CURSOR_DIR"] ?? join4(homedir4(), ".config", "tokenleak"), "cursor-cache");
2892
3220
  }
2893
3221
  function isCursorUsageFile(name) {
2894
3222
  if (name === "usage.csv") {
@@ -2901,7 +3229,7 @@ function isCursorUsageFile(name) {
2901
3229
  return stem.length > 0;
2902
3230
  }
2903
3231
  function collectUsageFiles(dir) {
2904
- if (!existsSync3(dir)) {
3232
+ if (!existsSync4(dir)) {
2905
3233
  return [];
2906
3234
  }
2907
3235
  const files = [];
@@ -2909,7 +3237,7 @@ function collectUsageFiles(dir) {
2909
3237
  if (entry === "archive") {
2910
3238
  continue;
2911
3239
  }
2912
- const fullPath = join3(dir, entry);
3240
+ const fullPath = join4(dir, entry);
2913
3241
  const stats = statSync3(fullPath);
2914
3242
  if (stats.isFile() && isCursorUsageFile(entry)) {
2915
3243
  files.push(fullPath);
@@ -2985,7 +3313,7 @@ function toCachePricing3(pricing) {
2985
3313
  function parseUsageFile(filePath) {
2986
3314
  let raw;
2987
3315
  try {
2988
- raw = readFileSync(filePath, "utf8");
3316
+ raw = readFileSync2(filePath, "utf8");
2989
3317
  } catch {
2990
3318
  return [];
2991
3319
  }
@@ -3173,9 +3501,9 @@ var init_cursor = __esm(() => {
3173
3501
  });
3174
3502
 
3175
3503
  // packages/registry/dist/providers/open-code.js
3176
- import { existsSync as existsSync4, readdirSync as readdirSync4, readFileSync as readFileSync2 } from "fs";
3177
- import { join as join4 } from "path";
3178
- import { homedir as homedir4 } from "os";
3504
+ import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync3 } from "fs";
3505
+ import { join as join5 } from "path";
3506
+ import { homedir as homedir5 } from "os";
3179
3507
  import { Database } from "bun:sqlite";
3180
3508
  function resolveBaseDir2(baseDir) {
3181
3509
  if (baseDir) {
@@ -3186,7 +3514,7 @@ function resolveBaseDir2(baseDir) {
3186
3514
  LEGACY_DEFAULT_BASE_DIR,
3187
3515
  CONFIG_DEFAULT_BASE_DIR
3188
3516
  ]) {
3189
- if (existsSync4(candidate)) {
3517
+ if (existsSync5(candidate)) {
3190
3518
  return candidate;
3191
3519
  }
3192
3520
  }
@@ -3370,7 +3698,7 @@ function loadFromLegacyJson(sessionsDir, range) {
3370
3698
  const records = [];
3371
3699
  for (const file of files) {
3372
3700
  try {
3373
- const content = readFileSync2(join4(sessionsDir, file), "utf-8");
3701
+ const content = readFileSync3(join5(sessionsDir, file), "utf-8");
3374
3702
  const session = JSON.parse(content);
3375
3703
  if (!Array.isArray(session.messages)) {
3376
3704
  continue;
@@ -3401,14 +3729,14 @@ function loadFromLegacyJson(sessionsDir, range) {
3401
3729
  return records;
3402
3730
  }
3403
3731
  function loadFromCurrentStorage(baseDir, range) {
3404
- const messagesRoot = join4(baseDir, "storage", "message");
3405
- if (!existsSync4(messagesRoot)) {
3732
+ const messagesRoot = join5(baseDir, "storage", "message");
3733
+ if (!existsSync5(messagesRoot)) {
3406
3734
  return [];
3407
3735
  }
3408
3736
  const recordsById = new Map;
3409
3737
  const recordsWithoutId = [];
3410
3738
  for (const sessionDir of readdirSync4(messagesRoot)) {
3411
- const sessionPath = join4(messagesRoot, sessionDir);
3739
+ const sessionPath = join5(messagesRoot, sessionDir);
3412
3740
  let messageFiles;
3413
3741
  try {
3414
3742
  messageFiles = readdirSync4(sessionPath).filter((file) => file.endsWith(".json"));
@@ -3417,7 +3745,7 @@ function loadFromCurrentStorage(baseDir, range) {
3417
3745
  }
3418
3746
  for (const file of messageFiles) {
3419
3747
  try {
3420
- const content = readFileSync2(join4(sessionPath, file), "utf-8");
3748
+ const content = readFileSync3(join5(sessionPath, file), "utf-8");
3421
3749
  const message = JSON.parse(content);
3422
3750
  if (message.role !== "assistant") {
3423
3751
  continue;
@@ -3480,32 +3808,32 @@ class OpenCodeProvider {
3480
3808
  }
3481
3809
  async isAvailable() {
3482
3810
  try {
3483
- if (!existsSync4(this.baseDir)) {
3811
+ if (!existsSync5(this.baseDir)) {
3484
3812
  return false;
3485
3813
  }
3486
- const hasCurrentStorage = existsSync4(join4(this.baseDir, "storage", "message"));
3487
- const hasLegacyDb = existsSync4(join4(this.baseDir, "opencode.db")) || existsSync4(join4(this.baseDir, "sessions.db"));
3488
- const hasLegacySessionsDir = existsSync4(join4(this.baseDir, "sessions"));
3814
+ const hasCurrentStorage = existsSync5(join5(this.baseDir, "storage", "message"));
3815
+ const hasLegacyDb = existsSync5(join5(this.baseDir, "opencode.db")) || existsSync5(join5(this.baseDir, "sessions.db"));
3816
+ const hasLegacySessionsDir = existsSync5(join5(this.baseDir, "sessions"));
3489
3817
  return hasCurrentStorage || hasLegacyDb || hasLegacySessionsDir;
3490
3818
  } catch {
3491
3819
  return false;
3492
3820
  }
3493
3821
  }
3494
3822
  async load(range) {
3495
- const currentMessagesRoot = join4(this.baseDir, "storage", "message");
3496
- if (existsSync4(currentMessagesRoot)) {
3823
+ const currentMessagesRoot = join5(this.baseDir, "storage", "message");
3824
+ if (existsSync5(currentMessagesRoot)) {
3497
3825
  const currentRecords = loadFromCurrentStorage(this.baseDir, range);
3498
3826
  return buildProviderData2(currentRecords);
3499
3827
  }
3500
- const opencodeDbPath = join4(this.baseDir, "opencode.db");
3501
- const sessionsDbPath = join4(this.baseDir, "sessions.db");
3502
- const sessionsDir = join4(this.baseDir, "sessions");
3828
+ const opencodeDbPath = join5(this.baseDir, "opencode.db");
3829
+ const sessionsDbPath = join5(this.baseDir, "sessions.db");
3830
+ const sessionsDir = join5(this.baseDir, "sessions");
3503
3831
  let records = [];
3504
- if (existsSync4(opencodeDbPath)) {
3832
+ if (existsSync5(opencodeDbPath)) {
3505
3833
  records = loadFromSqlite(opencodeDbPath, range);
3506
- } else if (existsSync4(sessionsDbPath)) {
3834
+ } else if (existsSync5(sessionsDbPath)) {
3507
3835
  records = loadFromSqlite(sessionsDbPath, range);
3508
- } else if (existsSync4(sessionsDir)) {
3836
+ } else if (existsSync5(sessionsDir)) {
3509
3837
  records = loadFromLegacyJson(sessionsDir, range);
3510
3838
  }
3511
3839
  return buildProviderData2(records);
@@ -3520,16 +3848,16 @@ var init_open_code = __esm(() => {
3520
3848
  secondary: "#a78bfa",
3521
3849
  gradient: ["#6366f1", "#a78bfa"]
3522
3850
  };
3523
- CURRENT_DEFAULT_BASE_DIR = join4(homedir4(), ".local", "share", "opencode");
3524
- LEGACY_DEFAULT_BASE_DIR = join4(homedir4(), ".opencode");
3525
- CONFIG_DEFAULT_BASE_DIR = join4(homedir4(), ".config", "opencode");
3851
+ CURRENT_DEFAULT_BASE_DIR = join5(homedir5(), ".local", "share", "opencode");
3852
+ LEGACY_DEFAULT_BASE_DIR = join5(homedir5(), ".opencode");
3853
+ CONFIG_DEFAULT_BASE_DIR = join5(homedir5(), ".config", "opencode");
3526
3854
  });
3527
3855
 
3528
3856
  // packages/registry/dist/providers/pi.js
3529
- import { existsSync as existsSync5 } from "fs";
3857
+ import { existsSync as existsSync6 } from "fs";
3530
3858
  import { readdir } from "fs/promises";
3531
- import { homedir as homedir5 } from "os";
3532
- import { join as join5, relative as relative4, sep as sep3 } from "path";
3859
+ import { homedir as homedir6 } from "os";
3860
+ import { join as join6, relative as relative4, sep as sep3 } from "path";
3533
3861
  function resolveAgentDir(baseDir) {
3534
3862
  if (baseDir) {
3535
3863
  return baseDir;
@@ -3537,15 +3865,15 @@ function resolveAgentDir(baseDir) {
3537
3865
  return process.env["PI_CODING_AGENT_DIR"] ?? DEFAULT_AGENT_DIR;
3538
3866
  }
3539
3867
  function getSessionsDir(agentDir) {
3540
- return join5(agentDir, "sessions");
3868
+ return join6(agentDir, "sessions");
3541
3869
  }
3542
3870
  async function collectJsonlFiles3(dir) {
3543
- if (!existsSync5(dir)) {
3871
+ if (!existsSync6(dir)) {
3544
3872
  return [];
3545
3873
  }
3546
3874
  const files = [];
3547
3875
  for (const entry of await readdir(dir, { withFileTypes: true })) {
3548
- const fullPath = join5(dir, entry.name);
3876
+ const fullPath = join6(dir, entry.name);
3549
3877
  if (entry.isDirectory()) {
3550
3878
  files.push(...await collectJsonlFiles3(fullPath));
3551
3879
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
@@ -3747,7 +4075,7 @@ class PiProvider {
3747
4075
  }
3748
4076
  async isAvailable() {
3749
4077
  try {
3750
- return existsSync5(getSessionsDir(this.agentDir));
4078
+ return existsSync6(getSessionsDir(this.agentDir));
3751
4079
  } catch {
3752
4080
  return false;
3753
4081
  }
@@ -3778,7 +4106,7 @@ var init_pi = __esm(() => {
3778
4106
  init_jsonl_splitter();
3779
4107
  init_normalizer();
3780
4108
  init_cost();
3781
- DEFAULT_AGENT_DIR = join5(homedir5(), ".pi", "agent");
4109
+ DEFAULT_AGENT_DIR = join6(homedir6(), ".pi", "agent");
3782
4110
  PI_COLORS = {
3783
4111
  primary: "#0ea5e9",
3784
4112
  secondary: "#67e8f9",
@@ -3797,22 +4125,22 @@ var init_providers = __esm(() => {
3797
4125
  });
3798
4126
 
3799
4127
  // packages/registry/dist/cursor-auth.js
3800
- import { chmodSync, existsSync as existsSync6, mkdirSync, readFileSync as readFileSync3, readdirSync as readdirSync5, renameSync, rmSync, unlinkSync, writeFileSync } from "fs";
4128
+ import { chmodSync, existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync4, readdirSync as readdirSync5, renameSync as renameSync2, rmSync as rmSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
3801
4129
  import { createHash } from "crypto";
3802
- import { homedir as homedir6 } from "os";
3803
- import { basename as basename2, dirname as dirname4, join as join6 } from "path";
4130
+ import { homedir as homedir7 } from "os";
4131
+ import { basename as basename2, dirname as dirname4, join as join7 } from "path";
3804
4132
  function getCursorRootDir() {
3805
- return process.env["TOKENLEAK_CURSOR_DIR"] ?? join6(homedir6(), ".config", "tokenleak");
4133
+ return process.env["TOKENLEAK_CURSOR_DIR"] ?? join7(homedir7(), ".config", "tokenleak");
3806
4134
  }
3807
4135
  function getCursorCredentialsPath() {
3808
- return join6(getCursorRootDir(), "cursor-credentials.json");
4136
+ return join7(getCursorRootDir(), "cursor-credentials.json");
3809
4137
  }
3810
4138
  function getCursorCacheDir() {
3811
- return join6(getCursorRootDir(), "cursor-cache");
4139
+ return join7(getCursorRootDir(), "cursor-cache");
3812
4140
  }
3813
4141
  function ensureDir(dirPath, mode) {
3814
- if (!existsSync6(dirPath)) {
3815
- mkdirSync(dirPath, { recursive: true });
4142
+ if (!existsSync7(dirPath)) {
4143
+ mkdirSync2(dirPath, { recursive: true });
3816
4144
  }
3817
4145
  if (mode !== undefined && process.platform !== "win32") {
3818
4146
  chmodSync(dirPath, mode);
@@ -3821,12 +4149,12 @@ function ensureDir(dirPath, mode) {
3821
4149
  function atomicWriteFile(path, contents, mode) {
3822
4150
  const dir = dirname4(path);
3823
4151
  ensureDir(dir);
3824
- const tempPath = join6(dir, `.tmp-${basename2(path)}-${process.pid}`);
3825
- writeFileSync(tempPath, contents, "utf8");
4152
+ const tempPath = join7(dir, `.tmp-${basename2(path)}-${process.pid}`);
4153
+ writeFileSync2(tempPath, contents, "utf8");
3826
4154
  if (mode !== undefined && process.platform !== "win32") {
3827
4155
  chmodSync(tempPath, mode);
3828
4156
  }
3829
- renameSync(tempPath, path);
4157
+ renameSync2(tempPath, path);
3830
4158
  }
3831
4159
  async function fetchWithTimeout(url, init) {
3832
4160
  const controller = new AbortController;
@@ -3881,13 +4209,13 @@ function countCursorCsvRows(csvText) {
3881
4209
  return rows.length > 0 ? rows.length - 1 : 0;
3882
4210
  }
3883
4211
  function archiveCacheFile(path, label) {
3884
- if (!existsSync6(path)) {
4212
+ if (!existsSync7(path)) {
3885
4213
  return;
3886
4214
  }
3887
- const archiveDir = join6(getCursorCacheDir(), "archive");
4215
+ const archiveDir = join7(getCursorCacheDir(), "archive");
3888
4216
  ensureDir(archiveDir, 448);
3889
4217
  const timestamp = new Date().toISOString().replaceAll(":", "-");
3890
- renameSync(path, join6(archiveDir, `${sanitizeAccountIdForFilename(label)}-${timestamp}.csv`));
4218
+ renameSync2(path, join7(archiveDir, `${sanitizeAccountIdForFilename(label)}-${timestamp}.csv`));
3891
4219
  }
3892
4220
  function resolveAccountId(store, nameOrId) {
3893
4221
  const needle = nameOrId.trim();
@@ -3926,11 +4254,11 @@ function isCursorAuthFailureReason(reason) {
3926
4254
  }
3927
4255
  function loadCursorCredentialsStore() {
3928
4256
  const path = getCursorCredentialsPath();
3929
- if (!existsSync6(path)) {
4257
+ if (!existsSync7(path)) {
3930
4258
  return null;
3931
4259
  }
3932
4260
  try {
3933
- const parsed = JSON.parse(readFileSync3(path, "utf8"));
4261
+ const parsed = JSON.parse(readFileSync4(path, "utf8"));
3934
4262
  if (typeof parsed !== "object" || parsed === null || typeof parsed.activeAccountId !== "string" || typeof parsed.accounts !== "object" || parsed.accounts === null) {
3935
4263
  return null;
3936
4264
  }
@@ -3968,7 +4296,7 @@ function listCursorAccounts() {
3968
4296
  }
3969
4297
  function hasCursorUsageCache() {
3970
4298
  const cacheDir = getCursorCacheDir();
3971
- if (!existsSync6(cacheDir)) {
4299
+ if (!existsSync7(cacheDir)) {
3972
4300
  return false;
3973
4301
  }
3974
4302
  return readdirSync5(cacheDir).some((entry) => {
@@ -4021,16 +4349,16 @@ function removeCursorAccount(nameOrId, purgeCache) {
4021
4349
  }
4022
4350
  const wasActive = accountId === store.activeAccountId;
4023
4351
  const cacheDir = getCursorCacheDir();
4024
- const accountCachePath = join6(cacheDir, `usage.${sanitizeAccountIdForFilename(accountId)}.csv`);
4025
- const activeCachePath = join6(cacheDir, "usage.csv");
4026
- if (existsSync6(accountCachePath)) {
4352
+ const accountCachePath = join7(cacheDir, `usage.${sanitizeAccountIdForFilename(accountId)}.csv`);
4353
+ const activeCachePath = join7(cacheDir, "usage.csv");
4354
+ if (existsSync7(accountCachePath)) {
4027
4355
  if (purgeCache) {
4028
4356
  unlinkSync(accountCachePath);
4029
4357
  } else {
4030
4358
  archiveCacheFile(accountCachePath, `usage.${accountId}`);
4031
4359
  }
4032
4360
  }
4033
- if (wasActive && existsSync6(activeCachePath)) {
4361
+ if (wasActive && existsSync7(activeCachePath)) {
4034
4362
  if (purgeCache) {
4035
4363
  unlinkSync(activeCachePath);
4036
4364
  } else {
@@ -4040,7 +4368,7 @@ function removeCursorAccount(nameOrId, purgeCache) {
4040
4368
  delete store.accounts[accountId];
4041
4369
  const remainingIds = Object.keys(store.accounts);
4042
4370
  if (remainingIds.length === 0) {
4043
- if (existsSync6(getCursorCredentialsPath())) {
4371
+ if (existsSync7(getCursorCredentialsPath())) {
4044
4372
  unlinkSync(getCursorCredentialsPath());
4045
4373
  }
4046
4374
  return;
@@ -4048,34 +4376,34 @@ function removeCursorAccount(nameOrId, purgeCache) {
4048
4376
  if (wasActive) {
4049
4377
  const nextAccountId = remainingIds[0];
4050
4378
  store.activeAccountId = nextAccountId;
4051
- const nextCachePath = join6(cacheDir, `usage.${sanitizeAccountIdForFilename(nextAccountId)}.csv`);
4052
- if (existsSync6(nextCachePath)) {
4053
- renameSync(nextCachePath, activeCachePath);
4379
+ const nextCachePath = join7(cacheDir, `usage.${sanitizeAccountIdForFilename(nextAccountId)}.csv`);
4380
+ if (existsSync7(nextCachePath)) {
4381
+ renameSync2(nextCachePath, activeCachePath);
4054
4382
  }
4055
4383
  }
4056
4384
  saveCursorCredentialsStore(store);
4057
4385
  }
4058
4386
  function removeAllCursorAccounts(purgeCache) {
4059
4387
  const cacheDir = getCursorCacheDir();
4060
- if (existsSync6(cacheDir)) {
4388
+ if (existsSync7(cacheDir)) {
4061
4389
  for (const entry of readdirSync5(cacheDir)) {
4062
4390
  if (entry === "archive") {
4063
4391
  if (purgeCache) {
4064
- rmSync(join6(cacheDir, entry), { recursive: true, force: true });
4392
+ rmSync2(join7(cacheDir, entry), { recursive: true, force: true });
4065
4393
  }
4066
4394
  continue;
4067
4395
  }
4068
4396
  if (entry === "usage.csv" || /^usage\.[^.].*\.csv$/.test(entry)) {
4069
- const fullPath = join6(cacheDir, entry);
4397
+ const fullPath = join7(cacheDir, entry);
4070
4398
  if (purgeCache) {
4071
- rmSync(fullPath, { force: true });
4399
+ rmSync2(fullPath, { force: true });
4072
4400
  } else {
4073
4401
  archiveCacheFile(fullPath, `usage.all.${entry}`);
4074
4402
  }
4075
4403
  }
4076
4404
  }
4077
4405
  }
4078
- if (existsSync6(getCursorCredentialsPath())) {
4406
+ if (existsSync7(getCursorCredentialsPath())) {
4079
4407
  unlinkSync(getCursorCredentialsPath());
4080
4408
  }
4081
4409
  }
@@ -4095,17 +4423,17 @@ function setActiveCursorAccount(nameOrId) {
4095
4423
  return;
4096
4424
  }
4097
4425
  const cacheDir = getCursorCacheDir();
4098
- const activeCachePath = join6(cacheDir, "usage.csv");
4099
- const oldActivePath = join6(cacheDir, `usage.${sanitizeAccountIdForFilename(store.activeAccountId)}.csv`);
4100
- const newActivePath = join6(cacheDir, `usage.${sanitizeAccountIdForFilename(accountId)}.csv`);
4101
- if (existsSync6(activeCachePath)) {
4102
- if (existsSync6(oldActivePath)) {
4426
+ const activeCachePath = join7(cacheDir, "usage.csv");
4427
+ const oldActivePath = join7(cacheDir, `usage.${sanitizeAccountIdForFilename(store.activeAccountId)}.csv`);
4428
+ const newActivePath = join7(cacheDir, `usage.${sanitizeAccountIdForFilename(accountId)}.csv`);
4429
+ if (existsSync7(activeCachePath)) {
4430
+ if (existsSync7(oldActivePath)) {
4103
4431
  archiveCacheFile(oldActivePath, `usage.${store.activeAccountId}`);
4104
4432
  }
4105
- renameSync(activeCachePath, oldActivePath);
4433
+ renameSync2(activeCachePath, oldActivePath);
4106
4434
  }
4107
- if (existsSync6(newActivePath)) {
4108
- renameSync(newActivePath, activeCachePath);
4435
+ if (existsSync7(newActivePath)) {
4436
+ renameSync2(newActivePath, activeCachePath);
4109
4437
  }
4110
4438
  store.activeAccountId = accountId;
4111
4439
  saveCursorCredentialsStore(store);
@@ -4214,11 +4542,11 @@ async function syncCursorCache() {
4214
4542
  for (const [accountId, credentials] of Object.entries(store.accounts)) {
4215
4543
  try {
4216
4544
  const csvText = await fetchCursorUsageCsv(credentials.sessionToken);
4217
- const filePath = accountId === store.activeAccountId ? join6(cacheDir, "usage.csv") : join6(cacheDir, `usage.${sanitizeAccountIdForFilename(accountId)}.csv`);
4545
+ const filePath = accountId === store.activeAccountId ? join7(cacheDir, "usage.csv") : join7(cacheDir, `usage.${sanitizeAccountIdForFilename(accountId)}.csv`);
4218
4546
  atomicWriteFile(filePath, csvText, process.platform === "win32" ? undefined : 384);
4219
4547
  if (accountId === store.activeAccountId) {
4220
- const activeDupPath = join6(cacheDir, `usage.${sanitizeAccountIdForFilename(store.activeAccountId)}.csv`);
4221
- if (existsSync6(activeDupPath)) {
4548
+ const activeDupPath = join7(cacheDir, `usage.${sanitizeAccountIdForFilename(store.activeAccountId)}.csv`);
4549
+ if (existsSync7(activeDupPath)) {
4222
4550
  unlinkSync(activeDupPath);
4223
4551
  }
4224
4552
  }
@@ -10234,11 +10562,15 @@ function createInitialState() {
10234
10562
  cursorSetupMessage: null,
10235
10563
  cursorSetupSubmitting: false,
10236
10564
  cursorSetupStatusOverride: null,
10565
+ replayDate: null,
10566
+ replayScrollOffset: 0,
10567
+ replayExpandedBlocks: new Set,
10237
10568
  cachedAdvisorReport: null,
10238
10569
  cachedFocusReport: null,
10239
10570
  cachedExplainReport: null,
10240
10571
  cachedCompareOutput: null,
10241
- cachedMoreStats: null
10572
+ cachedMoreStats: null,
10573
+ cachedReplayReport: null
10242
10574
  };
10243
10575
  }
10244
10576
  var WINDOW_LABELS, WINDOW_DAYS;
@@ -10411,6 +10743,22 @@ function ensureMoreStats(state) {
10411
10743
  state.cachedMoreStats = more;
10412
10744
  return more;
10413
10745
  }
10746
+ function ensureReplayReport(state) {
10747
+ if (!state.data)
10748
+ return null;
10749
+ if (state.cachedReplayReport && state.cachedReplayReport.date === state.replayDate) {
10750
+ return state.cachedReplayReport;
10751
+ }
10752
+ if (!state.replayDate) {
10753
+ state.replayDate = todayStr();
10754
+ }
10755
+ const scoped = getScopedWindowData(state);
10756
+ if (!scoped)
10757
+ return null;
10758
+ const report = buildReplayReport(scoped.scopedProviders, state.replayDate);
10759
+ state.cachedReplayReport = report;
10760
+ return report;
10761
+ }
10414
10762
  function getDayOfWeekForWindow(state) {
10415
10763
  if (!state.data)
10416
10764
  return [];
@@ -10424,7 +10772,7 @@ var init_data = __esm(() => {
10424
10772
  });
10425
10773
 
10426
10774
  // packages/tui/dist/lib/format.js
10427
- function formatTokens14(n) {
10775
+ function formatTokens15(n) {
10428
10776
  if (n >= 1e9)
10429
10777
  return `${(n / 1e9).toFixed(1)}B`;
10430
10778
  if (n >= 1e6)
@@ -10433,7 +10781,7 @@ function formatTokens14(n) {
10433
10781
  return `${(n / 1000).toFixed(1)}K`;
10434
10782
  return n.toLocaleString("en-US");
10435
10783
  }
10436
- function formatCost14(n) {
10784
+ function formatCost15(n) {
10437
10785
  return `$${n.toFixed(2)}`;
10438
10786
  }
10439
10787
  function formatPercent5(n) {
@@ -10445,7 +10793,7 @@ function padRight(s, width) {
10445
10793
  function padLeft(s, width) {
10446
10794
  return s.length >= width ? s.slice(0, width) : " ".repeat(width - s.length) + s;
10447
10795
  }
10448
- function truncate2(s, maxLen) {
10796
+ function truncate3(s, maxLen) {
10449
10797
  if (maxLen <= 0)
10450
10798
  return "";
10451
10799
  if (maxLen === 1)
@@ -10482,10 +10830,10 @@ function formatCompactTime() {
10482
10830
  return `${month}/${day} ${hours}:${minutes}`;
10483
10831
  }
10484
10832
  function buildHeader(state, renderer, onViewSwitch) {
10485
- const costStr = state.data ? formatCost14(state.data.allTimeStats.totalCost) : "$...";
10833
+ const costStr = state.data ? formatCost15(state.data.allTimeStats.totalCost) : "$...";
10486
10834
  const windowIdx = state.selectedWindowIndex;
10487
10835
  const stats = state.data?.windows[windowIdx]?.stats;
10488
- const windowCost = stats ? formatCost14(stats.totalCost) : costStr;
10836
+ const windowCost = stats ? formatCost15(stats.totalCost) : costStr;
10489
10837
  const tabParts = [];
10490
10838
  for (let i = 0;i < WINDOW_LABELS.length; i++) {
10491
10839
  const label = WINDOW_LABELS[i];
@@ -10540,7 +10888,8 @@ var init_header = __esm(() => {
10540
10888
  { key: "5", label: "Explain", mode: "explain" },
10541
10889
  { key: "6", label: "Compare", mode: "compare" },
10542
10890
  { key: "7", label: "Export", mode: "export" },
10543
- { key: "8", label: "Wrapped", mode: "wrapped" }
10891
+ { key: "8", label: "Wrapped", mode: "wrapped" },
10892
+ { key: "9", label: "Replay", mode: "replay" }
10544
10893
  ];
10545
10894
  });
10546
10895
 
@@ -10578,11 +10927,11 @@ function buildChart(daily, chartWidth, chartHeight) {
10578
10927
  const threshold = row / chartHeight * maxTokens;
10579
10928
  let yLabel = "";
10580
10929
  if (row === chartHeight - 1) {
10581
- yLabel = formatTokens14(maxTokens);
10930
+ yLabel = formatTokens15(maxTokens);
10582
10931
  } else if (row === 0) {
10583
10932
  yLabel = "0";
10584
10933
  } else if (row === Math.floor(chartHeight / 2)) {
10585
- yLabel = formatTokens14(maxTokens / 2);
10934
+ yLabel = formatTokens15(maxTokens / 2);
10586
10935
  }
10587
10936
  const labelPadded = yLabel.padStart(yAxisWidth - 1) + "\u2502";
10588
10937
  let barStr = "";
@@ -10602,7 +10951,7 @@ function buildChart(daily, chartWidth, chartHeight) {
10602
10951
  rows.push(Box2({ width: "100%", height: 1 }));
10603
10952
  const legendParts = [];
10604
10953
  for (const cm of chartModels) {
10605
- legendParts.push(Text2({ content: `\u25CF ${truncate2(cm.model, 18)} `, fg: cm.color }));
10954
+ legendParts.push(Text2({ content: `\u25CF ${truncate3(cm.model, 18)} `, fg: cm.color }));
10606
10955
  }
10607
10956
  rows.push(Box2({ flexDirection: "row", width: "100%", height: 1 }, Text2({ content: " ".repeat(yAxisWidth), fg: COLORS2.dimWhite }), ...legendParts));
10608
10957
  return Box2({ flexDirection: "column", width: "100%" }, ...rows);
@@ -10631,7 +10980,7 @@ function buildXLabels(days, width) {
10631
10980
  }
10632
10981
  function buildCompactList(daily, chartModels) {
10633
10982
  const sorted = [...daily].sort((a, b) => b.totalTokens - a.totalTokens).slice(0, 5);
10634
- const rows = sorted.map((d) => Box2({ flexDirection: "row", width: "100%" }, Text2({ content: `${formatShortDate(d.date)} `, fg: COLORS2.dimWhite }), Text2({ content: formatTokens14(d.totalTokens), fg: COLORS2.green, attributes: BOLD3 })));
10983
+ const rows = sorted.map((d) => Box2({ flexDirection: "row", width: "100%" }, Text2({ content: `${formatShortDate(d.date)} `, fg: COLORS2.dimWhite }), Text2({ content: formatTokens15(d.totalTokens), fg: COLORS2.green, attributes: BOLD3 })));
10635
10984
  const legendParts = chartModels.map((cm) => Text2({ content: `\u25CF ${cm.model} `, fg: cm.color }));
10636
10985
  return Box2({ flexDirection: "column", width: "100%" }, Text2({ content: "Top 5 Days", fg: COLORS2.amber, attributes: BOLD3 }), ...rows, Box2({ flexDirection: "row", width: "100%", paddingTop: 1 }, ...legendParts));
10637
10986
  }
@@ -10702,7 +11051,7 @@ function createStatsRow(state, stats) {
10702
11051
  height: 1,
10703
11052
  justifyContent: "flex-start",
10704
11053
  gap: 0
10705
- }, statCard("Tokens", formatTokens14(stats.totalTokens), COLORS2.green), sep4(), statCard("Cost", formatCost14(stats.totalCost), COLORS2.amber), sep4(), statCard("Active", `${stats.activeDays}/${stats.totalDays}d`, COLORS2.cyan), sep4(), statCard("Streak", `${stats.currentStreak}d`, COLORS2.green), sep4(), statCard("Cache", formatPercent5(stats.cacheHitRate), COLORS2.cyan));
11054
+ }, statCard("Tokens", formatTokens15(stats.totalTokens), COLORS2.green), sep4(), statCard("Cost", formatCost15(stats.totalCost), COLORS2.amber), sep4(), statCard("Active", `${stats.activeDays}/${stats.totalDays}d`, COLORS2.cyan), sep4(), statCard("Streak", `${stats.currentStreak}d`, COLORS2.green), sep4(), statCard("Cache", formatPercent5(stats.cacheHitRate), COLORS2.cyan));
10706
11055
  }
10707
11056
  var init_stats_row = __esm(() => {
10708
11057
  init_theme2();
@@ -10736,7 +11085,7 @@ function createModelList(state, stats) {
10736
11085
  fg: COLORS2.amber,
10737
11086
  attributes: BOLD3
10738
11087
  }), Text5({
10739
- content: `Total: ${formatCost14(totalCost)}`,
11088
+ content: `Total: ${formatCost15(totalCost)}`,
10740
11089
  fg: COLORS2.amber,
10741
11090
  attributes: BOLD3
10742
11091
  })));
@@ -10752,9 +11101,9 @@ function createModelList(state, stats) {
10752
11101
  const colorIdx = sortedModels.indexOf(m);
10753
11102
  const color2 = MODEL_COLORS2[colorIdx % MODEL_COLORS2.length];
10754
11103
  const pct = `(${m.percentage.toFixed(1)}%)`;
10755
- const nameStr = truncate2(m.model, 30);
10756
- const costStr = formatCost14(m.cost);
10757
- const tokStr = formatTokens14(m.tokens);
11104
+ const nameStr = truncate3(m.model, 30);
11105
+ const costStr = formatCost15(m.cost);
11106
+ const tokStr = formatTokens15(m.tokens);
10758
11107
  children.push(Box5({ flexDirection: "row", width: "100%" }, Text5({ content: "\u25CF ", fg: color2 }), Text5({
10759
11108
  content: padRight(nameStr, 31),
10760
11109
  fg: colorIdx === 0 ? COLORS2.amber : COLORS2.white,
@@ -10954,7 +11303,7 @@ function buildStatusBar(state) {
10954
11303
  }, Text7({ content: "tab:field enter:token submit esc:close", fg: COLORS2.dimWhite }), Text7({ content: `Updated ${formatUpdateTime()}`, fg: COLORS2.dimWhite }));
10955
11304
  }
10956
11305
  const helpHint = "?:help";
10957
- const nav = `\u2190\u2192:view tab/\u21E7tab:period 1-8:view`;
11306
+ const nav = `\u2190\u2192:view tab/\u21E7tab:period 1-9:view`;
10958
11307
  const cursorHint = getCursorBannerText(state) ? " c:cursor" : "";
10959
11308
  let keys;
10960
11309
  if (state.selectedView === "overview") {
@@ -10963,6 +11312,8 @@ function buildStatusBar(state) {
10963
11312
  keys = `${nav} [/]:page r:refresh${cursorHint} ${helpHint} q:quit`;
10964
11313
  } else if (state.selectedView === "explain") {
10965
11314
  keys = `${nav} h/l:date r:refresh${cursorHint} ${helpHint} q:quit`;
11315
+ } else if (state.selectedView === "replay") {
11316
+ keys = `${nav} h/l:date j/k:scroll enter:expand r:refresh${cursorHint} ${helpHint} q:quit`;
10966
11317
  } else if (state.selectedView === "advisor" || state.selectedView === "focus" || state.selectedView === "compare" || state.selectedView === "wrapped") {
10967
11318
  keys = `${nav} j/k:scroll r:refresh${cursorHint} ${helpHint} q:quit`;
10968
11319
  } else if (state.selectedView === "export") {
@@ -11024,7 +11375,7 @@ function createTimeWindowsPanel(props) {
11024
11375
  const avgTokenValues = windows.map((w) => w.stats.averageDailyTokens);
11025
11376
  const avgCostValues = windows.map((w) => w.stats.averageDailyCost);
11026
11377
  const activeDaysValues = windows.map((w) => w.stats.activeDays);
11027
- children.push(headerRow(labels), Text8({ content: "\u2500".repeat(66), fg: COLORS2.dimWhite }), dataRow("Tokens", tokenValues.map(formatTokens14), findMaxIndex(tokenValues)), dataRow("Cost", costValues.map(formatCost14), findMaxIndex(costValues)), dataRow("Avg Daily Tokens", avgTokenValues.map(formatTokens14), findMaxIndex(avgTokenValues)), dataRow("Avg Daily Cost", avgCostValues.map(formatCost14), findMaxIndex(avgCostValues)), dataRow("Active Days", activeDaysValues.map((v) => v.toString()), findMaxIndex(activeDaysValues)), dataRow("Cache Hit Rate", windows.map((w) => `${(w.stats.cacheHitRate * 100).toFixed(1)}%`), findMaxIndex(windows.map((w) => w.stats.cacheHitRate))), dataRow("Current Streak", windows.map((w) => `${w.stats.currentStreak}d`), findMaxIndex(windows.map((w) => w.stats.currentStreak))));
11378
+ children.push(headerRow(labels), Text8({ content: "\u2500".repeat(66), fg: COLORS2.dimWhite }), dataRow("Tokens", tokenValues.map(formatTokens15), findMaxIndex(tokenValues)), dataRow("Cost", costValues.map(formatCost15), findMaxIndex(costValues)), dataRow("Avg Daily Tokens", avgTokenValues.map(formatTokens15), findMaxIndex(avgTokenValues)), dataRow("Avg Daily Cost", avgCostValues.map(formatCost15), findMaxIndex(avgCostValues)), dataRow("Active Days", activeDaysValues.map((v) => v.toString()), findMaxIndex(activeDaysValues)), dataRow("Cache Hit Rate", windows.map((w) => `${(w.stats.cacheHitRate * 100).toFixed(1)}%`), findMaxIndex(windows.map((w) => w.stats.cacheHitRate))), dataRow("Current Streak", windows.map((w) => `${w.stats.currentStreak}d`), findMaxIndex(windows.map((w) => w.stats.currentStreak))));
11028
11379
  }
11029
11380
  return Box8({
11030
11381
  flexDirection: "column",
@@ -11049,7 +11400,7 @@ function createHourOfDayPanel(hourOfDay) {
11049
11400
  const ratio = entry.tokens / maxTokens;
11050
11401
  const hourLabel = String(entry.hour).padStart(2, "0");
11051
11402
  const barColor = entry.tokens === maxTokens ? COLORS2.amber : COLORS2.green;
11052
- children.push(Box9({ flexDirection: "row", width: "100%" }, Text9({ content: `${hourLabel} `, fg: COLORS2.dimWhite }), Text9({ content: asciiBar(ratio, 16), fg: barColor }), Text9({ content: ` ${padLeft(formatTokens14(entry.tokens), 8)}`, fg: COLORS2.green })));
11403
+ children.push(Box9({ flexDirection: "row", width: "100%" }, Text9({ content: `${hourLabel} `, fg: COLORS2.dimWhite }), Text9({ content: asciiBar(ratio, 16), fg: barColor }), Text9({ content: ` ${padLeft(formatTokens15(entry.tokens), 8)}`, fg: COLORS2.green })));
11053
11404
  }
11054
11405
  return Box9({
11055
11406
  flexDirection: "column",
@@ -11065,7 +11416,7 @@ function createDayOfWeekPanel(dayOfWeek) {
11065
11416
  for (const entry of dayOfWeek) {
11066
11417
  const ratio = entry.tokens / maxTokens;
11067
11418
  const barColor = entry.tokens === maxTokens ? COLORS2.amber : COLORS2.green;
11068
- children.push(Box9({ flexDirection: "row", width: "100%" }, Text9({ content: padRight(entry.label, 4), fg: COLORS2.dimWhite }), Text9({ content: asciiBar(ratio, 16), fg: barColor }), Text9({ content: ` ${padLeft(formatTokens14(entry.tokens), 8)}`, fg: COLORS2.green })));
11419
+ children.push(Box9({ flexDirection: "row", width: "100%" }, Text9({ content: padRight(entry.label, 4), fg: COLORS2.dimWhite }), Text9({ content: asciiBar(ratio, 16), fg: barColor }), Text9({ content: ` ${padLeft(formatTokens15(entry.tokens), 8)}`, fg: COLORS2.green })));
11069
11420
  }
11070
11421
  return Box9({
11071
11422
  flexDirection: "column",
@@ -11095,17 +11446,17 @@ function createInputOutputPanel(io, stats) {
11095
11446
  borderColor: COLORS2.cyan,
11096
11447
  padding: 1,
11097
11448
  flexGrow: 1
11098
- }, Text10({ content: " INPUT / OUTPUT ", fg: COLORS2.cyan, attributes: BOLD3 }), statRow("Input", `${formatTokens14(stats.totalInputTokens)} (${inputPct}%)`), statRow("Output", `${formatTokens14(stats.totalOutputTokens)} (${outputPct}%)`), statRow("I/O Ratio", `${ioRatio}:1`), statRow("Output Share", `${(io.outputShare * 100).toFixed(1)}%`, COLORS2.amber));
11449
+ }, Text10({ content: " INPUT / OUTPUT ", fg: COLORS2.cyan, attributes: BOLD3 }), statRow("Input", `${formatTokens15(stats.totalInputTokens)} (${inputPct}%)`), statRow("Output", `${formatTokens15(stats.totalOutputTokens)} (${outputPct}%)`), statRow("I/O Ratio", `${ioRatio}:1`), statRow("Output Share", `${(io.outputShare * 100).toFixed(1)}%`, COLORS2.amber));
11099
11450
  }
11100
11451
  function createMonthlyBurnPanel(burn) {
11101
- const burnRate = burn.observedDays > 0 ? formatCost14(burn.projectedCost / (burn.calendarDays || 30)) : "$0.00";
11452
+ const burnRate = burn.observedDays > 0 ? formatCost15(burn.projectedCost / (burn.calendarDays || 30)) : "$0.00";
11102
11453
  return Box10({
11103
11454
  flexDirection: "column",
11104
11455
  borderStyle: "single",
11105
11456
  borderColor: COLORS2.amber,
11106
11457
  padding: 1,
11107
11458
  flexGrow: 1
11108
- }, Text10({ content: " MONTHLY BURN ", fg: COLORS2.amber, attributes: BOLD3 }), statRow("Projected Tokens", formatTokens14(burn.projectedTokens)), statRow("Projected Cost", formatCost14(burn.projectedCost), COLORS2.amber), statRow("Observed", `${burn.observedDays}/${burn.calendarDays} days`), statRow("Burn Rate", `${burnRate}/day`, COLORS2.red));
11459
+ }, Text10({ content: " MONTHLY BURN ", fg: COLORS2.amber, attributes: BOLD3 }), statRow("Projected Tokens", formatTokens15(burn.projectedTokens)), statRow("Projected Cost", formatCost15(burn.projectedCost), COLORS2.amber), statRow("Observed", `${burn.observedDays}/${burn.calendarDays} days`), statRow("Burn Rate", `${burnRate}/day`, COLORS2.red));
11109
11460
  }
11110
11461
  var init_matrix_io = __esm(() => {
11111
11462
  init_theme2();
@@ -11124,7 +11475,7 @@ function createCacheEconomicsPanel(cache) {
11124
11475
  borderColor: COLORS2.cyan,
11125
11476
  padding: 1,
11126
11477
  flexGrow: 1
11127
- }, Text11({ content: " CACHE ECONOMICS ", fg: COLORS2.cyan, attributes: BOLD3 }), statRow2("Read Tokens", formatTokens14(cache.readTokens)), statRow2("Write Tokens", formatTokens14(cache.writeTokens)), statRow2("Read Coverage", `${(cache.readCoverage * 100).toFixed(1)}%`, COLORS2.amber), statRow2("Reuse Ratio", reuseStr));
11478
+ }, Text11({ content: " CACHE ECONOMICS ", fg: COLORS2.cyan, attributes: BOLD3 }), statRow2("Read Tokens", formatTokens15(cache.readTokens)), statRow2("Write Tokens", formatTokens15(cache.writeTokens)), statRow2("Read Coverage", `${(cache.readCoverage * 100).toFixed(1)}%`, COLORS2.amber), statRow2("Reuse Ratio", reuseStr));
11128
11479
  }
11129
11480
  function createCacheRoiPanel(roi) {
11130
11481
  if (!roi) {
@@ -11144,7 +11495,7 @@ function createCacheRoiPanel(roi) {
11144
11495
  borderColor: COLORS2.green,
11145
11496
  padding: 1,
11146
11497
  flexGrow: 1
11147
- }, Text11({ content: " CACHE ROI ", fg: COLORS2.green, attributes: BOLD3 }), statRow2("Read Savings", formatCost14(summary.readSavings), COLORS2.green), statRow2("Write Cost", formatCost14(summary.writeCost), COLORS2.red), statRow2("Net Savings", formatCost14(summary.netSavings), summary.netSavings >= 0 ? COLORS2.green : COLORS2.red), statRow2("Payback Ratio", paybackStr, COLORS2.amber));
11498
+ }, Text11({ content: " CACHE ROI ", fg: COLORS2.green, attributes: BOLD3 }), statRow2("Read Savings", formatCost15(summary.readSavings), COLORS2.green), statRow2("Write Cost", formatCost15(summary.writeCost), COLORS2.red), statRow2("Net Savings", formatCost15(summary.netSavings), summary.netSavings >= 0 ? COLORS2.green : COLORS2.red), statRow2("Payback Ratio", paybackStr, COLORS2.amber));
11148
11499
  }
11149
11500
  var init_matrix_cache = __esm(() => {
11150
11501
  init_theme2();
@@ -11155,7 +11506,7 @@ import { Box as Box12, Text as Text12 } from "@opentui/core";
11155
11506
  function statRow3(label, value, valueColor = COLORS2.green) {
11156
11507
  return Box12({ flexDirection: "row", width: "100%" }, Text12({ content: label, fg: COLORS2.dimWhite }), Text12({ content: " " }), Text12({ content: value, fg: valueColor, attributes: BOLD3 }));
11157
11508
  }
11158
- function formatDuration4(ms) {
11509
+ function formatDuration5(ms) {
11159
11510
  if (ms === null || ms <= 0)
11160
11511
  return "N/A";
11161
11512
  const totalMinutes = Math.floor(ms / 60000);
@@ -11166,14 +11517,14 @@ function formatDuration4(ms) {
11166
11517
  return `${hours}h ${minutes}m`;
11167
11518
  }
11168
11519
  function createSessionsPanel(sessions) {
11169
- const longestDuration = sessions.longestSession ? formatDuration4(sessions.longestSession.durationMs) : "N/A";
11520
+ const longestDuration = sessions.longestSession ? formatDuration5(sessions.longestSession.durationMs) : "N/A";
11170
11521
  return Box12({
11171
11522
  flexDirection: "column",
11172
11523
  borderStyle: "single",
11173
11524
  borderColor: COLORS2.cyan,
11174
11525
  padding: 1,
11175
11526
  flexGrow: 1
11176
- }, Text12({ content: " SESSIONS ", fg: COLORS2.cyan, attributes: BOLD3 }), statRow3("Total", `${sessions.totalSessions} sessions`), statRow3("Avg Tokens", `${formatTokens14(sessions.averageTokens)}/sess`), statRow3("Avg Cost", `${formatCost14(sessions.averageCost)}/sess`), statRow3("Longest", longestDuration), statRow3("Projects", `${sessions.projectCount}`));
11527
+ }, Text12({ content: " SESSIONS ", fg: COLORS2.cyan, attributes: BOLD3 }), statRow3("Total", `${sessions.totalSessions} sessions`), statRow3("Avg Tokens", `${formatTokens15(sessions.averageTokens)}/sess`), statRow3("Avg Cost", `${formatCost15(sessions.averageCost)}/sess`), statRow3("Longest", longestDuration), statRow3("Projects", `${sessions.projectCount}`));
11177
11528
  }
11178
11529
  function createTopProjectsPanel(projects) {
11179
11530
  const children = [];
@@ -11184,13 +11535,13 @@ function createTopProjectsPanel(projects) {
11184
11535
  for (let i = 0;i < top8.length; i++) {
11185
11536
  const p = top8[i];
11186
11537
  const rank = `${i + 1}.`;
11187
- const name = truncate2(p.directory ?? p.projectId, 20);
11538
+ const name = truncate3(p.directory ?? p.projectId, 20);
11188
11539
  const isTop = i === 0;
11189
11540
  children.push(Box12({ flexDirection: "row", width: "100%" }, Text12({ content: padRight(rank, 3), fg: COLORS2.dimWhite }), Text12({
11190
11541
  content: padRight(name, 21),
11191
11542
  fg: isTop ? COLORS2.amber : COLORS2.green,
11192
11543
  attributes: isTop ? BOLD3 : undefined
11193
- }), Text12({ content: padLeft(formatTokens14(p.totalTokens), 8), fg: COLORS2.green }), Text12({ content: padLeft(formatCost14(p.cost), 10), fg: COLORS2.amber })));
11544
+ }), Text12({ content: padLeft(formatTokens15(p.totalTokens), 8), fg: COLORS2.green }), Text12({ content: padLeft(formatCost15(p.cost), 10), fg: COLORS2.amber })));
11194
11545
  }
11195
11546
  }
11196
11547
  return Box12({
@@ -11222,7 +11573,7 @@ function createModelEfficiencyPanel(efficiency) {
11222
11573
  const oi = r.outputInputRatio.toFixed(2);
11223
11574
  const cache = `${(r.cacheCoverage * 100).toFixed(0)}%`;
11224
11575
  children.push(Box13({ flexDirection: "row", width: "100%" }, Text13({ content: padRight(`${i + 1}.`, 3), fg: COLORS2.dimWhite }), Text13({
11225
- content: padRight(truncate2(r.model, 21), 22),
11576
+ content: padRight(truncate3(r.model, 21), 22),
11226
11577
  fg: isTop ? COLORS2.amber : COLORS2.green,
11227
11578
  attributes: isTop ? BOLD3 : undefined
11228
11579
  }), Text13({ content: padLeft(r.score.toFixed(2), 7), fg: COLORS2.cyan, attributes: BOLD3 }), Text13({ content: padLeft(costPerM, 8), fg: COLORS2.amber }), Text13({ content: padLeft(oi, 6), fg: COLORS2.green }), Text13({ content: padLeft(cache, 7), fg: COLORS2.magenta })));
@@ -11248,10 +11599,10 @@ function createAttributionPanel(attribution) {
11248
11599
  const c = top8[i];
11249
11600
  const isTop = i === 0;
11250
11601
  children.push(Box13({ flexDirection: "row", width: "100%" }, Text13({
11251
- content: padRight(truncate2(c.label, 15), 16),
11602
+ content: padRight(truncate3(c.label, 15), 16),
11252
11603
  fg: isTop ? COLORS2.amber : COLORS2.green,
11253
11604
  attributes: isTop ? BOLD3 : undefined
11254
- }), Text13({ content: padRight(c.taskStyle, 10), fg: COLORS2.cyan }), Text13({ content: padLeft(formatTokens14(c.tokens), 10), fg: COLORS2.green }), Text13({ content: padLeft(String(c.sessionCount), 10), fg: COLORS2.white })));
11605
+ }), Text13({ content: padRight(c.taskStyle, 10), fg: COLORS2.cyan }), Text13({ content: padLeft(formatTokens15(c.tokens), 10), fg: COLORS2.green }), Text13({ content: padLeft(String(c.sessionCount), 10), fg: COLORS2.white })));
11255
11606
  }
11256
11607
  }
11257
11608
  return Box13({
@@ -11262,7 +11613,7 @@ function createAttributionPanel(attribution) {
11262
11613
  flexGrow: 1
11263
11614
  }, Text13({ content: " ATTRIBUTION ", fg: COLORS2.magenta, attributes: BOLD3 }), ...children);
11264
11615
  }
11265
- function formatDuration5(ms) {
11616
+ function formatDuration6(ms) {
11266
11617
  if (ms === null || ms <= 0)
11267
11618
  return "-";
11268
11619
  const hours = Math.floor(ms / 3600000);
@@ -11284,10 +11635,10 @@ function createTopSessionsPanel(sessions) {
11284
11635
  const name = s.directory ?? s.label;
11285
11636
  const isTop = i === 0;
11286
11637
  children.push(Box13({ flexDirection: "row", width: "100%" }, Text13({ content: padRight(`${i + 1}.`, 3), fg: COLORS2.dimWhite }), Text13({
11287
- content: padRight(truncate2(name, 18), 20),
11638
+ content: padRight(truncate3(name, 18), 20),
11288
11639
  fg: isTop ? COLORS2.amber : COLORS2.green,
11289
11640
  attributes: isTop ? BOLD3 : undefined
11290
- }), Text13({ content: padLeft(formatTokens14(s.totalTokens), 9), fg: COLORS2.green }), Text13({ content: padLeft(formatCost14(s.cost), 9), fg: COLORS2.amber }), Text13({ content: padLeft(formatDuration5(s.durationMs), 9), fg: COLORS2.cyan })));
11641
+ }), Text13({ content: padLeft(formatTokens15(s.totalTokens), 9), fg: COLORS2.green }), Text13({ content: padLeft(formatCost15(s.cost), 9), fg: COLORS2.amber }), Text13({ content: padLeft(formatDuration6(s.durationMs), 9), fg: COLORS2.cyan })));
11291
11642
  }
11292
11643
  }
11293
11644
  return Box13({
@@ -11311,10 +11662,10 @@ function createCacheRoiByModelPanel(cacheRoi) {
11311
11662
  const isTop = i === 0;
11312
11663
  const payback = m.paybackRatio !== null ? `${m.paybackRatio.toFixed(1)}x` : "-";
11313
11664
  children.push(Box13({ flexDirection: "row", width: "100%" }, Text13({
11314
- content: padRight(truncate2(m.label, 18), 20),
11665
+ content: padRight(truncate3(m.label, 18), 20),
11315
11666
  fg: isTop ? COLORS2.amber : COLORS2.green,
11316
11667
  attributes: isTop ? BOLD3 : undefined
11317
- }), Text13({ content: padLeft(formatCost14(m.netSavings), 10), fg: COLORS2.green }), Text13({ content: padLeft(payback, 10), fg: COLORS2.cyan })));
11668
+ }), Text13({ content: padLeft(formatCost15(m.netSavings), 10), fg: COLORS2.green }), Text13({ content: padLeft(payback, 10), fg: COLORS2.cyan })));
11318
11669
  }
11319
11670
  }
11320
11671
  return Box13({
@@ -11341,7 +11692,7 @@ function createOverviewPanel(state) {
11341
11692
  if (!stats || stats.totalTokens === 0 && stats.totalCost === 0) {
11342
11693
  children.push(Text14({ content: "No usage data for this period", fg: COLORS2.dimWhite }));
11343
11694
  } else {
11344
- children.push(statRow4("Total Tokens", formatTokens14(stats.totalTokens)), statRow4("Total Cost", formatCost14(stats.totalCost), COLORS2.amber), statRow4("Active / Total Days", `${stats.activeDays} / ${stats.totalDays}`), statRow4("Current Streak", `${stats.currentStreak}d`), statRow4("Longest Streak", `${stats.longestStreak}d`), statRow4("Cache Hit Rate", formatPercent5(stats.cacheHitRate), COLORS2.cyan), statRow4("Avg Daily Tokens", formatTokens14(stats.averageDailyTokens)), statRow4("Avg Daily Cost", formatCost14(stats.averageDailyCost), COLORS2.amber), statRow4("Providers", `${providers.length} active`), statRow4("Peak Day", stats.peakDay ? `${stats.peakDay.date} (${formatTokens14(stats.peakDay.tokens)})` : "N/A"), statRow4("Input Tokens", formatTokens14(stats.totalInputTokens)), statRow4("Output Tokens", formatTokens14(stats.totalOutputTokens)));
11695
+ children.push(statRow4("Total Tokens", formatTokens15(stats.totalTokens)), statRow4("Total Cost", formatCost15(stats.totalCost), COLORS2.amber), statRow4("Active / Total Days", `${stats.activeDays} / ${stats.totalDays}`), statRow4("Current Streak", `${stats.currentStreak}d`), statRow4("Longest Streak", `${stats.longestStreak}d`), statRow4("Cache Hit Rate", formatPercent5(stats.cacheHitRate), COLORS2.cyan), statRow4("Avg Daily Tokens", formatTokens15(stats.averageDailyTokens)), statRow4("Avg Daily Cost", formatCost15(stats.averageDailyCost), COLORS2.amber), statRow4("Providers", `${providers.length} active`), statRow4("Peak Day", stats.peakDay ? `${stats.peakDay.date} (${formatTokens15(stats.peakDay.tokens)})` : "N/A"), statRow4("Input Tokens", formatTokens15(stats.totalInputTokens)), statRow4("Output Tokens", formatTokens15(stats.totalOutputTokens)));
11345
11696
  }
11346
11697
  return Box14({
11347
11698
  flexDirection: "column",
@@ -11385,7 +11736,7 @@ function createProvidersPanel(state) {
11385
11736
  const p = sorted[i];
11386
11737
  const share = totalTokens > 0 ? p.windowTokens / totalTokens : 0;
11387
11738
  const color2 = getProviderColor3(p.provider, i);
11388
- children.push(Box14({ flexDirection: "row", width: "100%" }, Text14({ content: padRight(p.displayName, 14), fg: color2, attributes: BOLD3 }), Text14({ content: padLeft(formatTokens14(p.windowTokens), 10), fg: COLORS2.green }), Text14({ content: padLeft(formatCost14(p.windowCost), 10), fg: COLORS2.amber }), Text14({
11739
+ children.push(Box14({ flexDirection: "row", width: "100%" }, Text14({ content: padRight(p.displayName, 14), fg: color2, attributes: BOLD3 }), Text14({ content: padLeft(formatTokens15(p.windowTokens), 10), fg: COLORS2.green }), Text14({ content: padLeft(formatCost15(p.windowCost), 10), fg: COLORS2.amber }), Text14({
11389
11740
  content: padLeft(`${(share * 100).toFixed(1)}%`, 8),
11390
11741
  fg: COLORS2.white
11391
11742
  }), Text14({ content: " " }), Text14({ content: asciiBar(share, 10), fg: color2 })));
@@ -11415,10 +11766,10 @@ function createTopModelsPanel(state) {
11415
11766
  const isTop = i === 0;
11416
11767
  const nameColor = isTop ? COLORS2.amber : COLORS2.green;
11417
11768
  children.push(Box14({ flexDirection: "row", width: "100%" }, Text14({ content: padRight(`${i + 1}.`, 3), fg: COLORS2.dimWhite }), Text14({
11418
- content: padRight(truncate2(m.model, 27), 28),
11769
+ content: padRight(truncate3(m.model, 27), 28),
11419
11770
  fg: nameColor,
11420
11771
  attributes: isTop ? BOLD3 : undefined
11421
- }), Text14({ content: padLeft(formatTokens14(m.tokens), 10), fg: COLORS2.green }), Text14({ content: padLeft(formatCost14(m.cost), 10), fg: COLORS2.amber }), Text14({
11772
+ }), Text14({ content: padLeft(formatTokens15(m.tokens), 10), fg: COLORS2.green }), Text14({ content: padLeft(formatCost15(m.cost), 10), fg: COLORS2.amber }), Text14({
11422
11773
  content: padLeft(`${m.percentage.toFixed(1)}%`, 8),
11423
11774
  fg: COLORS2.white
11424
11775
  })));
@@ -11531,7 +11882,7 @@ function renderRecommendation2(rec) {
11531
11882
  fg: confidenceColor2(rec.confidence),
11532
11883
  attributes: BOLD3
11533
11884
  })), Text15({
11534
- content: ` ${rec.description}. Saves ${formatCost14(rec.monthlySavings)}/mo`,
11885
+ content: ` ${rec.description}. Saves ${formatCost15(rec.monthlySavings)}/mo`,
11535
11886
  fg: COLORS2.dimWhite
11536
11887
  }), Text15({ content: "", fg: COLORS2.dimWhite }));
11537
11888
  }
@@ -11552,13 +11903,13 @@ function createAdvisorPanel(state, report) {
11552
11903
  const offset = Math.min(state.advisorScrollOffset, maxOffset);
11553
11904
  const visibleRecs = recs.slice(offset, offset + VISIBLE_ROWS);
11554
11905
  const summaryRow = Box15({ flexDirection: "row", width: "100%", paddingLeft: 1, paddingRight: 1 }, Text15({
11555
- content: `Current: ${formatCost14(report.totalCurrentMonthlyCost)}/mo`,
11906
+ content: `Current: ${formatCost15(report.totalCurrentMonthlyCost)}/mo`,
11556
11907
  fg: COLORS2.white
11557
11908
  }), Text15({ content: " \u2192 ", fg: COLORS2.dimWhite }), Text15({
11558
- content: `Projected: ${formatCost14(report.totalProjectedMonthlyCost)}/mo`,
11909
+ content: `Projected: ${formatCost15(report.totalProjectedMonthlyCost)}/mo`,
11559
11910
  fg: COLORS2.green
11560
11911
  }), Text15({ content: " | ", fg: COLORS2.dimWhite }), Text15({
11561
- content: `Savings: ${formatCost14(report.totalMonthlySavings)}/mo`,
11912
+ content: `Savings: ${formatCost15(report.totalMonthlySavings)}/mo`,
11562
11913
  fg: COLORS2.amber,
11563
11914
  attributes: BOLD3
11564
11915
  }));
@@ -11592,7 +11943,7 @@ function formatHours(ms) {
11592
11943
  return `${(ms / 3600000).toFixed(1)}h`;
11593
11944
  }
11594
11945
  function formatTokPerHour(tokPerHour) {
11595
- return `${formatTokens14(tokPerHour)}/hr`;
11946
+ return `${formatTokens15(tokPerHour)}/hr`;
11596
11947
  }
11597
11948
  function renderEntry(entry, rank, sessionColWidth) {
11598
11949
  const barWidth = 10;
@@ -11600,11 +11951,11 @@ function renderEntry(entry, rank, sessionColWidth) {
11600
11951
  const bar = asciiBar(ratio, barWidth);
11601
11952
  const rankStr = padLeft(`${rank}.`, 3);
11602
11953
  const scoreStr = padLeft(`${Math.round(entry.score)}`, 4);
11603
- const label = truncate2(entry.label || entry.sessionId, sessionColWidth);
11954
+ const label = truncate3(entry.label || entry.sessionId, sessionColWidth);
11604
11955
  const startDate = entry.start ? formatShortDate(entry.start.slice(0, 10)) : "";
11605
11956
  const duration = formatHours(entry.durationMs);
11606
11957
  const density = formatTokPerHour(entry.tokensPerHour);
11607
- const cost = formatCost14(entry.cost);
11958
+ const cost = formatCost15(entry.cost);
11608
11959
  return Box16({ flexDirection: "column", width: "100%", paddingLeft: 1, paddingRight: 1 }, Box16({ flexDirection: "row", width: "100%" }, Text16({ content: `${rankStr} `, fg: COLORS2.dimWhite }), Text16({ content: bar, fg: COLORS2.green }), Text16({ content: ` ${scoreStr} `, fg: COLORS2.amber, attributes: BOLD3 }), Text16({ content: padRight(label, sessionColWidth + 2), fg: COLORS2.white }), Text16({ content: padLeft(startDate, 8), fg: COLORS2.dimWhite }), Text16({ content: padLeft(duration, 7), fg: COLORS2.cyan }), Text16({ content: padLeft(density, 13), fg: COLORS2.green }), Text16({ content: padLeft(cost, 10), fg: COLORS2.amber })), Text16({
11609
11960
  content: ` \u2192 ${entry.rationale.join("; ")}`,
11610
11961
  fg: COLORS2.dimWhite
@@ -11664,7 +12015,7 @@ function renderEvidenceTable2(title, rows) {
11664
12015
  if (rows.length === 0)
11665
12016
  return Box17({ flexDirection: "column", width: "100%" });
11666
12017
  const header = Box17({ flexDirection: "row", width: "100%", paddingLeft: 1, paddingRight: 1 }, Text17({ content: padRight(title, 22), fg: COLORS2.amber, attributes: BOLD3 }), Text17({ content: padLeft("Tokens", 12), fg: COLORS2.dimWhite }), Text17({ content: padLeft("Share", 10), fg: COLORS2.dimWhite }), Text17({ content: padLeft("Cost", 12), fg: COLORS2.dimWhite }));
11667
- const rowNodes = rows.slice(0, 8).map((row) => Box17({ flexDirection: "row", width: "100%", paddingLeft: 1, paddingRight: 1 }, Text17({ content: padRight(row.label, 22), fg: COLORS2.white }), Text17({ content: padLeft(formatTokens14(row.tokens), 12), fg: COLORS2.green }), Text17({ content: padLeft(formatPercent5(row.share), 10), fg: COLORS2.cyan }), Text17({ content: padLeft(formatCost14(row.cost), 12), fg: COLORS2.amber })));
12018
+ const rowNodes = rows.slice(0, 8).map((row) => Box17({ flexDirection: "row", width: "100%", paddingLeft: 1, paddingRight: 1 }, Text17({ content: padRight(row.label, 22), fg: COLORS2.white }), Text17({ content: padLeft(formatTokens15(row.tokens), 12), fg: COLORS2.green }), Text17({ content: padLeft(formatPercent5(row.share), 10), fg: COLORS2.cyan }), Text17({ content: padLeft(formatCost15(row.cost), 12), fg: COLORS2.amber })));
11668
12019
  return Box17({ flexDirection: "column", width: "100%" }, header, ...rowNodes, Text17({ content: "", fg: COLORS2.dimWhite }));
11669
12020
  }
11670
12021
  function renderAnomalies(anomalies) {
@@ -11708,7 +12059,7 @@ function createExplainPanel(report, explainDate) {
11708
12059
  fg: COLORS2.cyan,
11709
12060
  attributes: BOLD3
11710
12061
  }), Text17({ content: "", fg: COLORS2.dimWhite }), Text17({
11711
- content: `Tokens: ${formatTokens14(report.totalTokens)} Cost: ${formatCost14(report.totalCost)} vs 7d avg: ${report.comparedTo7dAverage >= 0 ? "+" : ""}${(report.comparedTo7dAverage * 100).toFixed(0)}% vs 30d avg: ${report.comparedTo30dAverage >= 0 ? "+" : ""}${(report.comparedTo30dAverage * 100).toFixed(0)}%`,
12062
+ content: `Tokens: ${formatTokens15(report.totalTokens)} Cost: ${formatCost15(report.totalCost)} vs 7d avg: ${report.comparedTo7dAverage >= 0 ? "+" : ""}${(report.comparedTo7dAverage * 100).toFixed(0)}% vs 30d avg: ${report.comparedTo30dAverage >= 0 ? "+" : ""}${(report.comparedTo30dAverage * 100).toFixed(0)}%`,
11712
12063
  fg: COLORS2.white
11713
12064
  }), Text17({ content: "", fg: COLORS2.dimWhite }), Text17({ content: "Summary:", fg: COLORS2.amber, attributes: BOLD3 }), ...summaryNodes, Text17({ content: "", fg: COLORS2.dimWhite })), renderEvidenceTable2("Top Providers", report.topProviders), renderEvidenceTable2("Top Models", report.topModels), renderAnomalies(report.anomalies));
11714
12065
  }
@@ -11734,16 +12085,16 @@ function buildMetricRows(output2) {
11734
12085
  return [
11735
12086
  {
11736
12087
  label: "Tokens",
11737
- current: formatTokens14(a.totalTokens),
11738
- previous: formatTokens14(b.totalTokens),
12088
+ current: formatTokens15(a.totalTokens),
12089
+ previous: formatTokens15(b.totalTokens),
11739
12090
  delta: d.tokens,
11740
12091
  deltaLabel: `${deltaArrow(d.tokens)} ${deltaStr(d.tokens, true)}`,
11741
12092
  invertColor: false
11742
12093
  },
11743
12094
  {
11744
12095
  label: "Cost",
11745
- current: formatCost14(a.totalCost),
11746
- previous: formatCost14(b.totalCost),
12096
+ current: formatCost15(a.totalCost),
12097
+ previous: formatCost15(b.totalCost),
11747
12098
  delta: d.cost,
11748
12099
  deltaLabel: `${deltaArrow(d.cost)} ${deltaStr(d.cost, true)}`,
11749
12100
  invertColor: true
@@ -11758,8 +12109,8 @@ function buildMetricRows(output2) {
11758
12109
  },
11759
12110
  {
11760
12111
  label: "Avg Daily Tokens",
11761
- current: formatTokens14(a.averageDailyTokens),
11762
- previous: formatTokens14(b.averageDailyTokens),
12112
+ current: formatTokens15(a.averageDailyTokens),
12113
+ previous: formatTokens15(b.averageDailyTokens),
11763
12114
  delta: d.averageDailyTokens,
11764
12115
  deltaLabel: `${deltaArrow(d.averageDailyTokens)} ${deltaStr(d.averageDailyTokens, true)}`,
11765
12116
  invertColor: false
@@ -11900,7 +12251,7 @@ function createWrappedPanel(stats, achievements, providers, scrollOffset, more)
11900
12251
  const activePct = `${stats.activeDays} / ${totalDays}`;
11901
12252
  const contentRows = [];
11902
12253
  const statRow5 = (...cells) => Box20({ flexDirection: "row", width: "100%", justifyContent: "space-evenly" }, ...cells);
11903
- contentRows.push(Text20({ content: "", fg: COLORS2.dimWhite }), statRow5(bigStat("TOTAL TOKENS", formatTokens14(stats.totalTokens), COLORS2.green), bigStat("TOTAL COST", formatCost14(stats.totalCost), COLORS2.amber), bigStat("ACTIVE DAYS", activePct, COLORS2.cyan)), Text20({ content: "", fg: COLORS2.dimWhite }), statRow5(bigStat("STREAK", `${stats.currentStreak} days`, COLORS2.green), bigStat("CACHE HIT", `${(stats.cacheHitRate * 100).toFixed(1)}%`, COLORS2.cyan), bigStat("AVG DAILY", formatTokens14(stats.averageDailyTokens) + " tok", COLORS2.green)), Text20({ content: "", fg: COLORS2.dimWhite }), statRow5(bigStat("INPUT TOKENS", formatTokens14(stats.totalInputTokens), COLORS2.green), bigStat("OUTPUT TOKENS", formatTokens14(stats.totalOutputTokens), COLORS2.amber), bigStat("PEAK DAY", stats.peakDay ? `${stats.peakDay.date}` : "N/A", COLORS2.cyan)));
12254
+ contentRows.push(Text20({ content: "", fg: COLORS2.dimWhite }), statRow5(bigStat("TOTAL TOKENS", formatTokens15(stats.totalTokens), COLORS2.green), bigStat("TOTAL COST", formatCost15(stats.totalCost), COLORS2.amber), bigStat("ACTIVE DAYS", activePct, COLORS2.cyan)), Text20({ content: "", fg: COLORS2.dimWhite }), statRow5(bigStat("STREAK", `${stats.currentStreak} days`, COLORS2.green), bigStat("CACHE HIT", `${(stats.cacheHitRate * 100).toFixed(1)}%`, COLORS2.cyan), bigStat("AVG DAILY", formatTokens15(stats.averageDailyTokens) + " tok", COLORS2.green)), Text20({ content: "", fg: COLORS2.dimWhite }), statRow5(bigStat("INPUT TOKENS", formatTokens15(stats.totalInputTokens), COLORS2.green), bigStat("OUTPUT TOKENS", formatTokens15(stats.totalOutputTokens), COLORS2.amber), bigStat("PEAK DAY", stats.peakDay ? `${stats.peakDay.date}` : "N/A", COLORS2.cyan)));
11904
12255
  if (achievements.length > 0) {
11905
12256
  contentRows.push(Text20({
11906
12257
  content: "\u2500\u2500\u2500 ACHIEVEMENTS " + "\u2500".repeat(40),
@@ -11924,8 +12275,8 @@ function createWrappedPanel(stats, achievements, providers, scrollOffset, more)
11924
12275
  const cacheReuse = more.cacheEconomics.reuseRatio !== null ? `${more.cacheEconomics.reuseRatio.toFixed(1)}x` : "-";
11925
12276
  contentRows.push(Box20({ flexDirection: "row", width: "100%" }, Text20({ content: " I/O Ratio: ", fg: COLORS2.dimWhite }), Text20({ content: padRight(ioRatio, 10), fg: COLORS2.green }), Text20({ content: "Output Share: ", fg: COLORS2.dimWhite }), Text20({ content: padRight(outputShare, 10), fg: COLORS2.green }), Text20({ content: "Cache Reuse: ", fg: COLORS2.dimWhite }), Text20({ content: cacheReuse, fg: COLORS2.cyan })));
11926
12277
  const burn = more.monthlyBurn;
11927
- const projCost = formatCost14(burn.projectedCost);
11928
- const dailyRate = burn.observedDays > 0 ? formatCost14(burn.projectedCost / (burn.calendarDays || 30)) : "-";
12278
+ const projCost = formatCost15(burn.projectedCost);
12279
+ const dailyRate = burn.observedDays > 0 ? formatCost15(burn.projectedCost / (burn.calendarDays || 30)) : "-";
11929
12280
  contentRows.push(Box20({ flexDirection: "row", width: "100%" }, Text20({ content: " Monthly Burn: ", fg: COLORS2.dimWhite }), Text20({ content: `${projCost} projected`, fg: COLORS2.amber }), Text20({ content: " Burn Rate: ", fg: COLORS2.dimWhite }), Text20({ content: `${dailyRate}/day`, fg: COLORS2.amber })));
11930
12281
  }
11931
12282
  const models = stats.topModels.slice(0, 5);
@@ -11944,7 +12295,7 @@ function createWrappedPanel(stats, achievements, providers, scrollOffset, more)
11944
12295
  content: padRight(m.model, 22),
11945
12296
  fg: isTop ? COLORS2.amber : COLORS2.green,
11946
12297
  attributes: isTop ? BOLD3 : undefined
11947
- }), Text20({ content: asciiBar(ratio, 15), fg: isTop ? COLORS2.amber : COLORS2.green }), Text20({ content: ` ${m.percentage.toFixed(1)}%`, fg: COLORS2.white }), Text20({ content: padLeft(formatCost14(m.cost), 10), fg: COLORS2.amber })));
12298
+ }), Text20({ content: asciiBar(ratio, 15), fg: isTop ? COLORS2.amber : COLORS2.green }), Text20({ content: ` ${m.percentage.toFixed(1)}%`, fg: COLORS2.white }), Text20({ content: padLeft(formatCost15(m.cost), 10), fg: COLORS2.amber })));
11948
12299
  }
11949
12300
  }
11950
12301
  if (providers.length > 0) {
@@ -11956,7 +12307,7 @@ function createWrappedPanel(stats, achievements, providers, scrollOffset, more)
11956
12307
  }));
11957
12308
  for (const p of providers.slice(0, 5)) {
11958
12309
  const pct = (p.totalTokens / totalTokens * 100).toFixed(0);
11959
- contentRows.push(Box20({ flexDirection: "row", width: "100%" }, Text20({ content: ` ${padRight(p.displayName, 16)}`, fg: COLORS2.green, attributes: BOLD3 }), Text20({ content: padLeft(`${formatTokens14(p.totalTokens)} tokens`, 16), fg: COLORS2.green }), Text20({ content: padLeft(formatCost14(p.totalCost), 10), fg: COLORS2.amber }), Text20({ content: padLeft(`${pct}%`, 7), fg: COLORS2.white })));
12310
+ contentRows.push(Box20({ flexDirection: "row", width: "100%" }, Text20({ content: ` ${padRight(p.displayName, 16)}`, fg: COLORS2.green, attributes: BOLD3 }), Text20({ content: padLeft(`${formatTokens15(p.totalTokens)} tokens`, 16), fg: COLORS2.green }), Text20({ content: padLeft(formatCost15(p.totalCost), 10), fg: COLORS2.amber }), Text20({ content: padLeft(`${pct}%`, 7), fg: COLORS2.white })));
11960
12311
  }
11961
12312
  }
11962
12313
  const visible = contentRows.slice(scrollOffset);
@@ -12046,12 +12397,169 @@ var init_help = __esm(() => {
12046
12397
  init_theme2();
12047
12398
  });
12048
12399
 
12400
+ // packages/tui/dist/panels/replay.js
12401
+ import { Box as Box22, Text as Text22 } from "@opentui/core";
12402
+ function formatTime2(iso) {
12403
+ const date = new Date(iso);
12404
+ return `${String(date.getUTCHours()).padStart(2, "0")}:${String(date.getUTCMinutes()).padStart(2, "0")}`;
12405
+ }
12406
+ function formatDuration7(ms) {
12407
+ if (ms <= 0)
12408
+ return "0s";
12409
+ const hours = Math.floor(ms / 3600000);
12410
+ const minutes = Math.floor(ms % 3600000 / 60000);
12411
+ if (hours > 0)
12412
+ return `${hours}h ${minutes}m`;
12413
+ return `${minutes}m`;
12414
+ }
12415
+ function renderActivityBar2(report) {
12416
+ if (report.events.length === 0) {
12417
+ return Text22({ content: " (no events)", fg: COLORS2.dimWhite });
12418
+ }
12419
+ const slotTokens = new Array(HEATMAP_SLOTS2).fill(0);
12420
+ for (const event of report.events) {
12421
+ const date = new Date(event.timestamp);
12422
+ const slot = Math.min(date.getUTCHours() * 2 + Math.floor(date.getUTCMinutes() / 30), HEATMAP_SLOTS2 - 1);
12423
+ slotTokens[slot] += event.totalTokens;
12424
+ }
12425
+ const maxTokens = Math.max(...slotTokens);
12426
+ let bar = "";
12427
+ for (let i = 0;i < HEATMAP_SLOTS2; i++) {
12428
+ const level = maxTokens > 0 ? Math.round(slotTokens[i] / maxTokens * (HEATMAP_BLOCKS3.length - 1)) : 0;
12429
+ bar += HEATMAP_BLOCKS3[level];
12430
+ }
12431
+ const firstTime = formatTime2(report.events[0].timestamp);
12432
+ const lastTime = formatTime2(report.events[report.events.length - 1].timestamp);
12433
+ return Box22({ flexDirection: "column", width: "100%", paddingLeft: 1, paddingRight: 1 }, Text22({ content: bar, fg: COLORS2.green }), Text22({ content: `${firstTime}${" ".repeat(Math.max(1, HEATMAP_SLOTS2 - firstTime.length - lastTime.length))}${lastTime}`, fg: COLORS2.dimWhite }));
12434
+ }
12435
+ function renderFlowBlockCard2(block, expanded) {
12436
+ const timeRange = `${formatTime2(block.start)}\u2013${formatTime2(block.end)}`;
12437
+ const headerText = `${timeRange} ${block.label} | ${block.eventCount} events | ${formatTokens15(block.totalTokens)} tok | ${formatCost15(block.cost)}`;
12438
+ const expandIcon = expanded ? "\u25BC" : "\u25B6";
12439
+ const headerLine = Text22({
12440
+ content: ` ${expandIcon} ${headerText}`,
12441
+ fg: block.label === "Deep Flow" ? COLORS2.cyan : block.label === "Quick Lookup" ? COLORS2.dimWhite : COLORS2.white,
12442
+ attributes: BOLD3
12443
+ });
12444
+ if (!expanded) {
12445
+ return Box22({ flexDirection: "column", width: "100%" }, headerLine);
12446
+ }
12447
+ const children = [headerLine];
12448
+ children.push(Text22({
12449
+ content: ` Model: ${block.dominantModel}${block.modelSwitches > 0 ? ` (${block.modelSwitches} switch${block.modelSwitches === 1 ? "" : "es"})` : ""}`,
12450
+ fg: COLORS2.white
12451
+ }));
12452
+ for (const event of block.events) {
12453
+ const time = formatTime2(event.timestamp);
12454
+ const cacheRate = event.inputTokens + event.cacheReadTokens > 0 ? event.cacheReadTokens / (event.inputTokens + event.cacheReadTokens) : 0;
12455
+ const line2 = ` ${padRight(time, 7)} ${padRight(truncate3(event.model, 18), 19)} ${padLeft(formatTokens15(event.totalTokens), 8)} cache:${formatPercent5(cacheRate).padStart(4)} ${formatCost15(event.cost).padStart(8)}`;
12456
+ children.push(Text22({ content: line2, fg: COLORS2.white }));
12457
+ }
12458
+ const trend = block.cacheHitRateTrend;
12459
+ if (trend.length > 1) {
12460
+ const first = (trend[0] * 100).toFixed(0);
12461
+ const last = (trend[trend.length - 1] * 100).toFixed(0);
12462
+ if (first !== last) {
12463
+ const direction = Number(last) > Number(first) ? "\u2191" : "\u2193";
12464
+ children.push(Text22({
12465
+ content: ` Cache: ${first}% \u2192 ${last}% ${direction}`,
12466
+ fg: Number(last) > Number(first) ? COLORS2.green : COLORS2.red
12467
+ }));
12468
+ }
12469
+ }
12470
+ children.push(Text22({ content: "", fg: COLORS2.dimWhite }));
12471
+ return Box22({ flexDirection: "column", width: "100%" }, ...children);
12472
+ }
12473
+ function renderPulseChart2(velocity) {
12474
+ if (velocity.length === 0) {
12475
+ return Box22({ flexDirection: "column", width: "100%", paddingLeft: 1, paddingRight: 1 }, Text22({ content: "Pulse", fg: COLORS2.amber, attributes: BOLD3 }), Text22({ content: " (no data)", fg: COLORS2.dimWhite }));
12476
+ }
12477
+ const maxTpm = Math.max(...velocity.map((v) => v.tokensPerMinute));
12478
+ const chartWidth = Math.min(velocity.length, 60);
12479
+ const chartHeight = 5;
12480
+ const step = velocity.length / chartWidth;
12481
+ const rows = [];
12482
+ for (let row = 0;row < chartHeight; row++) {
12483
+ let line2 = "";
12484
+ for (let col = 0;col < chartWidth; col++) {
12485
+ const idx = Math.floor(col * step);
12486
+ const tpm = velocity[idx].tokensPerMinute;
12487
+ const normalizedHeight = maxTpm > 0 ? tpm / maxTpm * (chartHeight - 1) : 0;
12488
+ const threshold = chartHeight - 1 - row;
12489
+ line2 += normalizedHeight >= threshold ? "\u2588" : " ";
12490
+ }
12491
+ rows.push(line2);
12492
+ }
12493
+ const children = [
12494
+ Text22({ content: "Pulse (tok/min)", fg: COLORS2.amber, attributes: BOLD3 })
12495
+ ];
12496
+ for (let i = 0;i < rows.length; i++) {
12497
+ const label = i === 0 ? padLeft(formatTokens15(maxTpm), 7) : i === rows.length - 1 ? padLeft("0", 7) : " ";
12498
+ children.push(Text22({ content: `${label} \u2502${rows[i]}`, fg: COLORS2.green }));
12499
+ }
12500
+ return Box22({ flexDirection: "column", width: "100%", paddingLeft: 1, paddingRight: 1 }, ...children);
12501
+ }
12502
+ function renderDaySummary(report) {
12503
+ const s = report.summary;
12504
+ const parts = [
12505
+ `Sessions: ${s.totalSessions}`,
12506
+ `Events: ${s.totalEvents}`,
12507
+ `Flow: ${formatDuration7(s.flowTimeMs)}`,
12508
+ `Think: ${formatDuration7(s.thinkTimeMs)}`,
12509
+ `Ratio: ${(s.flowThinkRatio * 100).toFixed(0)}%`
12510
+ ];
12511
+ if (s.peakMinute) {
12512
+ parts.push(`Peak: ${formatTokens15(s.peakMinute.tokensPerMinute)} tok/min at ${formatTime2(s.peakMinute.minute)}`);
12513
+ }
12514
+ return Box22({ flexDirection: "column", width: "100%", paddingLeft: 1, paddingRight: 1 }, Text22({ content: parts.join(" | "), fg: COLORS2.white }));
12515
+ }
12516
+ function createReplayPanel(report, replayDate, expandedBlocks, scrollOffset) {
12517
+ const dateLabel = replayDate ? formatShortDate(replayDate) : "\u2014";
12518
+ if (!report) {
12519
+ return Box22({
12520
+ flexDirection: "column",
12521
+ width: "100%",
12522
+ flexGrow: 1,
12523
+ borderStyle: "single",
12524
+ borderColor: COLORS2.dimWhite,
12525
+ paddingLeft: 1,
12526
+ paddingRight: 1
12527
+ }, Text22({
12528
+ content: ` Replay: ${dateLabel} \u25C4 \u25BA `,
12529
+ fg: COLORS2.amber,
12530
+ attributes: BOLD3
12531
+ }), Text22({ content: "", fg: COLORS2.dimWhite }), Text22({ content: "No data available for this date", fg: COLORS2.dimWhite }));
12532
+ }
12533
+ const totalCost = report.events.reduce((sum, e) => sum + e.cost, 0);
12534
+ const blockCards = report.flowBlocks.slice(scrollOffset, scrollOffset + 20).map((block) => renderFlowBlockCard2(block, expandedBlocks.has(block.blockIndex)));
12535
+ return Box22({
12536
+ flexDirection: "column",
12537
+ width: "100%",
12538
+ flexGrow: 1,
12539
+ borderStyle: "single",
12540
+ borderColor: COLORS2.dimWhite
12541
+ }, Text22({
12542
+ content: ` Replay: ${dateLabel} \u25C4 \u25BA `,
12543
+ fg: COLORS2.amber,
12544
+ attributes: BOLD3
12545
+ }), Box22({ flexDirection: "row", width: "100%", paddingLeft: 1, paddingRight: 1 }, Text22({ content: `Total: ${formatCost15(totalCost)}`, fg: COLORS2.green })), Text22({ content: "", fg: COLORS2.dimWhite }), renderActivityBar2(report), Text22({ content: "", fg: COLORS2.dimWhite }), Box22({ flexDirection: "column", width: "100%", paddingLeft: 1, paddingRight: 1 }, Text22({
12546
+ content: `Flow Blocks (${report.flowBlocks.length})`,
12547
+ fg: COLORS2.amber,
12548
+ attributes: BOLD3
12549
+ })), ...blockCards, Text22({ content: "", fg: COLORS2.dimWhite }), renderPulseChart2(report.tokenVelocity), Text22({ content: "", fg: COLORS2.dimWhite }), renderDaySummary(report));
12550
+ }
12551
+ var HEATMAP_BLOCKS3, HEATMAP_SLOTS2 = 48;
12552
+ var init_replay2 = __esm(() => {
12553
+ init_theme2();
12554
+ HEATMAP_BLOCKS3 = [" ", "\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
12555
+ });
12556
+
12049
12557
  // packages/tui/dist/index.js
12050
12558
  var exports_dist2 = {};
12051
12559
  __export(exports_dist2, {
12052
12560
  main: () => main
12053
12561
  });
12054
- import { Box as Box22, Text as Text22, createCliRenderer } from "@opentui/core";
12562
+ import { Box as Box23, Text as Text23, createCliRenderer } from "@opentui/core";
12055
12563
  function clearRoot(renderer) {
12056
12564
  const children = renderer.root.getChildren();
12057
12565
  for (const child of children) {
@@ -12085,7 +12593,7 @@ function buildContent(state, renderer) {
12085
12593
  const daily = state.data ? getDailyForWindow(state.data, state.selectedWindowIndex) : [];
12086
12594
  switch (state.selectedView) {
12087
12595
  case "overview":
12088
- return Box22({ flexDirection: "column", width: "100%", flexGrow: 1 }, createChartPanel(state, daily), createStatsRow(state, windowStats), createModelList(state, windowStats));
12596
+ return Box23({ flexDirection: "column", width: "100%", flexGrow: 1 }, createChartPanel(state, daily), createStatsRow(state, windowStats), createModelList(state, windowStats));
12089
12597
  case "matrix":
12090
12598
  return createMatrixView(state);
12091
12599
  case "advisor":
@@ -12098,6 +12606,8 @@ function buildContent(state, renderer) {
12098
12606
  return createComparePanel(state, ensureCompareOutput(state));
12099
12607
  case "export":
12100
12608
  return createExportPanel(state);
12609
+ case "replay":
12610
+ return createReplayPanel(ensureReplayReport(state), state.replayDate, state.replayExpandedBlocks, state.replayScrollOffset);
12101
12611
  case "wrapped": {
12102
12612
  const output2 = buildTokenleakOutput(state);
12103
12613
  const achievements = output2 ? computeAchievements(output2) : [];
@@ -12109,7 +12619,7 @@ function buildContent(state, renderer) {
12109
12619
  return createWrappedPanel(windowStats, achievements, providers, state.wrappedScrollOffset, ensureMoreStats(state));
12110
12620
  }
12111
12621
  default:
12112
- return Box22({ flexDirection: "column", width: "100%", flexGrow: 1 });
12622
+ return Box23({ flexDirection: "column", width: "100%", flexGrow: 1 });
12113
12623
  }
12114
12624
  }
12115
12625
  function buildTokenleakOutput(state) {
@@ -12164,6 +12674,9 @@ function applyLoadedData(state, freshData) {
12164
12674
  state.focusScrollOffset = 0;
12165
12675
  state.compareScrollOffset = 0;
12166
12676
  state.wrappedScrollOffset = 0;
12677
+ state.replayScrollOffset = 0;
12678
+ state.replayExpandedBlocks = new Set;
12679
+ state.replayDate = null;
12167
12680
  state.explainDate = null;
12168
12681
  state.cursorSetupStatusOverride = null;
12169
12682
  }
@@ -12253,6 +12766,8 @@ function handleViewSwitch(mode) {
12253
12766
  currentState.focusScrollOffset = 0;
12254
12767
  currentState.compareScrollOffset = 0;
12255
12768
  currentState.wrappedScrollOffset = 0;
12769
+ currentState.replayScrollOffset = 0;
12770
+ currentState.replayExpandedBlocks = new Set;
12256
12771
  if (mode === "matrix") {
12257
12772
  currentState.matrixPage = 0;
12258
12773
  }
@@ -12261,7 +12776,7 @@ function handleViewSwitch(mode) {
12261
12776
  }
12262
12777
  function buildLayout(state, renderer) {
12263
12778
  const cursorBanner = buildCursorBanner(state);
12264
- return Box22({
12779
+ return Box23({
12265
12780
  flexDirection: "column",
12266
12781
  width: "100%",
12267
12782
  height: "100%",
@@ -12290,7 +12805,9 @@ function invalidateWindowCaches(state) {
12290
12805
  state.cachedFocusReport = null;
12291
12806
  state.cachedExplainReport = null;
12292
12807
  state.cachedMoreStats = null;
12808
+ state.cachedReplayReport = null;
12293
12809
  state.explainDate = null;
12810
+ state.replayDate = null;
12294
12811
  }
12295
12812
  function invalidateAllCaches(state) {
12296
12813
  state.cachedAdvisorReport = null;
@@ -12298,6 +12815,17 @@ function invalidateAllCaches(state) {
12298
12815
  state.cachedExplainReport = null;
12299
12816
  state.cachedCompareOutput = null;
12300
12817
  state.cachedMoreStats = null;
12818
+ state.cachedReplayReport = null;
12819
+ }
12820
+ function shiftReplayDate(state, direction) {
12821
+ if (!state.replayDate)
12822
+ return;
12823
+ const d = new Date(state.replayDate + "T12:00:00Z");
12824
+ d.setUTCDate(d.getUTCDate() + direction);
12825
+ state.replayDate = d.toISOString().slice(0, 10);
12826
+ state.cachedReplayReport = null;
12827
+ state.replayScrollOffset = 0;
12828
+ state.replayExpandedBlocks = new Set;
12301
12829
  }
12302
12830
  function shiftExplainDate(state, direction) {
12303
12831
  if (!state.explainDate)
@@ -12319,6 +12847,8 @@ function getScrollableItemCount(state) {
12319
12847
  return 6;
12320
12848
  case "wrapped":
12321
12849
  return 30;
12850
+ case "replay":
12851
+ return ensureReplayReport(state)?.flowBlocks.length ?? 0;
12322
12852
  default:
12323
12853
  return 0;
12324
12854
  }
@@ -12333,6 +12863,8 @@ function getVisibleCount(view) {
12333
12863
  return 6;
12334
12864
  case "wrapped":
12335
12865
  return 20;
12866
+ case "replay":
12867
+ return 15;
12336
12868
  default:
12337
12869
  return 10;
12338
12870
  }
@@ -12347,6 +12879,8 @@ function getScrollOffset(state) {
12347
12879
  return state.compareScrollOffset;
12348
12880
  case "wrapped":
12349
12881
  return state.wrappedScrollOffset;
12882
+ case "replay":
12883
+ return state.replayScrollOffset;
12350
12884
  default:
12351
12885
  return 0;
12352
12886
  }
@@ -12365,6 +12899,9 @@ function setScrollOffset(state, value) {
12365
12899
  case "wrapped":
12366
12900
  state.wrappedScrollOffset = value;
12367
12901
  break;
12902
+ case "replay":
12903
+ state.replayScrollOffset = value;
12904
+ break;
12368
12905
  }
12369
12906
  }
12370
12907
  async function handleExport(key, state, renderer) {
@@ -12388,18 +12925,18 @@ async function handleExport(key, state, renderer) {
12388
12925
  noColor: false,
12389
12926
  output: null
12390
12927
  });
12391
- const { writeFileSync: writeFileSync2 } = await import("fs");
12928
+ const { writeFileSync: writeFileSync3 } = await import("fs");
12392
12929
  const outputPath = "tokenleak.png";
12393
- writeFileSync2(outputPath, buffer);
12930
+ writeFileSync3(outputPath, buffer);
12394
12931
  state.exportStatus = `Saved to ${outputPath}`;
12395
12932
  } else if (key === "w") {
12396
12933
  state.exportStatus = "Rendering Wrapped PNG...";
12397
12934
  render(state, renderer);
12398
12935
  const { renderWrappedPng: renderWrappedPng2 } = await Promise.resolve().then(() => (init_dist3(), exports_dist));
12399
12936
  const buffer = await renderWrappedPng2(output2, { theme: "dark" });
12400
- const { writeFileSync: writeFileSync2 } = await import("fs");
12937
+ const { writeFileSync: writeFileSync3 } = await import("fs");
12401
12938
  const outputPath = "tokenleak-wrapped.png";
12402
- writeFileSync2(outputPath, buffer);
12939
+ writeFileSync3(outputPath, buffer);
12403
12940
  state.exportStatus = `Saved to ${outputPath}`;
12404
12941
  } else if (key === "l") {
12405
12942
  state.exportStatus = "Starting live server...";
@@ -12547,11 +13084,31 @@ async function main() {
12547
13084
  render(state, renderer);
12548
13085
  return true;
12549
13086
  }
13087
+ if (sequence === "h" && state.selectedView === "replay") {
13088
+ shiftReplayDate(state, -1);
13089
+ render(state, renderer);
13090
+ return true;
13091
+ }
12550
13092
  if (sequence === "l" && state.selectedView === "explain") {
12551
13093
  shiftExplainDate(state, 1);
12552
13094
  render(state, renderer);
12553
13095
  return true;
12554
13096
  }
13097
+ if (sequence === "l" && state.selectedView === "replay") {
13098
+ shiftReplayDate(state, 1);
13099
+ render(state, renderer);
13100
+ return true;
13101
+ }
13102
+ if (sequence === "\r" && state.selectedView === "replay") {
13103
+ const blockIndex = state.replayScrollOffset;
13104
+ if (state.replayExpandedBlocks.has(blockIndex)) {
13105
+ state.replayExpandedBlocks.delete(blockIndex);
13106
+ } else {
13107
+ state.replayExpandedBlocks.add(blockIndex);
13108
+ }
13109
+ render(state, renderer);
13110
+ return true;
13111
+ }
12555
13112
  if (sequence === "s") {
12556
13113
  state.sortMode = state.sortMode === "cost" ? "tokens" : "cost";
12557
13114
  state.modelScrollOffset = 0;
@@ -12570,21 +13127,21 @@ async function main() {
12570
13127
  });
12571
13128
  } catch (err) {
12572
13129
  clearRoot(renderer);
12573
- renderer.root.add(Box22({
13130
+ renderer.root.add(Box23({
12574
13131
  flexDirection: "column",
12575
13132
  width: "100%",
12576
13133
  height: "100%",
12577
13134
  backgroundColor: COLORS2.bg,
12578
13135
  justifyContent: "center",
12579
13136
  alignItems: "center"
12580
- }, Text22({
13137
+ }, Text23({
12581
13138
  content: "TOKENLEAK TUI",
12582
13139
  fg: COLORS2.amber,
12583
13140
  attributes: BOLD3
12584
- }), Text22({
13141
+ }), Text23({
12585
13142
  content: `Error: ${err instanceof Error ? err.message : String(err)}`,
12586
13143
  fg: COLORS2.red
12587
- }), Text22({
13144
+ }), Text23({
12588
13145
  content: "Press q to quit",
12589
13146
  fg: COLORS2.dimWhite
12590
13147
  })));
@@ -12619,6 +13176,7 @@ var init_dist4 = __esm(() => {
12619
13176
  init_export();
12620
13177
  init_wrapped2();
12621
13178
  init_help();
13179
+ init_replay2();
12622
13180
  init_cursor_setup();
12623
13181
  VIEW_KEYS = {
12624
13182
  "1": "overview",
@@ -12628,7 +13186,8 @@ var init_dist4 = __esm(() => {
12628
13186
  "5": "explain",
12629
13187
  "6": "compare",
12630
13188
  "7": "export",
12631
- "8": "wrapped"
13189
+ "8": "wrapped",
13190
+ "9": "replay"
12632
13191
  };
12633
13192
  VIEW_ORDER = [
12634
13193
  "overview",
@@ -12638,9 +13197,10 @@ var init_dist4 = __esm(() => {
12638
13197
  "explain",
12639
13198
  "compare",
12640
13199
  "export",
12641
- "wrapped"
13200
+ "wrapped",
13201
+ "replay"
12642
13202
  ];
12643
- SCROLLABLE_VIEWS = new Set(["advisor", "focus", "compare", "wrapped"]);
13203
+ SCROLLABLE_VIEWS = new Set(["advisor", "focus", "compare", "wrapped", "replay"]);
12644
13204
  });
12645
13205
 
12646
13206
  // node_modules/citty/dist/_chunks/libs/scule.mjs
@@ -13059,17 +13619,17 @@ async function runMain(cmd, opts = {}) {
13059
13619
  init_dist();
13060
13620
  init_dist2();
13061
13621
  init_dist3();
13062
- import { writeFileSync as writeFileSync2 } from "fs";
13622
+ import { writeFileSync as writeFileSync3 } from "fs";
13063
13623
 
13064
13624
  // packages/cli/src/config.ts
13065
- import { readFileSync as readFileSync4 } from "fs";
13066
- import { join as join7 } from "path";
13067
- import { homedir as homedir7 } from "os";
13625
+ import { readFileSync as readFileSync5 } from "fs";
13626
+ import { join as join8 } from "path";
13627
+ import { homedir as homedir8 } from "os";
13068
13628
  var CONFIG_FILENAME = ".tokenleakrc";
13069
13629
  function loadConfig() {
13070
13630
  try {
13071
- const configPath = join7(homedir7(), CONFIG_FILENAME);
13072
- const raw = readFileSync4(configPath, "utf-8");
13631
+ const configPath = join8(homedir8(), CONFIG_FILENAME);
13632
+ const raw = readFileSync5(configPath, "utf-8");
13073
13633
  const parsed = JSON.parse(raw);
13074
13634
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
13075
13635
  return parsed;
@@ -13591,6 +14151,189 @@ function buildExplainHelpText() {
13591
14151
  `);
13592
14152
  }
13593
14153
 
14154
+ // packages/cli/src/replay.ts
14155
+ function formatTokens14(tokens) {
14156
+ if (tokens >= 1e6) {
14157
+ return `${(tokens / 1e6).toFixed(1)}M`;
14158
+ }
14159
+ if (tokens >= 1000) {
14160
+ return `${(tokens / 1000).toFixed(1)}K`;
14161
+ }
14162
+ return `${Math.round(tokens)}`;
14163
+ }
14164
+ function formatCost14(cost) {
14165
+ return `$${cost.toFixed(2)}`;
14166
+ }
14167
+ function formatDuration4(ms) {
14168
+ if (ms <= 0) {
14169
+ return "0s";
14170
+ }
14171
+ const hours = Math.floor(ms / 3600000);
14172
+ const minutes = Math.floor(ms % 3600000 / 60000);
14173
+ const seconds = Math.floor(ms % 60000 / 1000);
14174
+ if (hours > 0) {
14175
+ return `${hours}h ${minutes}m`;
14176
+ }
14177
+ if (minutes > 0) {
14178
+ return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
14179
+ }
14180
+ return `${seconds}s`;
14181
+ }
14182
+ function formatTime(iso) {
14183
+ const date = new Date(iso);
14184
+ return `${String(date.getUTCHours()).padStart(2, "0")}:${String(date.getUTCMinutes()).padStart(2, "0")}`;
14185
+ }
14186
+ function truncate2(value, width) {
14187
+ if (value.length <= width) {
14188
+ return value;
14189
+ }
14190
+ if (width <= 3) {
14191
+ return ".".repeat(Math.max(0, width));
14192
+ }
14193
+ return `${value.slice(0, width - 3)}...`;
14194
+ }
14195
+ var HEATMAP_SLOTS = 48;
14196
+ var HEATMAP_BLOCKS2 = [" ", "\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
14197
+ function renderActivityBar(report, width) {
14198
+ if (report.events.length === 0) {
14199
+ return ["Activity", " (no events)"];
14200
+ }
14201
+ const slotTokens = new Array(HEATMAP_SLOTS).fill(0);
14202
+ for (const event of report.events) {
14203
+ const date = new Date(event.timestamp);
14204
+ const hour = date.getUTCHours();
14205
+ const minute = date.getUTCMinutes();
14206
+ const slot = Math.min(hour * 2 + Math.floor(minute / 30), HEATMAP_SLOTS - 1);
14207
+ slotTokens[slot] += event.totalTokens;
14208
+ }
14209
+ const maxTokens = Math.max(...slotTokens);
14210
+ const barWidth = Math.min(HEATMAP_SLOTS, width - 4);
14211
+ const step = HEATMAP_SLOTS / barWidth;
14212
+ let bar = " ";
14213
+ for (let i = 0;i < barWidth; i++) {
14214
+ const slotIndex = Math.floor(i * step);
14215
+ const tokens = slotTokens[slotIndex];
14216
+ const level = maxTokens > 0 ? Math.round(tokens / maxTokens * (HEATMAP_BLOCKS2.length - 1)) : 0;
14217
+ bar += HEATMAP_BLOCKS2[level];
14218
+ }
14219
+ const firstTime = formatTime(report.events[0].timestamp);
14220
+ const lastTime = formatTime(report.events[report.events.length - 1].timestamp);
14221
+ const timeLabel = ` ${firstTime}${" ".repeat(Math.max(1, barWidth - firstTime.length - lastTime.length))}${lastTime}`;
14222
+ return ["Activity", bar, timeLabel];
14223
+ }
14224
+ function renderFlowBlockCard(block, width) {
14225
+ const timeRange = `${formatTime(block.start)}-${formatTime(block.end)}`;
14226
+ const header = ` [${timeRange}] ${block.label} | ${block.eventCount} events | ${formatTokens14(block.totalTokens)} tok | ${formatCost14(block.cost)}`;
14227
+ const lines = [truncate2(header, width)];
14228
+ const modelInfo = ` Model: ${block.dominantModel}${block.modelSwitches > 0 ? ` (${block.modelSwitches} switch${block.modelSwitches === 1 ? "" : "es"})` : ""}`;
14229
+ lines.push(truncate2(modelInfo, width));
14230
+ const trend = block.cacheHitRateTrend;
14231
+ if (trend.length > 0) {
14232
+ const firstRate = (trend[0] * 100).toFixed(0);
14233
+ const lastRate = (trend[trend.length - 1] * 100).toFixed(0);
14234
+ if (trend.length > 1 && firstRate !== lastRate) {
14235
+ lines.push(` Cache: ${firstRate}% -> ${lastRate}%`);
14236
+ } else {
14237
+ lines.push(` Cache: ${firstRate}%`);
14238
+ }
14239
+ }
14240
+ return lines;
14241
+ }
14242
+ function renderPulseChart(velocity, width) {
14243
+ if (velocity.length === 0) {
14244
+ return ["Pulse", " (no data)"];
14245
+ }
14246
+ const maxTpm = Math.max(...velocity.map((v) => v.tokensPerMinute));
14247
+ const chartWidth = Math.min(velocity.length, width - 10);
14248
+ const chartHeight = 5;
14249
+ const step = velocity.length / chartWidth;
14250
+ const grid = [];
14251
+ for (let row = 0;row < chartHeight; row++) {
14252
+ grid.push(new Array(chartWidth).fill(" "));
14253
+ }
14254
+ for (let col = 0;col < chartWidth; col++) {
14255
+ const idx = Math.floor(col * step);
14256
+ const tpm = velocity[idx].tokensPerMinute;
14257
+ const height = maxTpm > 0 ? Math.round(tpm / maxTpm * (chartHeight - 1)) : 0;
14258
+ for (let row = chartHeight - 1;row >= chartHeight - 1 - height; row--) {
14259
+ if (row >= 0) {
14260
+ grid[row][col] = "\u2588";
14261
+ }
14262
+ }
14263
+ }
14264
+ const lines = ["Pulse (tokens/min)"];
14265
+ const maxLabel = formatTokens14(maxTpm);
14266
+ for (let row = 0;row < chartHeight; row++) {
14267
+ const label = row === 0 ? maxLabel.padStart(7) : row === chartHeight - 1 ? " 0" : " ";
14268
+ lines.push(`${label} |${grid[row].join("")}`);
14269
+ }
14270
+ return lines;
14271
+ }
14272
+ function renderReplayTerminal(report, width = 80) {
14273
+ const lines = [
14274
+ `Session Replay: ${report.date}`,
14275
+ ""
14276
+ ];
14277
+ lines.push(...renderActivityBar(report, width));
14278
+ lines.push("");
14279
+ if (report.flowBlocks.length === 0) {
14280
+ lines.push("Flow Blocks", " (no activity)");
14281
+ } else {
14282
+ lines.push(`Flow Blocks (${report.flowBlocks.length})`);
14283
+ for (const block of report.flowBlocks) {
14284
+ lines.push(...renderFlowBlockCard(block, width));
14285
+ lines.push("");
14286
+ }
14287
+ }
14288
+ lines.push(...renderPulseChart(report.tokenVelocity, width));
14289
+ lines.push("");
14290
+ const s = report.summary;
14291
+ const summaryParts = [
14292
+ `Sessions: ${s.totalSessions}`,
14293
+ `Events: ${s.totalEvents}`,
14294
+ `Flow: ${formatDuration4(s.flowTimeMs)}`,
14295
+ `Think: ${formatDuration4(s.thinkTimeMs)}`,
14296
+ `Ratio: ${(s.flowThinkRatio * 100).toFixed(0)}%`
14297
+ ];
14298
+ if (s.peakMinute) {
14299
+ summaryParts.push(`Peak: ${formatTokens14(s.peakMinute.tokensPerMinute)} tok/min at ${formatTime(s.peakMinute.minute)}`);
14300
+ }
14301
+ lines.push(truncate2(summaryParts.join(" | "), width));
14302
+ return lines.join(`
14303
+ `);
14304
+ }
14305
+ function buildReplayHelpText() {
14306
+ return [
14307
+ "Usage:",
14308
+ " tokenleak replay [date] [flags]",
14309
+ "",
14310
+ "Arguments:",
14311
+ " date Date to replay in YYYY-MM-DD format (defaults to today)",
14312
+ "",
14313
+ "Replay Flags:",
14314
+ " -f, --format <format> Output format: terminal, json",
14315
+ " -o, --output <path> Write output to a file and infer format from extension",
14316
+ " -w, --width <number> Terminal render width",
14317
+ " -p, --provider <list> Provider filter list, comma-separated",
14318
+ " --claude Only include Claude Code",
14319
+ " --codex Only include Codex",
14320
+ " --cursor Only include Cursor",
14321
+ " --pi Only include Pi",
14322
+ " --open-code Only include OpenCode",
14323
+ " --all-providers Ignore provider filters and use every available provider",
14324
+ " --no-color Accepted for parity with terminal output",
14325
+ " --help Show replay help",
14326
+ "",
14327
+ "Examples:",
14328
+ " tokenleak replay",
14329
+ " tokenleak replay 2026-03-10",
14330
+ " tokenleak replay 2026-03-10 --format json",
14331
+ " tokenleak replay --provider claude --output replay.json",
14332
+ ""
14333
+ ].join(`
14334
+ `);
14335
+ }
14336
+
13594
14337
  // packages/cli/src/flags.ts
13595
14338
  var CLI_FLAG_ORDER = [
13596
14339
  "format",
@@ -15173,11 +15916,11 @@ async function uploadToGist(content, filename, description) {
15173
15916
  if (!available) {
15174
15917
  throw new Error("GitHub CLI (gh) is not installed or not authenticated. " + "Install it from https://cli.github.com and run `gh auth login`.");
15175
15918
  }
15176
- const { join: join8 } = await import("path");
15177
- const { tmpdir } = await import("os");
15178
- const { writeFileSync: writeFileSync2, unlinkSync: unlinkSync2 } = await import("fs");
15179
- const tmpPath = join8(tmpdir(), `tokenleak-gist-${Date.now()}-${filename}`);
15180
- writeFileSync2(tmpPath, content, "utf-8");
15919
+ const { join: join9 } = await import("path");
15920
+ const { tmpdir: tmpdir2 } = await import("os");
15921
+ const { writeFileSync: writeFileSync3, unlinkSync: unlinkSync2 } = await import("fs");
15922
+ const tmpPath = join9(tmpdir2(), `tokenleak-gist-${Date.now()}-${filename}`);
15923
+ writeFileSync3(tmpPath, content, "utf-8");
15181
15924
  try {
15182
15925
  const proc = Bun.spawn(["gh", "gist", "create", tmpPath, "--desc", description, "--public"], {
15183
15926
  stdout: "pipe",
@@ -15594,11 +16337,13 @@ function buildHelpText() {
15594
16337
  " tokenleak [flags]",
15595
16338
  " tokenleak explain <date> [flags]",
15596
16339
  " tokenleak focus [flags]",
16340
+ " tokenleak replay [date] [flags]",
15597
16341
  " tokenleak cursor <command>",
15598
16342
  "",
15599
16343
  "Subcommands:",
15600
16344
  " explain <date> Explain what drove usage on one day",
15601
16345
  " focus Rank sessions by deep-work score",
16346
+ " replay [date] Replay a day's session timeline (defaults to today)",
15602
16347
  " cursor Manage Cursor auth and cache sync",
15603
16348
  "",
15604
16349
  "Provider Shortcuts:",
@@ -15648,6 +16393,8 @@ function buildHelpText() {
15648
16393
  " tokenleak explain 2026-03-10",
15649
16394
  " tokenleak explain 2026-03-10 --format json",
15650
16395
  " tokenleak focus --provider codex --days 30",
16396
+ " tokenleak replay",
16397
+ " tokenleak replay 2026-03-10 --format json",
15651
16398
  "",
15652
16399
  "Version:",
15653
16400
  ` CLI ${VERSION}`,
@@ -16361,7 +17108,7 @@ async function runFocus(cliArgs) {
16361
17108
  if (events.length === 0) {
16362
17109
  const emptyMsg = config.format === "json" ? JSON.stringify({ method: "No event data", entries: [] }, null, 2) : renderFocusReport({ method: "No event-level data found for focus analysis.", entries: [] }, config.width, config.noColor);
16363
17110
  if (config.output) {
16364
- writeFileSync2(config.output, emptyMsg);
17111
+ writeFileSync3(config.output, emptyMsg);
16365
17112
  } else {
16366
17113
  process.stdout.write(`${emptyMsg}
16367
17114
  `);
@@ -16371,7 +17118,7 @@ async function runFocus(cliArgs) {
16371
17118
  const report = buildFocusReport(events);
16372
17119
  const rendered = config.format === "json" ? JSON.stringify(report, null, 2) : renderFocusReport(report, config.width, config.noColor);
16373
17120
  if (config.output) {
16374
- writeFileSync2(config.output, rendered);
17121
+ writeFileSync3(config.output, rendered);
16375
17122
  } else {
16376
17123
  process.stdout.write(`${rendered}
16377
17124
  `);
@@ -16420,7 +17167,7 @@ async function run(cliArgs) {
16420
17167
  const rendered3 = await renderer2.render(compareResult.output, renderOptions2);
16421
17168
  if (config.output) {
16422
17169
  const data = typeof rendered3 === "string" ? rendered3 : Buffer.from(rendered3);
16423
- writeFileSync2(config.output, data);
17170
+ writeFileSync3(config.output, data);
16424
17171
  } else {
16425
17172
  const text2 = typeof rendered3 === "string" ? rendered3 : rendered3.toString("utf-8");
16426
17173
  process.stdout.write(text2 + `
@@ -16441,7 +17188,7 @@ async function run(cliArgs) {
16441
17188
  };
16442
17189
  const rendered3 = await renderer2.render(compareResult.output, renderOptions2);
16443
17190
  if (config.output) {
16444
- writeFileSync2(config.output, rendered3);
17191
+ writeFileSync3(config.output, rendered3);
16445
17192
  } else {
16446
17193
  process.stdout.write(`${rendered3}
16447
17194
  `);
@@ -16454,7 +17201,7 @@ async function run(cliArgs) {
16454
17201
  }
16455
17202
  const rendered2 = JSON.stringify(compareResult.compareOutput, null, 2);
16456
17203
  if (config.output) {
16457
- writeFileSync2(config.output, rendered2);
17204
+ writeFileSync3(config.output, rendered2);
16458
17205
  } else {
16459
17206
  process.stdout.write(rendered2 + `
16460
17207
  `);
@@ -16497,7 +17244,7 @@ async function run(cliArgs) {
16497
17244
  if (config.format === "json") {
16498
17245
  const rendered3 = JSON.stringify(advisorReport, null, 2);
16499
17246
  if (config.output) {
16500
- writeFileSync2(config.output, rendered3);
17247
+ writeFileSync3(config.output, rendered3);
16501
17248
  } else {
16502
17249
  process.stdout.write(rendered3 + `
16503
17250
  `);
@@ -16509,7 +17256,7 @@ async function run(cliArgs) {
16509
17256
  noColor: config.noColor
16510
17257
  });
16511
17258
  if (config.output) {
16512
- writeFileSync2(config.output, rendered2);
17259
+ writeFileSync3(config.output, rendered2);
16513
17260
  } else {
16514
17261
  process.stdout.write(rendered2 + `
16515
17262
  `);
@@ -16519,7 +17266,7 @@ async function run(cliArgs) {
16519
17266
  if (config.format === "wrapped") {
16520
17267
  const outputPath = config.output ?? "tokenleak-wrapped.png";
16521
17268
  const wrappedBuffer = await renderWrappedPng(output2, { theme: config.theme });
16522
- writeFileSync2(outputPath, wrappedBuffer);
17269
+ writeFileSync3(outputPath, wrappedBuffer);
16523
17270
  process.stderr.write(`Wrapped PNG written to ${outputPath}
16524
17271
  `);
16525
17272
  if (config.clipboard) {
@@ -16628,7 +17375,7 @@ Shutting down wrapped live server...
16628
17375
  const rendered = await renderer.render(output2, renderOptions);
16629
17376
  if (config.output) {
16630
17377
  const data = typeof rendered === "string" ? rendered : Buffer.from(rendered);
16631
- writeFileSync2(config.output, data);
17378
+ writeFileSync3(config.output, data);
16632
17379
  } else {
16633
17380
  const text2 = typeof rendered === "string" ? rendered : rendered.toString("utf-8");
16634
17381
  process.stdout.write(text2 + `
@@ -16759,6 +17506,138 @@ function parseExplainArgs(argv) {
16759
17506
  }
16760
17507
  return { date, cliArgs };
16761
17508
  }
17509
+ function parseReplayArgs(argv) {
17510
+ let date = null;
17511
+ if (argv.length > 0 && !argv[0].startsWith("-")) {
17512
+ date = argv[0];
17513
+ if (!isValidDateArgument(date)) {
17514
+ throw new TokenleakError("tokenleak replay date must be in YYYY-MM-DD format");
17515
+ }
17516
+ }
17517
+ if (date === null) {
17518
+ date = new Date().toISOString().slice(0, 10);
17519
+ }
17520
+ const cliArgs = {};
17521
+ let index = argv[0]?.startsWith("-") ? 0 : 1;
17522
+ while (index < argv.length) {
17523
+ const arg = argv[index];
17524
+ switch (arg) {
17525
+ case "--help":
17526
+ case "-h":
17527
+ cliArgs["help"] = true;
17528
+ index += 1;
17529
+ break;
17530
+ case "--version":
17531
+ case "-v":
17532
+ cliArgs["version"] = true;
17533
+ index += 1;
17534
+ break;
17535
+ case "--format":
17536
+ case "-f":
17537
+ if (argv[index + 1] === undefined) {
17538
+ throw new TokenleakError(`${arg} requires a value`);
17539
+ }
17540
+ cliArgs["format"] = argv[index + 1];
17541
+ index += 2;
17542
+ break;
17543
+ case "--output":
17544
+ case "-o":
17545
+ if (argv[index + 1] === undefined) {
17546
+ throw new TokenleakError(`${arg} requires a value`);
17547
+ }
17548
+ cliArgs["output"] = argv[index + 1];
17549
+ index += 2;
17550
+ break;
17551
+ case "--width":
17552
+ case "-w":
17553
+ if (argv[index + 1] === undefined) {
17554
+ throw new TokenleakError(`${arg} requires a value`);
17555
+ }
17556
+ cliArgs["width"] = Number(argv[index + 1]);
17557
+ index += 2;
17558
+ break;
17559
+ case "--provider":
17560
+ case "-p":
17561
+ if (argv[index + 1] === undefined) {
17562
+ throw new TokenleakError(`${arg} requires a value`);
17563
+ }
17564
+ cliArgs["provider"] = argv[index + 1];
17565
+ index += 2;
17566
+ break;
17567
+ case "--claude":
17568
+ cliArgs["claude"] = true;
17569
+ index += 1;
17570
+ break;
17571
+ case "--codex":
17572
+ cliArgs["codex"] = true;
17573
+ index += 1;
17574
+ break;
17575
+ case "--cursor":
17576
+ cliArgs["cursor"] = true;
17577
+ index += 1;
17578
+ break;
17579
+ case "--pi":
17580
+ cliArgs["pi"] = true;
17581
+ index += 1;
17582
+ break;
17583
+ case "--openCode":
17584
+ case "--open-code":
17585
+ cliArgs["openCode"] = true;
17586
+ index += 1;
17587
+ break;
17588
+ case "--allProviders":
17589
+ case "--all-providers":
17590
+ cliArgs["allProviders"] = true;
17591
+ index += 1;
17592
+ break;
17593
+ case "--noColor":
17594
+ case "--no-color":
17595
+ cliArgs["noColor"] = true;
17596
+ index += 1;
17597
+ break;
17598
+ default:
17599
+ throw new TokenleakError(`Unknown replay flag "${arg}"`);
17600
+ }
17601
+ }
17602
+ return { date, cliArgs };
17603
+ }
17604
+ function resolveReplayFormat(cliArgs) {
17605
+ if (typeof cliArgs["format"] === "string") {
17606
+ const format = cliArgs["format"];
17607
+ if (format === "json" || format === "terminal") {
17608
+ return format;
17609
+ }
17610
+ throw new TokenleakError("tokenleak replay only supports --format terminal or --format json");
17611
+ }
17612
+ if (typeof cliArgs["output"] === "string") {
17613
+ const inferred = inferFormatFromPath(cliArgs["output"]);
17614
+ if (inferred === "json") {
17615
+ return "json";
17616
+ }
17617
+ }
17618
+ return "terminal";
17619
+ }
17620
+ async function runReplay(date, cliArgs) {
17621
+ const config = resolveConfig(cliArgs);
17622
+ const format = resolveReplayFormat(cliArgs);
17623
+ if (config.allProviders && (config.provider || config.claude || config.codex || config.cursor || config.pi || config.openCode)) {
17624
+ throw new TokenleakError("--all-providers cannot be combined with provider filters");
17625
+ }
17626
+ const replayRange = computeDateRange({ since: date, until: date });
17627
+ const available = await selectAvailableProviders(config);
17628
+ if (available.length === 0) {
17629
+ throw new TokenleakError("No provider data found");
17630
+ }
17631
+ const replayOutput = await loadTokenleakData(available, replayRange);
17632
+ const report = buildReplayReport(replayOutput.providers, date);
17633
+ const rendered = format === "json" ? JSON.stringify(report, null, 2) : renderReplayTerminal(report, config.width);
17634
+ if (config.output) {
17635
+ writeFileSync3(config.output, rendered);
17636
+ } else {
17637
+ process.stdout.write(rendered + `
17638
+ `);
17639
+ }
17640
+ }
16762
17641
  function resolveExplainFormat(cliArgs) {
16763
17642
  if (typeof cliArgs["format"] === "string") {
16764
17643
  const format = cliArgs["format"];
@@ -16790,7 +17669,7 @@ async function runExplain(date, cliArgs) {
16790
17669
  const report = buildExplainReport(explainOutput.providers, date);
16791
17670
  const rendered = format === "json" ? JSON.stringify(report, null, 2) : renderExplainTerminal(report, config.width);
16792
17671
  if (config.output) {
16793
- writeFileSync2(config.output, rendered);
17672
+ writeFileSync3(config.output, rendered);
16794
17673
  } else {
16795
17674
  process.stdout.write(rendered + `
16796
17675
  `);
@@ -17116,6 +17995,7 @@ var focusMain = defineCommand({
17116
17995
  });
17117
17996
  var isDirectExecution = typeof Bun !== "undefined" ? Bun.main === import.meta.path : process.argv[1] !== undefined && import.meta.url.endsWith(process.argv[1].replace(/\\/g, "/"));
17118
17997
  if (isDirectExecution) {
17998
+ await initPricing();
17119
17999
  const normalizedArgv = normalizeCliArgv(process.argv.slice(2));
17120
18000
  const argv = normalizedArgv;
17121
18001
  if (argv[0] === "explain") {
@@ -17135,6 +18015,23 @@ if (isDirectExecution) {
17135
18015
  handleError(error);
17136
18016
  }
17137
18017
  }
18018
+ if (argv[0] === "replay") {
18019
+ try {
18020
+ const { date, cliArgs } = parseReplayArgs(argv.slice(1));
18021
+ if (cliArgs["help"]) {
18022
+ process.stdout.write(buildReplayHelpText());
18023
+ process.exit(0);
18024
+ }
18025
+ if (cliArgs["version"]) {
18026
+ process.stdout.write(buildVersionText());
18027
+ process.exit(0);
18028
+ }
18029
+ await runReplay(date, cliArgs);
18030
+ process.exit(0);
18031
+ } catch (error) {
18032
+ handleError(error);
18033
+ }
18034
+ }
17138
18035
  if (argv[0] === "focus") {
17139
18036
  const focusArgv = argv.slice(1);
17140
18037
  process.argv = [...process.argv.slice(0, 2), ...focusArgv];