ccusage 9.0.1 → 10.0.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.
package/README.md CHANGED
@@ -230,6 +230,55 @@ ccusage session --offline # Use pre-cached pricing data
230
230
  ccusage session -O # Short alias for --offline
231
231
  ```
232
232
 
233
+ ### 5-Hour Blocks Report
234
+
235
+ Shows usage grouped by Claude's 5-hour billing windows:
236
+
237
+ ```bash
238
+ # Show all 5-hour blocks
239
+ ccusage blocks
240
+
241
+ # Show only the active block with detailed projections
242
+ ccusage blocks --active
243
+
244
+ # Show blocks from the last 3 days (including active)
245
+ ccusage blocks --recent
246
+
247
+ # Set a token limit to see if you'll exceed it
248
+ ccusage blocks -t 500000
249
+
250
+ # Use the highest previous block as the token limit
251
+ ccusage blocks -t max
252
+
253
+ # Combine options
254
+ ccusage blocks --recent -t max
255
+
256
+ # Output in JSON format
257
+ ccusage blocks --json
258
+
259
+ # Control cost calculation mode
260
+ ccusage blocks --mode auto # Use costUSD when available, calculate otherwise (default)
261
+ ccusage blocks --mode calculate # Always calculate costs from tokens
262
+ ccusage blocks --mode display # Always show pre-calculated costUSD values
263
+
264
+ # Control sort order
265
+ ccusage blocks --order asc # Show oldest blocks first
266
+ ccusage blocks --order desc # Show newest blocks first (default)
267
+ ```
268
+
269
+ The blocks report helps you understand Claude Code's 5-hour rolling session windows:
270
+
271
+ - Sessions start with your first message and last for 5 hours
272
+ - Shows active blocks with time remaining and burn rate projections
273
+ - Helps track if you're approaching token limits within a session
274
+ - The `-t max` option automatically uses your highest previous block as the limit
275
+
276
+ #### Blocks-specific options:
277
+
278
+ - `-t, --token-limit <number|max>`: Set token limit for quota warnings (use "max" for highest previous block)
279
+ - `-a, --active`: Show only active block with detailed projections
280
+ - `-r, --recent`: Show blocks from last 3 days (including active)
281
+
233
282
  ### Options
234
283
 
235
284
  All commands support the following options:
@@ -1,5 +1,5 @@
1
1
  import "./pricing-fetcher-BkSZh4lR.js";
2
- import { DailyUsage, MonthlyUsage, SessionUsage } from "./data-loader-OGaMjZTD.js";
2
+ import { DailyUsage, MonthlyUsage, SessionUsage } from "./data-loader-DPQaq8_n.js";
3
3
 
4
4
  //#region src/calculate-cost.d.ts
5
5
  type TokenData = {
@@ -1,7 +1,7 @@
1
1
  import { __commonJS, __require, __toESM } from "./chunk-BLXvPPr8.js";
2
2
  import { array, number, object, optional, pipe, regex, safeParse, string } from "./dist-DCvt9hEv.js";
3
- import { logger } from "./logger-BgKOQAEs.js";
4
- import { PricingFetcher } from "./pricing-fetcher-1KHPTXC3.js";
3
+ import { logger } from "./logger-C2GZVy1v.js";
4
+ import { PricingFetcher } from "./pricing-fetcher-BxlbW4km.js";
5
5
  import fsPromises, { readFile } from "node:fs/promises";
6
6
  import { homedir } from "node:os";
7
7
  import path from "node:path";
@@ -3041,6 +3041,144 @@ async function glob(patternsOrOptions, options) {
3041
3041
  return crawl(opts, cwd, false);
3042
3042
  }
3043
3043
 
3044
+ //#endregion
3045
+ //#region src/five-hour-blocks.internal.ts
3046
+ const FIVE_HOURS_MS = 5 * 60 * 60 * 1e3;
3047
+ const DEFAULT_RECENT_DAYS = 3;
3048
+ function identifyFiveHourBlocks(entries) {
3049
+ if (entries.length === 0) return [];
3050
+ const blocks = [];
3051
+ const sortedEntries = [...entries].sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
3052
+ let currentBlockStart = null;
3053
+ let currentBlockEntries = [];
3054
+ const now = /* @__PURE__ */ new Date();
3055
+ for (const entry of sortedEntries) {
3056
+ const entryTime = entry.timestamp;
3057
+ if (currentBlockStart == null) {
3058
+ currentBlockStart = entryTime;
3059
+ currentBlockEntries = [entry];
3060
+ } else {
3061
+ const timeSinceBlockStart = entryTime.getTime() - currentBlockStart.getTime();
3062
+ const lastEntry = currentBlockEntries.at(-1);
3063
+ if (lastEntry == null) continue;
3064
+ const lastEntryTime = lastEntry.timestamp;
3065
+ const timeSinceLastEntry = entryTime.getTime() - lastEntryTime.getTime();
3066
+ if (timeSinceBlockStart > FIVE_HOURS_MS || timeSinceLastEntry > FIVE_HOURS_MS) {
3067
+ const block = createBlock(currentBlockStart, currentBlockEntries, now);
3068
+ blocks.push(block);
3069
+ if (timeSinceLastEntry > FIVE_HOURS_MS) {
3070
+ const gapBlock = createGapBlock(lastEntryTime, entryTime);
3071
+ if (gapBlock != null) blocks.push(gapBlock);
3072
+ }
3073
+ currentBlockStart = entryTime;
3074
+ currentBlockEntries = [entry];
3075
+ } else currentBlockEntries.push(entry);
3076
+ }
3077
+ }
3078
+ if (currentBlockStart != null && currentBlockEntries.length > 0) {
3079
+ const block = createBlock(currentBlockStart, currentBlockEntries, now);
3080
+ blocks.push(block);
3081
+ }
3082
+ return blocks;
3083
+ }
3084
+ function createBlock(startTime, entries, now) {
3085
+ const endTime = new Date(startTime.getTime() + FIVE_HOURS_MS);
3086
+ const lastEntry = entries[entries.length - 1];
3087
+ const actualEndTime = lastEntry != null ? lastEntry.timestamp : startTime;
3088
+ const isActive = now.getTime() - actualEndTime.getTime() < FIVE_HOURS_MS && now < endTime;
3089
+ const tokenCounts = {
3090
+ inputTokens: 0,
3091
+ outputTokens: 0,
3092
+ cacheCreationInputTokens: 0,
3093
+ cacheReadInputTokens: 0
3094
+ };
3095
+ let costUSD = 0;
3096
+ const modelsSet = /* @__PURE__ */ new Set();
3097
+ for (const entry of entries) {
3098
+ tokenCounts.inputTokens += entry.usage.inputTokens;
3099
+ tokenCounts.outputTokens += entry.usage.outputTokens;
3100
+ tokenCounts.cacheCreationInputTokens += entry.usage.cacheCreationInputTokens;
3101
+ tokenCounts.cacheReadInputTokens += entry.usage.cacheReadInputTokens;
3102
+ costUSD += entry.costUSD != null ? entry.costUSD : 0;
3103
+ modelsSet.add(entry.model);
3104
+ }
3105
+ return {
3106
+ id: startTime.toISOString(),
3107
+ startTime,
3108
+ endTime,
3109
+ actualEndTime,
3110
+ isActive,
3111
+ entries,
3112
+ tokenCounts,
3113
+ costUSD,
3114
+ models: Array.from(modelsSet)
3115
+ };
3116
+ }
3117
+ function createGapBlock(lastActivityTime, nextActivityTime) {
3118
+ const gapDuration = nextActivityTime.getTime() - lastActivityTime.getTime();
3119
+ if (gapDuration <= FIVE_HOURS_MS) return null;
3120
+ const gapStart = new Date(lastActivityTime.getTime() + FIVE_HOURS_MS);
3121
+ const gapEnd = nextActivityTime;
3122
+ return {
3123
+ id: `gap-${gapStart.toISOString()}`,
3124
+ startTime: gapStart,
3125
+ endTime: gapEnd,
3126
+ isActive: false,
3127
+ isGap: true,
3128
+ entries: [],
3129
+ tokenCounts: {
3130
+ inputTokens: 0,
3131
+ outputTokens: 0,
3132
+ cacheCreationInputTokens: 0,
3133
+ cacheReadInputTokens: 0
3134
+ },
3135
+ costUSD: 0,
3136
+ models: []
3137
+ };
3138
+ }
3139
+ function calculateBurnRate(block) {
3140
+ if (block.entries.length === 0 || (block.isGap ?? false)) return null;
3141
+ const firstEntryData = block.entries[0];
3142
+ const lastEntryData = block.entries[block.entries.length - 1];
3143
+ if (firstEntryData == null || lastEntryData == null) return null;
3144
+ const firstEntry = firstEntryData.timestamp;
3145
+ const lastEntry = lastEntryData.timestamp;
3146
+ const durationMinutes = (lastEntry.getTime() - firstEntry.getTime()) / (1e3 * 60);
3147
+ if (durationMinutes <= 0) return null;
3148
+ const totalTokens = block.tokenCounts.inputTokens + block.tokenCounts.outputTokens;
3149
+ const tokensPerMinute = totalTokens / durationMinutes;
3150
+ const costPerHour = block.costUSD / durationMinutes * 60;
3151
+ return {
3152
+ tokensPerMinute,
3153
+ costPerHour
3154
+ };
3155
+ }
3156
+ function projectBlockUsage(block) {
3157
+ if (!block.isActive || (block.isGap ?? false)) return null;
3158
+ const burnRate = calculateBurnRate(block);
3159
+ if (burnRate == null) return null;
3160
+ const now = /* @__PURE__ */ new Date();
3161
+ const remainingTime = block.endTime.getTime() - now.getTime();
3162
+ const remainingMinutes = Math.max(0, remainingTime / (1e3 * 60));
3163
+ const currentTokens = block.tokenCounts.inputTokens + block.tokenCounts.outputTokens;
3164
+ const projectedAdditionalTokens = burnRate.tokensPerMinute * remainingMinutes;
3165
+ const totalTokens = currentTokens + projectedAdditionalTokens;
3166
+ const projectedAdditionalCost = burnRate.costPerHour / 60 * remainingMinutes;
3167
+ const totalCost = block.costUSD + projectedAdditionalCost;
3168
+ return {
3169
+ totalTokens: Math.round(totalTokens),
3170
+ totalCost: Math.round(totalCost * 100) / 100,
3171
+ remainingMinutes: Math.round(remainingMinutes)
3172
+ };
3173
+ }
3174
+ function filterRecentBlocks(blocks, days = DEFAULT_RECENT_DAYS) {
3175
+ const now = /* @__PURE__ */ new Date();
3176
+ const cutoffTime = new Date(now.getTime() - days * 24 * 60 * 60 * 1e3);
3177
+ return blocks.filter((block) => {
3178
+ return block.startTime >= cutoffTime || block.isActive;
3179
+ });
3180
+ }
3181
+
3044
3182
  //#endregion
3045
3183
  //#region node_modules/rolldown/node_modules/@oxc-project/runtime/src/helpers/usingCtx.js
3046
3184
  var require_usingCtx = __commonJS({ "node_modules/rolldown/node_modules/@oxc-project/runtime/src/helpers/usingCtx.js"(exports, module) {
@@ -3553,6 +3691,63 @@ async function loadMonthlyUsageData(options) {
3553
3691
  }
3554
3692
  return sortByDate(monthlyArray, (item) => `${item.month}-01`, options?.order);
3555
3693
  }
3694
+ async function loadFiveHourBlockData(options) {
3695
+ try {
3696
+ var _usingCtx4 = (0, import_usingCtx.default)();
3697
+ const claudePath = options?.claudePath ?? getDefaultClaudePath();
3698
+ const claudeDir = path.join(claudePath, "projects");
3699
+ const files = await glob(["**/*.jsonl"], {
3700
+ cwd: claudeDir,
3701
+ absolute: true
3702
+ });
3703
+ if (files.length === 0) return [];
3704
+ const sortedFiles = await sortFilesByTimestamp(files);
3705
+ const mode = options?.mode ?? "auto";
3706
+ const fetcher = _usingCtx4.u(mode === "display" ? null : new PricingFetcher());
3707
+ const processedHashes = /* @__PURE__ */ new Set();
3708
+ const allEntries = [];
3709
+ for (const file of sortedFiles) {
3710
+ const content = await readFile(file, "utf-8");
3711
+ const lines = content.trim().split("\n").filter((line) => line.length > 0);
3712
+ for (const line of lines) try {
3713
+ const parsed = JSON.parse(line);
3714
+ const result = safeParse(UsageDataSchema, parsed);
3715
+ if (!result.success) continue;
3716
+ const data = result.output;
3717
+ const uniqueHash = createUniqueHash(data);
3718
+ if (isDuplicateEntry(uniqueHash, processedHashes)) continue;
3719
+ markAsProcessed(uniqueHash, processedHashes);
3720
+ const cost = fetcher != null ? await calculateCostForEntry(data, mode, fetcher) : data.costUSD ?? 0;
3721
+ allEntries.push({
3722
+ timestamp: new Date(data.timestamp),
3723
+ usage: {
3724
+ inputTokens: data.message.usage.input_tokens,
3725
+ outputTokens: data.message.usage.output_tokens,
3726
+ cacheCreationInputTokens: data.message.usage.cache_creation_input_tokens ?? 0,
3727
+ cacheReadInputTokens: data.message.usage.cache_read_input_tokens ?? 0
3728
+ },
3729
+ costUSD: cost,
3730
+ model: data.message.model ?? "unknown",
3731
+ version: data.version
3732
+ });
3733
+ } catch (error) {
3734
+ logger.debug(`Skipping invalid JSON line in 5-hour blocks: ${error instanceof Error ? error.message : String(error)}`);
3735
+ }
3736
+ }
3737
+ const blocks = identifyFiveHourBlocks(allEntries);
3738
+ const filtered = options?.since != null && options.since !== "" || options?.until != null && options.until !== "" ? blocks.filter((block) => {
3739
+ const blockDateStr = formatDate(block.startTime.toISOString()).replace(/-/g, "");
3740
+ if (options.since != null && options.since !== "" && blockDateStr < options.since) return false;
3741
+ if (options.until != null && options.until !== "" && blockDateStr > options.until) return false;
3742
+ return true;
3743
+ }) : blocks;
3744
+ return sortByDate(filtered, (block) => block.startTime, options?.order);
3745
+ } catch (_) {
3746
+ _usingCtx4.e = _;
3747
+ } finally {
3748
+ _usingCtx4.d();
3749
+ }
3750
+ }
3556
3751
 
3557
3752
  //#endregion
3558
- export { DailyUsageSchema, ModelBreakdownSchema, MonthlyUsageSchema, SessionUsageSchema, UsageDataSchema, calculateCostForEntry, createUniqueHash, formatDate, formatDateCompact, getDefaultClaudePath, getEarliestTimestamp, glob, loadDailyUsageData, loadMonthlyUsageData, loadSessionData, require_usingCtx, sortFilesByTimestamp };
3753
+ export { DailyUsageSchema, ModelBreakdownSchema, MonthlyUsageSchema, SessionUsageSchema, UsageDataSchema, calculateBurnRate, calculateCostForEntry, createUniqueHash, filterRecentBlocks, formatDate, formatDateCompact, getDefaultClaudePath, getEarliestTimestamp, glob, loadDailyUsageData, loadFiveHourBlockData, loadMonthlyUsageData, loadSessionData, projectBlockUsage, require_usingCtx, sortFilesByTimestamp };
@@ -1,7 +1,38 @@
1
1
  import { ArraySchema, CostMode, InferOutput, NumberSchema, ObjectSchema, OptionalSchema, PricingFetcher, RegexAction, SchemaWithPipe, SortOrder, StringSchema } from "./pricing-fetcher-BkSZh4lR.js";
2
2
 
3
+ //#region src/five-hour-blocks.internal.d.ts
4
+ type LoadedUsageEntry = {
5
+ timestamp: Date;
6
+ usage: {
7
+ inputTokens: number;
8
+ outputTokens: number;
9
+ cacheCreationInputTokens: number;
10
+ cacheReadInputTokens: number;
11
+ };
12
+ costUSD: number | null;
13
+ model: string;
14
+ version?: string;
15
+ };
16
+ type TokenCounts = {
17
+ inputTokens: number;
18
+ outputTokens: number;
19
+ cacheCreationInputTokens: number;
20
+ cacheReadInputTokens: number;
21
+ };
22
+ type FiveHourBlock = {
23
+ id: string;
24
+ startTime: Date;
25
+ endTime: Date;
26
+ actualEndTime?: Date;
27
+ isActive: boolean;
28
+ isGap?: boolean;
29
+ entries: LoadedUsageEntry[];
30
+ tokenCounts: TokenCounts;
31
+ costUSD: number;
32
+ models: string[];
33
+ };
34
+ //#endregion
3
35
  //#region src/data-loader.d.ts
4
-
5
36
  /**
6
37
  * Default path for Claude data directory
7
38
  * Uses environment variable CLAUDE_CONFIG_DIR if set, otherwise defaults to ~/.claude
@@ -120,5 +151,6 @@ type LoadOptions = {
120
151
  declare function loadDailyUsageData(options?: LoadOptions): Promise<DailyUsage[]>;
121
152
  declare function loadSessionData(options?: LoadOptions): Promise<SessionUsage[]>;
122
153
  declare function loadMonthlyUsageData(options?: LoadOptions): Promise<MonthlyUsage[]>;
154
+ declare function loadFiveHourBlockData(options?: LoadOptions): Promise<FiveHourBlock[]>;
123
155
  //#endregion
124
- export { DailyUsage, DailyUsageSchema, DateFilter, LoadOptions, ModelBreakdown, ModelBreakdownSchema, MonthlyUsage, MonthlyUsageSchema, SessionUsage, SessionUsageSchema, UsageData, UsageDataSchema, calculateCostForEntry, createUniqueHash, formatDate, formatDateCompact, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadMonthlyUsageData, loadSessionData, sortFilesByTimestamp };
156
+ export { DailyUsage, DailyUsageSchema, DateFilter, LoadOptions, ModelBreakdown, ModelBreakdownSchema, MonthlyUsage, MonthlyUsageSchema, SessionUsage, SessionUsageSchema, UsageData, UsageDataSchema, calculateCostForEntry, createUniqueHash, formatDate, formatDateCompact, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadFiveHourBlockData, loadMonthlyUsageData, loadSessionData, sortFilesByTimestamp };
@@ -1,3 +1,3 @@
1
1
  import "./pricing-fetcher-BkSZh4lR.js";
2
- import { DailyUsage, DailyUsageSchema, DateFilter, LoadOptions, ModelBreakdown, ModelBreakdownSchema, MonthlyUsage, MonthlyUsageSchema, SessionUsage, SessionUsageSchema, UsageData, UsageDataSchema, calculateCostForEntry, createUniqueHash, formatDate, formatDateCompact, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadMonthlyUsageData, loadSessionData, sortFilesByTimestamp } from "./data-loader-OGaMjZTD.js";
3
- export { DailyUsage, DailyUsageSchema, DateFilter, LoadOptions, ModelBreakdown, ModelBreakdownSchema, MonthlyUsage, MonthlyUsageSchema, SessionUsage, SessionUsageSchema, UsageData, UsageDataSchema, calculateCostForEntry, createUniqueHash, formatDate, formatDateCompact, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadMonthlyUsageData, loadSessionData, sortFilesByTimestamp };
2
+ import { DailyUsage, DailyUsageSchema, DateFilter, LoadOptions, ModelBreakdown, ModelBreakdownSchema, MonthlyUsage, MonthlyUsageSchema, SessionUsage, SessionUsageSchema, UsageData, UsageDataSchema, calculateCostForEntry, createUniqueHash, formatDate, formatDateCompact, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadFiveHourBlockData, loadMonthlyUsageData, loadSessionData, sortFilesByTimestamp } from "./data-loader-DPQaq8_n.js";
3
+ export { DailyUsage, DailyUsageSchema, DateFilter, LoadOptions, ModelBreakdown, ModelBreakdownSchema, MonthlyUsage, MonthlyUsageSchema, SessionUsage, SessionUsageSchema, UsageData, UsageDataSchema, calculateCostForEntry, createUniqueHash, formatDate, formatDateCompact, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadFiveHourBlockData, loadMonthlyUsageData, loadSessionData, sortFilesByTimestamp };
@@ -1,6 +1,6 @@
1
- import { DailyUsageSchema, ModelBreakdownSchema, MonthlyUsageSchema, SessionUsageSchema, UsageDataSchema, calculateCostForEntry, createUniqueHash, formatDate, formatDateCompact, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadMonthlyUsageData, loadSessionData, sortFilesByTimestamp } from "./data-loader-CotJGlGF.js";
1
+ import { DailyUsageSchema, ModelBreakdownSchema, MonthlyUsageSchema, SessionUsageSchema, UsageDataSchema, calculateCostForEntry, createUniqueHash, formatDate, formatDateCompact, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadFiveHourBlockData, loadMonthlyUsageData, loadSessionData, sortFilesByTimestamp } from "./data-loader-Bp9KV0_a.js";
2
2
  import "./dist-DCvt9hEv.js";
3
- import "./logger-BgKOQAEs.js";
4
- import "./pricing-fetcher-1KHPTXC3.js";
3
+ import "./logger-C2GZVy1v.js";
4
+ import "./pricing-fetcher-BxlbW4km.js";
5
5
 
6
- export { DailyUsageSchema, ModelBreakdownSchema, MonthlyUsageSchema, SessionUsageSchema, UsageDataSchema, calculateCostForEntry, createUniqueHash, formatDate, formatDateCompact, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadMonthlyUsageData, loadSessionData, sortFilesByTimestamp };
6
+ export { DailyUsageSchema, ModelBreakdownSchema, MonthlyUsageSchema, SessionUsageSchema, UsageDataSchema, calculateCostForEntry, createUniqueHash, formatDate, formatDateCompact, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadFiveHourBlockData, loadMonthlyUsageData, loadSessionData, sortFilesByTimestamp };
@@ -1,8 +1,8 @@
1
1
  import { __toESM } from "./chunk-BLXvPPr8.js";
2
- import { UsageDataSchema, glob, require_usingCtx } from "./data-loader-CotJGlGF.js";
2
+ import { UsageDataSchema, glob, require_usingCtx } from "./data-loader-Bp9KV0_a.js";
3
3
  import { safeParse } from "./dist-DCvt9hEv.js";
4
- import { logger } from "./logger-BgKOQAEs.js";
5
- import { PricingFetcher } from "./pricing-fetcher-1KHPTXC3.js";
4
+ import { logger } from "./logger-C2GZVy1v.js";
5
+ import { PricingFetcher } from "./pricing-fetcher-BxlbW4km.js";
6
6
  import { readFile } from "node:fs/promises";
7
7
  import { homedir } from "node:os";
8
8
  import path from "node:path";
package/dist/debug.js CHANGED
@@ -1,7 +1,7 @@
1
- import "./data-loader-CotJGlGF.js";
1
+ import "./data-loader-Bp9KV0_a.js";
2
2
  import "./dist-DCvt9hEv.js";
3
- import "./logger-BgKOQAEs.js";
4
- import "./pricing-fetcher-1KHPTXC3.js";
5
- import { detectMismatches, printMismatchReport } from "./debug-DH_5GWYh.js";
3
+ import "./logger-C2GZVy1v.js";
4
+ import "./pricing-fetcher-BxlbW4km.js";
5
+ import { detectMismatches, printMismatchReport } from "./debug-bjc38_Gx.js";
6
6
 
7
7
  export { detectMismatches, printMismatchReport };
package/dist/index.js CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import { __commonJS, __toESM } from "./chunk-BLXvPPr8.js";
3
3
  import { calculateTotals, createTotalsObject, getTotalTokens } from "./calculate-cost-2IwHSzmi.js";
4
- import { formatDateCompact, getDefaultClaudePath, loadDailyUsageData, loadMonthlyUsageData, loadSessionData } from "./data-loader-CotJGlGF.js";
4
+ import { calculateBurnRate, filterRecentBlocks, formatDateCompact, getDefaultClaudePath, loadDailyUsageData, loadFiveHourBlockData, loadMonthlyUsageData, loadSessionData, projectBlockUsage } from "./data-loader-Bp9KV0_a.js";
5
5
  import { safeParse } from "./dist-DCvt9hEv.js";
6
- import { description, log, logger, name, version } from "./logger-BgKOQAEs.js";
7
- import { CostModes, SortOrders, dateSchema } from "./pricing-fetcher-1KHPTXC3.js";
8
- import { detectMismatches, printMismatchReport } from "./debug-DH_5GWYh.js";
9
- import { ResponsiveTable } from "./utils.table-DRzF8vZJ.js";
6
+ import { description, log, logger, name, version } from "./logger-C2GZVy1v.js";
7
+ import { CostModes, SortOrders, dateSchema } from "./pricing-fetcher-BxlbW4km.js";
8
+ import { detectMismatches, printMismatchReport } from "./debug-bjc38_Gx.js";
9
+ import { ResponsiveTable } from "./utils.table-USks3NGv.js";
10
10
  import "./types-BlyCnKwN.js";
11
- import { createMcpServer } from "./mcp-BUmEAQZm.js";
11
+ import { createMcpServer } from "./mcp-DVjQeqCy.js";
12
12
  import "./index-CISmcbXk-CW1Gj6Ab.js";
13
13
  import process$1 from "node:process";
14
14
 
@@ -1284,7 +1284,7 @@ const sharedCommandConfig = {
1284
1284
 
1285
1285
  //#endregion
1286
1286
  //#region src/utils.internal.ts
1287
- var import_picocolors$3 = __toESM(require_picocolors(), 1);
1287
+ var import_picocolors$4 = __toESM(require_picocolors(), 1);
1288
1288
  function formatNumber(num) {
1289
1289
  return num.toLocaleString("en-US");
1290
1290
  }
@@ -1313,12 +1313,294 @@ function pushBreakdownRows(table, breakdowns, extraColumns = 1, trailingColumns
1313
1313
  const row = [` └─ ${formatModelName(breakdown.modelName)}`];
1314
1314
  for (let i = 0; i < extraColumns; i++) row.push("");
1315
1315
  const totalTokens = breakdown.inputTokens + breakdown.outputTokens + breakdown.cacheCreationTokens + breakdown.cacheReadTokens;
1316
- row.push(import_picocolors$3.default.gray(formatNumber(breakdown.inputTokens)), import_picocolors$3.default.gray(formatNumber(breakdown.outputTokens)), import_picocolors$3.default.gray(formatNumber(breakdown.cacheCreationTokens)), import_picocolors$3.default.gray(formatNumber(breakdown.cacheReadTokens)), import_picocolors$3.default.gray(formatNumber(totalTokens)), import_picocolors$3.default.gray(formatCurrency(breakdown.cost)));
1316
+ row.push(import_picocolors$4.default.gray(formatNumber(breakdown.inputTokens)), import_picocolors$4.default.gray(formatNumber(breakdown.outputTokens)), import_picocolors$4.default.gray(formatNumber(breakdown.cacheCreationTokens)), import_picocolors$4.default.gray(formatNumber(breakdown.cacheReadTokens)), import_picocolors$4.default.gray(formatNumber(totalTokens)), import_picocolors$4.default.gray(formatCurrency(breakdown.cost)));
1317
1317
  for (let i = 0; i < trailingColumns; i++) row.push("");
1318
1318
  table.push(row);
1319
1319
  }
1320
1320
  }
1321
1321
 
1322
+ //#endregion
1323
+ //#region src/commands/blocks.ts
1324
+ var import_picocolors$3 = __toESM(require_picocolors(), 1);
1325
+ const RECENT_DAYS_DEFAULT = 3;
1326
+ const WARNING_THRESHOLD = .8;
1327
+ const COMPACT_WIDTH_THRESHOLD = 120;
1328
+ const DEFAULT_TERMINAL_WIDTH = 120;
1329
+ function formatBlockTime(block, compact = false) {
1330
+ const start = compact ? block.startTime.toLocaleString(void 0, {
1331
+ month: "2-digit",
1332
+ day: "2-digit",
1333
+ hour: "2-digit",
1334
+ minute: "2-digit"
1335
+ }) : block.startTime.toLocaleString();
1336
+ if (block.isGap ?? false) {
1337
+ const end = compact ? block.endTime.toLocaleString(void 0, {
1338
+ hour: "2-digit",
1339
+ minute: "2-digit"
1340
+ }) : block.endTime.toLocaleString();
1341
+ const duration$1 = Math.round((block.endTime.getTime() - block.startTime.getTime()) / (1e3 * 60 * 60));
1342
+ return compact ? `${start}-${end}\n(${duration$1}h gap)` : `${start} - ${end} (${duration$1}h gap)`;
1343
+ }
1344
+ const duration = block.actualEndTime != null ? Math.round((block.actualEndTime.getTime() - block.startTime.getTime()) / (1e3 * 60)) : 0;
1345
+ if (block.isActive) {
1346
+ const now = /* @__PURE__ */ new Date();
1347
+ const elapsed = Math.round((now.getTime() - block.startTime.getTime()) / (1e3 * 60));
1348
+ const remaining = Math.round((block.endTime.getTime() - now.getTime()) / (1e3 * 60));
1349
+ const elapsedHours = Math.floor(elapsed / 60);
1350
+ const elapsedMins = elapsed % 60;
1351
+ const remainingHours = Math.floor(remaining / 60);
1352
+ const remainingMins = remaining % 60;
1353
+ if (compact) return `${start}\n(${elapsedHours}h${elapsedMins}m/${remainingHours}h${remainingMins}m)`;
1354
+ return `${start} (${elapsedHours}h ${elapsedMins}m elapsed, ${remainingHours}h ${remainingMins}m remaining)`;
1355
+ }
1356
+ const hours = Math.floor(duration / 60);
1357
+ const mins = duration % 60;
1358
+ if (compact) return hours > 0 ? `${start}\n(${hours}h${mins}m)` : `${start}\n(${mins}m)`;
1359
+ if (hours > 0) return `${start} (${hours}h ${mins}m)`;
1360
+ return `${start} (${mins}m)`;
1361
+ }
1362
+ function formatModels(models, compact = false) {
1363
+ if (models.length === 0) return "-";
1364
+ return compact ? formatModelsDisplay(models) : formatModelsDisplay(models);
1365
+ }
1366
+ function parseTokenLimit(value, maxFromAll) {
1367
+ if (value == null || value === "") return void 0;
1368
+ if (value === "max") return maxFromAll > 0 ? maxFromAll : void 0;
1369
+ const limit = Number.parseInt(value, 10);
1370
+ return Number.isNaN(limit) ? void 0 : limit;
1371
+ }
1372
+ const blocksCommand = define({
1373
+ name: "blocks",
1374
+ description: "Show usage report grouped by 5-hour billing blocks",
1375
+ args: {
1376
+ ...sharedCommandConfig.args,
1377
+ active: {
1378
+ type: "boolean",
1379
+ short: "a",
1380
+ description: "Show only active block with projections",
1381
+ default: false
1382
+ },
1383
+ recent: {
1384
+ type: "boolean",
1385
+ short: "r",
1386
+ description: `Show blocks from last ${RECENT_DAYS_DEFAULT} days (including active)`,
1387
+ default: false
1388
+ },
1389
+ tokenLimit: {
1390
+ type: "string",
1391
+ short: "t",
1392
+ description: "Token limit for quota warnings (e.g., 500000 or \"max\" for highest previous block)"
1393
+ }
1394
+ },
1395
+ toKebab: true,
1396
+ async run(ctx) {
1397
+ if (ctx.values.json) logger.level = 0;
1398
+ let blocks = await loadFiveHourBlockData({
1399
+ since: ctx.values.since,
1400
+ until: ctx.values.until,
1401
+ claudePath: getDefaultClaudePath(),
1402
+ mode: ctx.values.mode,
1403
+ order: ctx.values.order
1404
+ });
1405
+ if (blocks.length === 0) {
1406
+ if (ctx.values.json) log(JSON.stringify({ blocks: [] }));
1407
+ else logger.warn("No Claude usage data found.");
1408
+ process$1.exit(0);
1409
+ }
1410
+ let maxTokensFromAll = 0;
1411
+ if (ctx.values.tokenLimit === "max") {
1412
+ for (const block of blocks) if (!(block.isGap ?? false) && !block.isActive) {
1413
+ const blockTokens = block.tokenCounts.inputTokens + block.tokenCounts.outputTokens;
1414
+ if (blockTokens > maxTokensFromAll) maxTokensFromAll = blockTokens;
1415
+ }
1416
+ if (!ctx.values.json && maxTokensFromAll > 0) logger.info(`Using max tokens from previous sessions: ${formatNumber(maxTokensFromAll)}`);
1417
+ }
1418
+ if (ctx.values.recent) blocks = filterRecentBlocks(blocks, RECENT_DAYS_DEFAULT);
1419
+ if (ctx.values.active) {
1420
+ blocks = blocks.filter((block) => block.isActive);
1421
+ if (blocks.length === 0) {
1422
+ if (ctx.values.json) log(JSON.stringify({
1423
+ blocks: [],
1424
+ message: "No active block"
1425
+ }));
1426
+ else logger.info("No active 5-hour block found.");
1427
+ process$1.exit(0);
1428
+ }
1429
+ }
1430
+ if (ctx.values.json) {
1431
+ const jsonOutput = { blocks: blocks.map((block) => {
1432
+ const burnRate = block.isActive ? calculateBurnRate(block) : null;
1433
+ const projection = block.isActive ? projectBlockUsage(block) : null;
1434
+ return {
1435
+ id: block.id,
1436
+ startTime: block.startTime.toISOString(),
1437
+ endTime: block.endTime.toISOString(),
1438
+ actualEndTime: block.actualEndTime != null ? block.actualEndTime.toISOString() : null,
1439
+ isActive: block.isActive,
1440
+ isGap: block.isGap ?? false,
1441
+ entries: block.entries.length,
1442
+ tokenCounts: block.tokenCounts,
1443
+ totalTokens: block.tokenCounts.inputTokens + block.tokenCounts.outputTokens,
1444
+ costUSD: block.costUSD,
1445
+ models: block.models,
1446
+ burnRate,
1447
+ projection,
1448
+ tokenLimitStatus: projection != null && ctx.values.tokenLimit != null ? (() => {
1449
+ const limit = parseTokenLimit(ctx.values.tokenLimit, maxTokensFromAll);
1450
+ return limit != null ? {
1451
+ limit,
1452
+ projectedUsage: projection.totalTokens,
1453
+ percentUsed: projection.totalTokens / limit * 100,
1454
+ status: projection.totalTokens > limit ? "exceeds" : projection.totalTokens > limit * WARNING_THRESHOLD ? "warning" : "ok"
1455
+ } : void 0;
1456
+ })() : void 0
1457
+ };
1458
+ }) };
1459
+ log(JSON.stringify(jsonOutput, null, 2));
1460
+ } else if (ctx.values.active && blocks.length === 1) {
1461
+ const block = blocks[0];
1462
+ if (block == null) {
1463
+ logger.warn("No active block found.");
1464
+ process$1.exit(0);
1465
+ }
1466
+ const burnRate = calculateBurnRate(block);
1467
+ const projection = projectBlockUsage(block);
1468
+ logger.box("Current 5-Hour Block Status");
1469
+ const now = /* @__PURE__ */ new Date();
1470
+ const elapsed = Math.round((now.getTime() - block.startTime.getTime()) / (1e3 * 60));
1471
+ const remaining = Math.round((block.endTime.getTime() - now.getTime()) / (1e3 * 60));
1472
+ log(`Block Started: ${import_picocolors$3.default.cyan(block.startTime.toLocaleString())} (${import_picocolors$3.default.yellow(`${Math.floor(elapsed / 60)}h ${elapsed % 60}m`)} ago)`);
1473
+ log(`Time Remaining: ${import_picocolors$3.default.green(`${Math.floor(remaining / 60)}h ${remaining % 60}m`)}\n`);
1474
+ log(import_picocolors$3.default.bold("Current Usage:"));
1475
+ log(` Input Tokens: ${formatNumber(block.tokenCounts.inputTokens)}`);
1476
+ log(` Output Tokens: ${formatNumber(block.tokenCounts.outputTokens)}`);
1477
+ log(` Total Cost: ${formatCurrency(block.costUSD)}\n`);
1478
+ if (burnRate != null) {
1479
+ log(import_picocolors$3.default.bold("Burn Rate:"));
1480
+ log(` Tokens/minute: ${formatNumber(burnRate.tokensPerMinute)}`);
1481
+ log(` Cost/hour: ${formatCurrency(burnRate.costPerHour)}\n`);
1482
+ }
1483
+ if (projection != null) {
1484
+ log(import_picocolors$3.default.bold("Projected Usage (if current rate continues):"));
1485
+ log(` Total Tokens: ${formatNumber(projection.totalTokens)}`);
1486
+ log(` Total Cost: ${formatCurrency(projection.totalCost)}\n`);
1487
+ if (ctx.values.tokenLimit != null) {
1488
+ const limit = parseTokenLimit(ctx.values.tokenLimit, maxTokensFromAll);
1489
+ if (limit != null && limit > 0) {
1490
+ const currentTokens = block.tokenCounts.inputTokens + block.tokenCounts.outputTokens;
1491
+ const remainingTokens = Math.max(0, limit - currentTokens);
1492
+ const percentUsed = projection.totalTokens / limit * 100;
1493
+ const status = percentUsed > 100 ? import_picocolors$3.default.red("EXCEEDS LIMIT") : percentUsed > WARNING_THRESHOLD * 100 ? import_picocolors$3.default.yellow("WARNING") : import_picocolors$3.default.green("OK");
1494
+ log(import_picocolors$3.default.bold("Token Limit Status:"));
1495
+ log(` Limit: ${formatNumber(limit)} tokens`);
1496
+ log(` Current Usage: ${formatNumber(currentTokens)} (${(currentTokens / limit * 100).toFixed(1)}%)`);
1497
+ log(` Remaining: ${formatNumber(remainingTokens)} tokens`);
1498
+ log(` Projected Usage: ${percentUsed.toFixed(1)}% ${status}`);
1499
+ }
1500
+ }
1501
+ }
1502
+ } else {
1503
+ logger.box("Claude Code Token Usage Report - 5-Hour Blocks");
1504
+ const actualTokenLimit = parseTokenLimit(ctx.values.tokenLimit, maxTokensFromAll);
1505
+ const tableHeaders = [
1506
+ "Block Start",
1507
+ "Duration/Status",
1508
+ "Models",
1509
+ "Tokens"
1510
+ ];
1511
+ const tableAligns = [
1512
+ "left",
1513
+ "left",
1514
+ "left",
1515
+ "right"
1516
+ ];
1517
+ if (actualTokenLimit != null && actualTokenLimit > 0) {
1518
+ tableHeaders.push("%");
1519
+ tableAligns.push("right");
1520
+ }
1521
+ tableHeaders.push("Cost");
1522
+ tableAligns.push("right");
1523
+ const table = new ResponsiveTable({
1524
+ head: tableHeaders,
1525
+ style: { head: ["cyan"] },
1526
+ colAligns: tableAligns
1527
+ });
1528
+ const terminalWidth = process$1.stdout.columns || DEFAULT_TERMINAL_WIDTH;
1529
+ const useCompactFormat = terminalWidth < COMPACT_WIDTH_THRESHOLD;
1530
+ for (const block of blocks) if (block.isGap ?? false) {
1531
+ const gapRow = [
1532
+ import_picocolors$3.default.gray(formatBlockTime(block, useCompactFormat)),
1533
+ import_picocolors$3.default.gray("(inactive)"),
1534
+ import_picocolors$3.default.gray("-"),
1535
+ import_picocolors$3.default.gray("-")
1536
+ ];
1537
+ if (actualTokenLimit != null && actualTokenLimit > 0) gapRow.push(import_picocolors$3.default.gray("-"));
1538
+ gapRow.push(import_picocolors$3.default.gray("-"));
1539
+ table.push(gapRow);
1540
+ } else {
1541
+ const totalTokens = block.tokenCounts.inputTokens + block.tokenCounts.outputTokens;
1542
+ const status = block.isActive ? import_picocolors$3.default.green("ACTIVE") : "";
1543
+ const row = [
1544
+ formatBlockTime(block, useCompactFormat),
1545
+ status,
1546
+ formatModels(block.models, useCompactFormat),
1547
+ formatNumber(totalTokens)
1548
+ ];
1549
+ if (actualTokenLimit != null && actualTokenLimit > 0) {
1550
+ const percentage = totalTokens / actualTokenLimit * 100;
1551
+ const percentText = `${percentage.toFixed(1)}%`;
1552
+ row.push(percentage > 100 ? import_picocolors$3.default.red(percentText) : percentText);
1553
+ }
1554
+ row.push(formatCurrency(block.costUSD));
1555
+ table.push(row);
1556
+ if (block.isActive) {
1557
+ if (actualTokenLimit != null && actualTokenLimit > 0) {
1558
+ const currentTokens = block.tokenCounts.inputTokens + block.tokenCounts.outputTokens;
1559
+ const remainingTokens = Math.max(0, actualTokenLimit - currentTokens);
1560
+ const remainingText = remainingTokens > 0 ? formatNumber(remainingTokens) : import_picocolors$3.default.red("0");
1561
+ const remainingPercent = (actualTokenLimit - currentTokens) / actualTokenLimit * 100;
1562
+ const remainingPercentText = remainingPercent > 0 ? `${remainingPercent.toFixed(1)}%` : import_picocolors$3.default.red("0.0%");
1563
+ const remainingRow = [
1564
+ {
1565
+ content: import_picocolors$3.default.gray(`(assuming ${formatNumber(actualTokenLimit)} token limit)`),
1566
+ hAlign: "right"
1567
+ },
1568
+ import_picocolors$3.default.blue("REMAINING"),
1569
+ "",
1570
+ remainingText,
1571
+ remainingPercentText,
1572
+ ""
1573
+ ];
1574
+ table.push(remainingRow);
1575
+ }
1576
+ const projection = projectBlockUsage(block);
1577
+ if (projection != null) {
1578
+ const projectedTokens = formatNumber(projection.totalTokens);
1579
+ const projectedText = actualTokenLimit != null && actualTokenLimit > 0 && projection.totalTokens > actualTokenLimit ? import_picocolors$3.default.red(projectedTokens) : projectedTokens;
1580
+ const projectedRow = [
1581
+ {
1582
+ content: import_picocolors$3.default.gray("(assuming current burn rate)"),
1583
+ hAlign: "right"
1584
+ },
1585
+ import_picocolors$3.default.yellow("PROJECTED"),
1586
+ "",
1587
+ projectedText
1588
+ ];
1589
+ if (actualTokenLimit != null && actualTokenLimit > 0) {
1590
+ const percentage = projection.totalTokens / actualTokenLimit * 100;
1591
+ const percentText = `${percentage.toFixed(1)}%`;
1592
+ projectedRow.push(percentText);
1593
+ }
1594
+ projectedRow.push(formatCurrency(projection.totalCost));
1595
+ table.push(projectedRow);
1596
+ }
1597
+ }
1598
+ }
1599
+ log(table.toString());
1600
+ }
1601
+ }
1602
+ });
1603
+
1322
1604
  //#endregion
1323
1605
  //#region src/commands/daily.ts
1324
1606
  var import_picocolors$2 = __toESM(require_picocolors(), 1);
@@ -1701,6 +1983,7 @@ const subCommands = /* @__PURE__ */ new Map();
1701
1983
  subCommands.set("daily", dailyCommand);
1702
1984
  subCommands.set("monthly", monthlyCommand);
1703
1985
  subCommands.set("session", sessionCommand);
1986
+ subCommands.set("blocks", blocksCommand);
1704
1987
  subCommands.set("mcp", mcpCommand);
1705
1988
  const mainCommand = dailyCommand;
1706
1989
  await cli(process$1.argv.slice(2), mainCommand, {
@@ -965,7 +965,7 @@ const consola = createConsola$1();
965
965
  //#endregion
966
966
  //#region package.json
967
967
  var name = "ccusage";
968
- var version = "9.0.1";
968
+ var version = "10.0.0";
969
969
  var description = "Usage analysis tool for Claude Code";
970
970
 
971
971
  //#endregion
package/dist/logger.js CHANGED
@@ -1,3 +1,3 @@
1
- import { log, logger } from "./logger-BgKOQAEs.js";
1
+ import { log, logger } from "./logger-C2GZVy1v.js";
2
2
 
3
3
  export { log, logger };
@@ -1,8 +1,8 @@
1
1
  import { __commonJS, __require, __toESM } from "./chunk-BLXvPPr8.js";
2
- import { getDefaultClaudePath, loadDailyUsageData, loadSessionData } from "./data-loader-CotJGlGF.js";
2
+ import { getDefaultClaudePath, loadDailyUsageData, loadSessionData } from "./data-loader-Bp9KV0_a.js";
3
3
  import { description, literal, object, optional, pipe, union } from "./dist-DCvt9hEv.js";
4
- import { name, version } from "./logger-BgKOQAEs.js";
5
- import { CostModes, dateSchema } from "./pricing-fetcher-1KHPTXC3.js";
4
+ import { name, version } from "./logger-C2GZVy1v.js";
5
+ import { CostModes, dateSchema } from "./pricing-fetcher-BxlbW4km.js";
6
6
  import { anyType, arrayType, booleanType, discriminatedUnionType, enumType, literalType, numberType, objectType, optionalType, recordType, stringType, unionType, unknownType } from "./types-BlyCnKwN.js";
7
7
  import { toJsonSchema } from "./index-CISmcbXk-CW1Gj6Ab.js";
8
8
  import process$1 from "node:process";
package/dist/mcp.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import "./pricing-fetcher-BkSZh4lR.js";
2
- import { LoadOptions } from "./data-loader-OGaMjZTD.js";
2
+ import { LoadOptions } from "./data-loader-DPQaq8_n.js";
3
3
  import { FastMCP } from "fastmcp";
4
4
 
5
5
  //#region src/mcp.d.ts
package/dist/mcp.js CHANGED
@@ -1,9 +1,9 @@
1
- import "./data-loader-CotJGlGF.js";
1
+ import "./data-loader-Bp9KV0_a.js";
2
2
  import "./dist-DCvt9hEv.js";
3
- import "./logger-BgKOQAEs.js";
4
- import "./pricing-fetcher-1KHPTXC3.js";
3
+ import "./logger-C2GZVy1v.js";
4
+ import "./pricing-fetcher-BxlbW4km.js";
5
5
  import "./types-BlyCnKwN.js";
6
- import { createMcpServer } from "./mcp-BUmEAQZm.js";
6
+ import { createMcpServer } from "./mcp-DVjQeqCy.js";
7
7
  import "./index-CISmcbXk-CW1Gj6Ab.js";
8
8
 
9
9
  export { createMcpServer };
@@ -1,5 +1,5 @@
1
1
  import { number, object, optional, pipe, regex, safeParse, string } from "./dist-DCvt9hEv.js";
2
- import { logger } from "./logger-BgKOQAEs.js";
2
+ import { logger } from "./logger-C2GZVy1v.js";
3
3
 
4
4
  //#region src/consts.internal.ts
5
5
  const LITELLM_PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
@@ -1,5 +1,5 @@
1
1
  import "./dist-DCvt9hEv.js";
2
- import "./logger-BgKOQAEs.js";
3
- import { PricingFetcher } from "./pricing-fetcher-1KHPTXC3.js";
2
+ import "./logger-C2GZVy1v.js";
3
+ import { PricingFetcher } from "./pricing-fetcher-BxlbW4km.js";
4
4
 
5
5
  export { PricingFetcher };
@@ -1769,7 +1769,10 @@ var ResponsiveTable = class {
1769
1769
  toString() {
1770
1770
  const terminalWidth = process$1.stdout.columns || 120;
1771
1771
  const dataRows = this.rows.filter((row) => !this.isSeparatorRow(row));
1772
- const allRows = [this.head.map(String), ...dataRows.map((row) => row.map(String))];
1772
+ const allRows = [this.head.map(String), ...dataRows.map((row) => row.map((cell) => {
1773
+ if (typeof cell === "object" && cell != null && "content" in cell) return String(cell.content);
1774
+ return String(cell ?? "");
1775
+ }))];
1773
1776
  const contentWidths = this.head.map((_, colIndex) => {
1774
1777
  const maxLength = Math.max(...allRows.map((row) => stringWidth(String(row[colIndex] ?? ""))));
1775
1778
  return maxLength;
@@ -1827,7 +1830,10 @@ var ResponsiveTable = class {
1827
1830
  }
1828
1831
  }
1829
1832
  isSeparatorRow(row) {
1830
- return row.every((cell) => typeof cell === "string" && (cell === "" || /^─+$/.test(cell)));
1833
+ return row.every((cell) => {
1834
+ if (typeof cell === "object" && cell != null && "content" in cell) return cell.content === "" || /^─+$/.test(cell.content);
1835
+ return typeof cell === "string" && (cell === "" || /^─+$/.test(cell));
1836
+ });
1831
1837
  }
1832
1838
  isDateString(text) {
1833
1839
  return /^\d{4}-\d{2}-\d{2}$/.test(text);
@@ -1,5 +1,8 @@
1
1
  //#region src/utils.table.d.ts
2
- type TableRow = (string | number)[];
2
+ type TableRow = (string | number | {
3
+ content: string;
4
+ hAlign?: 'left' | 'right' | 'center';
5
+ })[];
3
6
  type TableOptions = {
4
7
  head: string[];
5
8
  colAligns?: ('left' | 'right' | 'center')[];
@@ -1,3 +1,3 @@
1
- import { ResponsiveTable } from "./utils.table-DRzF8vZJ.js";
1
+ import { ResponsiveTable } from "./utils.table-USks3NGv.js";
2
2
 
3
3
  export { ResponsiveTable };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ccusage",
3
3
  "type": "module",
4
- "version": "9.0.1",
4
+ "version": "10.0.0",
5
5
  "description": "Usage analysis tool for Claude Code",
6
6
  "author": "ryoppippi",
7
7
  "license": "MIT",