ccusage 15.9.9 → 16.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/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, DEFAULT_RECENT_DAYS, DEFAULT_REFRESH_INTERVAL_SECONDS, MAX_REFRESH_INTERVAL_SECONDS, MCP_DEFAULT_PORT, MIN_REFRESH_INTERVAL_SECONDS, MIN_RENDER_INTERVAL_MS, PROJECT_ALIASES_ENV, PricingFetcher, WEEK_DAYS, __commonJSMin, __require, __toESM, inspectError, isFailure, isSuccess, map, pipe, require_usingCtx, succeed, try_ } from "./pricing-fetcher-CbLCyFQr.js";
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, PROJECT_ALIASES_ENV, PricingFetcher, WEEK_DAYS, __commonJSMin, __require, __toESM, inspect, inspectError, isFailure, isSuccess, map, pipe, require_usingCtx, succeed, try_ } from "./pricing-fetcher-CUQk_dDN.js";
3
3
  import { getTotalTokens } from "./_token-utils-WjkbrjKv.js";
4
4
  import { CostModes, SortOrders, filterDateSchema, statuslineHookJsonSchema } from "./_types-BbEk8t2a.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, uniq, unwrap, usageDataSchema } from "./data-loader-CM8RKePp.js";
7
- import { description, log, logger, name, version } from "./logger-m79TqYfY.js";
8
- import { detectMismatches, printMismatchReport } from "./debug-D5u7ZVKi.js";
9
- import { createMcpHttpApp, createMcpServer, startMcpServerStdio } from "./mcp-r529IhIF.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-Bf4qMLf2.js";
7
+ import { description, log, logger, name, version } from "./logger-ClcgjXEW.js";
8
+ import { detectMismatches, printMismatchReport } from "./debug-Oi3WqR-w.js";
9
+ import { createMcpHttpApp, createMcpServer, startMcpServerStdio } from "./mcp-DW2TXqtN.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";
@@ -817,7 +817,94 @@ 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);
820
+ })), import_picocolors$8 = /* @__PURE__ */ __toESM(require_picocolors(), 1), import_usingCtx$3 = /* @__PURE__ */ __toESM(require_usingCtx(), 1);
821
+ function extractExplicitArgs(tokens) {
822
+ const explicit = {};
823
+ for (const token of tokens) if (typeof token === "object" && token !== null) {
824
+ const t = token;
825
+ if (t.kind === "option" && typeof t.name === "string") explicit[t.name] = true;
826
+ }
827
+ return explicit;
828
+ }
829
+ function getConfigSearchPaths() {
830
+ const claudeConfigDirs = [join(process$1.cwd(), ".ccusage"), ...toArray(getClaudePaths())];
831
+ return claudeConfigDirs.map((dir) => join(dir, CONFIG_FILE_NAME));
832
+ }
833
+ function validateConfigJson(data) {
834
+ if (typeof data !== "object" || data === null) return false;
835
+ const config = data;
836
+ return !(config.$schema != null && typeof config.$schema !== "string" || config.defaults != null && (typeof config.defaults !== "object" || config.defaults === null) || config.commands != null && (typeof config.commands !== "object" || config.commands === null));
837
+ }
838
+ function loadConfigFile(filePath, debug$4 = false) {
839
+ if (!existsSync(filePath)) {
840
+ if (debug$4) logger.info(` • Checking: ${filePath} (not found)`);
841
+ return void 0;
842
+ }
843
+ const parseConfigFileResult = pipe(try_({
844
+ try: () => {
845
+ const content = readFileSync(filePath, "utf-8"), data = JSON.parse(content);
846
+ if (!validateConfigJson(data)) throw new Error("Invalid configuration structure");
847
+ return data.source = filePath, data;
848
+ },
849
+ catch: (error) => error
850
+ })(), inspect(() => {
851
+ if (logger.debug(`Parsed configuration file: ${filePath}`), debug$4) logger.info(` • Checking: ${filePath} (found ✓)`);
852
+ }), inspectError((error) => {
853
+ const errorMessage = error instanceof Error ? error.message : String(error);
854
+ if (logger.warn(`Error parsing configuration file at ${filePath}: ${errorMessage}`), debug$4) logger.info(` • Checking: ${filePath} (error: ${errorMessage})`);
855
+ }), unwrap(void 0));
856
+ return parseConfigFileResult;
857
+ }
858
+ function loadConfig(configPath, debug$4 = false) {
859
+ if (debug$4) logger.info("Debug mode enabled - showing config loading details\n");
860
+ if (configPath != null) {
861
+ if (debug$4) logger.info("Using specified config file:"), logger.info(` • Path: ${configPath}`);
862
+ const config = loadConfigFile(configPath, debug$4);
863
+ if (config == null) logger.warn(`Configuration file not found or invalid: ${configPath}`);
864
+ else if (debug$4) logger.info(""), logger.info(`Loaded config from: ${configPath}`), logger.info(` • Schema: ${config.$schema ?? "none"}`), logger.info(` • Has defaults: ${config.defaults != null ? "yes" : "no"}${config.defaults != null ? ` (${Object.keys(config.defaults).length} options)` : ""}`), logger.info(` • Has command configs: ${config.commands != null ? "yes" : "no"}${config.commands != null ? ` (${Object.keys(config.commands).join(", ")})` : ""}`);
865
+ return config;
866
+ }
867
+ if (debug$4) logger.info("Searching for config files:");
868
+ for (const searchPath of getConfigSearchPaths()) {
869
+ const config = loadConfigFile(searchPath, debug$4);
870
+ if (config != null) {
871
+ if (debug$4) logger.info(""), logger.info(`Loaded config from: ${searchPath}`), logger.info(` • Schema: ${config.$schema ?? "none"}`), logger.info(` • Has defaults: ${config.defaults != null ? "yes" : "no"}${config.defaults != null ? ` (${Object.keys(config.defaults).length} options)` : ""}`), logger.info(` • Has command configs: ${config.commands != null ? "yes" : "no"}${config.commands != null ? ` (${Object.keys(config.commands).join(", ")})` : ""}`);
872
+ return config;
873
+ }
874
+ }
875
+ if (logger.debug("No valid configuration file found"), debug$4) logger.info(""), logger.info("No valid configuration file found");
876
+ return void 0;
877
+ }
878
+ function mergeConfigWithArgs(ctx, config, debug$4 = false) {
879
+ if (config == null) {
880
+ if (debug$4) logger.info(""), logger.info(`No config file loaded, using CLI args only for '${ctx.name ?? "unknown"}' command`);
881
+ return ctx.values;
882
+ }
883
+ const merged = {}, commandName = ctx.name, sources = {};
884
+ if (config.defaults != null) for (const [key, value$1] of Object.entries(config.defaults)) merged[key] = value$1, sources[key] = "defaults";
885
+ if (commandName != null && config.commands?.[commandName] != null) for (const [key, value$1] of Object.entries(config.commands[commandName])) merged[key] = value$1, sources[key] = "command config";
886
+ const explicit = extractExplicitArgs(ctx.tokens);
887
+ for (const [key, value$1] of Object.entries(ctx.values)) if (value$1 != null && explicit[key] === true) merged[key] = value$1, sources[key] = "CLI";
888
+ if (logger.debug(`Merged config for ${commandName ?? "unknown"}:`, merged), debug$4) {
889
+ logger.info(""), logger.info(`Merging options for '${commandName ?? "unknown"}' command:`);
890
+ const bySource = {
891
+ "defaults": [],
892
+ "command config": [],
893
+ "CLI": []
894
+ };
895
+ for (const [key, source] of Object.entries(sources)) if (bySource[source] != null) bySource[source].push(`${key}=${JSON.stringify(merged[key])}`);
896
+ if (bySource.defaults.length > 0) logger.info(` • From defaults: ${bySource.defaults.join(", ")}`);
897
+ if (bySource["command config"].length > 0) logger.info(` • From command config: ${bySource["command config"].join(", ")}`);
898
+ if (bySource.CLI.length > 0) logger.info(` • From CLI args: ${bySource.CLI.join(", ")}`);
899
+ logger.info(" • Final merged options: {");
900
+ for (const [key, value$1] of Object.entries(merged)) {
901
+ const source = sources[key] ?? "unknown";
902
+ logger.info(` ${key}: ${JSON.stringify(value$1)} (from ${source}),`);
903
+ }
904
+ logger.info(" }");
905
+ }
906
+ return merged;
907
+ }
821
908
  const getContext = (raw) => ({
822
909
  start: process$1.hrtime.bigint(),
823
910
  command: raw.map((part) => getCommandPart(stripVTControlCharacters(part))).join(" "),
@@ -1076,6 +1163,15 @@ const sharedArgs = {
1076
1163
  type: "string",
1077
1164
  short: "q",
1078
1165
  description: "Process JSON output with jq command (requires jq binary, implies --json)"
1166
+ },
1167
+ config: {
1168
+ type: "string",
1169
+ description: "Path to configuration file (default: auto-discovery)"
1170
+ },
1171
+ compact: {
1172
+ type: "boolean",
1173
+ description: "Force compact mode for narrow displays (better for screenshots)",
1174
+ default: false
1079
1175
  }
1080
1176
  }, sharedCommandConfig = {
1081
1177
  args: sharedArgs,
@@ -2381,8 +2477,9 @@ var import_usingCtx$2 = /* @__PURE__ */ __toESM(require_usingCtx(), 1), Responsi
2381
2477
  compactColAligns;
2382
2478
  compactThreshold;
2383
2479
  compactMode = false;
2480
+ forceCompact;
2384
2481
  constructor(options) {
2385
- this.head = options.head, this.colAligns = options.colAligns ?? Array.from({ length: this.head.length }, () => "left"), this.style = options.style, this.dateFormatter = options.dateFormatter, this.compactHead = options.compactHead, this.compactColAligns = options.compactColAligns, this.compactThreshold = options.compactThreshold ?? 100;
2482
+ this.head = options.head, this.colAligns = options.colAligns ?? Array.from({ length: this.head.length }, () => "left"), this.style = options.style, this.dateFormatter = options.dateFormatter, this.compactHead = options.compactHead, this.compactColAligns = options.compactColAligns, this.compactThreshold = options.compactThreshold ?? 100, this.forceCompact = options.forceCompact ?? false;
2386
2483
  }
2387
2484
  push(row) {
2388
2485
  this.rows.push(row);
@@ -2410,7 +2507,7 @@ var import_usingCtx$2 = /* @__PURE__ */ __toESM(require_usingCtx(), 1), Responsi
2410
2507
  }
2411
2508
  toString() {
2412
2509
  const terminalWidth = Number.parseInt(process$1.env.COLUMNS ?? "", 10) || process$1.stdout.columns || 120;
2413
- this.compactMode = terminalWidth < this.compactThreshold && this.compactHead != null;
2510
+ this.compactMode = this.forceCompact || terminalWidth < this.compactThreshold && this.compactHead != null;
2414
2511
  const { head, colAligns } = this.getCurrentTableConfig(), compactIndices = this.getCompactIndices(), dataRows = this.rows.filter((row) => !this.isSeparatorRow(row)), processedDataRows = this.compactMode ? dataRows.map((row) => this.filterRowToCompact(row, compactIndices)) : dataRows, allRows = [head.map(String), ...processedDataRows.map((row) => row.map((cell) => {
2415
2512
  return typeof cell === "object" && cell != null && "content" in cell ? String(cell.content) : String(cell ?? "");
2416
2513
  }))], contentWidths = head.map((_, colIndex) => {
@@ -3066,7 +3163,7 @@ const blocksCommand = define({
3066
3163
  },
3067
3164
  toKebab: true,
3068
3165
  async run(ctx) {
3069
- const useJson = ctx.values.json || ctx.values.jq != null;
3166
+ const config = loadConfig(ctx.values.config, ctx.values.debug), mergedOptions = mergeConfigWithArgs(ctx, config, ctx.values.debug), useJson = mergedOptions.json || mergedOptions.jq != null;
3070
3167
  if (useJson) logger.level = 0;
3071
3168
  if (ctx.values.sessionLength <= 0) logger.error("Session length must be a positive number"), process$1.exit(1);
3072
3169
  let blocks = await loadSessionBlockData({
@@ -3192,7 +3289,7 @@ const blocksCommand = define({
3192
3289
  head: tableHeaders,
3193
3290
  style: { head: ["cyan"] },
3194
3291
  colAligns: tableAligns
3195
- }), terminalWidth = process$1.stdout.columns || BLOCKS_DEFAULT_TERMINAL_WIDTH, useCompactFormat = terminalWidth < BLOCKS_COMPACT_WIDTH_THRESHOLD;
3292
+ }), terminalWidth = process$1.stdout.columns || BLOCKS_DEFAULT_TERMINAL_WIDTH, isNarrowTerminal = terminalWidth < BLOCKS_COMPACT_WIDTH_THRESHOLD, useCompactFormat = ctx.values.compact || isNarrowTerminal;
3196
3293
  for (const block of blocks) if (block.isGap ?? false) {
3197
3294
  const gapRow = [
3198
3295
  import_picocolors$5.default.gray(formatBlockTime(block, useCompactFormat, ctx.values.locale)),
@@ -3348,18 +3445,11 @@ const dailyCommand = define({
3348
3445
  }
3349
3446
  },
3350
3447
  async run(ctx) {
3351
- const useJson = ctx.values.json || ctx.values.jq != null;
3448
+ const config = loadConfig(ctx.values.config, ctx.values.debug), mergedOptions = mergeConfigWithArgs(ctx, config, ctx.values.debug), useJson = Boolean(mergedOptions.json) || mergedOptions.jq != null;
3352
3449
  if (useJson) logger.level = 0;
3353
3450
  const dailyData = await loadDailyUsageData({
3354
- since: ctx.values.since,
3355
- until: ctx.values.until,
3356
- mode: ctx.values.mode,
3357
- order: ctx.values.order,
3358
- offline: ctx.values.offline,
3359
- groupByProject: ctx.values.instances,
3360
- project: ctx.values.project,
3361
- timezone: ctx.values.timezone,
3362
- locale: ctx.values.locale
3451
+ ...mergedOptions,
3452
+ groupByProject: mergedOptions.instances
3363
3453
  });
3364
3454
  if (dailyData.length === 0) {
3365
3455
  if (useJson) log(JSON.stringify([]));
@@ -3367,12 +3457,12 @@ const dailyCommand = define({
3367
3457
  process$1.exit(0);
3368
3458
  }
3369
3459
  const totals = calculateTotals(dailyData);
3370
- if (ctx.values.debug && !useJson) {
3460
+ if (mergedOptions.debug && !useJson) {
3371
3461
  const mismatchStats = await detectMismatches(void 0);
3372
- printMismatchReport(mismatchStats, ctx.values.debugSamples);
3462
+ printMismatchReport(mismatchStats, mergedOptions.debugSamples);
3373
3463
  }
3374
3464
  if (useJson) {
3375
- const jsonOutput = ctx.values.instances && dailyData.some((d) => d.project != null) ? {
3465
+ const jsonOutput = Boolean(mergedOptions.instances) && dailyData.some((d) => d.project != null) ? {
3376
3466
  projects: groupByProject(dailyData),
3377
3467
  totals: createTotalsObject(totals)
3378
3468
  } : {
@@ -3390,8 +3480,8 @@ const dailyCommand = define({
3390
3480
  })),
3391
3481
  totals: createTotalsObject(totals)
3392
3482
  };
3393
- if (ctx.values.jq != null) {
3394
- const jqResult = await processWithJq(jsonOutput, ctx.values.jq);
3483
+ if (mergedOptions.jq != null) {
3484
+ const jqResult = await processWithJq(jsonOutput, mergedOptions.jq);
3395
3485
  if (isFailure(jqResult)) logger.error(jqResult.error.message), process$1.exit(1);
3396
3486
  log(jqResult.value);
3397
3487
  } else log(JSON.stringify(jsonOutput, null, 2));
@@ -3419,7 +3509,7 @@ const dailyCommand = define({
3419
3509
  "right",
3420
3510
  "right"
3421
3511
  ],
3422
- dateFormatter: (dateStr) => formatDateCompact(dateStr, ctx.values.timezone, ctx.values.locale),
3512
+ dateFormatter: (dateStr) => formatDateCompact(dateStr, mergedOptions.timezone, mergedOptions.locale ?? void 0),
3423
3513
  compactHead: [
3424
3514
  "Date",
3425
3515
  "Models",
@@ -3434,9 +3524,10 @@ const dailyCommand = define({
3434
3524
  "right",
3435
3525
  "right"
3436
3526
  ],
3437
- compactThreshold: 100
3527
+ compactThreshold: 100,
3528
+ forceCompact: ctx.values.compact
3438
3529
  });
3439
- if (ctx.values.instances && dailyData.some((d) => d.project != null)) {
3530
+ if (Boolean(mergedOptions.instances) && dailyData.some((d) => d.project != null)) {
3440
3531
  const projectGroups = groupDataByProject(dailyData);
3441
3532
  let isFirstProject = true;
3442
3533
  for (const [projectName, projectData] of Object.entries(projectGroups)) {
@@ -3469,7 +3560,7 @@ const dailyCommand = define({
3469
3560
  formatNumber(data.cacheReadTokens),
3470
3561
  formatNumber(getTotalTokens(data)),
3471
3562
  formatCurrency(data.totalCost)
3472
- ]), ctx.values.breakdown) pushBreakdownRows(table, data.modelBreakdowns);
3563
+ ]), mergedOptions.breakdown) pushBreakdownRows(table, data.modelBreakdowns);
3473
3564
  isFirstProject = false;
3474
3565
  }
3475
3566
  } else for (const data of dailyData) if (table.push([
@@ -3481,7 +3572,7 @@ const dailyCommand = define({
3481
3572
  formatNumber(data.cacheReadTokens),
3482
3573
  formatNumber(getTotalTokens(data)),
3483
3574
  formatCurrency(data.totalCost)
3484
- ]), ctx.values.breakdown) pushBreakdownRows(table, data.modelBreakdowns);
3575
+ ]), mergedOptions.breakdown) pushBreakdownRows(table, data.modelBreakdowns);
3485
3576
  if (table.push([
3486
3577
  "",
3487
3578
  "",
@@ -3893,17 +3984,9 @@ const monthlyCommand = define({
3893
3984
  description: "Show usage report grouped by month",
3894
3985
  ...sharedCommandConfig,
3895
3986
  async run(ctx) {
3896
- const useJson = ctx.values.json || ctx.values.jq != null;
3987
+ const config = loadConfig(ctx.values.config, ctx.values.debug), mergedOptions = mergeConfigWithArgs(ctx, config, ctx.values.debug), useJson = Boolean(mergedOptions.json) || mergedOptions.jq != null;
3897
3988
  if (useJson) logger.level = 0;
3898
- const monthlyData = await loadMonthlyUsageData({
3899
- since: ctx.values.since,
3900
- until: ctx.values.until,
3901
- mode: ctx.values.mode,
3902
- order: ctx.values.order,
3903
- offline: ctx.values.offline,
3904
- timezone: ctx.values.timezone,
3905
- locale: ctx.values.locale
3906
- });
3989
+ const monthlyData = await loadMonthlyUsageData(mergedOptions);
3907
3990
  if (monthlyData.length === 0) {
3908
3991
  if (useJson) {
3909
3992
  const emptyOutput = {
@@ -3922,9 +4005,9 @@ const monthlyCommand = define({
3922
4005
  process$1.exit(0);
3923
4006
  }
3924
4007
  const totals = calculateTotals(monthlyData);
3925
- if (ctx.values.debug && !useJson) {
4008
+ if (mergedOptions.debug && !useJson) {
3926
4009
  const mismatchStats = await detectMismatches(void 0);
3927
- printMismatchReport(mismatchStats, ctx.values.debugSamples);
4010
+ printMismatchReport(mismatchStats, mergedOptions.debugSamples);
3928
4011
  }
3929
4012
  if (useJson) {
3930
4013
  const jsonOutput = {
@@ -3941,8 +4024,8 @@ const monthlyCommand = define({
3941
4024
  })),
3942
4025
  totals: createTotalsObject(totals)
3943
4026
  };
3944
- if (ctx.values.jq != null) {
3945
- const jqResult = await processWithJq(jsonOutput, ctx.values.jq);
4027
+ if (mergedOptions.jq != null) {
4028
+ const jqResult = await processWithJq(jsonOutput, mergedOptions.jq);
3946
4029
  if (isFailure(jqResult)) logger.error(jqResult.error.message), process$1.exit(1);
3947
4030
  log(jqResult.value);
3948
4031
  } else log(JSON.stringify(jsonOutput, null, 2));
@@ -3970,7 +4053,7 @@ const monthlyCommand = define({
3970
4053
  "right",
3971
4054
  "right"
3972
4055
  ],
3973
- dateFormatter: (dateStr) => formatDateCompact(dateStr, ctx.values.timezone, ctx.values.locale),
4056
+ dateFormatter: (dateStr) => formatDateCompact(dateStr, mergedOptions.timezone, mergedOptions.locale ?? "en-CA"),
3974
4057
  compactHead: [
3975
4058
  "Month",
3976
4059
  "Models",
@@ -3985,7 +4068,8 @@ const monthlyCommand = define({
3985
4068
  "right",
3986
4069
  "right"
3987
4070
  ],
3988
- compactThreshold: 100
4071
+ compactThreshold: 100,
4072
+ forceCompact: ctx.values.compact
3989
4073
  });
3990
4074
  for (const data of monthlyData) if (table.push([
3991
4075
  data.month,
@@ -3996,7 +4080,7 @@ const monthlyCommand = define({
3996
4080
  formatNumber(data.cacheReadTokens),
3997
4081
  formatNumber(getTotalTokens(data)),
3998
4082
  formatCurrency(data.totalCost)
3999
- ]), ctx.values.breakdown) pushBreakdownRows(table, data.modelBreakdowns);
4083
+ ]), mergedOptions.breakdown) pushBreakdownRows(table, data.modelBreakdowns);
4000
4084
  if (table.push([
4001
4085
  "",
4002
4086
  "",
@@ -4108,15 +4192,15 @@ const sessionCommand = define({
4108
4192
  },
4109
4193
  toKebab: true,
4110
4194
  async run(ctx) {
4111
- const useJson = ctx.values.json || ctx.values.jq != null;
4195
+ const config = loadConfig(ctx.values.config, ctx.values.debug), mergedOptions = mergeConfigWithArgs(ctx, config, ctx.values.debug), useJson = mergedOptions.json || mergedOptions.jq != null;
4112
4196
  if (useJson) logger.level = 0;
4113
- if (ctx.values.id != null) return handleSessionIdLookup({ values: {
4114
- id: ctx.values.id,
4115
- mode: ctx.values.mode,
4116
- offline: ctx.values.offline,
4117
- jq: ctx.values.jq,
4118
- timezone: ctx.values.timezone,
4119
- locale: ctx.values.locale ?? "en-CA"
4197
+ if (mergedOptions.id != null) return handleSessionIdLookup({ values: {
4198
+ id: mergedOptions.id,
4199
+ mode: mergedOptions.mode,
4200
+ offline: mergedOptions.offline,
4201
+ jq: mergedOptions.jq,
4202
+ timezone: mergedOptions.timezone,
4203
+ locale: mergedOptions.locale ?? "en-CA"
4120
4204
  } }, useJson);
4121
4205
  const sessionData = await loadSessionData({
4122
4206
  since: ctx.values.since,
@@ -4202,7 +4286,8 @@ const sessionCommand = define({
4202
4286
  "right",
4203
4287
  "left"
4204
4288
  ],
4205
- compactThreshold: 100
4289
+ compactThreshold: 100,
4290
+ forceCompact: ctx.values.compact
4206
4291
  });
4207
4292
  let maxSessionLength = 0;
4208
4293
  for (const data of sessionData) {
@@ -5123,7 +5208,12 @@ function getSemaphore(sessionId) {
5123
5208
  const semaphore = createLimoJson(semaphorePath);
5124
5209
  return semaphore;
5125
5210
  }
5126
- const statuslineCommand = define({
5211
+ const visualBurnRateChoices = [
5212
+ "off",
5213
+ "emoji",
5214
+ "text",
5215
+ "emoji-text"
5216
+ ], statuslineCommand = define({
5127
5217
  name: "statusline",
5128
5218
  description: "Display compact status line for Claude Code hooks with hybrid time+file caching (Beta)",
5129
5219
  toKebab: true,
@@ -5132,6 +5222,15 @@ const statuslineCommand = define({
5132
5222
  ...sharedArgs.offline,
5133
5223
  default: true
5134
5224
  },
5225
+ visualBurnRate: {
5226
+ type: "enum",
5227
+ choices: visualBurnRateChoices,
5228
+ description: "Controls the visualization of the burn rate status",
5229
+ default: "off",
5230
+ short: "vb",
5231
+ negatable: false,
5232
+ toKebab: true
5233
+ },
5135
5234
  cache: {
5136
5235
  type: "boolean",
5137
5236
  description: "Enable cache for status line output (default: true)",
@@ -5141,16 +5240,18 @@ const statuslineCommand = define({
5141
5240
  type: "number",
5142
5241
  description: `Refresh interval in seconds for cache expiry (default: ${DEFAULT_REFRESH_INTERVAL_SECONDS})`,
5143
5242
  default: DEFAULT_REFRESH_INTERVAL_SECONDS
5144
- }
5243
+ },
5244
+ config: sharedArgs.config,
5245
+ debug: sharedArgs.debug
5145
5246
  },
5146
5247
  async run(ctx) {
5147
5248
  logger.level = 0;
5148
- const refreshInterval = ctx.values.refreshInterval, stdin$2 = await getStdin();
5249
+ const config = loadConfig(ctx.values.config, ctx.values.debug), mergedOptions = mergeConfigWithArgs(ctx, config, ctx.values.debug), refreshInterval = mergedOptions.refreshInterval, stdin$2 = await getStdin();
5149
5250
  if (stdin$2.length === 0) log("❌ No input provided"), process$1.exit(1);
5150
5251
  const hookDataJson = JSON.parse(stdin$2.trim()), hookDataParseResult = statuslineHookJsonSchema.safeParse(hookDataJson);
5151
5252
  if (!hookDataParseResult.success) log("❌ Invalid input format:", hookDataParseResult.error.message), process$1.exit(1);
5152
5253
  const hookData = hookDataParseResult.data, sessionId = hookData.session_id, initialSemaphoreState = pipe(succeed(getSemaphore(sessionId)), map((semaphore) => semaphore.data), unwrap(void 0)), currentMtime = await getFileModifiedTime(hookData.transcript_path);
5153
- if (ctx.values.cache && initialSemaphoreState != null) {
5254
+ if (mergedOptions.cache && initialSemaphoreState != null) {
5154
5255
  const now = Date.now(), timeElapsed = now - (initialSemaphoreState.lastUpdateTime ?? 0), isExpired = timeElapsed >= refreshInterval * 1e3, isFileModified = initialSemaphoreState.transcriptMtime !== currentMtime;
5155
5256
  if (!isExpired && !isFileModified) {
5156
5257
  log(initialSemaphoreState.lastOutput);
@@ -5200,7 +5301,7 @@ const statuslineCommand = define({
5200
5301
  const sessionCost = await pipe(try_({
5201
5302
  try: loadSessionUsageById(sessionId, {
5202
5303
  mode: "auto",
5203
- offline: ctx.values.offline
5304
+ offline: mergedOptions.offline
5204
5305
  }),
5205
5306
  catch: (error) => error
5206
5307
  }), map((sessionCost$1) => sessionCost$1?.totalCost), inspectError((error) => logger.error("Failed to load session data:", error)), unwrap(void 0)), today = /* @__PURE__ */ new Date(), todayStr = today.toISOString().split("T")[0]?.replace(/-/g, "") ?? "", todayCost = await pipe(try_({
@@ -5208,7 +5309,7 @@ const statuslineCommand = define({
5208
5309
  since: todayStr,
5209
5310
  until: todayStr,
5210
5311
  mode: "auto",
5211
- offline: ctx.values.offline
5312
+ offline: mergedOptions.offline
5212
5313
  }),
5213
5314
  catch: (error) => error
5214
5315
  }), map((dailyData) => {
@@ -5220,7 +5321,7 @@ const statuslineCommand = define({
5220
5321
  }), inspectError((error) => logger.error("Failed to load daily data:", error)), unwrap(0)), { blockInfo, burnRateInfo } = await pipe(try_({
5221
5322
  try: loadSessionBlockData({
5222
5323
  mode: "auto",
5223
- offline: ctx.values.offline
5324
+ offline: mergedOptions.offline
5224
5325
  }),
5225
5326
  catch: (error) => error
5226
5327
  }), map((blocks) => {
@@ -5233,8 +5334,26 @@ const statuslineCommand = define({
5233
5334
  });
5234
5335
  if (activeBlock != null) {
5235
5336
  const now = /* @__PURE__ */ new Date(), remaining = Math.round((activeBlock.endTime.getTime() - now.getTime()) / (1e3 * 60)), blockCost = activeBlock.costUSD, blockInfo$1 = `${formatCurrency(blockCost)} block (${formatRemainingTime(remaining)})`, burnRate = calculateBurnRate(activeBlock), burnRateInfo$1 = burnRate != null ? (() => {
5236
- const costPerHour = burnRate.costPerHour, costPerHourStr = `${formatCurrency(costPerHour)}/hr`, coloredBurnRate = burnRate.tokensPerMinuteForIndicator < 2e3 ? import_picocolors$1.default.green(costPerHourStr) : burnRate.tokensPerMinuteForIndicator < 5e3 ? import_picocolors$1.default.yellow(costPerHourStr) : import_picocolors$1.default.red(costPerHourStr);
5237
- return ` | 🔥 ${coloredBurnRate}`;
5337
+ const renderEmojiStatus = ctx.values.visualBurnRate === "emoji" || ctx.values.visualBurnRate === "emoji-text", renderTextStatus = ctx.values.visualBurnRate === "text" || ctx.values.visualBurnRate === "emoji-text", costPerHour = burnRate.costPerHour, costPerHourStr = `${formatCurrency(costPerHour)}/hr`, burnStatus = burnRate.tokensPerMinuteForIndicator < 2e3 ? "normal" : burnRate.tokensPerMinuteForIndicator < 5e3 ? "moderate" : "high", burnStatusMappings = {
5338
+ normal: {
5339
+ emoji: "🟢",
5340
+ textValue: "Normal",
5341
+ coloredString: import_picocolors$1.default.green
5342
+ },
5343
+ moderate: {
5344
+ emoji: "⚠️",
5345
+ textValue: "Moderate",
5346
+ coloredString: import_picocolors$1.default.yellow
5347
+ },
5348
+ high: {
5349
+ emoji: "🚨",
5350
+ textValue: "High",
5351
+ coloredString: import_picocolors$1.default.red
5352
+ }
5353
+ }, { emoji, textValue, coloredString } = burnStatusMappings[burnStatus], burnRateOutputSegments = [coloredString(costPerHourStr)];
5354
+ if (renderEmojiStatus) burnRateOutputSegments.push(emoji);
5355
+ if (renderTextStatus) burnRateOutputSegments.push(coloredString(`(${textValue})`));
5356
+ return ` | 🔥 ${burnRateOutputSegments.join(" ")}`;
5238
5357
  })() : "";
5239
5358
  return {
5240
5359
  blockInfo: blockInfo$1,
@@ -5249,7 +5368,7 @@ const statuslineCommand = define({
5249
5368
  blockInfo: "No active block",
5250
5369
  burnRateInfo: ""
5251
5370
  })), contextInfo = await pipe(try_({
5252
- try: calculateContextTokens(hookData.transcript_path, hookData.model.id, ctx.values.offline),
5371
+ try: calculateContextTokens(hookData.transcript_path, hookData.model.id, mergedOptions.offline),
5253
5372
  catch: (error) => error
5254
5373
  }), inspectError((error) => logger.debug(`Failed to calculate context tokens: ${error instanceof Error ? error.message : String(error)}`)), map((ctx$1) => {
5255
5374
  if (ctx$1 == null) return void 0;
@@ -5263,7 +5382,7 @@ const statuslineCommand = define({
5263
5382
  if (isSuccess(mainProcessingResult)) try {
5264
5383
  var _usingCtx3 = (0, import_usingCtx.default)();
5265
5384
  const statusLine = mainProcessingResult.value;
5266
- if (log(statusLine), !ctx.values.cache) return;
5385
+ if (log(statusLine), !mergedOptions.cache) return;
5267
5386
  const semaphore = _usingCtx3.u(getSemaphore(sessionId));
5268
5387
  semaphore.data = {
5269
5388
  date: (/* @__PURE__ */ new Date()).toISOString(),
@@ -5284,7 +5403,7 @@ const statuslineCommand = define({
5284
5403
  var _usingCtx4 = (0, import_usingCtx.default)();
5285
5404
  if (initialSemaphoreState?.lastOutput != null && initialSemaphoreState.lastOutput !== "") log(initialSemaphoreState.lastOutput);
5286
5405
  else log("❌ Error generating status");
5287
- if (logger.error("Error in statusline command:", mainProcessingResult.error), !ctx.values.cache) return;
5406
+ if (logger.error("Error in statusline command:", mainProcessingResult.error), !mergedOptions.cache) return;
5288
5407
  const semaphore = _usingCtx4.u(getSemaphore(sessionId));
5289
5408
  if (semaphore.data != null) semaphore.data.isUpdating = false, semaphore.data.pid = void 0;
5290
5409
  } catch (_) {
@@ -5310,18 +5429,9 @@ const weeklyCommand = define({
5310
5429
  },
5311
5430
  toKebab: true,
5312
5431
  async run(ctx) {
5313
- const useJson = ctx.values.json || ctx.values.jq != null;
5432
+ const config = loadConfig(ctx.values.config, ctx.values.debug), mergedOptions = mergeConfigWithArgs(ctx, config, ctx.values.debug), useJson = Boolean(mergedOptions.json) || mergedOptions.jq != null;
5314
5433
  if (useJson) logger.level = 0;
5315
- const weeklyData = await loadWeeklyUsageData({
5316
- since: ctx.values.since,
5317
- until: ctx.values.until,
5318
- timezone: ctx.values.timezone,
5319
- mode: ctx.values.mode,
5320
- order: ctx.values.order,
5321
- offline: ctx.values.offline,
5322
- startOfWeek: ctx.values.startOfWeek,
5323
- locale: ctx.values.locale
5324
- });
5434
+ const weeklyData = await loadWeeklyUsageData(mergedOptions);
5325
5435
  if (weeklyData.length === 0) {
5326
5436
  if (useJson) {
5327
5437
  const emptyOutput = {
@@ -5340,9 +5450,9 @@ const weeklyCommand = define({
5340
5450
  process$1.exit(0);
5341
5451
  }
5342
5452
  const totals = calculateTotals(weeklyData);
5343
- if (ctx.values.debug && !useJson) {
5453
+ if (mergedOptions.debug && !useJson) {
5344
5454
  const mismatchStats = await detectMismatches(void 0);
5345
- printMismatchReport(mismatchStats, ctx.values.debugSamples);
5455
+ printMismatchReport(mismatchStats, mergedOptions.debugSamples);
5346
5456
  }
5347
5457
  if (useJson) {
5348
5458
  const jsonOutput = {
@@ -5359,8 +5469,8 @@ const weeklyCommand = define({
5359
5469
  })),
5360
5470
  totals: createTotalsObject(totals)
5361
5471
  };
5362
- if (ctx.values.jq != null) {
5363
- const jqResult = await processWithJq(jsonOutput, ctx.values.jq);
5472
+ if (mergedOptions.jq != null) {
5473
+ const jqResult = await processWithJq(jsonOutput, mergedOptions.jq);
5364
5474
  if (isFailure(jqResult)) logger.error(jqResult.error.message), process$1.exit(1);
5365
5475
  log(jqResult.value);
5366
5476
  } else log(JSON.stringify(jsonOutput, null, 2));
@@ -5388,7 +5498,7 @@ const weeklyCommand = define({
5388
5498
  "right",
5389
5499
  "right"
5390
5500
  ],
5391
- dateFormatter: (dateStr) => formatDateCompact(dateStr, ctx.values.timezone, ctx.values.locale),
5501
+ dateFormatter: (dateStr) => formatDateCompact(dateStr, mergedOptions.timezone, mergedOptions.locale ?? void 0),
5392
5502
  compactHead: [
5393
5503
  "Week",
5394
5504
  "Models",
@@ -5403,7 +5513,8 @@ const weeklyCommand = define({
5403
5513
  "right",
5404
5514
  "right"
5405
5515
  ],
5406
- compactThreshold: 100
5516
+ compactThreshold: 100,
5517
+ forceCompact: ctx.values.compact
5407
5518
  });
5408
5519
  for (const data of weeklyData) if (table.push([
5409
5520
  data.week,
@@ -5414,7 +5525,7 @@ const weeklyCommand = define({
5414
5525
  formatNumber(data.cacheReadTokens),
5415
5526
  formatNumber(getTotalTokens(data)),
5416
5527
  formatCurrency(data.totalCost)
5417
- ]), ctx.values.breakdown) pushBreakdownRows(table, data.modelBreakdowns);
5528
+ ]), mergedOptions.breakdown) pushBreakdownRows(table, data.modelBreakdowns);
5418
5529
  if (table.push([
5419
5530
  "",
5420
5531
  "",
@@ -5436,14 +5547,25 @@ const weeklyCommand = define({
5436
5547
  ]), log(table.toString()), table.isCompactMode()) logger.info("\nRunning in Compact Mode"), logger.info("Expand terminal width to see cache metrics and total tokens");
5437
5548
  }
5438
5549
  }
5439
- }), subCommands = /* @__PURE__ */ new Map();
5440
- subCommands.set("daily", dailyCommand), subCommands.set("monthly", monthlyCommand), subCommands.set("weekly", weeklyCommand), subCommands.set("session", sessionCommand), subCommands.set("blocks", blocksCommand), subCommands.set("mcp", mcpCommand), subCommands.set("statusline", statuslineCommand);
5550
+ }), subCommandUnion = [
5551
+ ["daily", dailyCommand],
5552
+ ["monthly", monthlyCommand],
5553
+ ["weekly", weeklyCommand],
5554
+ ["session", sessionCommand],
5555
+ ["blocks", blocksCommand],
5556
+ ["mcp", mcpCommand],
5557
+ ["statusline", statuslineCommand]
5558
+ ], subCommands = /* @__PURE__ */ new Map();
5559
+ for (const [name$1, command] of subCommandUnion) subCommands.set(name$1, command);
5441
5560
  const mainCommand = dailyCommand;
5442
- await cli(process$1.argv.slice(2), mainCommand, {
5443
- name,
5444
- version,
5445
- description,
5446
- subCommands,
5447
- renderHeader: null
5448
- });
5561
+ async function run() {
5562
+ await cli(process$1.argv.slice(2), mainCommand, {
5563
+ name,
5564
+ version,
5565
+ description,
5566
+ subCommands,
5567
+ renderHeader: null
5568
+ });
5569
+ }
5570
+ await run();
5449
5571
  export {};
@@ -761,7 +761,7 @@ function _getDefaultLogLevel() {
761
761
  return g ? LogLevels.debug : R ? LogLevels.warn : LogLevels.info;
762
762
  }
763
763
  const consola = createConsola$1();
764
- var name = "ccusage", version = "15.9.9", description = "Usage analysis tool for Claude Code";
764
+ var name = "ccusage", version = "16.0.0", description = "Usage analysis tool for Claude Code";
765
765
  const logger = consola.withTag(name);
766
766
  if (process$1.env.LOG_LEVEL != null) {
767
767
  const level = Number.parseInt(process$1.env.LOG_LEVEL, 10);
package/dist/logger.js CHANGED
@@ -1,2 +1,2 @@
1
- import { log, logger } from "./logger-m79TqYfY.js";
1
+ import { log, logger } from "./logger-ClcgjXEW.js";
2
2
  export { log, logger };
@@ -1,9 +1,9 @@
1
- import { __commonJSMin, __toESM, require_usingCtx } from "./pricing-fetcher-CbLCyFQr.js";
1
+ import { __commonJSMin, __toESM, require_usingCtx } from "./pricing-fetcher-CUQk_dDN.js";
2
2
  import { getTotalTokens } from "./_token-utils-WjkbrjKv.js";
3
3
  import { ZodFirstPartyTypeKind, ZodOptional, ZodType, arrayType, booleanType, discriminatedUnionType, enumType, filterDateSchema, literalType, numberType, objectType, optionalType, recordType, stringType, unionType, unknownType } from "./_types-BbEk8t2a.js";
4
4
  import { calculateTotals, createTotalsObject } from "./calculate-cost-BDqO4yWA.js";
5
- import { getClaudePaths, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData } from "./data-loader-CM8RKePp.js";
6
- import { name, version } from "./logger-m79TqYfY.js";
5
+ import { getClaudePaths, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData } from "./data-loader-Bf4qMLf2.js";
6
+ import { name, version } from "./logger-ClcgjXEW.js";
7
7
  import process from "node:process";
8
8
  const LATEST_PROTOCOL_VERSION = "2025-06-18", SUPPORTED_PROTOCOL_VERSIONS = [
9
9
  LATEST_PROTOCOL_VERSION,
@@ -6670,12 +6670,12 @@ function transformUsageDataWithTotals(data, totals, mapper, key) {
6670
6670
  totals: createTotalsObject(totals)
6671
6671
  };
6672
6672
  }
6673
- const defaultOptions = { claudePath: (() => {
6673
+ function defaultOptions() {
6674
6674
  const paths = getClaudePaths();
6675
6675
  if (paths.length === 0) throw new Error("No valid Claude path found. Ensure getClaudePaths() returns at least one valid path.");
6676
- return paths[0];
6677
- })() };
6678
- function createMcpServer({ claudePath } = defaultOptions) {
6676
+ return { claudePath: paths[0] };
6677
+ }
6678
+ function createMcpServer(options) {
6679
6679
  const server = new McpServer({
6680
6680
  name,
6681
6681
  version
@@ -6689,7 +6689,7 @@ function createMcpServer({ claudePath } = defaultOptions) {
6689
6689
  ]).default("auto").optional(),
6690
6690
  timezone: stringType().optional(),
6691
6691
  locale: stringType().default("en-CA").optional()
6692
- };
6692
+ }, { claudePath } = options ?? defaultOptions();
6693
6693
  return server.registerTool("daily", {
6694
6694
  description: "Show usage report grouped by date",
6695
6695
  inputSchema: parametersZodSchema,
@@ -6807,8 +6807,8 @@ async function startMcpServerStdio(server) {
6807
6807
  const transport = new StdioServerTransport();
6808
6808
  await server.connect(transport);
6809
6809
  }
6810
- function createMcpHttpApp(options = defaultOptions) {
6811
- const app = new Hono(), mcpServer = createMcpServer(options);
6810
+ function createMcpHttpApp(options) {
6811
+ const app = new Hono(), mcpServer = createMcpServer(options ?? defaultOptions());
6812
6812
  return app.all("/", async (c) => {
6813
6813
  const transport = new StreamableHTTPTransport();
6814
6814
  return await mcpServer.connect(transport), transport.handleRequest(c);