ccusage 0.6.2 → 0.8.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.
@@ -1,5 +1,6 @@
1
1
  import { array, number, object, optional, pipe, regex, safeParse, string } from "./dist-BEQ1tJCL.js";
2
- import { PricingFetcher } from "./pricing-fetcher-BY3-ryVq.js";
2
+ import { logger } from "./logger-DN-RT9jL.js";
3
+ import { PricingFetcher } from "./pricing-fetcher-DMNBE90M.js";
3
4
  import { createRequire } from "node:module";
4
5
  import { readFile } from "node:fs/promises";
5
6
  import { homedir } from "node:os";
@@ -2496,7 +2497,7 @@ var require_picomatch$1 = __commonJS({ "node_modules/picomatch/lib/picomatch.js"
2496
2497
  * @api public
2497
2498
  */
2498
2499
  picomatch$2.parse = (pattern, options) => {
2499
- if (Array.isArray(pattern)) return pattern.map((p) => picomatch$2.parse(p, options));
2500
+ if (Array.isArray(pattern)) return pattern.map((p$1) => picomatch$2.parse(p$1, options));
2500
2501
  return parse(pattern, {
2501
2502
  ...options,
2502
2503
  fastpaths: false
@@ -2793,7 +2794,7 @@ var require_dist$1 = __commonJS({ "node_modules/fdir/dist/index.js"(exports) {
2793
2794
  o[k2] = m[k];
2794
2795
  });
2795
2796
  var __exportStar = void 0 && (void 0).__exportStar || function(m, exports$1) {
2796
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports$1, p)) __createBinding(exports$1, m, p);
2797
+ for (var p$1 in m) if (p$1 !== "default" && !Object.prototype.hasOwnProperty.call(exports$1, p$1)) __createBinding(exports$1, m, p$1);
2797
2798
  };
2798
2799
  Object.defineProperty(exports, "__esModule", { value: true });
2799
2800
  exports.fdir = void 0;
@@ -2973,20 +2974,20 @@ function crawl(options, cwd, sync$1) {
2973
2974
  nocase
2974
2975
  });
2975
2976
  const fdirOptions = {
2976
- filters: [options.debug ? (p, isDirectory) => {
2977
- const path$1$1 = processPath(p, cwd, props.root, isDirectory, options.absolute);
2977
+ filters: [options.debug ? (p$1, isDirectory) => {
2978
+ const path$1$1 = processPath(p$1, cwd, props.root, isDirectory, options.absolute);
2978
2979
  const matches = matcher(path$1$1);
2979
2980
  if (matches) log(`matched ${path$1$1}`);
2980
2981
  return matches;
2981
- } : (p, isDirectory) => matcher(processPath(p, cwd, props.root, isDirectory, options.absolute))],
2982
- exclude: options.debug ? (_, p) => {
2983
- const relativePath = processPath(p, cwd, props.root, true, true);
2982
+ } : (p$1, isDirectory) => matcher(processPath(p$1, cwd, props.root, isDirectory, options.absolute))],
2983
+ exclude: options.debug ? (_, p$1) => {
2984
+ const relativePath = processPath(p$1, cwd, props.root, true, true);
2984
2985
  const skipped = relativePath !== "." && !partialMatcher(relativePath) || ignore(relativePath);
2985
- if (skipped) log(`skipped ${p}`);
2986
- else log(`crawling ${p}`);
2986
+ if (skipped) log(`skipped ${p$1}`);
2987
+ else log(`crawling ${p$1}`);
2987
2988
  return skipped;
2988
- } : (_, p) => {
2989
- const relativePath = processPath(p, cwd, props.root, true, true);
2989
+ } : (_, p$1) => {
2990
+ const relativePath = processPath(p$1, cwd, props.root, true, true);
2990
2991
  return relativePath !== "." && !partialMatcher(relativePath) || ignore(relativePath);
2991
2992
  },
2992
2993
  pathSeparator: "/",
@@ -3024,8 +3025,78 @@ async function glob(patternsOrOptions, options) {
3024
3025
  return crawl(opts, cwd, false);
3025
3026
  }
3026
3027
 
3028
+ //#endregion
3029
+ //#region node_modules/picocolors/picocolors.js
3030
+ var require_picocolors = __commonJS({ "node_modules/picocolors/picocolors.js"(exports, module) {
3031
+ let p = process || {}, argv = p.argv || [], env = p.env || {};
3032
+ let isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
3033
+ let formatter = (open, close, replace = open) => (input) => {
3034
+ let string$1 = "" + input, index = string$1.indexOf(close, open.length);
3035
+ return ~index ? open + replaceClose(string$1, close, replace, index) + close : open + string$1 + close;
3036
+ };
3037
+ let replaceClose = (string$1, close, replace, index) => {
3038
+ let result = "", cursor = 0;
3039
+ do {
3040
+ result += string$1.substring(cursor, index) + replace;
3041
+ cursor = index + close.length;
3042
+ index = string$1.indexOf(close, cursor);
3043
+ } while (~index);
3044
+ return result + string$1.substring(cursor);
3045
+ };
3046
+ let createColors = (enabled = isColorSupported) => {
3047
+ let f = enabled ? formatter : () => String;
3048
+ return {
3049
+ isColorSupported: enabled,
3050
+ reset: f("\x1B[0m", "\x1B[0m"),
3051
+ bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
3052
+ dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
3053
+ italic: f("\x1B[3m", "\x1B[23m"),
3054
+ underline: f("\x1B[4m", "\x1B[24m"),
3055
+ inverse: f("\x1B[7m", "\x1B[27m"),
3056
+ hidden: f("\x1B[8m", "\x1B[28m"),
3057
+ strikethrough: f("\x1B[9m", "\x1B[29m"),
3058
+ black: f("\x1B[30m", "\x1B[39m"),
3059
+ red: f("\x1B[31m", "\x1B[39m"),
3060
+ green: f("\x1B[32m", "\x1B[39m"),
3061
+ yellow: f("\x1B[33m", "\x1B[39m"),
3062
+ blue: f("\x1B[34m", "\x1B[39m"),
3063
+ magenta: f("\x1B[35m", "\x1B[39m"),
3064
+ cyan: f("\x1B[36m", "\x1B[39m"),
3065
+ white: f("\x1B[37m", "\x1B[39m"),
3066
+ gray: f("\x1B[90m", "\x1B[39m"),
3067
+ bgBlack: f("\x1B[40m", "\x1B[49m"),
3068
+ bgRed: f("\x1B[41m", "\x1B[49m"),
3069
+ bgGreen: f("\x1B[42m", "\x1B[49m"),
3070
+ bgYellow: f("\x1B[43m", "\x1B[49m"),
3071
+ bgBlue: f("\x1B[44m", "\x1B[49m"),
3072
+ bgMagenta: f("\x1B[45m", "\x1B[49m"),
3073
+ bgCyan: f("\x1B[46m", "\x1B[49m"),
3074
+ bgWhite: f("\x1B[47m", "\x1B[49m"),
3075
+ blackBright: f("\x1B[90m", "\x1B[39m"),
3076
+ redBright: f("\x1B[91m", "\x1B[39m"),
3077
+ greenBright: f("\x1B[92m", "\x1B[39m"),
3078
+ yellowBright: f("\x1B[93m", "\x1B[39m"),
3079
+ blueBright: f("\x1B[94m", "\x1B[39m"),
3080
+ magentaBright: f("\x1B[95m", "\x1B[39m"),
3081
+ cyanBright: f("\x1B[96m", "\x1B[39m"),
3082
+ whiteBright: f("\x1B[97m", "\x1B[39m"),
3083
+ bgBlackBright: f("\x1B[100m", "\x1B[49m"),
3084
+ bgRedBright: f("\x1B[101m", "\x1B[49m"),
3085
+ bgGreenBright: f("\x1B[102m", "\x1B[49m"),
3086
+ bgYellowBright: f("\x1B[103m", "\x1B[49m"),
3087
+ bgBlueBright: f("\x1B[104m", "\x1B[49m"),
3088
+ bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
3089
+ bgCyanBright: f("\x1B[106m", "\x1B[49m"),
3090
+ bgWhiteBright: f("\x1B[107m", "\x1B[49m")
3091
+ };
3092
+ };
3093
+ module.exports = createColors();
3094
+ module.exports.createColors = createColors;
3095
+ } });
3096
+
3027
3097
  //#endregion
3028
3098
  //#region src/utils.internal.ts
3099
+ var import_picocolors = __toESM(require_picocolors(), 1);
3029
3100
  function formatNumber(num) {
3030
3101
  return num.toLocaleString("en-US");
3031
3102
  }
@@ -3043,6 +3114,32 @@ function groupBy(array$1, keyFn) {
3043
3114
  return groups;
3044
3115
  }, {});
3045
3116
  }
3117
+ function formatModelName(modelName) {
3118
+ const match = modelName.match(/claude-(\w+)-(\d+)-\d+/);
3119
+ if (match != null) return `${match[1]}-${match[2]}`;
3120
+ return modelName;
3121
+ }
3122
+ function formatModelsDisplay(models) {
3123
+ const uniqueModels = [...new Set(models.map(formatModelName))];
3124
+ return uniqueModels.sort().join(", ");
3125
+ }
3126
+ /**
3127
+ * Pushes model breakdown rows to a table
3128
+ * @param table - The table to push rows to
3129
+ * @param breakdowns - Array of model breakdowns
3130
+ * @param extraColumns - Number of extra empty columns before the data (default: 1 for models column)
3131
+ * @param trailingColumns - Number of extra empty columns after the data (default: 0)
3132
+ */
3133
+ function pushBreakdownRows(table, breakdowns, extraColumns = 1, trailingColumns = 0) {
3134
+ for (const breakdown of breakdowns) {
3135
+ const row = [` └─ ${formatModelName(breakdown.modelName)}`];
3136
+ for (let i = 0; i < extraColumns; i++) row.push("");
3137
+ const totalTokens = breakdown.inputTokens + breakdown.outputTokens + breakdown.cacheCreationTokens + breakdown.cacheReadTokens;
3138
+ row.push(import_picocolors.default.gray(formatNumber(breakdown.inputTokens)), import_picocolors.default.gray(formatNumber(breakdown.outputTokens)), import_picocolors.default.gray(formatNumber(breakdown.cacheCreationTokens)), import_picocolors.default.gray(formatNumber(breakdown.cacheReadTokens)), import_picocolors.default.gray(formatNumber(totalTokens)), import_picocolors.default.gray(formatCurrency(breakdown.cost)));
3139
+ for (let i = 0; i < trailingColumns; i++) row.push("");
3140
+ table.push(row);
3141
+ }
3142
+ }
3046
3143
 
3047
3144
  //#endregion
3048
3145
  //#region node_modules/rolldown/node_modules/@oxc-project/runtime/src/helpers/usingCtx.js
@@ -3120,9 +3217,19 @@ const UsageDataSchema = object({
3120
3217
  cache_creation_input_tokens: optional(number()),
3121
3218
  cache_read_input_tokens: optional(number())
3122
3219
  }),
3123
- model: optional(string())
3220
+ model: optional(string()),
3221
+ id: optional(string())
3124
3222
  }),
3125
- costUSD: optional(number())
3223
+ costUSD: optional(number()),
3224
+ requestId: optional(string())
3225
+ });
3226
+ const ModelBreakdownSchema = object({
3227
+ modelName: string(),
3228
+ inputTokens: number(),
3229
+ outputTokens: number(),
3230
+ cacheCreationTokens: number(),
3231
+ cacheReadTokens: number(),
3232
+ cost: number()
3126
3233
  });
3127
3234
  const DailyUsageSchema = object({
3128
3235
  date: pipe(string(), regex(/^\d{4}-\d{2}-\d{2}$/)),
@@ -3130,7 +3237,9 @@ const DailyUsageSchema = object({
3130
3237
  outputTokens: number(),
3131
3238
  cacheCreationTokens: number(),
3132
3239
  cacheReadTokens: number(),
3133
- totalCost: number()
3240
+ totalCost: number(),
3241
+ modelsUsed: array(string()),
3242
+ modelBreakdowns: array(ModelBreakdownSchema)
3134
3243
  });
3135
3244
  const SessionUsageSchema = object({
3136
3245
  sessionId: string(),
@@ -3141,7 +3250,9 @@ const SessionUsageSchema = object({
3141
3250
  cacheReadTokens: number(),
3142
3251
  totalCost: number(),
3143
3252
  lastActivity: string(),
3144
- versions: array(string())
3253
+ versions: array(string()),
3254
+ modelsUsed: array(string()),
3255
+ modelBreakdowns: array(ModelBreakdownSchema)
3145
3256
  });
3146
3257
  const MonthlyUsageSchema = object({
3147
3258
  month: pipe(string(), regex(/^\d{4}-\d{2}$/)),
@@ -3149,7 +3260,9 @@ const MonthlyUsageSchema = object({
3149
3260
  outputTokens: number(),
3150
3261
  cacheCreationTokens: number(),
3151
3262
  cacheReadTokens: number(),
3152
- totalCost: number()
3263
+ totalCost: number(),
3264
+ modelsUsed: array(string()),
3265
+ modelBreakdowns: array(ModelBreakdownSchema)
3153
3266
  });
3154
3267
  function formatDate(dateStr) {
3155
3268
  const date = new Date(dateStr);
@@ -3158,6 +3271,60 @@ function formatDate(dateStr) {
3158
3271
  const day = String(date.getDate()).padStart(2, "0");
3159
3272
  return `${year}-${month}-${day}`;
3160
3273
  }
3274
+ /**
3275
+ * Create a unique identifier for deduplication using message ID and request ID
3276
+ */
3277
+ function createUniqueHash(data) {
3278
+ const messageId = data.message.id;
3279
+ const requestId = data.requestId;
3280
+ if (messageId == null || requestId == null) return null;
3281
+ return `${messageId}:${requestId}`;
3282
+ }
3283
+ /**
3284
+ * Extract the earliest timestamp from a JSONL file
3285
+ * Scans through the file until it finds a valid timestamp
3286
+ */
3287
+ async function getEarliestTimestamp(filePath) {
3288
+ try {
3289
+ const content = await readFile(filePath, "utf-8");
3290
+ const lines = content.trim().split("\n");
3291
+ let earliestDate = null;
3292
+ for (const line of lines) {
3293
+ if (line.trim() === "") continue;
3294
+ try {
3295
+ const json = JSON.parse(line);
3296
+ if (json.timestamp != null && typeof json.timestamp === "string") {
3297
+ const date = new Date(json.timestamp);
3298
+ if (!Number.isNaN(date.getTime())) {
3299
+ if (earliestDate == null || date < earliestDate) earliestDate = date;
3300
+ }
3301
+ }
3302
+ } catch {
3303
+ continue;
3304
+ }
3305
+ }
3306
+ return earliestDate;
3307
+ } catch (error) {
3308
+ logger.debug(`Failed to get earliest timestamp for ${filePath}:`, error);
3309
+ return null;
3310
+ }
3311
+ }
3312
+ /**
3313
+ * Sort files by their earliest timestamp
3314
+ * Files without valid timestamps are placed at the end
3315
+ */
3316
+ async function sortFilesByTimestamp(files) {
3317
+ const filesWithTimestamps = await Promise.all(files.map(async (file) => ({
3318
+ file,
3319
+ timestamp: await getEarliestTimestamp(file)
3320
+ })));
3321
+ return filesWithTimestamps.sort((a, b) => {
3322
+ if (a.timestamp == null && b.timestamp == null) return 0;
3323
+ if (a.timestamp == null) return 1;
3324
+ if (b.timestamp == null) return -1;
3325
+ return a.timestamp.getTime() - b.timestamp.getTime();
3326
+ }).map((item) => item.file);
3327
+ }
3161
3328
  async function calculateCostForEntry(data, mode, fetcher) {
3162
3329
  if (mode === "display") return data.costUSD ?? 0;
3163
3330
  if (mode === "calculate") {
@@ -3181,10 +3348,12 @@ async function loadDailyUsageData(options) {
3181
3348
  absolute: true
3182
3349
  });
3183
3350
  if (files.length === 0) return [];
3351
+ const sortedFiles = await sortFilesByTimestamp(files);
3184
3352
  const mode = options?.mode ?? "auto";
3185
3353
  const fetcher = _usingCtx$1.u(mode === "display" ? null : new PricingFetcher());
3354
+ const processedHashes = /* @__PURE__ */ new Set();
3186
3355
  const allEntries = [];
3187
- for (const file of files) {
3356
+ for (const file of sortedFiles) {
3188
3357
  const content = await readFile(file, "utf-8");
3189
3358
  const lines = content.trim().split("\n").filter((line) => line.length > 0);
3190
3359
  for (const line of lines) try {
@@ -3192,33 +3361,65 @@ async function loadDailyUsageData(options) {
3192
3361
  const result = safeParse(UsageDataSchema, parsed);
3193
3362
  if (!result.success) continue;
3194
3363
  const data = result.output;
3364
+ const uniqueHash = createUniqueHash(data);
3365
+ if (uniqueHash != null && processedHashes.has(uniqueHash)) continue;
3366
+ if (uniqueHash != null) processedHashes.add(uniqueHash);
3195
3367
  const date = formatDate(data.timestamp);
3196
3368
  const cost = fetcher != null ? await calculateCostForEntry(data, mode, fetcher) : data.costUSD ?? 0;
3197
3369
  allEntries.push({
3198
3370
  data,
3199
3371
  date,
3200
- cost
3372
+ cost,
3373
+ model: data.message.model
3201
3374
  });
3202
3375
  } catch {}
3203
3376
  }
3204
3377
  const groupedByDate = groupBy(allEntries, (entry) => entry.date);
3205
3378
  const results = Object.entries(groupedByDate).map(([date, entries]) => {
3206
3379
  if (entries == null) return void 0;
3207
- return entries.reduce((acc, entry) => ({
3208
- date,
3380
+ const modelAggregates = /* @__PURE__ */ new Map();
3381
+ for (const entry of entries) {
3382
+ const modelName = entry.model ?? "unknown";
3383
+ if (modelName === "<synthetic>") continue;
3384
+ const existing = modelAggregates.get(modelName) ?? {
3385
+ inputTokens: 0,
3386
+ outputTokens: 0,
3387
+ cacheCreationTokens: 0,
3388
+ cacheReadTokens: 0,
3389
+ cost: 0
3390
+ };
3391
+ modelAggregates.set(modelName, {
3392
+ inputTokens: existing.inputTokens + (entry.data.message.usage.input_tokens ?? 0),
3393
+ outputTokens: existing.outputTokens + (entry.data.message.usage.output_tokens ?? 0),
3394
+ cacheCreationTokens: existing.cacheCreationTokens + (entry.data.message.usage.cache_creation_input_tokens ?? 0),
3395
+ cacheReadTokens: existing.cacheReadTokens + (entry.data.message.usage.cache_read_input_tokens ?? 0),
3396
+ cost: existing.cost + entry.cost
3397
+ });
3398
+ }
3399
+ const modelBreakdowns = Array.from(modelAggregates.entries()).map(([modelName, stats]) => ({
3400
+ modelName,
3401
+ ...stats
3402
+ })).sort((a, b) => b.cost - a.cost);
3403
+ const totals = entries.reduce((acc, entry) => ({
3209
3404
  inputTokens: acc.inputTokens + (entry.data.message.usage.input_tokens ?? 0),
3210
3405
  outputTokens: acc.outputTokens + (entry.data.message.usage.output_tokens ?? 0),
3211
3406
  cacheCreationTokens: acc.cacheCreationTokens + (entry.data.message.usage.cache_creation_input_tokens ?? 0),
3212
3407
  cacheReadTokens: acc.cacheReadTokens + (entry.data.message.usage.cache_read_input_tokens ?? 0),
3213
3408
  totalCost: acc.totalCost + entry.cost
3214
3409
  }), {
3215
- date,
3216
3410
  inputTokens: 0,
3217
3411
  outputTokens: 0,
3218
3412
  cacheCreationTokens: 0,
3219
3413
  cacheReadTokens: 0,
3220
3414
  totalCost: 0
3221
3415
  });
3416
+ const modelsUsed = [...new Set(entries.map((e) => e.model).filter((m) => m != null && m !== "<synthetic>"))];
3417
+ return {
3418
+ date,
3419
+ ...totals,
3420
+ modelsUsed,
3421
+ modelBreakdowns
3422
+ };
3222
3423
  }).filter((item) => item != null).filter((item) => {
3223
3424
  if (options?.since != null || options?.until != null) {
3224
3425
  const dateStr = item.date.replace(/-/g, "");
@@ -3250,10 +3451,12 @@ async function loadSessionData(options) {
3250
3451
  absolute: true
3251
3452
  });
3252
3453
  if (files.length === 0) return [];
3454
+ const sortedFiles = await sortFilesByTimestamp(files);
3253
3455
  const mode = options?.mode ?? "auto";
3254
3456
  const fetcher = _usingCtx3.u(mode === "display" ? null : new PricingFetcher());
3457
+ const processedHashes = /* @__PURE__ */ new Set();
3255
3458
  const allEntries = [];
3256
- for (const file of files) {
3459
+ for (const file of sortedFiles) {
3257
3460
  const relativePath = path.relative(claudeDir, file);
3258
3461
  const parts = relativePath.split(path.sep);
3259
3462
  const sessionId = parts[parts.length - 2] ?? "unknown";
@@ -3266,6 +3469,9 @@ async function loadSessionData(options) {
3266
3469
  const result = safeParse(UsageDataSchema, parsed);
3267
3470
  if (!result.success) continue;
3268
3471
  const data = result.output;
3472
+ const uniqueHash = createUniqueHash(data);
3473
+ if (uniqueHash != null && processedHashes.has(uniqueHash)) continue;
3474
+ if (uniqueHash != null) processedHashes.add(uniqueHash);
3269
3475
  const sessionKey = `${projectPath}/${sessionId}`;
3270
3476
  const cost = fetcher != null ? await calculateCostForEntry(data, mode, fetcher) : data.costUSD ?? 0;
3271
3477
  allEntries.push({
@@ -3274,7 +3480,8 @@ async function loadSessionData(options) {
3274
3480
  sessionId,
3275
3481
  projectPath,
3276
3482
  cost,
3277
- timestamp: data.timestamp
3483
+ timestamp: data.timestamp,
3484
+ model: data.message.model
3278
3485
  });
3279
3486
  } catch {}
3280
3487
  }
@@ -3284,28 +3491,52 @@ async function loadSessionData(options) {
3284
3491
  const latestEntry = entries.reduce((latest, current) => current.timestamp > latest.timestamp ? current : latest);
3285
3492
  const versionSet = /* @__PURE__ */ new Set();
3286
3493
  for (const entry of entries) if (entry.data.version != null) versionSet.add(entry.data.version);
3287
- const aggregated = entries.reduce((acc, entry) => ({
3288
- sessionId: latestEntry.sessionId,
3289
- projectPath: latestEntry.projectPath,
3494
+ const modelAggregates = /* @__PURE__ */ new Map();
3495
+ for (const entry of entries) {
3496
+ const modelName = entry.model ?? "unknown";
3497
+ if (modelName === "<synthetic>") continue;
3498
+ const existing = modelAggregates.get(modelName) ?? {
3499
+ inputTokens: 0,
3500
+ outputTokens: 0,
3501
+ cacheCreationTokens: 0,
3502
+ cacheReadTokens: 0,
3503
+ cost: 0
3504
+ };
3505
+ modelAggregates.set(modelName, {
3506
+ inputTokens: existing.inputTokens + (entry.data.message.usage.input_tokens ?? 0),
3507
+ outputTokens: existing.outputTokens + (entry.data.message.usage.output_tokens ?? 0),
3508
+ cacheCreationTokens: existing.cacheCreationTokens + (entry.data.message.usage.cache_creation_input_tokens ?? 0),
3509
+ cacheReadTokens: existing.cacheReadTokens + (entry.data.message.usage.cache_read_input_tokens ?? 0),
3510
+ cost: existing.cost + entry.cost
3511
+ });
3512
+ }
3513
+ const modelBreakdowns = Array.from(modelAggregates.entries()).map(([modelName, stats]) => ({
3514
+ modelName,
3515
+ ...stats
3516
+ })).sort((a, b) => b.cost - a.cost);
3517
+ const totals = entries.reduce((acc, entry) => ({
3290
3518
  inputTokens: acc.inputTokens + (entry.data.message.usage.input_tokens ?? 0),
3291
3519
  outputTokens: acc.outputTokens + (entry.data.message.usage.output_tokens ?? 0),
3292
3520
  cacheCreationTokens: acc.cacheCreationTokens + (entry.data.message.usage.cache_creation_input_tokens ?? 0),
3293
3521
  cacheReadTokens: acc.cacheReadTokens + (entry.data.message.usage.cache_read_input_tokens ?? 0),
3294
- totalCost: acc.totalCost + entry.cost,
3295
- lastActivity: formatDate(latestEntry.timestamp),
3296
- versions: Array.from(versionSet).sort()
3522
+ totalCost: acc.totalCost + entry.cost
3297
3523
  }), {
3298
- sessionId: latestEntry.sessionId,
3299
- projectPath: latestEntry.projectPath,
3300
3524
  inputTokens: 0,
3301
3525
  outputTokens: 0,
3302
3526
  cacheCreationTokens: 0,
3303
3527
  cacheReadTokens: 0,
3304
- totalCost: 0,
3305
- lastActivity: formatDate(latestEntry.timestamp),
3306
- versions: Array.from(versionSet).sort()
3528
+ totalCost: 0
3307
3529
  });
3308
- return aggregated;
3530
+ const modelsUsed = [...new Set(entries.map((e) => e.model).filter((m) => m != null && m !== "<synthetic>"))];
3531
+ return {
3532
+ sessionId: latestEntry.sessionId,
3533
+ projectPath: latestEntry.projectPath,
3534
+ ...totals,
3535
+ lastActivity: formatDate(latestEntry.timestamp),
3536
+ versions: Array.from(versionSet).sort(),
3537
+ modelsUsed,
3538
+ modelBreakdowns
3539
+ };
3309
3540
  }).filter((item) => item != null).filter((item) => {
3310
3541
  if (options?.since != null || options?.until != null) {
3311
3542
  const dateStr = item.lastActivity.replace(/-/g, "");
@@ -3326,28 +3557,61 @@ async function loadSessionData(options) {
3326
3557
  async function loadMonthlyUsageData(options) {
3327
3558
  const dailyData = await loadDailyUsageData(options);
3328
3559
  const groupedByMonth = groupBy(dailyData, (data) => data.date.substring(0, 7));
3329
- const monthlyArray = Object.entries(groupedByMonth).map(([month, dailyEntries]) => {
3330
- if (dailyEntries == null) return void 0;
3331
- return dailyEntries.reduce((acc, data) => ({
3332
- month,
3333
- inputTokens: acc.inputTokens + data.inputTokens,
3334
- outputTokens: acc.outputTokens + data.outputTokens,
3335
- cacheCreationTokens: acc.cacheCreationTokens + data.cacheCreationTokens,
3336
- cacheReadTokens: acc.cacheReadTokens + data.cacheReadTokens,
3337
- totalCost: acc.totalCost + data.totalCost
3338
- }), {
3560
+ const monthlyArray = [];
3561
+ for (const [month, dailyEntries] of Object.entries(groupedByMonth)) {
3562
+ if (dailyEntries == null) continue;
3563
+ const modelAggregates = /* @__PURE__ */ new Map();
3564
+ for (const daily of dailyEntries) for (const breakdown of daily.modelBreakdowns) {
3565
+ if (breakdown.modelName === "<synthetic>") continue;
3566
+ const existing = modelAggregates.get(breakdown.modelName) ?? {
3567
+ inputTokens: 0,
3568
+ outputTokens: 0,
3569
+ cacheCreationTokens: 0,
3570
+ cacheReadTokens: 0,
3571
+ cost: 0
3572
+ };
3573
+ modelAggregates.set(breakdown.modelName, {
3574
+ inputTokens: existing.inputTokens + breakdown.inputTokens,
3575
+ outputTokens: existing.outputTokens + breakdown.outputTokens,
3576
+ cacheCreationTokens: existing.cacheCreationTokens + breakdown.cacheCreationTokens,
3577
+ cacheReadTokens: existing.cacheReadTokens + breakdown.cacheReadTokens,
3578
+ cost: existing.cost + breakdown.cost
3579
+ });
3580
+ }
3581
+ const modelBreakdowns = Array.from(modelAggregates.entries()).map(([modelName, stats]) => ({
3582
+ modelName,
3583
+ ...stats
3584
+ })).sort((a, b) => b.cost - a.cost);
3585
+ const modelsSet = /* @__PURE__ */ new Set();
3586
+ for (const data of dailyEntries) for (const model of data.modelsUsed) if (model !== "<synthetic>") modelsSet.add(model);
3587
+ let totalInputTokens = 0;
3588
+ let totalOutputTokens = 0;
3589
+ let totalCacheCreationTokens = 0;
3590
+ let totalCacheReadTokens = 0;
3591
+ let totalCost = 0;
3592
+ for (const daily of dailyEntries) {
3593
+ totalInputTokens += daily.inputTokens;
3594
+ totalOutputTokens += daily.outputTokens;
3595
+ totalCacheCreationTokens += daily.cacheCreationTokens;
3596
+ totalCacheReadTokens += daily.cacheReadTokens;
3597
+ totalCost += daily.totalCost;
3598
+ }
3599
+ const monthlyUsage = {
3339
3600
  month,
3340
- inputTokens: 0,
3341
- outputTokens: 0,
3342
- cacheCreationTokens: 0,
3343
- cacheReadTokens: 0,
3344
- totalCost: 0
3345
- });
3346
- }).filter((item) => item != null);
3601
+ inputTokens: totalInputTokens,
3602
+ outputTokens: totalOutputTokens,
3603
+ cacheCreationTokens: totalCacheCreationTokens,
3604
+ cacheReadTokens: totalCacheReadTokens,
3605
+ totalCost,
3606
+ modelsUsed: Array.from(modelsSet),
3607
+ modelBreakdowns
3608
+ };
3609
+ monthlyArray.push(monthlyUsage);
3610
+ }
3347
3611
  const sortOrder = options?.order ?? "desc";
3348
3612
  const sortedMonthly = sort(monthlyArray);
3349
3613
  return sortOrder === "desc" ? sortedMonthly.desc((item) => item.month) : sortedMonthly.asc((item) => item.month);
3350
3614
  }
3351
3615
 
3352
3616
  //#endregion
3353
- export { DailyUsageSchema, MonthlyUsageSchema, SessionUsageSchema, UsageDataSchema, __commonJS, __require, __toESM, calculateCostForEntry, formatCurrency, formatDate, formatNumber, getDefaultClaudePath, glob, loadDailyUsageData, loadMonthlyUsageData, loadSessionData, require_usingCtx };
3617
+ export { DailyUsageSchema, ModelBreakdownSchema, MonthlyUsageSchema, SessionUsageSchema, UsageDataSchema, __commonJS, __require, __toESM, calculateCostForEntry, createUniqueHash, formatCurrency, formatDate, formatModelsDisplay, formatNumber, getDefaultClaudePath, getEarliestTimestamp, glob, loadDailyUsageData, loadMonthlyUsageData, loadSessionData, pushBreakdownRows, require_picocolors, require_usingCtx, sortFilesByTimestamp };
@@ -92,10 +92,21 @@ declare const UsageDataSchema: ObjectSchema<{
92
92
  readonly cache_read_input_tokens: OptionalSchema<NumberSchema<undefined>, undefined>;
93
93
  }, undefined>;
94
94
  readonly model: OptionalSchema<StringSchema<undefined>, undefined>;
95
+ readonly id: OptionalSchema<StringSchema<undefined>, undefined>;
95
96
  }, undefined>;
96
97
  readonly costUSD: OptionalSchema<NumberSchema<undefined>, undefined>;
98
+ readonly requestId: OptionalSchema<StringSchema<undefined>, undefined>;
97
99
  }, undefined>;
98
100
  type UsageData = InferOutput<typeof UsageDataSchema>;
101
+ declare const ModelBreakdownSchema: ObjectSchema<{
102
+ readonly modelName: StringSchema<undefined>;
103
+ readonly inputTokens: NumberSchema<undefined>;
104
+ readonly outputTokens: NumberSchema<undefined>;
105
+ readonly cacheCreationTokens: NumberSchema<undefined>;
106
+ readonly cacheReadTokens: NumberSchema<undefined>;
107
+ readonly cost: NumberSchema<undefined>;
108
+ }, undefined>;
109
+ type ModelBreakdown = InferOutput<typeof ModelBreakdownSchema>;
99
110
  declare const DailyUsageSchema: ObjectSchema<{
100
111
  readonly date: SchemaWithPipe<readonly [StringSchema<undefined>, RegexAction<string, undefined>]>;
101
112
  readonly inputTokens: NumberSchema<undefined>;
@@ -103,6 +114,15 @@ declare const DailyUsageSchema: ObjectSchema<{
103
114
  readonly cacheCreationTokens: NumberSchema<undefined>;
104
115
  readonly cacheReadTokens: NumberSchema<undefined>;
105
116
  readonly totalCost: NumberSchema<undefined>;
117
+ readonly modelsUsed: ArraySchema<StringSchema<undefined>, undefined>;
118
+ readonly modelBreakdowns: ArraySchema<ObjectSchema<{
119
+ readonly modelName: StringSchema<undefined>;
120
+ readonly inputTokens: NumberSchema<undefined>;
121
+ readonly outputTokens: NumberSchema<undefined>;
122
+ readonly cacheCreationTokens: NumberSchema<undefined>;
123
+ readonly cacheReadTokens: NumberSchema<undefined>;
124
+ readonly cost: NumberSchema<undefined>;
125
+ }, undefined>, undefined>;
106
126
  }, undefined>;
107
127
  type DailyUsage = InferOutput<typeof DailyUsageSchema>;
108
128
  declare const SessionUsageSchema: ObjectSchema<{
@@ -115,6 +135,15 @@ declare const SessionUsageSchema: ObjectSchema<{
115
135
  readonly totalCost: NumberSchema<undefined>;
116
136
  readonly lastActivity: StringSchema<undefined>;
117
137
  readonly versions: ArraySchema<StringSchema<undefined>, undefined>;
138
+ readonly modelsUsed: ArraySchema<StringSchema<undefined>, undefined>;
139
+ readonly modelBreakdowns: ArraySchema<ObjectSchema<{
140
+ readonly modelName: StringSchema<undefined>;
141
+ readonly inputTokens: NumberSchema<undefined>;
142
+ readonly outputTokens: NumberSchema<undefined>;
143
+ readonly cacheCreationTokens: NumberSchema<undefined>;
144
+ readonly cacheReadTokens: NumberSchema<undefined>;
145
+ readonly cost: NumberSchema<undefined>;
146
+ }, undefined>, undefined>;
118
147
  }, undefined>;
119
148
  type SessionUsage = InferOutput<typeof SessionUsageSchema>;
120
149
  declare const MonthlyUsageSchema: ObjectSchema<{
@@ -124,9 +153,32 @@ declare const MonthlyUsageSchema: ObjectSchema<{
124
153
  readonly cacheCreationTokens: NumberSchema<undefined>;
125
154
  readonly cacheReadTokens: NumberSchema<undefined>;
126
155
  readonly totalCost: NumberSchema<undefined>;
156
+ readonly modelsUsed: ArraySchema<StringSchema<undefined>, undefined>;
157
+ readonly modelBreakdowns: ArraySchema<ObjectSchema<{
158
+ readonly modelName: StringSchema<undefined>;
159
+ readonly inputTokens: NumberSchema<undefined>;
160
+ readonly outputTokens: NumberSchema<undefined>;
161
+ readonly cacheCreationTokens: NumberSchema<undefined>;
162
+ readonly cacheReadTokens: NumberSchema<undefined>;
163
+ readonly cost: NumberSchema<undefined>;
164
+ }, undefined>, undefined>;
127
165
  }, undefined>;
128
166
  type MonthlyUsage = InferOutput<typeof MonthlyUsageSchema>;
129
167
  declare function formatDate(dateStr: string): string;
168
+ /**
169
+ * Create a unique identifier for deduplication using message ID and request ID
170
+ */
171
+ declare function createUniqueHash(data: UsageData): string | null;
172
+ /**
173
+ * Extract the earliest timestamp from a JSONL file
174
+ * Scans through the file until it finds a valid timestamp
175
+ */
176
+ declare function getEarliestTimestamp(filePath: string): Promise<Date | null>;
177
+ /**
178
+ * Sort files by their earliest timestamp
179
+ * Files without valid timestamps are placed at the end
180
+ */
181
+ declare function sortFilesByTimestamp(files: string[]): Promise<string[]>;
130
182
  declare function calculateCostForEntry(data: UsageData, mode: CostMode, fetcher: PricingFetcher): Promise<number>;
131
183
  type DateFilter = {
132
184
  since?: string;
@@ -141,4 +193,4 @@ declare function loadDailyUsageData(options?: LoadOptions): Promise<DailyUsage[]
141
193
  declare function loadSessionData(options?: LoadOptions): Promise<SessionUsage[]>;
142
194
  declare function loadMonthlyUsageData(options?: LoadOptions): Promise<MonthlyUsage[]>;
143
195
  //#endregion
144
- export { DailyUsage, DailyUsageSchema, DateFilter, LoadOptions, MonthlyUsage, MonthlyUsageSchema, SessionUsage, SessionUsageSchema, UsageData, UsageDataSchema, calculateCostForEntry, formatDate, getDefaultClaudePath, loadDailyUsageData, loadMonthlyUsageData, loadSessionData };
196
+ export { DailyUsage, DailyUsageSchema, DateFilter, LoadOptions, ModelBreakdown, ModelBreakdownSchema, MonthlyUsage, MonthlyUsageSchema, SessionUsage, SessionUsageSchema, UsageData, UsageDataSchema, calculateCostForEntry, createUniqueHash, formatDate, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadMonthlyUsageData, loadSessionData, sortFilesByTimestamp };
@@ -1,3 +1,3 @@
1
- import { DailyUsage, DailyUsageSchema, DateFilter, LoadOptions, MonthlyUsage, MonthlyUsageSchema, SessionUsage, SessionUsageSchema, UsageData, UsageDataSchema, calculateCostForEntry, formatDate, getDefaultClaudePath, loadDailyUsageData, loadMonthlyUsageData, loadSessionData } from "./data-loader-B0tJZeHI.js";
1
+ import { DailyUsage, DailyUsageSchema, DateFilter, LoadOptions, ModelBreakdown, ModelBreakdownSchema, MonthlyUsage, MonthlyUsageSchema, SessionUsage, SessionUsageSchema, UsageData, UsageDataSchema, calculateCostForEntry, createUniqueHash, formatDate, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadMonthlyUsageData, loadSessionData, sortFilesByTimestamp } from "./data-loader-amTZCtBR.js";
2
2
  import "./pricing-fetcher-Dq-OLBp4.js";
3
- export { DailyUsage, DailyUsageSchema, DateFilter, LoadOptions, MonthlyUsage, MonthlyUsageSchema, SessionUsage, SessionUsageSchema, UsageData, UsageDataSchema, calculateCostForEntry, formatDate, getDefaultClaudePath, loadDailyUsageData, loadMonthlyUsageData, loadSessionData };
3
+ export { DailyUsage, DailyUsageSchema, DateFilter, LoadOptions, ModelBreakdown, ModelBreakdownSchema, MonthlyUsage, MonthlyUsageSchema, SessionUsage, SessionUsageSchema, UsageData, UsageDataSchema, calculateCostForEntry, createUniqueHash, formatDate, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadMonthlyUsageData, loadSessionData, sortFilesByTimestamp };
@@ -1,6 +1,6 @@
1
- import { DailyUsageSchema, MonthlyUsageSchema, SessionUsageSchema, UsageDataSchema, calculateCostForEntry, formatDate, getDefaultClaudePath, loadDailyUsageData, loadMonthlyUsageData, loadSessionData } from "./data-loader-BfnzLKGl.js";
1
+ import { DailyUsageSchema, ModelBreakdownSchema, MonthlyUsageSchema, SessionUsageSchema, UsageDataSchema, calculateCostForEntry, createUniqueHash, formatDate, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadMonthlyUsageData, loadSessionData, sortFilesByTimestamp } from "./data-loader-Bz6Vemxw.js";
2
2
  import "./dist-BEQ1tJCL.js";
3
- import "./logger-DixU80sg.js";
4
- import "./pricing-fetcher-BY3-ryVq.js";
3
+ import "./logger-DN-RT9jL.js";
4
+ import "./pricing-fetcher-DMNBE90M.js";
5
5
 
6
- export { DailyUsageSchema, MonthlyUsageSchema, SessionUsageSchema, UsageDataSchema, calculateCostForEntry, formatDate, getDefaultClaudePath, loadDailyUsageData, loadMonthlyUsageData, loadSessionData };
6
+ export { DailyUsageSchema, ModelBreakdownSchema, MonthlyUsageSchema, SessionUsageSchema, UsageDataSchema, calculateCostForEntry, createUniqueHash, formatDate, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadMonthlyUsageData, loadSessionData, sortFilesByTimestamp };
@@ -1,7 +1,7 @@
1
- import { UsageDataSchema, __toESM, glob, require_usingCtx } from "./data-loader-BfnzLKGl.js";
1
+ import { UsageDataSchema, __toESM, glob, require_usingCtx } from "./data-loader-Bz6Vemxw.js";
2
2
  import { safeParse } from "./dist-BEQ1tJCL.js";
3
- import { logger } from "./logger-DixU80sg.js";
4
- import { PricingFetcher } from "./pricing-fetcher-BY3-ryVq.js";
3
+ import { logger } from "./logger-DN-RT9jL.js";
4
+ import { PricingFetcher } from "./pricing-fetcher-DMNBE90M.js";
5
5
  import { readFile } from "node:fs/promises";
6
6
  import { homedir } from "node:os";
7
7
  import path from "node:path";