ccusage 16.1.2 → 16.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { BLOCKS_COMPACT_WIDTH_THRESHOLD, BLOCKS_DEFAULT_TERMINAL_WIDTH, BLOCKS_WARNING_THRESHOLD, BURN_RATE_THRESHOLDS, CONFIG_FILE_NAME, DEFAULT_RECENT_DAYS, DEFAULT_REFRESH_INTERVAL_SECONDS, MAX_REFRESH_INTERVAL_SECONDS, MCP_DEFAULT_PORT, MIN_REFRESH_INTERVAL_SECONDS, MIN_RENDER_INTERVAL_MS, PricingFetcher, WEEK_DAYS, __commonJSMin, __require, __toESM, inspect, inspectError, isFailure, isSuccess, map, pipe, require_usingCtx, succeed, try_ } from "./pricing-fetcher-BrJoueZ1.js";
2
+ import { BLOCKS_COMPACT_WIDTH_THRESHOLD, BLOCKS_DEFAULT_TERMINAL_WIDTH, BLOCKS_WARNING_THRESHOLD, BURN_RATE_THRESHOLDS, CONFIG_FILE_NAME, DEFAULT_CONTEXT_USAGE_THRESHOLDS, DEFAULT_LOCALE, DEFAULT_RECENT_DAYS, DEFAULT_REFRESH_INTERVAL_SECONDS, MAX_REFRESH_INTERVAL_SECONDS, MCP_DEFAULT_PORT, MIN_REFRESH_INTERVAL_SECONDS, MIN_RENDER_INTERVAL_MS, PricingFetcher, WEEK_DAYS, __commonJSMin, __require, __toESM, andThen, fail, inspect, inspectError, isFailure, isSuccess, map, pipe, require_usingCtx, succeed, try_ } from "./pricing-fetcher-_ZIEzYHY.js";
3
3
  import { getTotalTokens } from "./_token-utils-WjkbrjKv.js";
4
- import { CostModes, SortOrders, filterDateSchema, statuslineHookJsonSchema } from "./_types-BXB_jFi9.js";
4
+ import { CostModes, SortOrders, coerce, filterDateSchema, statuslineHookJsonSchema } from "./_types-DIdtMJ6V.js";
5
5
  import { calculateTotals, createTotalsObject } from "./calculate-cost-BDqO4yWA.js";
6
- import { DEFAULT_SESSION_DURATION_HOURS, calculateBurnRate, calculateContextTokens, calculateCostForEntry, createUniqueHash, filterRecentBlocks, formatDateCompact, getClaudePaths, getContextUsageThresholds, getEarliestTimestamp, getUsageLimitResetTime, globUsageFiles, identifySessionBlocks, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, loadSessionUsageById, loadWeeklyUsageData, projectBlockUsage, sortFilesByTimestamp, toArray, uniq, unwrap, usageDataSchema } from "./data-loader-DUaYLdYR.js";
7
- import { description, log, logger, name, version } from "./logger-vSpPcCiQ.js";
8
- import { detectMismatches, printMismatchReport } from "./debug-hX9iI1FE.js";
9
- import { createMcpHttpApp, createMcpServer, startMcpServerStdio } from "./mcp-D343KJOh.js";
6
+ import { DEFAULT_SESSION_DURATION_HOURS, calculateBurnRate, calculateContextTokens, calculateCostForEntry, createUniqueHash, filterRecentBlocks, formatDateCompact, getClaudePaths, getEarliestTimestamp, getFileModifiedTime, getUsageLimitResetTime, globUsageFiles, identifySessionBlocks, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, loadSessionUsageById, loadWeeklyUsageData, projectBlockUsage, sortFilesByTimestamp, toArray, uniq, unreachable, unwrap, usageDataSchema } from "./data-loader-DzuyD9Va.js";
7
+ import { description, log, logger, name, version } from "./logger-Cl0x4-O7.js";
8
+ import { detectMismatches, printMismatchReport } from "./debug-CWK-ECNy.js";
9
+ import { createMcpHttpApp, createMcpServer, startMcpServerStdio } from "./mcp-CzT8_3kj.js";
10
10
  import a, { readFile, stat } from "node:fs/promises";
11
11
  import path, { join } from "node:path";
12
12
  import process$1 from "node:process";
@@ -22,7 +22,7 @@ import { createServer } from "node:http";
22
22
  import { Http2ServerRequest } from "node:http2";
23
23
  import { Readable } from "node:stream";
24
24
  import crypto from "node:crypto";
25
- const DEFAULT_LOCALE = "en-US", BUILT_IN_PREFIX = "_", ARG_PREFIX = "arg", BUILT_IN_KEY_SEPARATOR = ":", ANONYMOUS_COMMAND_NAME = "(anonymous)", NOOP = () => {}, COMMON_ARGS = {
25
+ const DEFAULT_LOCALE$1 = "en-US", BUILT_IN_PREFIX = "_", ARG_PREFIX = "arg", BUILT_IN_KEY_SEPARATOR = ":", ANONYMOUS_COMMAND_NAME = "(anonymous)", NOOP = () => {}, COMMON_ARGS = {
26
26
  help: {
27
27
  type: "boolean",
28
28
  short: "h",
@@ -149,16 +149,16 @@ async function createCommandContext({ args, values, positionals, rest, argv: arg
149
149
  return acc[key] = Object.assign(create(), value$1), acc;
150
150
  }, create()), env$2 = Object.assign(create(), COMMAND_OPTIONS_DEFAULT, cliOptions), locale = resolveLocale(cliOptions.locale), localeStr = locale.toString(), translationAdapterFactory = cliOptions.translationAdapterFactory || createTranslationAdapter, adapter = translationAdapterFactory({
151
151
  locale: localeStr,
152
- fallbackLocale: DEFAULT_LOCALE
152
+ fallbackLocale: DEFAULT_LOCALE$1
153
153
  }), localeResources = /* @__PURE__ */ new Map();
154
154
  let builtInLoadedResources;
155
- if (localeResources.set(DEFAULT_LOCALE, mapResourceWithBuiltinKey(en_US_default)), DEFAULT_LOCALE !== localeStr) try {
155
+ if (localeResources.set(DEFAULT_LOCALE$1, mapResourceWithBuiltinKey(en_US_default)), DEFAULT_LOCALE$1 !== localeStr) try {
156
156
  builtInLoadedResources = (await import(`./locales/${localeStr}.json`, { with: { type: "json" } })).default, localeResources.set(localeStr, mapResourceWithBuiltinKey(builtInLoadedResources));
157
157
  } catch {}
158
158
  function translate(key, values$1 = create()) {
159
159
  const strKey = key;
160
160
  if (strKey.codePointAt(0) === BUILT_IN_PREFIX_CODE) {
161
- const resource = localeResources.get(localeStr) || localeResources.get(DEFAULT_LOCALE);
161
+ const resource = localeResources.get(localeStr) || localeResources.get(DEFAULT_LOCALE$1);
162
162
  return resource[strKey] || strKey;
163
163
  } else return adapter.translate(locale.toString(), strKey, values$1) || "";
164
164
  }
@@ -191,7 +191,7 @@ async function createCommandContext({ args, values, positionals, rest, argv: arg
191
191
  }), defaultCommandResource = loadedOptionsResources.reduce((res, [key, value$1]) => {
192
192
  return res[resolveArgKey(key)] = value$1, res;
193
193
  }, create());
194
- defaultCommandResource.description = command.description || "", defaultCommandResource.examples = await resolveExamples(ctx, command.examples), adapter.setResource(DEFAULT_LOCALE, defaultCommandResource);
194
+ defaultCommandResource.description = command.description || "", defaultCommandResource.examples = await resolveExamples(ctx, command.examples), adapter.setResource(DEFAULT_LOCALE$1, defaultCommandResource);
195
195
  const originalResource = await loadCommandResource(ctx, command);
196
196
  if (originalResource) {
197
197
  const resource = Object.assign(create(), originalResource, { examples: await resolveExamples(ctx, originalResource.examples) });
@@ -204,7 +204,7 @@ function getCommandName(cmd) {
204
204
  return isLazyCommand(cmd) ? cmd.commandName || cmd.name || ANONYMOUS_COMMAND_NAME : typeof cmd === "object" && cmd.name || ANONYMOUS_COMMAND_NAME;
205
205
  }
206
206
  function resolveLocale(locale) {
207
- return locale instanceof Intl.Locale ? locale : typeof locale === "string" ? new Intl.Locale(locale) : new Intl.Locale(DEFAULT_LOCALE);
207
+ return locale instanceof Intl.Locale ? locale : typeof locale === "string" ? new Intl.Locale(locale) : new Intl.Locale(DEFAULT_LOCALE$1);
208
208
  }
209
209
  async function loadCommandResource(ctx, command) {
210
210
  let resource;
@@ -817,7 +817,7 @@ var require_picocolors = /* @__PURE__ */ __commonJSMin(((exports, module) => {
817
817
  };
818
818
  };
819
819
  module.exports = createColors(), module.exports.createColors = createColors;
820
- })), import_picocolors$8 = /* @__PURE__ */ __toESM(require_picocolors(), 1), import_usingCtx$3 = /* @__PURE__ */ __toESM(require_usingCtx(), 1);
820
+ })), import_picocolors$5 = /* @__PURE__ */ __toESM(require_picocolors(), 1), import_usingCtx$3 = /* @__PURE__ */ __toESM(require_usingCtx(), 1);
821
821
  function extractExplicitArgs(tokens) {
822
822
  const explicit = {};
823
823
  for (const token of tokens) if (typeof token === "object" && token !== null) {
@@ -1157,7 +1157,7 @@ const sharedArgs = {
1157
1157
  type: "string",
1158
1158
  short: "l",
1159
1159
  description: "Locale for date/time formatting (e.g., en-US, ja-JP, de-DE)",
1160
- default: "en-CA"
1160
+ default: DEFAULT_LOCALE
1161
1161
  },
1162
1162
  jq: {
1163
1163
  type: "string",
@@ -2467,7 +2467,7 @@ function stringWidth(string, options = {}) {
2467
2467
  }
2468
2468
  return width;
2469
2469
  }
2470
- var import_usingCtx$2 = /* @__PURE__ */ __toESM(require_usingCtx(), 1), ResponsiveTable = class {
2470
+ var ResponsiveTable = class {
2471
2471
  head;
2472
2472
  rows = [];
2473
2473
  colAligns;
@@ -2499,7 +2499,7 @@ var import_usingCtx$2 = /* @__PURE__ */ __toESM(require_usingCtx(), 1), Responsi
2499
2499
  getCompactIndices() {
2500
2500
  return this.compactHead == null || !this.compactMode ? Array.from({ length: this.head.length }, (_, i) => i) : this.compactHead.map((compactHeader) => {
2501
2501
  const index = this.head.indexOf(compactHeader);
2502
- return index < 0 ? (console.warn(`Warning: Compact header "${compactHeader}" not found in table headers [${this.head.join(", ")}]. Using first column as fallback.`), 0) : index;
2502
+ return index < 0 ? (logger.warn(`Warning: Compact header "${compactHeader}" not found in table headers [${this.head.join(", ")}]. Using first column as fallback.`), 0) : index;
2503
2503
  });
2504
2504
  }
2505
2505
  isCompactMode() {
@@ -2592,73 +2592,254 @@ function pushBreakdownRows(table, breakdowns, extraColumns = 1, trailingColumns
2592
2592
  const row = [` └─ ${formatModelName(breakdown.modelName)}`];
2593
2593
  for (let i = 0; i < extraColumns; i++) row.push("");
2594
2594
  const totalTokens = breakdown.inputTokens + breakdown.outputTokens + breakdown.cacheCreationTokens + breakdown.cacheReadTokens;
2595
- row.push(import_picocolors$8.default.gray(formatNumber(breakdown.inputTokens)), import_picocolors$8.default.gray(formatNumber(breakdown.outputTokens)), import_picocolors$8.default.gray(formatNumber(breakdown.cacheCreationTokens)), import_picocolors$8.default.gray(formatNumber(breakdown.cacheReadTokens)), import_picocolors$8.default.gray(formatNumber(totalTokens)), import_picocolors$8.default.gray(formatCurrency(breakdown.cost)));
2595
+ row.push(import_picocolors$5.default.gray(formatNumber(breakdown.inputTokens)), import_picocolors$5.default.gray(formatNumber(breakdown.outputTokens)), import_picocolors$5.default.gray(formatNumber(breakdown.cacheCreationTokens)), import_picocolors$5.default.gray(formatNumber(breakdown.cacheReadTokens)), import_picocolors$5.default.gray(formatNumber(totalTokens)), import_picocolors$5.default.gray(formatCurrency(breakdown.cost)));
2596
2596
  for (let i = 0; i < trailingColumns; i++) row.push("");
2597
2597
  table.push(row);
2598
2598
  }
2599
2599
  }
2600
- async function getFileModifiedTime(filePath) {
2600
+ function createUsageReportTable(config) {
2601
+ const baseHeaders = [
2602
+ config.firstColumnName,
2603
+ "Models",
2604
+ "Input",
2605
+ "Output",
2606
+ "Cache Create",
2607
+ "Cache Read",
2608
+ "Total Tokens",
2609
+ "Cost (USD)"
2610
+ ], baseAligns = [
2611
+ "left",
2612
+ "left",
2613
+ "right",
2614
+ "right",
2615
+ "right",
2616
+ "right",
2617
+ "right",
2618
+ "right"
2619
+ ], compactHeaders = [
2620
+ config.firstColumnName,
2621
+ "Models",
2622
+ "Input",
2623
+ "Output",
2624
+ "Cost (USD)"
2625
+ ], compactAligns = [
2626
+ "left",
2627
+ "left",
2628
+ "right",
2629
+ "right",
2630
+ "right"
2631
+ ];
2632
+ if (config.includeLastActivity ?? false) baseHeaders.push("Last Activity"), baseAligns.push("left"), compactHeaders.push("Last Activity"), compactAligns.push("left");
2633
+ return new ResponsiveTable({
2634
+ head: baseHeaders,
2635
+ style: { head: ["cyan"] },
2636
+ colAligns: baseAligns,
2637
+ dateFormatter: config.dateFormatter,
2638
+ compactHead: compactHeaders,
2639
+ compactColAligns: compactAligns,
2640
+ compactThreshold: 100,
2641
+ forceCompact: config.forceCompact
2642
+ });
2643
+ }
2644
+ function formatUsageDataRow(firstColumnValue, data, lastActivity) {
2645
+ const totalTokens = data.inputTokens + data.outputTokens + data.cacheCreationTokens + data.cacheReadTokens, row = [
2646
+ firstColumnValue,
2647
+ data.modelsUsed != null ? formatModelsDisplayMultiline(data.modelsUsed) : "",
2648
+ formatNumber(data.inputTokens),
2649
+ formatNumber(data.outputTokens),
2650
+ formatNumber(data.cacheCreationTokens),
2651
+ formatNumber(data.cacheReadTokens),
2652
+ formatNumber(totalTokens),
2653
+ formatCurrency(data.totalCost)
2654
+ ];
2655
+ if (lastActivity !== void 0) row.push(lastActivity);
2656
+ return row;
2657
+ }
2658
+ function formatTotalsRow(totals, includeLastActivity = false) {
2659
+ const totalTokens = totals.inputTokens + totals.outputTokens + totals.cacheCreationTokens + totals.cacheReadTokens, row = [
2660
+ import_picocolors$5.default.yellow("Total"),
2661
+ "",
2662
+ import_picocolors$5.default.yellow(formatNumber(totals.inputTokens)),
2663
+ import_picocolors$5.default.yellow(formatNumber(totals.outputTokens)),
2664
+ import_picocolors$5.default.yellow(formatNumber(totals.cacheCreationTokens)),
2665
+ import_picocolors$5.default.yellow(formatNumber(totals.cacheReadTokens)),
2666
+ import_picocolors$5.default.yellow(formatNumber(totalTokens)),
2667
+ import_picocolors$5.default.yellow(formatCurrency(totals.totalCost))
2668
+ ];
2669
+ if (includeLastActivity) row.push("");
2670
+ return row;
2671
+ }
2672
+ function addEmptySeparatorRow(table, columnCount) {
2673
+ const emptyRow = Array.from({ length: columnCount }, () => "");
2674
+ table.push(emptyRow);
2675
+ }
2676
+ var Node = class {
2677
+ value;
2678
+ next;
2679
+ constructor(value$1) {
2680
+ this.value = value$1;
2681
+ }
2682
+ }, Queue = class {
2683
+ #head;
2684
+ #tail;
2685
+ #size;
2686
+ constructor() {
2687
+ this.clear();
2688
+ }
2689
+ enqueue(value$1) {
2690
+ const node = new Node(value$1);
2691
+ if (this.#head) this.#tail.next = node, this.#tail = node;
2692
+ else this.#head = node, this.#tail = node;
2693
+ this.#size++;
2694
+ }
2695
+ dequeue() {
2696
+ const current = this.#head;
2697
+ if (current) return this.#head = this.#head.next, this.#size--, current.value;
2698
+ }
2699
+ peek() {
2700
+ if (this.#head) return this.#head.value;
2701
+ }
2702
+ clear() {
2703
+ this.#head = void 0, this.#tail = void 0, this.#size = 0;
2704
+ }
2705
+ get size() {
2706
+ return this.#size;
2707
+ }
2708
+ *[Symbol.iterator]() {
2709
+ let current = this.#head;
2710
+ while (current) yield current.value, current = current.next;
2711
+ }
2712
+ *drain() {
2713
+ while (this.#head) yield this.dequeue();
2714
+ }
2715
+ };
2716
+ function pLimit(concurrency) {
2717
+ validateConcurrency(concurrency);
2718
+ const queue = new Queue();
2719
+ let activeCount = 0;
2720
+ const resumeNext = () => {
2721
+ if (activeCount < concurrency && queue.size > 0) activeCount++, queue.dequeue()();
2722
+ }, next = () => {
2723
+ activeCount--, resumeNext();
2724
+ }, run$1 = async (function_, resolve, arguments_) => {
2725
+ const result = (async () => function_(...arguments_))();
2726
+ resolve(result);
2727
+ try {
2728
+ await result;
2729
+ } catch {}
2730
+ next();
2731
+ }, enqueue = (function_, resolve, arguments_) => {
2732
+ if (new Promise((internalResolve) => {
2733
+ queue.enqueue(internalResolve);
2734
+ }).then(run$1.bind(void 0, function_, resolve, arguments_)), activeCount < concurrency) resumeNext();
2735
+ }, generator = (function_, ...arguments_) => new Promise((resolve) => {
2736
+ enqueue(function_, resolve, arguments_);
2737
+ });
2738
+ return Object.defineProperties(generator, {
2739
+ activeCount: { get: () => activeCount },
2740
+ pendingCount: { get: () => queue.size },
2741
+ clearQueue: { value() {
2742
+ queue.clear();
2743
+ } },
2744
+ concurrency: {
2745
+ get: () => concurrency,
2746
+ set(newConcurrency) {
2747
+ validateConcurrency(newConcurrency), concurrency = newConcurrency, queueMicrotask(() => {
2748
+ while (activeCount < concurrency && queue.size > 0) resumeNext();
2749
+ });
2750
+ }
2751
+ },
2752
+ map: { async value(array, function_) {
2753
+ const promises = array.map((value$1, index) => this(function_, value$1, index));
2754
+ return Promise.all(promises);
2755
+ } }
2756
+ }), generator;
2757
+ }
2758
+ function validateConcurrency(concurrency) {
2759
+ if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) throw new TypeError("Expected `concurrency` to be a number from 1 and up");
2760
+ }
2761
+ var import_usingCtx$2 = /* @__PURE__ */ __toESM(require_usingCtx(), 1);
2762
+ const RETENTION_HOURS = 24, FILE_CONCURRENCY = 5;
2763
+ async function isRecentFile(filePath, cutoffTime) {
2601
2764
  return pipe(try_({
2602
2765
  try: stat(filePath),
2603
2766
  catch: (error) => error
2604
- }), map((stats) => stats.mtime.getTime()), unwrap(0));
2605
- }
2606
- var LiveMonitor = class {
2607
- config;
2608
- fetcher = null;
2609
- lastFileTimestamps = /* @__PURE__ */ new Map();
2610
- processedHashes = /* @__PURE__ */ new Set();
2611
- allEntries = [];
2612
- constructor(config) {
2613
- if (this.config = config, config.mode !== "display") this.fetcher = new PricingFetcher();
2614
- }
2615
- [Symbol.dispose]() {
2616
- this.fetcher?.[Symbol.dispose]();
2617
- }
2618
- async getActiveBlock() {
2619
- const results = await globUsageFiles(this.config.claudePaths), allFiles = results.map((r) => r.file);
2620
- if (allFiles.length === 0) return null;
2621
- const filesToRead = [];
2622
- for (const file of allFiles) {
2623
- const timestamp$1 = await getEarliestTimestamp(file), lastTimestamp = this.lastFileTimestamps.get(file);
2624
- if (timestamp$1 != null && (lastTimestamp == null || timestamp$1.getTime() > lastTimestamp)) filesToRead.push(file), this.lastFileTimestamps.set(file, timestamp$1.getTime());
2625
- }
2626
- if (filesToRead.length > 0) {
2627
- const sortedFiles = await sortFilesByTimestamp(filesToRead);
2628
- for (const file of sortedFiles) {
2629
- const content = await readFile(file, "utf-8").catch(() => {
2630
- return "";
2631
- }), lines = content.trim().split("\n").filter((line) => line.length > 0);
2632
- for (const line of lines) try {
2633
- const parsed = JSON.parse(line), result = usageDataSchema.safeParse(parsed);
2634
- if (!result.success) continue;
2635
- const data = result.data, uniqueHash = createUniqueHash(data);
2636
- if (uniqueHash != null && this.processedHashes.has(uniqueHash)) continue;
2637
- if (uniqueHash != null) this.processedHashes.add(uniqueHash);
2638
- const costUSD = await (this.config.mode === "display" ? Promise.resolve(data.costUSD ?? 0) : calculateCostForEntry(data, this.config.mode, this.fetcher)), usageLimitResetTime = getUsageLimitResetTime(data);
2639
- this.allEntries.push({
2640
- timestamp: new Date(data.timestamp),
2641
- usage: {
2642
- inputTokens: data.message.usage.input_tokens ?? 0,
2643
- outputTokens: data.message.usage.output_tokens ?? 0,
2644
- cacheCreationInputTokens: data.message.usage.cache_creation_input_tokens ?? 0,
2645
- cacheReadInputTokens: data.message.usage.cache_read_input_tokens ?? 0
2646
- },
2647
- costUSD,
2648
- model: data.message.model ?? "<synthetic>",
2649
- version: data.version,
2650
- usageLimitResetTime: usageLimitResetTime ?? void 0
2651
- });
2652
- } catch {}
2653
- }
2767
+ }), map((fileStats) => fileStats.mtime >= cutoffTime), unwrap(false));
2768
+ }
2769
+ function createLiveMonitorState(config) {
2770
+ const fetcher = config.mode !== "display" ? new PricingFetcher() : null;
2771
+ return {
2772
+ fetcher,
2773
+ lastFileTimestamps: /* @__PURE__ */ new Map(),
2774
+ processedHashes: /* @__PURE__ */ new Set(),
2775
+ allEntries: [],
2776
+ [Symbol.dispose]() {
2777
+ fetcher?.[Symbol.dispose]();
2654
2778
  }
2655
- const blocks = identifySessionBlocks(this.allEntries, this.config.sessionDurationHours), sortedBlocks = this.config.order === "asc" ? blocks : blocks.reverse();
2656
- return sortedBlocks.find((block) => block.isActive) ?? null;
2779
+ };
2780
+ }
2781
+ function clearLiveMonitorCache(state) {
2782
+ state.lastFileTimestamps.clear(), state.processedHashes.clear(), state.allEntries = [];
2783
+ }
2784
+ function cleanupOldEntries(state, cutoffTime) {
2785
+ const initialCount = state.allEntries.length;
2786
+ if (state.allEntries = state.allEntries.filter((entry) => entry.timestamp >= cutoffTime), initialCount - state.allEntries.length > 100) state.processedHashes.clear();
2787
+ }
2788
+ async function processFileContent(state, config, content, cutoffTime) {
2789
+ const lines = content.trim().split("\n").filter((line) => line.length > 0);
2790
+ for (const line of lines) {
2791
+ const dataResult = pipe(try_({
2792
+ try: () => JSON.parse(line),
2793
+ catch: (error) => error
2794
+ })(), andThen((data$1) => {
2795
+ const parseResult = usageDataSchema.safeParse(data$1);
2796
+ return parseResult.success ? succeed(parseResult.data) : fail(parseResult.error);
2797
+ }));
2798
+ if (isFailure(dataResult)) continue;
2799
+ const data = unwrap(dataResult), entryTime = new Date(data.timestamp);
2800
+ if (entryTime < cutoffTime) continue;
2801
+ const uniqueHash = createUniqueHash(data);
2802
+ if (uniqueHash != null && state.processedHashes.has(uniqueHash)) continue;
2803
+ if (uniqueHash != null) state.processedHashes.add(uniqueHash);
2804
+ const costUSD = await (config.mode === "display" ? Promise.resolve(data.costUSD ?? 0) : calculateCostForEntry(data, config.mode, state.fetcher)), usageLimitResetTime = getUsageLimitResetTime(data);
2805
+ state.allEntries.push({
2806
+ timestamp: entryTime,
2807
+ usage: {
2808
+ inputTokens: data.message.usage.input_tokens ?? 0,
2809
+ outputTokens: data.message.usage.output_tokens ?? 0,
2810
+ cacheCreationInputTokens: data.message.usage.cache_creation_input_tokens ?? 0,
2811
+ cacheReadInputTokens: data.message.usage.cache_read_input_tokens ?? 0
2812
+ },
2813
+ costUSD,
2814
+ model: data.message.model ?? "<synthetic>",
2815
+ version: data.version,
2816
+ usageLimitResetTime: usageLimitResetTime ?? void 0
2817
+ });
2657
2818
  }
2658
- clearCache() {
2659
- this.lastFileTimestamps.clear(), this.processedHashes.clear(), this.allEntries = [];
2819
+ }
2820
+ async function getActiveBlock(state, config) {
2821
+ const cutoffTime = /* @__PURE__ */ new Date(Date.now() - RETENTION_HOURS * 60 * 60 * 1e3), results = await globUsageFiles(config.claudePaths), allFiles = results.map((r) => r.file);
2822
+ if (allFiles.length === 0) return null;
2823
+ const candidateFiles = [];
2824
+ for (const file of allFiles) if (await isRecentFile(file, cutoffTime)) candidateFiles.push(file);
2825
+ const filesToRead = [];
2826
+ for (const file of candidateFiles) {
2827
+ const timestamp$1 = await getEarliestTimestamp(file), lastTimestamp = state.lastFileTimestamps.get(file);
2828
+ if (timestamp$1 != null && (lastTimestamp == null || timestamp$1.getTime() > lastTimestamp)) filesToRead.push(file), state.lastFileTimestamps.set(file, timestamp$1.getTime());
2660
2829
  }
2661
- };
2830
+ if (cleanupOldEntries(state, cutoffTime), filesToRead.length > 0) {
2831
+ const sortedFiles = await sortFilesByTimestamp(filesToRead), fileLimit = pLimit(FILE_CONCURRENCY), fileResults = await Promise.allSettled(sortedFiles.map(async (file) => fileLimit(async () => ({
2832
+ file,
2833
+ content: await readFile(file, "utf-8")
2834
+ }))));
2835
+ for (const result of fileResults) if (result.status === "fulfilled") {
2836
+ const { content } = result.value;
2837
+ await processFileContent(state, config, content, cutoffTime);
2838
+ }
2839
+ }
2840
+ const blocks = identifySessionBlocks(state.allEntries, config.sessionDurationHours), sortedBlocks = config.order === "asc" ? blocks : blocks.reverse();
2841
+ return sortedBlocks.find((block) => block.isActive) ?? null;
2842
+ }
2662
2843
  function delay(ms, options = {}) {
2663
2844
  const { signal, persistent = true } = options;
2664
2845
  return signal?.aborted ? Promise.reject(signal.reason) : new Promise((resolve, reject) => {
@@ -2884,20 +3065,20 @@ const SAVE_CURSOR = "\x1B7", RESTORE_CURSOR = "\x1B8";
2884
3065
  function drawEmoji(emoji) {
2885
3066
  return `${SAVE_CURSOR}${emoji}${RESTORE_CURSOR}${cursorForward(stringWidth(emoji))}`;
2886
3067
  }
2887
- var import_picocolors$7 = /* @__PURE__ */ __toESM(require_picocolors(), 1);
3068
+ var import_picocolors$4 = /* @__PURE__ */ __toESM(require_picocolors(), 1);
2888
3069
  function getRateIndicator(burnRate) {
2889
3070
  if (burnRate == null) return "";
2890
3071
  switch (true) {
2891
- case burnRate.tokensPerMinuteForIndicator > BURN_RATE_THRESHOLDS.HIGH: return import_picocolors$7.default.red(`${drawEmoji("⚡")} HIGH`);
2892
- case burnRate.tokensPerMinuteForIndicator > BURN_RATE_THRESHOLDS.MODERATE: return import_picocolors$7.default.yellow(`${drawEmoji("⚡")} MODERATE`);
2893
- default: return import_picocolors$7.default.green(`${drawEmoji("✓")} NORMAL`);
3072
+ case burnRate.tokensPerMinuteForIndicator > BURN_RATE_THRESHOLDS.HIGH: return import_picocolors$4.default.red(`${drawEmoji("⚡")} HIGH`);
3073
+ case burnRate.tokensPerMinuteForIndicator > BURN_RATE_THRESHOLDS.MODERATE: return import_picocolors$4.default.yellow(`${drawEmoji("⚡")} MODERATE`);
3074
+ default: return import_picocolors$4.default.green(`${drawEmoji("✓")} NORMAL`);
2894
3075
  }
2895
3076
  }
2896
3077
  async function delayWithAbort(ms, signal) {
2897
3078
  await delay(ms, { signal });
2898
3079
  }
2899
3080
  async function renderWaitingState(terminal, config, signal) {
2900
- terminal.startBuffering(), terminal.write(cursorTo(0, 0)), terminal.write(eraseDown), terminal.write(import_picocolors$7.default.yellow("No active session block found. Waiting...\n")), terminal.write(cursorHide), terminal.flush(), await delayWithAbort(config.refreshInterval, signal);
3081
+ terminal.startBuffering(), terminal.write(cursorTo(0, 0)), terminal.write(eraseDown), terminal.write(import_picocolors$4.default.yellow("No active session block found. Waiting...\n")), terminal.write(cursorHide), terminal.flush(), await delayWithAbort(config.refreshInterval, signal);
2901
3082
  }
2902
3083
  function renderActiveBlock(terminal, activeBlock, config) {
2903
3084
  terminal.startBuffering(), terminal.write(cursorTo(0, 0)), terminal.write(eraseDown), renderLiveDisplay(terminal, activeBlock, config), terminal.write(cursorHide), terminal.flush();
@@ -2915,8 +3096,8 @@ function renderLiveDisplay(terminal, block, config) {
2915
3096
  }
2916
3097
  const boxWidth = Math.min(120, width - 2), boxMargin = Math.floor((width - boxWidth) / 2), marginStr = " ".repeat(boxMargin), sessionDuration = elapsed + remaining, sessionPercent = elapsed / sessionDuration * 100, sessionRightText = `${sessionPercent.toFixed(1).padStart(6)}%`, tokenPercent = config.tokenLimit != null && config.tokenLimit > 0 ? totalTokens / config.tokenLimit * 100 : 0, usageRightText = config.tokenLimit != null && config.tokenLimit > 0 ? `${tokenPercent.toFixed(1).padStart(6)}% (${formatTokensShort(totalTokens)}/${formatTokensShort(config.tokenLimit)})` : `(${formatTokensShort(totalTokens)} tokens)`, projection = projectBlockUsage(block), projectedPercent = projection != null && config.tokenLimit != null && config.tokenLimit > 0 ? projection.totalTokens / config.tokenLimit * 100 : 0, projectionRightText = projection != null ? config.tokenLimit != null && config.tokenLimit > 0 ? `${projectedPercent.toFixed(1).padStart(6)}% (${formatTokensShort(projection.totalTokens)}/${formatTokensShort(config.tokenLimit)})` : `(${formatTokensShort(projection.totalTokens)} tokens)` : "", maxRightTextWidth = Math.max(stringWidth(sessionRightText), stringWidth(usageRightText), projection != null ? stringWidth(projectionRightText) : 0), labelWidth = 14, spacing = 4, boxPadding = 4, barWidth = boxWidth - labelWidth - maxRightTextWidth - spacing - boxPadding, sessionProgressBar = createProgressBar(elapsed, sessionDuration, barWidth, {
2917
3098
  showPercentage: false,
2918
- fillChar: import_picocolors$7.default.cyan("█"),
2919
- emptyChar: import_picocolors$7.default.gray("░"),
3099
+ fillChar: import_picocolors$4.default.cyan("█"),
3100
+ emptyChar: import_picocolors$4.default.gray("░"),
2920
3101
  leftBracket: "[",
2921
3102
  rightBracket: "]"
2922
3103
  }), startTime = block.startTime.toLocaleTimeString(void 0, {
@@ -2930,10 +3111,10 @@ function renderLiveDisplay(terminal, block, config) {
2930
3111
  second: "2-digit",
2931
3112
  hour12: true
2932
3113
  }), detailsIndent = 3, detailsSpacing = 2, detailsAvailableWidth = boxWidth - 3 - detailsIndent;
2933
- terminal.write(`${marginStr}┌${"─".repeat(boxWidth - 2)}┐\n`), terminal.write(`${marginStr}│${import_picocolors$7.default.bold(centerText("CLAUDE CODE - LIVE TOKEN USAGE MONITOR", boxWidth - 2))}│\n`), terminal.write(`${marginStr}├${"─".repeat(boxWidth - 2)}┤\n`), terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
2934
- const sessionLabel = `${drawEmoji("⏱️")}${import_picocolors$7.default.bold(" SESSION")}`, sessionLabelWidth = stringWidth(sessionLabel), sessionBarStr = `${sessionLabel}${"".padEnd(Math.max(0, labelWidth - sessionLabelWidth))} ${sessionProgressBar} ${sessionRightText}`, sessionBarPadded = sessionBarStr + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(sessionBarStr)));
3114
+ terminal.write(`${marginStr}┌${"─".repeat(boxWidth - 2)}┐\n`), terminal.write(`${marginStr}│${import_picocolors$4.default.bold(centerText("CLAUDE CODE - LIVE TOKEN USAGE MONITOR", boxWidth - 2))}│\n`), terminal.write(`${marginStr}├${"─".repeat(boxWidth - 2)}┤\n`), terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
3115
+ const sessionLabel = `${drawEmoji("⏱️")}${import_picocolors$4.default.bold(" SESSION")}`, sessionLabelWidth = stringWidth(sessionLabel), sessionBarStr = `${sessionLabel}${"".padEnd(Math.max(0, labelWidth - sessionLabelWidth))} ${sessionProgressBar} ${sessionRightText}`, sessionBarPadded = sessionBarStr + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(sessionBarStr)));
2935
3116
  terminal.write(`${marginStr}│ ${sessionBarPadded}│\n`);
2936
- const sessionCol1 = `${import_picocolors$7.default.gray("Started:")} ${startTime}`, sessionCol2 = `${import_picocolors$7.default.gray("Elapsed:")} ${prettyMilliseconds(elapsed * 60 * 1e3, { compact: true })}`, sessionCol3 = `${import_picocolors$7.default.gray("Remaining:")} ${prettyMilliseconds(remaining * 60 * 1e3, { compact: true })} (${endTime})`;
3117
+ const sessionCol1 = `${import_picocolors$4.default.gray("Started:")} ${startTime}`, sessionCol2 = `${import_picocolors$4.default.gray("Elapsed:")} ${prettyMilliseconds(elapsed * 60 * 1e3, { compact: true })}`, sessionCol3 = `${import_picocolors$4.default.gray("Remaining:")} ${prettyMilliseconds(remaining * 60 * 1e3, { compact: true })} (${endTime})`;
2937
3118
  let sessionDetails = `${" ".repeat(detailsIndent)}${sessionCol1}${" ".repeat(detailsSpacing)}${sessionCol2}${" ".repeat(detailsSpacing)}${sessionCol3}`;
2938
3119
  const sessionDetailsWidth = stringWidth(sessionCol1) + stringWidth(sessionCol2) + stringWidth(sessionCol3) + detailsSpacing * 2;
2939
3120
  if (sessionDetailsWidth > detailsAvailableWidth) sessionDetails = `${" ".repeat(detailsIndent)}${sessionCol1}${" ".repeat(detailsSpacing)}${sessionCol3}`;
@@ -2944,33 +3125,33 @@ function renderLiveDisplay(terminal, block, config) {
2944
3125
  hour: "2-digit",
2945
3126
  minute: "2-digit",
2946
3127
  hour12: true
2947
- }) ?? null, usageLimitResetTime = resetTime !== null ? import_picocolors$7.default.red(`${drawEmoji("❌")} USAGE LIMIT. RESET AT ${resetTime}`) : "";
3128
+ }) ?? null, usageLimitResetTime = resetTime !== null ? import_picocolors$4.default.red(`${drawEmoji("❌")} USAGE LIMIT. RESET AT ${resetTime}`) : "";
2948
3129
  usageLimitResetTimePadded = resetTime !== null ? usageLimitResetTime + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(usageLimitResetTime))) : null;
2949
3130
  }
2950
3131
  if (terminal.write(`${marginStr}│ ${sessionDetailsPadded}│\n`), usageLimitResetTimePadded !== null) terminal.write(`${marginStr}│ ${usageLimitResetTimePadded}│\n`);
2951
3132
  terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`), terminal.write(`${marginStr}├${"─".repeat(boxWidth - 2)}┤\n`), terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
2952
- let barColor = import_picocolors$7.default.green;
2953
- if (tokenPercent > 100) barColor = import_picocolors$7.default.red;
2954
- else if (tokenPercent > 80) barColor = import_picocolors$7.default.yellow;
3133
+ let barColor = import_picocolors$4.default.green;
3134
+ if (tokenPercent > 100) barColor = import_picocolors$4.default.red;
3135
+ else if (tokenPercent > 80) barColor = import_picocolors$4.default.yellow;
2955
3136
  const usageBar = config.tokenLimit != null && config.tokenLimit > 0 ? createProgressBar(totalTokens, config.tokenLimit, barWidth, {
2956
3137
  showPercentage: false,
2957
3138
  fillChar: barColor("█"),
2958
- emptyChar: import_picocolors$7.default.gray("░"),
3139
+ emptyChar: import_picocolors$4.default.gray("░"),
2959
3140
  leftBracket: "[",
2960
3141
  rightBracket: "]"
2961
- }) : `[${import_picocolors$7.default.green("█".repeat(Math.floor(barWidth * .1)))}${import_picocolors$7.default.gray("░".repeat(barWidth - Math.floor(barWidth * .1)))}]`, burnRate = calculateBurnRate(block), rateIndicator = getRateIndicator(burnRate), buildRateDisplay = (useShort) => {
2962
- if (burnRate == null) return `${import_picocolors$7.default.bold("Burn Rate:")} N/A`;
3142
+ }) : `[${import_picocolors$4.default.green("█".repeat(Math.floor(barWidth * .1)))}${import_picocolors$4.default.gray("░".repeat(barWidth - Math.floor(barWidth * .1)))}]`, burnRate = calculateBurnRate(block), rateIndicator = getRateIndicator(burnRate), buildRateDisplay = (useShort) => {
3143
+ if (burnRate == null) return `${import_picocolors$4.default.bold("Burn Rate:")} N/A`;
2963
3144
  const rateValue = Math.round(burnRate.tokensPerMinute), formattedRate = useShort ? formatTokensShort(rateValue) : formatNumber(rateValue);
2964
- return `${import_picocolors$7.default.bold("Burn Rate:")} ${formattedRate} token/min ${rateIndicator}`;
2965
- }, usageLabel = `${drawEmoji("🔥")}${import_picocolors$7.default.bold(" USAGE")}`, usageLabelWidth = stringWidth(usageLabel), usageBarStr = `${usageLabel}${"".padEnd(Math.max(0, labelWidth - usageLabelWidth))} ${usageBar} ${usageRightText}`;
2966
- let rateDisplay = buildRateDisplay(false), usageCol1 = `${import_picocolors$7.default.gray("Tokens:")} ${formatTokenDisplay(totalTokens, false)} (${rateDisplay})`, usageCol2 = config.tokenLimit != null && config.tokenLimit > 0 ? `${import_picocolors$7.default.gray("Limit:")} ${formatTokenDisplay(config.tokenLimit, false)} tokens` : "";
2967
- const usageCol3 = `${import_picocolors$7.default.gray("Cost:")} ${formatCurrency(block.costUSD)}`;
3145
+ return `${import_picocolors$4.default.bold("Burn Rate:")} ${formattedRate} token/min ${rateIndicator}`;
3146
+ }, usageLabel = `${drawEmoji("🔥")}${import_picocolors$4.default.bold(" USAGE")}`, usageLabelWidth = stringWidth(usageLabel), usageBarStr = `${usageLabel}${"".padEnd(Math.max(0, labelWidth - usageLabelWidth))} ${usageBar} ${usageRightText}`;
3147
+ let rateDisplay = buildRateDisplay(false), usageCol1 = `${import_picocolors$4.default.gray("Tokens:")} ${formatTokenDisplay(totalTokens, false)} (${rateDisplay})`, usageCol2 = config.tokenLimit != null && config.tokenLimit > 0 ? `${import_picocolors$4.default.gray("Limit:")} ${formatTokenDisplay(config.tokenLimit, false)} tokens` : "";
3148
+ const usageCol3 = `${import_picocolors$4.default.gray("Cost:")} ${formatCurrency(block.costUSD)}`;
2968
3149
  let totalWidth = stringWidth(usageCol1);
2969
3150
  if (usageCol2.length > 0) totalWidth += detailsSpacing + stringWidth(usageCol2);
2970
3151
  totalWidth += detailsSpacing + stringWidth(usageCol3);
2971
3152
  let useTwoLineLayout = false;
2972
3153
  if (totalWidth > detailsAvailableWidth) {
2973
- if (useTwoLineLayout = true, rateDisplay = buildRateDisplay(true), usageCol1 = `${import_picocolors$7.default.gray("Tokens:")} ${formatTokenDisplay(totalTokens, true)} (${rateDisplay})`, usageCol2.length > 0) usageCol2 = `${import_picocolors$7.default.gray("Limit:")} ${formatTokenDisplay(config.tokenLimit, true)} tokens`;
3154
+ if (useTwoLineLayout = true, rateDisplay = buildRateDisplay(true), usageCol1 = `${import_picocolors$4.default.gray("Tokens:")} ${formatTokenDisplay(totalTokens, true)} (${rateDisplay})`, usageCol2.length > 0) usageCol2 = `${import_picocolors$4.default.gray("Limit:")} ${formatTokenDisplay(config.tokenLimit, true)} tokens`;
2974
3155
  }
2975
3156
  const usageBarPadded = usageBarStr + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(usageBarStr)));
2976
3157
  if (terminal.write(`${marginStr}│ ${usageBarPadded}│\n`), useTwoLineLayout) {
@@ -2989,22 +3170,22 @@ function renderLiveDisplay(terminal, block, config) {
2989
3170
  terminal.write(`${marginStr}│ ${usageDetailsPadded}│\n`);
2990
3171
  }
2991
3172
  if (terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`), terminal.write(`${marginStr}├${"─".repeat(boxWidth - 2)}┤\n`), terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`), projection != null) {
2992
- let projBarColor = import_picocolors$7.default.green;
2993
- if (projectedPercent > 100) projBarColor = import_picocolors$7.default.red;
2994
- else if (projectedPercent > 80) projBarColor = import_picocolors$7.default.yellow;
3173
+ let projBarColor = import_picocolors$4.default.green;
3174
+ if (projectedPercent > 100) projBarColor = import_picocolors$4.default.red;
3175
+ else if (projectedPercent > 80) projBarColor = import_picocolors$4.default.yellow;
2995
3176
  const projectionBar = config.tokenLimit != null && config.tokenLimit > 0 ? createProgressBar(projection.totalTokens, config.tokenLimit, barWidth, {
2996
3177
  showPercentage: false,
2997
3178
  fillChar: projBarColor("█"),
2998
- emptyChar: import_picocolors$7.default.gray("░"),
3179
+ emptyChar: import_picocolors$4.default.gray("░"),
2999
3180
  leftBracket: "[",
3000
3181
  rightBracket: "]"
3001
- }) : `[${import_picocolors$7.default.green("█".repeat(Math.floor(barWidth * .15)))}${import_picocolors$7.default.gray("░".repeat(barWidth - Math.floor(barWidth * .15)))}]`, limitStatus = config.tokenLimit != null && config.tokenLimit > 0 ? projectedPercent > 100 ? import_picocolors$7.default.red(`${drawEmoji("❌")} WILL EXCEED LIMIT`) : projectedPercent > 80 ? import_picocolors$7.default.yellow(`${drawEmoji("⚠️")} APPROACHING LIMIT`) : import_picocolors$7.default.green(`${drawEmoji("✓")} WITHIN LIMIT`) : import_picocolors$7.default.green(`${drawEmoji("✓")} ON TRACK`), projLabel = `${drawEmoji("📈")}${import_picocolors$7.default.bold(" PROJECTION")}`, projLabelWidth = stringWidth(projLabel), projBarStr = `${projLabel}${"".padEnd(Math.max(0, labelWidth - projLabelWidth))} ${projectionBar} ${projectionRightText}`, projBarPadded = projBarStr + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(projBarStr)));
3182
+ }) : `[${import_picocolors$4.default.green("█".repeat(Math.floor(barWidth * .15)))}${import_picocolors$4.default.gray("░".repeat(barWidth - Math.floor(barWidth * .15)))}]`, limitStatus = config.tokenLimit != null && config.tokenLimit > 0 ? projectedPercent > 100 ? import_picocolors$4.default.red(`${drawEmoji("❌")} WILL EXCEED LIMIT`) : projectedPercent > 80 ? import_picocolors$4.default.yellow(`${drawEmoji("⚠️")} APPROACHING LIMIT`) : import_picocolors$4.default.green(`${drawEmoji("✓")} WITHIN LIMIT`) : import_picocolors$4.default.green(`${drawEmoji("✓")} ON TRACK`), projLabel = `${drawEmoji("📈")}${import_picocolors$4.default.bold(" PROJECTION")}`, projLabelWidth = stringWidth(projLabel), projBarStr = `${projLabel}${"".padEnd(Math.max(0, labelWidth - projLabelWidth))} ${projectionBar} ${projectionRightText}`, projBarPadded = projBarStr + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(projBarStr)));
3002
3183
  terminal.write(`${marginStr}│ ${projBarPadded}│\n`);
3003
- const projCol1 = `${import_picocolors$7.default.gray("Status:")} ${limitStatus}`;
3004
- let projCol2 = `${import_picocolors$7.default.gray("Tokens:")} ${formatTokenDisplay(projection.totalTokens, false)}`;
3005
- const projCol3 = `${import_picocolors$7.default.gray("Cost:")} ${formatCurrency(projection.totalCost)}`, projTotalWidth = stringWidth(projCol1) + stringWidth(projCol2) + stringWidth(projCol3) + detailsSpacing * 2;
3184
+ const projCol1 = `${import_picocolors$4.default.gray("Status:")} ${limitStatus}`;
3185
+ let projCol2 = `${import_picocolors$4.default.gray("Tokens:")} ${formatTokenDisplay(projection.totalTokens, false)}`;
3186
+ const projCol3 = `${import_picocolors$4.default.gray("Cost:")} ${formatCurrency(projection.totalCost)}`, projTotalWidth = stringWidth(projCol1) + stringWidth(projCol2) + stringWidth(projCol3) + detailsSpacing * 2;
3006
3187
  let projUseTwoLineLayout = false;
3007
- if (projTotalWidth > detailsAvailableWidth) projUseTwoLineLayout = true, projCol2 = `${import_picocolors$7.default.gray("Tokens:")} ${formatTokenDisplay(projection.totalTokens, true)}`;
3188
+ if (projTotalWidth > detailsAvailableWidth) projUseTwoLineLayout = true, projCol2 = `${import_picocolors$4.default.gray("Tokens:")} ${formatTokenDisplay(projection.totalTokens, true)}`;
3008
3189
  if (projUseTwoLineLayout) {
3009
3190
  const projDetailsLine1 = `${" ".repeat(detailsIndent)}${projCol1}`, projDetailsLine1Padded = projDetailsLine1 + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(projDetailsLine1)));
3010
3191
  terminal.write(`${marginStr}│ ${projDetailsLine1Padded}│\n`);
@@ -3023,37 +3204,37 @@ function renderLiveDisplay(terminal, block, config) {
3023
3204
  }
3024
3205
  terminal.write(`${marginStr}├${"─".repeat(boxWidth - 2)}┤\n`);
3025
3206
  const refreshText = `${drawEmoji("↻")} Refreshing every ${config.refreshInterval / 1e3}s • Press Ctrl+C to stop`;
3026
- terminal.write(`${marginStr}│${import_picocolors$7.default.gray(centerText(refreshText, boxWidth - 2))}│\n`), terminal.write(`${marginStr}└${"─".repeat(boxWidth - 2)}┘\n`);
3207
+ terminal.write(`${marginStr}│${import_picocolors$4.default.gray(centerText(refreshText, boxWidth - 2))}│\n`), terminal.write(`${marginStr}└${"─".repeat(boxWidth - 2)}┘\n`);
3027
3208
  }
3028
3209
  function renderCompactLiveDisplay(terminal, block, config, totalTokens, elapsed, remaining) {
3029
3210
  const width = terminal.width;
3030
- terminal.write(`${import_picocolors$7.default.bold(centerText("LIVE MONITOR", width))}\n`), terminal.write(`${"─".repeat(width)}\n`);
3211
+ terminal.write(`${import_picocolors$4.default.bold(centerText("LIVE MONITOR", width))}\n`), terminal.write(`${"─".repeat(width)}\n`);
3031
3212
  const sessionPercent = elapsed / (elapsed + remaining) * 100;
3032
3213
  if (terminal.write(`Session: ${sessionPercent.toFixed(1)}% (${Math.floor(elapsed / 60)}h ${Math.floor(elapsed % 60)}m)\n`), config.tokenLimit != null && config.tokenLimit > 0) {
3033
- const tokenPercent = totalTokens / config.tokenLimit * 100, status = tokenPercent > 100 ? import_picocolors$7.default.red("OVER") : tokenPercent > 80 ? import_picocolors$7.default.yellow("WARN") : import_picocolors$7.default.green("OK");
3214
+ const tokenPercent = totalTokens / config.tokenLimit * 100, status = tokenPercent > 100 ? import_picocolors$4.default.red("OVER") : tokenPercent > 80 ? import_picocolors$4.default.yellow("WARN") : import_picocolors$4.default.green("OK");
3034
3215
  terminal.write(`Tokens: ${formatNumber(totalTokens)}/${formatNumber(config.tokenLimit)} ${status}\n`);
3035
3216
  } else terminal.write(`Tokens: ${formatNumber(totalTokens)}\n`);
3036
3217
  terminal.write(`Cost: ${formatCurrency(block.costUSD)}\n`);
3037
3218
  const burnRate = calculateBurnRate(block);
3038
3219
  if (burnRate != null) terminal.write(`Rate: ${formatNumber(burnRate.tokensPerMinute)}/min\n`);
3039
- terminal.write(`${"─".repeat(width)}\n`), terminal.write(import_picocolors$7.default.gray(`Refresh: ${config.refreshInterval / 1e3}s | Ctrl+C: stop\n`));
3220
+ terminal.write(`${"─".repeat(width)}\n`), terminal.write(import_picocolors$4.default.gray(`Refresh: ${config.refreshInterval / 1e3}s | Ctrl+C: stop\n`));
3040
3221
  }
3041
- var import_picocolors$6 = /* @__PURE__ */ __toESM(require_picocolors(), 1), import_usingCtx$1 = /* @__PURE__ */ __toESM(require_usingCtx(), 1);
3222
+ var import_picocolors$3 = /* @__PURE__ */ __toESM(require_picocolors(), 1), import_usingCtx$1 = /* @__PURE__ */ __toESM(require_usingCtx(), 1);
3042
3223
  async function startLiveMonitoring(config) {
3043
3224
  try {
3044
3225
  var _usingCtx = (0, import_usingCtx$1.default)();
3045
3226
  const terminal = new TerminalManager(), abortController = new AbortController();
3046
3227
  let lastRenderTime = 0;
3047
- const cleanup = () => {
3048
- if (abortController.abort(), terminal.cleanup(), terminal.clearScreen(), logger.info("Live monitoring stopped."), process$1.exitCode == null) process$1.exit(0);
3049
- };
3050
- process$1.on("SIGINT", cleanup), process$1.on("SIGTERM", cleanup), terminal.enterAlternateScreen(), terminal.enableSyncMode(), terminal.clearScreen(), terminal.hideCursor();
3051
- const monitor = _usingCtx.u(new LiveMonitor({
3228
+ const monitorConfig = {
3052
3229
  claudePaths: config.claudePaths,
3053
3230
  sessionDurationHours: config.sessionDurationHours,
3054
3231
  mode: config.mode,
3055
3232
  order: config.order
3056
- })), monitoringResult = await try_({
3233
+ }, monitorState = _usingCtx.u(createLiveMonitorState(monitorConfig)), cleanup = () => {
3234
+ if (abortController.abort(), terminal.cleanup(), terminal.clearScreen(), logger.info("Live monitoring stopped."), process$1.exitCode == null) process$1.exit(0);
3235
+ };
3236
+ process$1.on("SIGINT", cleanup), process$1.on("SIGTERM", cleanup), terminal.enterAlternateScreen(), terminal.enableSyncMode(), terminal.clearScreen(), terminal.hideCursor();
3237
+ const monitoringResult = await try_({
3057
3238
  try: async () => {
3058
3239
  while (!abortController.signal.aborted) {
3059
3240
  const now = Date.now(), timeSinceLastRender = now - lastRenderTime;
@@ -3061,8 +3242,8 @@ async function startLiveMonitoring(config) {
3061
3242
  await delayWithAbort(MIN_RENDER_INTERVAL_MS - timeSinceLastRender, abortController.signal);
3062
3243
  continue;
3063
3244
  }
3064
- const activeBlock = await monitor.getActiveBlock();
3065
- if (monitor.clearCache(), activeBlock == null) {
3245
+ const activeBlock = await getActiveBlock(monitorState, monitorConfig);
3246
+ if (clearLiveMonitorCache(monitorState), activeBlock == null) {
3066
3247
  await renderWaitingState(terminal, config, abortController.signal);
3067
3248
  continue;
3068
3249
  }
@@ -3083,7 +3264,7 @@ async function startLiveMonitoring(config) {
3083
3264
  const error = monitoringResult.error;
3084
3265
  if ((error instanceof DOMException || error instanceof Error) && error.name === "AbortError") return;
3085
3266
  const errorMessage = error instanceof Error ? error.message : String(error);
3086
- terminal.startBuffering(), terminal.clearScreen(), terminal.write(import_picocolors$6.default.red(`Error: ${errorMessage}\n`)), terminal.flush(), logger.error(`Live monitoring error: ${errorMessage}`), await delayWithAbort(config.refreshInterval, abortController.signal).catch(() => {});
3267
+ terminal.startBuffering(), terminal.clearScreen(), terminal.write(import_picocolors$3.default.red(`Error: ${errorMessage}\n`)), terminal.flush(), logger.error(`Live monitoring error: ${errorMessage}`), await delayWithAbort(config.refreshInterval, abortController.signal).catch(() => {});
3087
3268
  }
3088
3269
  } catch (_) {
3089
3270
  _usingCtx.e = _;
@@ -3091,7 +3272,7 @@ async function startLiveMonitoring(config) {
3091
3272
  _usingCtx.d();
3092
3273
  }
3093
3274
  }
3094
- var import_picocolors$5 = /* @__PURE__ */ __toESM(require_picocolors(), 1);
3275
+ var import_picocolors$2 = /* @__PURE__ */ __toESM(require_picocolors(), 1);
3095
3276
  function formatBlockTime(block, compact = false, locale) {
3096
3277
  const start = compact ? block.startTime.toLocaleString(locale, {
3097
3278
  month: "2-digit",
@@ -3260,13 +3441,13 @@ const blocksCommand = define({
3260
3441
  const burnRate = calculateBurnRate(block), projection = projectBlockUsage(block);
3261
3442
  logger.box("Current Session Block Status");
3262
3443
  const now = /* @__PURE__ */ new Date(), elapsed = Math.round((now.getTime() - block.startTime.getTime()) / (1e3 * 60)), remaining = Math.round((block.endTime.getTime() - now.getTime()) / (1e3 * 60));
3263
- if (log(`Block Started: ${import_picocolors$5.default.cyan(block.startTime.toLocaleString())} (${import_picocolors$5.default.yellow(`${Math.floor(elapsed / 60)}h ${elapsed % 60}m`)} ago)`), log(`Time Remaining: ${import_picocolors$5.default.green(`${Math.floor(remaining / 60)}h ${remaining % 60}m`)}\n`), log(import_picocolors$5.default.bold("Current Usage:")), log(` Input Tokens: ${formatNumber(block.tokenCounts.inputTokens)}`), log(` Output Tokens: ${formatNumber(block.tokenCounts.outputTokens)}`), log(` Total Cost: ${formatCurrency(block.costUSD)}\n`), burnRate != null) log(import_picocolors$5.default.bold("Burn Rate:")), log(` Tokens/minute: ${formatNumber(burnRate.tokensPerMinute)}`), log(` Cost/hour: ${formatCurrency(burnRate.costPerHour)}\n`);
3444
+ if (log(`Block Started: ${import_picocolors$2.default.cyan(block.startTime.toLocaleString())} (${import_picocolors$2.default.yellow(`${Math.floor(elapsed / 60)}h ${elapsed % 60}m`)} ago)`), log(`Time Remaining: ${import_picocolors$2.default.green(`${Math.floor(remaining / 60)}h ${remaining % 60}m`)}\n`), log(import_picocolors$2.default.bold("Current Usage:")), log(` Input Tokens: ${formatNumber(block.tokenCounts.inputTokens)}`), log(` Output Tokens: ${formatNumber(block.tokenCounts.outputTokens)}`), log(` Total Cost: ${formatCurrency(block.costUSD)}\n`), burnRate != null) log(import_picocolors$2.default.bold("Burn Rate:")), log(` Tokens/minute: ${formatNumber(burnRate.tokensPerMinute)}`), log(` Cost/hour: ${formatCurrency(burnRate.costPerHour)}\n`);
3264
3445
  if (projection != null) {
3265
- if (log(import_picocolors$5.default.bold("Projected Usage (if current rate continues):")), log(` Total Tokens: ${formatNumber(projection.totalTokens)}`), log(` Total Cost: ${formatCurrency(projection.totalCost)}\n`), ctx.values.tokenLimit != null) {
3446
+ if (log(import_picocolors$2.default.bold("Projected Usage (if current rate continues):")), log(` Total Tokens: ${formatNumber(projection.totalTokens)}`), log(` Total Cost: ${formatCurrency(projection.totalCost)}\n`), ctx.values.tokenLimit != null) {
3266
3447
  const limit = parseTokenLimit(ctx.values.tokenLimit, maxTokensFromAll);
3267
3448
  if (limit != null && limit > 0) {
3268
- const currentTokens = getTotalTokens(block.tokenCounts), remainingTokens = Math.max(0, limit - currentTokens), percentUsed = projection.totalTokens / limit * 100, status = percentUsed > 100 ? import_picocolors$5.default.red("EXCEEDS LIMIT") : percentUsed > BLOCKS_WARNING_THRESHOLD * 100 ? import_picocolors$5.default.yellow("WARNING") : import_picocolors$5.default.green("OK");
3269
- log(import_picocolors$5.default.bold("Token Limit Status:")), log(` Limit: ${formatNumber(limit)} tokens`), log(` Current Usage: ${formatNumber(currentTokens)} (${(currentTokens / limit * 100).toFixed(1)}%)`), log(` Remaining: ${formatNumber(remainingTokens)} tokens`), log(` Projected Usage: ${percentUsed.toFixed(1)}% ${status}`);
3449
+ const currentTokens = getTotalTokens(block.tokenCounts), remainingTokens = Math.max(0, limit - currentTokens), percentUsed = projection.totalTokens / limit * 100, status = percentUsed > 100 ? import_picocolors$2.default.red("EXCEEDS LIMIT") : percentUsed > BLOCKS_WARNING_THRESHOLD * 100 ? import_picocolors$2.default.yellow("WARNING") : import_picocolors$2.default.green("OK");
3450
+ log(import_picocolors$2.default.bold("Token Limit Status:")), log(` Limit: ${formatNumber(limit)} tokens`), log(` Current Usage: ${formatNumber(currentTokens)} (${(currentTokens / limit * 100).toFixed(1)}%)`), log(` Remaining: ${formatNumber(remainingTokens)} tokens`), log(` Projected Usage: ${percentUsed.toFixed(1)}% ${status}`);
3270
3451
  }
3271
3452
  }
3272
3453
  }
@@ -3292,15 +3473,15 @@ const blocksCommand = define({
3292
3473
  }), terminalWidth = process$1.stdout.columns || BLOCKS_DEFAULT_TERMINAL_WIDTH, isNarrowTerminal = terminalWidth < BLOCKS_COMPACT_WIDTH_THRESHOLD, useCompactFormat = ctx.values.compact || isNarrowTerminal;
3293
3474
  for (const block of blocks) if (block.isGap ?? false) {
3294
3475
  const gapRow = [
3295
- import_picocolors$5.default.gray(formatBlockTime(block, useCompactFormat, ctx.values.locale)),
3296
- import_picocolors$5.default.gray("(inactive)"),
3297
- import_picocolors$5.default.gray("-"),
3298
- import_picocolors$5.default.gray("-")
3476
+ import_picocolors$2.default.gray(formatBlockTime(block, useCompactFormat, ctx.values.locale)),
3477
+ import_picocolors$2.default.gray("(inactive)"),
3478
+ import_picocolors$2.default.gray("-"),
3479
+ import_picocolors$2.default.gray("-")
3299
3480
  ];
3300
- if (actualTokenLimit != null && actualTokenLimit > 0) gapRow.push(import_picocolors$5.default.gray("-"));
3301
- gapRow.push(import_picocolors$5.default.gray("-")), table.push(gapRow);
3481
+ if (actualTokenLimit != null && actualTokenLimit > 0) gapRow.push(import_picocolors$2.default.gray("-"));
3482
+ gapRow.push(import_picocolors$2.default.gray("-")), table.push(gapRow);
3302
3483
  } else {
3303
- const totalTokens = getTotalTokens(block.tokenCounts), status = block.isActive ? import_picocolors$5.default.green("ACTIVE") : "", row = [
3484
+ const totalTokens = getTotalTokens(block.tokenCounts), status = block.isActive ? import_picocolors$2.default.green("ACTIVE") : "", row = [
3304
3485
  formatBlockTime(block, useCompactFormat, ctx.values.locale),
3305
3486
  status,
3306
3487
  formatModels(block.models),
@@ -3308,16 +3489,16 @@ const blocksCommand = define({
3308
3489
  ];
3309
3490
  if (actualTokenLimit != null && actualTokenLimit > 0) {
3310
3491
  const percentage = totalTokens / actualTokenLimit * 100, percentText = `${percentage.toFixed(1)}%`;
3311
- row.push(percentage > 100 ? import_picocolors$5.default.red(percentText) : percentText);
3492
+ row.push(percentage > 100 ? import_picocolors$2.default.red(percentText) : percentText);
3312
3493
  }
3313
3494
  if (row.push(formatCurrency(block.costUSD)), table.push(row), block.isActive) {
3314
3495
  if (actualTokenLimit != null && actualTokenLimit > 0) {
3315
- const currentTokens = getTotalTokens(block.tokenCounts), remainingTokens = Math.max(0, actualTokenLimit - currentTokens), remainingText = remainingTokens > 0 ? formatNumber(remainingTokens) : import_picocolors$5.default.red("0"), remainingPercent = (actualTokenLimit - currentTokens) / actualTokenLimit * 100, remainingPercentText = remainingPercent > 0 ? `${remainingPercent.toFixed(1)}%` : import_picocolors$5.default.red("0.0%"), remainingRow = [
3496
+ const currentTokens = getTotalTokens(block.tokenCounts), remainingTokens = Math.max(0, actualTokenLimit - currentTokens), remainingText = remainingTokens > 0 ? formatNumber(remainingTokens) : import_picocolors$2.default.red("0"), remainingPercent = (actualTokenLimit - currentTokens) / actualTokenLimit * 100, remainingPercentText = remainingPercent > 0 ? `${remainingPercent.toFixed(1)}%` : import_picocolors$2.default.red("0.0%"), remainingRow = [
3316
3497
  {
3317
- content: import_picocolors$5.default.gray(`(assuming ${formatNumber(actualTokenLimit)} token limit)`),
3498
+ content: import_picocolors$2.default.gray(`(assuming ${formatNumber(actualTokenLimit)} token limit)`),
3318
3499
  hAlign: "right"
3319
3500
  },
3320
- import_picocolors$5.default.blue("REMAINING"),
3501
+ import_picocolors$2.default.blue("REMAINING"),
3321
3502
  "",
3322
3503
  remainingText,
3323
3504
  remainingPercentText,
@@ -3327,12 +3508,12 @@ const blocksCommand = define({
3327
3508
  }
3328
3509
  const projection = projectBlockUsage(block);
3329
3510
  if (projection != null) {
3330
- const projectedTokens = formatNumber(projection.totalTokens), projectedText = actualTokenLimit != null && actualTokenLimit > 0 && projection.totalTokens > actualTokenLimit ? import_picocolors$5.default.red(projectedTokens) : projectedTokens, projectedRow = [
3511
+ const projectedTokens = formatNumber(projection.totalTokens), projectedText = actualTokenLimit != null && actualTokenLimit > 0 && projection.totalTokens > actualTokenLimit ? import_picocolors$2.default.red(projectedTokens) : projectedTokens, projectedRow = [
3331
3512
  {
3332
- content: import_picocolors$5.default.gray("(assuming current burn rate)"),
3513
+ content: import_picocolors$2.default.gray("(assuming current burn rate)"),
3333
3514
  hAlign: "right"
3334
3515
  },
3335
- import_picocolors$5.default.yellow("PROJECTED"),
3516
+ import_picocolors$2.default.yellow("PROJECTED"),
3336
3517
  "",
3337
3518
  projectedText
3338
3519
  ];
@@ -3411,7 +3592,7 @@ function formatProjectName(projectName, aliases) {
3411
3592
  const parsed = parseProjectName(projectName);
3412
3593
  return aliases != null && aliases.has(parsed) ? aliases.get(parsed) : parsed;
3413
3594
  }
3414
- var import_picocolors$4 = /* @__PURE__ */ __toESM(require_picocolors(), 1);
3595
+ var import_picocolors$1 = /* @__PURE__ */ __toESM(require_picocolors(), 1);
3415
3596
  const dailyCommand = define({
3416
3597
  name: "daily",
3417
3598
  description: "Show usage report grouped by date",
@@ -3488,46 +3669,11 @@ const dailyCommand = define({
3488
3669
  } else log(JSON.stringify(jsonOutput, null, 2));
3489
3670
  } else {
3490
3671
  logger.box("Claude Code Token Usage Report - Daily");
3491
- const table = new ResponsiveTable({
3492
- head: [
3493
- "Date",
3494
- "Models",
3495
- "Input",
3496
- "Output",
3497
- "Cache Create",
3498
- "Cache Read",
3499
- "Total Tokens",
3500
- "Cost (USD)"
3501
- ],
3502
- style: { head: ["cyan"] },
3503
- colAligns: [
3504
- "left",
3505
- "left",
3506
- "right",
3507
- "right",
3508
- "right",
3509
- "right",
3510
- "right",
3511
- "right"
3512
- ],
3672
+ const tableConfig = {
3673
+ firstColumnName: "Date",
3513
3674
  dateFormatter: (dateStr) => formatDateCompact(dateStr, mergedOptions.timezone, mergedOptions.locale ?? void 0),
3514
- compactHead: [
3515
- "Date",
3516
- "Models",
3517
- "Input",
3518
- "Output",
3519
- "Cost (USD)"
3520
- ],
3521
- compactColAligns: [
3522
- "left",
3523
- "left",
3524
- "right",
3525
- "right",
3526
- "right"
3527
- ],
3528
- compactThreshold: 100,
3529
3675
  forceCompact: ctx.values.compact
3530
- });
3676
+ }, table = createUsageReportTable(tableConfig);
3531
3677
  if (Boolean(mergedOptions.instances) && dailyData.some((d) => d.project != null)) {
3532
3678
  const projectGroups = groupDataByProject(dailyData);
3533
3679
  let isFirstProject = true;
@@ -3543,7 +3689,7 @@ const dailyCommand = define({
3543
3689
  ""
3544
3690
  ]);
3545
3691
  table.push([
3546
- import_picocolors$4.default.cyan(`Project: ${formatProjectName(projectName, projectAliases)}`),
3692
+ import_picocolors$1.default.cyan(`Project: ${formatProjectName(projectName, projectAliases)}`),
3547
3693
  "",
3548
3694
  "",
3549
3695
  "",
@@ -3552,47 +3698,39 @@ const dailyCommand = define({
3552
3698
  "",
3553
3699
  ""
3554
3700
  ]);
3555
- for (const data of projectData) if (table.push([
3556
- data.date,
3557
- formatModelsDisplayMultiline(data.modelsUsed),
3558
- formatNumber(data.inputTokens),
3559
- formatNumber(data.outputTokens),
3560
- formatNumber(data.cacheCreationTokens),
3561
- formatNumber(data.cacheReadTokens),
3562
- formatNumber(getTotalTokens(data)),
3563
- formatCurrency(data.totalCost)
3564
- ]), mergedOptions.breakdown) pushBreakdownRows(table, data.modelBreakdowns);
3701
+ for (const data of projectData) {
3702
+ const row = formatUsageDataRow(data.date, {
3703
+ inputTokens: data.inputTokens,
3704
+ outputTokens: data.outputTokens,
3705
+ cacheCreationTokens: data.cacheCreationTokens,
3706
+ cacheReadTokens: data.cacheReadTokens,
3707
+ totalCost: data.totalCost,
3708
+ modelsUsed: data.modelsUsed
3709
+ });
3710
+ if (table.push(row), mergedOptions.breakdown) pushBreakdownRows(table, data.modelBreakdowns);
3711
+ }
3565
3712
  isFirstProject = false;
3566
3713
  }
3567
- } else for (const data of dailyData) if (table.push([
3568
- data.date,
3569
- formatModelsDisplayMultiline(data.modelsUsed),
3570
- formatNumber(data.inputTokens),
3571
- formatNumber(data.outputTokens),
3572
- formatNumber(data.cacheCreationTokens),
3573
- formatNumber(data.cacheReadTokens),
3574
- formatNumber(getTotalTokens(data)),
3575
- formatCurrency(data.totalCost)
3576
- ]), mergedOptions.breakdown) pushBreakdownRows(table, data.modelBreakdowns);
3577
- if (table.push([
3578
- "",
3579
- "",
3580
- "",
3581
- "",
3582
- "",
3583
- "",
3584
- "",
3585
- ""
3586
- ]), table.push([
3587
- import_picocolors$4.default.yellow("Total"),
3588
- "",
3589
- import_picocolors$4.default.yellow(formatNumber(totals.inputTokens)),
3590
- import_picocolors$4.default.yellow(formatNumber(totals.outputTokens)),
3591
- import_picocolors$4.default.yellow(formatNumber(totals.cacheCreationTokens)),
3592
- import_picocolors$4.default.yellow(formatNumber(totals.cacheReadTokens)),
3593
- import_picocolors$4.default.yellow(formatNumber(getTotalTokens(totals))),
3594
- import_picocolors$4.default.yellow(formatCurrency(totals.totalCost))
3595
- ]), log(table.toString()), table.isCompactMode()) logger.info("\nRunning in Compact Mode"), logger.info("Expand terminal width to see cache metrics and total tokens");
3714
+ } else for (const data of dailyData) {
3715
+ const row = formatUsageDataRow(data.date, {
3716
+ inputTokens: data.inputTokens,
3717
+ outputTokens: data.outputTokens,
3718
+ cacheCreationTokens: data.cacheCreationTokens,
3719
+ cacheReadTokens: data.cacheReadTokens,
3720
+ totalCost: data.totalCost,
3721
+ modelsUsed: data.modelsUsed
3722
+ });
3723
+ if (table.push(row), mergedOptions.breakdown) pushBreakdownRows(table, data.modelBreakdowns);
3724
+ }
3725
+ addEmptySeparatorRow(table, 8);
3726
+ const totalsRow = formatTotalsRow({
3727
+ inputTokens: totals.inputTokens,
3728
+ outputTokens: totals.outputTokens,
3729
+ cacheCreationTokens: totals.cacheCreationTokens,
3730
+ cacheReadTokens: totals.cacheReadTokens,
3731
+ totalCost: totals.totalCost
3732
+ });
3733
+ if (table.push(totalsRow), log(table.toString()), table.isCompactMode()) logger.info("\nRunning in Compact Mode"), logger.info("Expand terminal width to see cache metrics and total tokens");
3596
3734
  }
3597
3735
  }
3598
3736
  });
@@ -3978,9 +4116,7 @@ const mcpCommand = define({
3978
4116
  }), logger.info(`MCP server is running on http://localhost:${port}`);
3979
4117
  }
3980
4118
  }
3981
- });
3982
- var import_picocolors$3 = /* @__PURE__ */ __toESM(require_picocolors(), 1);
3983
- const monthlyCommand = define({
4119
+ }), monthlyCommand = define({
3984
4120
  name: "monthly",
3985
4121
  description: "Show usage report grouped by month",
3986
4122
  ...sharedCommandConfig,
@@ -4032,75 +4168,31 @@ const monthlyCommand = define({
4032
4168
  } else log(JSON.stringify(jsonOutput, null, 2));
4033
4169
  } else {
4034
4170
  logger.box("Claude Code Token Usage Report - Monthly");
4035
- const table = new ResponsiveTable({
4036
- head: [
4037
- "Month",
4038
- "Models",
4039
- "Input",
4040
- "Output",
4041
- "Cache Create",
4042
- "Cache Read",
4043
- "Total Tokens",
4044
- "Cost (USD)"
4045
- ],
4046
- style: { head: ["cyan"] },
4047
- colAligns: [
4048
- "left",
4049
- "left",
4050
- "right",
4051
- "right",
4052
- "right",
4053
- "right",
4054
- "right",
4055
- "right"
4056
- ],
4057
- dateFormatter: (dateStr) => formatDateCompact(dateStr, mergedOptions.timezone, mergedOptions.locale ?? "en-CA"),
4058
- compactHead: [
4059
- "Month",
4060
- "Models",
4061
- "Input",
4062
- "Output",
4063
- "Cost (USD)"
4064
- ],
4065
- compactColAligns: [
4066
- "left",
4067
- "left",
4068
- "right",
4069
- "right",
4070
- "right"
4071
- ],
4072
- compactThreshold: 100,
4171
+ const tableConfig = {
4172
+ firstColumnName: "Month",
4173
+ dateFormatter: (dateStr) => formatDateCompact(dateStr, mergedOptions.timezone, mergedOptions.locale ?? DEFAULT_LOCALE),
4073
4174
  forceCompact: ctx.values.compact
4175
+ }, table = createUsageReportTable(tableConfig);
4176
+ for (const data of monthlyData) {
4177
+ const row = formatUsageDataRow(data.month, {
4178
+ inputTokens: data.inputTokens,
4179
+ outputTokens: data.outputTokens,
4180
+ cacheCreationTokens: data.cacheCreationTokens,
4181
+ cacheReadTokens: data.cacheReadTokens,
4182
+ totalCost: data.totalCost,
4183
+ modelsUsed: data.modelsUsed
4184
+ });
4185
+ if (table.push(row), mergedOptions.breakdown) pushBreakdownRows(table, data.modelBreakdowns);
4186
+ }
4187
+ addEmptySeparatorRow(table, 8);
4188
+ const totalsRow = formatTotalsRow({
4189
+ inputTokens: totals.inputTokens,
4190
+ outputTokens: totals.outputTokens,
4191
+ cacheCreationTokens: totals.cacheCreationTokens,
4192
+ cacheReadTokens: totals.cacheReadTokens,
4193
+ totalCost: totals.totalCost
4074
4194
  });
4075
- for (const data of monthlyData) if (table.push([
4076
- data.month,
4077
- formatModelsDisplayMultiline(data.modelsUsed),
4078
- formatNumber(data.inputTokens),
4079
- formatNumber(data.outputTokens),
4080
- formatNumber(data.cacheCreationTokens),
4081
- formatNumber(data.cacheReadTokens),
4082
- formatNumber(getTotalTokens(data)),
4083
- formatCurrency(data.totalCost)
4084
- ]), mergedOptions.breakdown) pushBreakdownRows(table, data.modelBreakdowns);
4085
- if (table.push([
4086
- "",
4087
- "",
4088
- "",
4089
- "",
4090
- "",
4091
- "",
4092
- "",
4093
- ""
4094
- ]), table.push([
4095
- import_picocolors$3.default.yellow("Total"),
4096
- "",
4097
- import_picocolors$3.default.yellow(formatNumber(totals.inputTokens)),
4098
- import_picocolors$3.default.yellow(formatNumber(totals.outputTokens)),
4099
- import_picocolors$3.default.yellow(formatNumber(totals.cacheCreationTokens)),
4100
- import_picocolors$3.default.yellow(formatNumber(totals.cacheReadTokens)),
4101
- import_picocolors$3.default.yellow(formatNumber(getTotalTokens(totals))),
4102
- import_picocolors$3.default.yellow(formatCurrency(totals.totalCost))
4103
- ]), log(table.toString()), table.isCompactMode()) logger.info("\nRunning in Compact Mode"), logger.info("Expand terminal width to see cache metrics and total tokens");
4195
+ if (table.push(totalsRow), log(table.toString()), table.isCompactMode()) logger.info("\nRunning in Compact Mode"), logger.info("Expand terminal width to see cache metrics and total tokens");
4104
4196
  }
4105
4197
  }
4106
4198
  });
@@ -4178,7 +4270,6 @@ function calculateSessionTotalTokens(entries) {
4178
4270
  return sum + usage.input_tokens + usage.output_tokens + (usage.cache_creation_input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0);
4179
4271
  }, 0);
4180
4272
  }
4181
- var import_picocolors$2 = /* @__PURE__ */ __toESM(require_picocolors(), 1);
4182
4273
  const sessionCommand = define({
4183
4274
  name: "session",
4184
4275
  description: "Show usage report grouped by conversation session",
@@ -4201,7 +4292,7 @@ const sessionCommand = define({
4201
4292
  offline: mergedOptions.offline,
4202
4293
  jq: mergedOptions.jq,
4203
4294
  timezone: mergedOptions.timezone,
4204
- locale: mergedOptions.locale ?? "en-CA"
4295
+ locale: mergedOptions.locale ?? DEFAULT_LOCALE
4205
4296
  } }, useJson);
4206
4297
  const sessionData = await loadSessionData({
4207
4298
  since: ctx.values.since,
@@ -4246,86 +4337,35 @@ const sessionCommand = define({
4246
4337
  } else log(JSON.stringify(jsonOutput, null, 2));
4247
4338
  } else {
4248
4339
  logger.box("Claude Code Token Usage Report - By Session");
4249
- const table = new ResponsiveTable({
4250
- head: [
4251
- "Session",
4252
- "Models",
4253
- "Input",
4254
- "Output",
4255
- "Cache Create",
4256
- "Cache Read",
4257
- "Total Tokens",
4258
- "Cost (USD)",
4259
- "Last Activity"
4260
- ],
4261
- style: { head: ["cyan"] },
4262
- colAligns: [
4263
- "left",
4264
- "left",
4265
- "right",
4266
- "right",
4267
- "right",
4268
- "right",
4269
- "right",
4270
- "right",
4271
- "left"
4272
- ],
4340
+ const tableConfig = {
4341
+ firstColumnName: "Session",
4342
+ includeLastActivity: true,
4273
4343
  dateFormatter: (dateStr) => formatDateCompact(dateStr, ctx.values.timezone, ctx.values.locale),
4274
- compactHead: [
4275
- "Session",
4276
- "Models",
4277
- "Input",
4278
- "Output",
4279
- "Cost (USD)",
4280
- "Last Activity"
4281
- ],
4282
- compactColAligns: [
4283
- "left",
4284
- "left",
4285
- "right",
4286
- "right",
4287
- "right",
4288
- "left"
4289
- ],
4290
- compactThreshold: 100,
4291
4344
  forceCompact: ctx.values.compact
4292
- });
4345
+ }, table = createUsageReportTable(tableConfig);
4293
4346
  let maxSessionLength = 0;
4294
4347
  for (const data of sessionData) {
4295
4348
  const sessionDisplay = data.sessionId.split("-").slice(-2).join("-");
4296
- if (maxSessionLength = Math.max(maxSessionLength, sessionDisplay.length), table.push([
4297
- sessionDisplay,
4298
- formatModelsDisplayMultiline(data.modelsUsed),
4299
- formatNumber(data.inputTokens),
4300
- formatNumber(data.outputTokens),
4301
- formatNumber(data.cacheCreationTokens),
4302
- formatNumber(data.cacheReadTokens),
4303
- formatNumber(getTotalTokens(data)),
4304
- formatCurrency(data.totalCost),
4305
- data.lastActivity
4306
- ]), ctx.values.breakdown) pushBreakdownRows(table, data.modelBreakdowns, 1, 1);
4349
+ maxSessionLength = Math.max(maxSessionLength, sessionDisplay.length);
4350
+ const row = formatUsageDataRow(sessionDisplay, {
4351
+ inputTokens: data.inputTokens,
4352
+ outputTokens: data.outputTokens,
4353
+ cacheCreationTokens: data.cacheCreationTokens,
4354
+ cacheReadTokens: data.cacheReadTokens,
4355
+ totalCost: data.totalCost,
4356
+ modelsUsed: data.modelsUsed
4357
+ }, data.lastActivity);
4358
+ if (table.push(row), ctx.values.breakdown) pushBreakdownRows(table, data.modelBreakdowns, 1, 1);
4307
4359
  }
4308
- if (table.push([
4309
- "",
4310
- "",
4311
- "",
4312
- "",
4313
- "",
4314
- "",
4315
- "",
4316
- "",
4317
- ""
4318
- ]), table.push([
4319
- import_picocolors$2.default.yellow("Total"),
4320
- "",
4321
- import_picocolors$2.default.yellow(formatNumber(totals.inputTokens)),
4322
- import_picocolors$2.default.yellow(formatNumber(totals.outputTokens)),
4323
- import_picocolors$2.default.yellow(formatNumber(totals.cacheCreationTokens)),
4324
- import_picocolors$2.default.yellow(formatNumber(totals.cacheReadTokens)),
4325
- import_picocolors$2.default.yellow(formatNumber(getTotalTokens(totals))),
4326
- import_picocolors$2.default.yellow(formatCurrency(totals.totalCost)),
4327
- ""
4328
- ]), log(table.toString()), table.isCompactMode()) logger.info("\nRunning in Compact Mode"), logger.info("Expand terminal width to see cache metrics and total tokens");
4360
+ addEmptySeparatorRow(table, 9);
4361
+ const totalsRow = formatTotalsRow({
4362
+ inputTokens: totals.inputTokens,
4363
+ outputTokens: totals.outputTokens,
4364
+ cacheCreationTokens: totals.cacheCreationTokens,
4365
+ cacheReadTokens: totals.cacheReadTokens,
4366
+ totalCost: totals.totalCost
4367
+ }, true);
4368
+ if (table.push(totalsRow), log(table.toString()), table.isCompactMode()) logger.info("\nRunning in Compact Mode"), logger.info("Expand terminal width to see cache metrics and total tokens");
4329
4369
  }
4330
4370
  }
4331
4371
  });
@@ -5198,7 +5238,7 @@ getStdin.buffer = async () => {
5198
5238
  for await (const chunk of stdin$1) result.push(chunk), length += chunk.length;
5199
5239
  return Buffer.concat(result, length);
5200
5240
  };
5201
- var import_picocolors$1 = /* @__PURE__ */ __toESM(require_picocolors(), 1), import_usingCtx = /* @__PURE__ */ __toESM(require_usingCtx(), 1);
5241
+ var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1), import_usingCtx = /* @__PURE__ */ __toESM(require_usingCtx(), 1);
5202
5242
  function formatRemainingTime(remaining) {
5203
5243
  const remainingHours = Math.floor(remaining / 60), remainingMins = remaining % 60;
5204
5244
  return remainingHours > 0 ? `${remainingHours}h ${remainingMins}m left` : `${remainingMins}m left`;
@@ -5219,7 +5259,7 @@ const visualBurnRateChoices = [
5219
5259
  "ccusage",
5220
5260
  "cc",
5221
5261
  "both"
5222
- ], statuslineCommand = define({
5262
+ ], contextThresholdSchema = coerce.number().int().min(0, "Context threshold must be at least 0").max(100, "Context threshold must be at most 100"), statuslineCommand = define({
5223
5263
  name: "statusline",
5224
5264
  description: "Display compact status line for Claude Code hooks with hybrid time+file caching (Beta)",
5225
5265
  toKebab: true,
@@ -5255,11 +5295,23 @@ const visualBurnRateChoices = [
5255
5295
  description: `Refresh interval in seconds for cache expiry (default: ${DEFAULT_REFRESH_INTERVAL_SECONDS})`,
5256
5296
  default: DEFAULT_REFRESH_INTERVAL_SECONDS
5257
5297
  },
5298
+ contextLowThreshold: {
5299
+ type: "custom",
5300
+ description: "Context usage percentage below which status is shown in green (0-100)",
5301
+ parse: (value$1) => contextThresholdSchema.parse(value$1),
5302
+ default: DEFAULT_CONTEXT_USAGE_THRESHOLDS.LOW
5303
+ },
5304
+ contextMediumThreshold: {
5305
+ type: "custom",
5306
+ description: "Context usage percentage below which status is shown in yellow (0-100)",
5307
+ parse: (value$1) => contextThresholdSchema.parse(value$1),
5308
+ default: DEFAULT_CONTEXT_USAGE_THRESHOLDS.MEDIUM
5309
+ },
5258
5310
  config: sharedArgs.config,
5259
5311
  debug: sharedArgs.debug
5260
5312
  },
5261
5313
  async run(ctx) {
5262
- logger.level = 0;
5314
+ if (logger.level = 0, ctx.values.contextLowThreshold >= ctx.values.contextMediumThreshold) throw new Error(`Context low threshold (${ctx.values.contextLowThreshold}) must be less than medium threshold (${ctx.values.contextMediumThreshold})`);
5263
5315
  const config = loadConfig(ctx.values.config, ctx.values.debug), mergedOptions = mergeConfigWithArgs(ctx, config, ctx.values.debug), refreshInterval = mergedOptions.refreshInterval, stdin$2 = await getStdin();
5264
5316
  if (stdin$2.length === 0) log("❌ No input provided"), process$1.exit(1);
5265
5317
  const hookDataJson = JSON.parse(stdin$2.trim()), hookDataParseResult = statuslineHookJsonSchema.safeParse(hookDataJson);
@@ -5339,7 +5391,7 @@ const visualBurnRateChoices = [
5339
5391
  const cost = await getCcusageCost();
5340
5392
  return { sessionCost: cost };
5341
5393
  }
5342
- return {};
5394
+ return unreachable(costSource), {};
5343
5395
  })(), today = /* @__PURE__ */ new Date(), todayStr = today.toISOString().split("T")[0]?.replace(/-/g, "") ?? "", todayCost = await pipe(try_({
5344
5396
  try: async () => loadDailyUsageData({
5345
5397
  since: todayStr,
@@ -5374,17 +5426,17 @@ const visualBurnRateChoices = [
5374
5426
  normal: {
5375
5427
  emoji: "🟢",
5376
5428
  textValue: "Normal",
5377
- coloredString: import_picocolors$1.default.green
5429
+ coloredString: import_picocolors.default.green
5378
5430
  },
5379
5431
  moderate: {
5380
5432
  emoji: "⚠️",
5381
5433
  textValue: "Moderate",
5382
- coloredString: import_picocolors$1.default.yellow
5434
+ coloredString: import_picocolors.default.yellow
5383
5435
  },
5384
5436
  high: {
5385
5437
  emoji: "🚨",
5386
5438
  textValue: "High",
5387
- coloredString: import_picocolors$1.default.red
5439
+ coloredString: import_picocolors.default.red
5388
5440
  }
5389
5441
  }, { emoji, textValue, coloredString } = burnStatusMappings[burnStatus], burnRateOutputSegments = [coloredString(costPerHourStr)];
5390
5442
  if (renderEmojiStatus) burnRateOutputSegments.push(emoji);
@@ -5406,9 +5458,9 @@ const visualBurnRateChoices = [
5406
5458
  })), contextInfo = await pipe(try_({
5407
5459
  try: calculateContextTokens(hookData.transcript_path, hookData.model.id, mergedOptions.offline),
5408
5460
  catch: (error) => error
5409
- }), inspectError((error) => logger.debug(`Failed to calculate context tokens: ${error instanceof Error ? error.message : String(error)}`)), map((ctx$1) => {
5410
- if (ctx$1 == null) return void 0;
5411
- const thresholds = getContextUsageThresholds(), color = ctx$1.percentage < thresholds.LOW ? import_picocolors$1.default.green : ctx$1.percentage < thresholds.MEDIUM ? import_picocolors$1.default.yellow : import_picocolors$1.default.red, coloredPercentage = color(`${ctx$1.percentage}%`), tokenDisplay = ctx$1.inputTokens.toLocaleString();
5461
+ }), inspectError((error) => logger.debug(`Failed to calculate context tokens: ${error instanceof Error ? error.message : String(error)}`)), map((contextResult) => {
5462
+ if (contextResult == null) return void 0;
5463
+ const color = contextResult.percentage < ctx.values.contextLowThreshold ? import_picocolors.default.green : contextResult.percentage < ctx.values.contextMediumThreshold ? import_picocolors.default.yellow : import_picocolors.default.red, coloredPercentage = color(`${contextResult.percentage}%`), tokenDisplay = contextResult.inputTokens.toLocaleString();
5412
5464
  return `${tokenDisplay} (${coloredPercentage})`;
5413
5465
  }), unwrap(void 0)), modelName = hookData.model.display_name, sessionDisplay = (() => {
5414
5466
  if (ccCost != null || ccusageCost != null) {
@@ -5454,9 +5506,7 @@ const visualBurnRateChoices = [
5454
5506
  _usingCtx4.d();
5455
5507
  }
5456
5508
  }
5457
- });
5458
- var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
5459
- const weeklyCommand = define({
5509
+ }), weeklyCommand = define({
5460
5510
  name: "weekly",
5461
5511
  description: "Show usage report grouped by week",
5462
5512
  args: {
@@ -5518,75 +5568,31 @@ const weeklyCommand = define({
5518
5568
  } else log(JSON.stringify(jsonOutput, null, 2));
5519
5569
  } else {
5520
5570
  logger.box("Claude Code Token Usage Report - Weekly");
5521
- const table = new ResponsiveTable({
5522
- head: [
5523
- "Week",
5524
- "Models",
5525
- "Input",
5526
- "Output",
5527
- "Cache Create",
5528
- "Cache Read",
5529
- "Total Tokens",
5530
- "Cost (USD)"
5531
- ],
5532
- style: { head: ["cyan"] },
5533
- colAligns: [
5534
- "left",
5535
- "left",
5536
- "right",
5537
- "right",
5538
- "right",
5539
- "right",
5540
- "right",
5541
- "right"
5542
- ],
5571
+ const tableConfig = {
5572
+ firstColumnName: "Week",
5543
5573
  dateFormatter: (dateStr) => formatDateCompact(dateStr, mergedOptions.timezone, mergedOptions.locale ?? void 0),
5544
- compactHead: [
5545
- "Week",
5546
- "Models",
5547
- "Input",
5548
- "Output",
5549
- "Cost (USD)"
5550
- ],
5551
- compactColAligns: [
5552
- "left",
5553
- "left",
5554
- "right",
5555
- "right",
5556
- "right"
5557
- ],
5558
- compactThreshold: 100,
5559
5574
  forceCompact: ctx.values.compact
5575
+ }, table = createUsageReportTable(tableConfig);
5576
+ for (const data of weeklyData) {
5577
+ const row = formatUsageDataRow(data.week, {
5578
+ inputTokens: data.inputTokens,
5579
+ outputTokens: data.outputTokens,
5580
+ cacheCreationTokens: data.cacheCreationTokens,
5581
+ cacheReadTokens: data.cacheReadTokens,
5582
+ totalCost: data.totalCost,
5583
+ modelsUsed: data.modelsUsed
5584
+ });
5585
+ if (table.push(row), mergedOptions.breakdown) pushBreakdownRows(table, data.modelBreakdowns);
5586
+ }
5587
+ addEmptySeparatorRow(table, 8);
5588
+ const totalsRow = formatTotalsRow({
5589
+ inputTokens: totals.inputTokens,
5590
+ outputTokens: totals.outputTokens,
5591
+ cacheCreationTokens: totals.cacheCreationTokens,
5592
+ cacheReadTokens: totals.cacheReadTokens,
5593
+ totalCost: totals.totalCost
5560
5594
  });
5561
- for (const data of weeklyData) if (table.push([
5562
- data.week,
5563
- formatModelsDisplayMultiline(data.modelsUsed),
5564
- formatNumber(data.inputTokens),
5565
- formatNumber(data.outputTokens),
5566
- formatNumber(data.cacheCreationTokens),
5567
- formatNumber(data.cacheReadTokens),
5568
- formatNumber(getTotalTokens(data)),
5569
- formatCurrency(data.totalCost)
5570
- ]), mergedOptions.breakdown) pushBreakdownRows(table, data.modelBreakdowns);
5571
- if (table.push([
5572
- "",
5573
- "",
5574
- "",
5575
- "",
5576
- "",
5577
- "",
5578
- "",
5579
- ""
5580
- ]), table.push([
5581
- import_picocolors.default.yellow("Total"),
5582
- "",
5583
- import_picocolors.default.yellow(formatNumber(totals.inputTokens)),
5584
- import_picocolors.default.yellow(formatNumber(totals.outputTokens)),
5585
- import_picocolors.default.yellow(formatNumber(totals.cacheCreationTokens)),
5586
- import_picocolors.default.yellow(formatNumber(totals.cacheReadTokens)),
5587
- import_picocolors.default.yellow(formatNumber(getTotalTokens(totals))),
5588
- import_picocolors.default.yellow(formatCurrency(totals.totalCost))
5589
- ]), log(table.toString()), table.isCompactMode()) logger.info("\nRunning in Compact Mode"), logger.info("Expand terminal width to see cache metrics and total tokens");
5595
+ if (table.push(totalsRow), log(table.toString()), table.isCompactMode()) logger.info("\nRunning in Compact Mode"), logger.info("Expand terminal width to see cache metrics and total tokens");
5590
5596
  }
5591
5597
  }
5592
5598
  }), subCommandUnion = [