ccusage 15.4.0 → 15.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -62,6 +62,11 @@ ccusage blocks --live # Real-time usage dashboard
62
62
  ccusage daily --since 20250525 --until 20250530
63
63
  ccusage daily --json # JSON output
64
64
  ccusage daily --breakdown # Per-model cost breakdown
65
+
66
+ # Project analysis
67
+ ccusage daily --instances # Group by project/instance
68
+ ccusage daily --project myproject # Filter to specific project
69
+ ccusage daily --instances --project myproject --json # Combined usage
65
70
  ```
66
71
 
67
72
  ## Features
@@ -83,6 +88,7 @@ ccusage daily --breakdown # Per-model cost breakdown
83
88
  - 🔄 **Cache Token Support**: Tracks and displays cache creation and cache read tokens separately
84
89
  - 🌐 **Offline Mode**: Use pre-cached pricing data without network connectivity with `--offline` (Claude models only)
85
90
  - 🔌 **MCP Integration**: Built-in Model Context Protocol server for integration with other tools
91
+ - 🏗️ **Multi-Instance Support**: Group usage by project with `--instances` flag and filter by specific projects
86
92
  - 🚀 **Ultra-Small Bundle**: Unlike other CLI tools, we pay extreme attention to bundle size - incredibly small even without minification!
87
93
 
88
94
  ## Documentation
@@ -1,4 +1,4 @@
1
- import { DailyUsage, MonthlyUsage, SessionUsage } from "./data-loader-7c4yXbqG.js";
1
+ import { DailyUsage, MonthlyUsage, SessionUsage } from "./data-loader-CgvyDaQD.js";
2
2
  import "./pricing-fetcher-B3SvKOod.js";
3
3
 
4
4
  //#region src/_token-utils.d.ts
@@ -1,7 +1,7 @@
1
- import { CLAUDE_CONFIG_DIR_ENV, CLAUDE_PROJECTS_DIR_NAME, DEFAULT_CLAUDE_CODE_PATH, DEFAULT_CLAUDE_CONFIG_PATH, DEFAULT_RECENT_DAYS, PricingFetcher, USAGE_DATA_GLOB_PATTERN, USER_HOME_DIR, __commonJSMin, __require, __toESM, isFailure, isPromise, require_usingCtx } from "./pricing-fetcher-KIIpXo8L.js";
1
+ import { CLAUDE_CONFIG_DIR_ENV, CLAUDE_PROJECTS_DIR_NAME, DEFAULT_CLAUDE_CODE_PATH, DEFAULT_CLAUDE_CONFIG_PATH, DEFAULT_RECENT_DAYS, PricingFetcher, USAGE_DATA_GLOB_PATTERN, USER_HOME_DIR, __commonJSMin, __require, __toESM, isFailure, isPromise, require_usingCtx } from "./pricing-fetcher-eNV76BjF.js";
2
2
  import { getTotalTokens } from "./_token-utils-WjkbrjKv.js";
3
3
  import { activityDateSchema, arrayType, booleanType, createDailyDate, createMonthlyDate, createProjectPath, createSessionId, dailyDateSchema, isoTimestampSchema, messageIdSchema, modelNameSchema, monthlyDateSchema, numberType, objectType, projectPathSchema, requestIdSchema, sessionIdSchema, stringType, versionSchema } from "./_types-BHFM59hI.js";
4
- import { logger } from "./logger-mXUMXvyg.js";
4
+ import { logger } from "./logger-COLgmk2z.js";
5
5
  import a, { readFile } from "node:fs/promises";
6
6
  import path, { posix } from "node:path";
7
7
  import process$1 from "node:process";
@@ -3327,6 +3327,19 @@ function getClaudePaths() {
3327
3327
  return paths;
3328
3328
  }
3329
3329
  /**
3330
+ * Extract project name from Claude JSONL file path
3331
+ * @param jsonlPath - Absolute path to JSONL file
3332
+ * @returns Project name extracted from path, or "unknown" if malformed
3333
+ */
3334
+ function extractProjectFromPath(jsonlPath) {
3335
+ const normalizedPath = jsonlPath.replace(/[/\\]/g, path.sep);
3336
+ const segments = normalizedPath.split(path.sep);
3337
+ const projectsIndex = segments.findIndex((segment) => segment === CLAUDE_PROJECTS_DIR_NAME);
3338
+ if (projectsIndex === -1 || projectsIndex + 1 >= segments.length) return "unknown";
3339
+ const projectName = segments[projectsIndex + 1];
3340
+ return projectName != null && projectName.trim() !== "" ? projectName : "unknown";
3341
+ }
3342
+ /**
3330
3343
  * Zod schema for validating Claude usage data from JSONL files
3331
3344
  */
3332
3345
  const usageDataSchema = objectType({
@@ -3369,7 +3382,8 @@ const dailyUsageSchema = objectType({
3369
3382
  cacheReadTokens: numberType(),
3370
3383
  totalCost: numberType(),
3371
3384
  modelsUsed: arrayType(modelNameSchema),
3372
- modelBreakdowns: arrayType(modelBreakdownSchema)
3385
+ modelBreakdowns: arrayType(modelBreakdownSchema),
3386
+ project: stringType().optional()
3373
3387
  });
3374
3388
  /**
3375
3389
  * Zod schema for session-based usage aggregation data
@@ -3398,7 +3412,8 @@ const monthlyUsageSchema = objectType({
3398
3412
  cacheReadTokens: numberType(),
3399
3413
  totalCost: numberType(),
3400
3414
  modelsUsed: arrayType(modelNameSchema),
3401
- modelBreakdowns: arrayType(modelBreakdownSchema)
3415
+ modelBreakdowns: arrayType(modelBreakdownSchema),
3416
+ project: stringType().optional()
3402
3417
  });
3403
3418
  /**
3404
3419
  * Aggregates token counts and costs by model name
@@ -3499,6 +3514,16 @@ function filterByDateRange(items, getDate, since, until) {
3499
3514
  });
3500
3515
  }
3501
3516
  /**
3517
+ * Filters items by project name
3518
+ */
3519
+ function filterByProject(items, getProject, projectFilter) {
3520
+ if (projectFilter == null) return items;
3521
+ return items.filter((item) => {
3522
+ const projectName = getProject(item);
3523
+ return projectName === projectFilter;
3524
+ });
3525
+ }
3526
+ /**
3502
3527
  * Checks if an entry is a duplicate based on hash
3503
3528
  */
3504
3529
  function isDuplicateEntry(uniqueHash, processedHashes) {
@@ -3684,7 +3709,8 @@ async function loadDailyUsageData(options) {
3684
3709
  const allFiles = await globUsageFiles(claudePaths);
3685
3710
  const fileList = allFiles.map((f$1) => f$1.file);
3686
3711
  if (fileList.length === 0) return [];
3687
- const sortedFiles = await sortFilesByTimestamp(fileList);
3712
+ const projectFilteredFiles = filterByProject(fileList, (filePath) => extractProjectFromPath(filePath), options?.project);
3713
+ const sortedFiles = await sortFilesByTimestamp(projectFilteredFiles);
3688
3714
  const mode = options?.mode ?? "auto";
3689
3715
  const fetcher = _usingCtx.u(mode === "display" ? null : new PricingFetcher(options?.offline));
3690
3716
  const processedHashes = /* @__PURE__ */ new Set();
@@ -3702,17 +3728,24 @@ async function loadDailyUsageData(options) {
3702
3728
  markAsProcessed(uniqueHash, processedHashes);
3703
3729
  const date = formatDate(data.timestamp);
3704
3730
  const cost = fetcher != null ? await calculateCostForEntry(data, mode, fetcher) : data.costUSD ?? 0;
3731
+ const project = extractProjectFromPath(file);
3705
3732
  allEntries.push({
3706
3733
  data,
3707
3734
  date,
3708
3735
  cost,
3709
- model: data.message.model
3736
+ model: data.message.model,
3737
+ project
3710
3738
  });
3711
3739
  } catch {}
3712
3740
  }
3713
- const groupedByDate = groupBy(allEntries, (entry) => entry.date);
3714
- const results = Object.entries(groupedByDate).map(([date, entries]) => {
3741
+ const needsProjectGrouping = options?.groupByProject === true || options?.project != null;
3742
+ const groupingKey = needsProjectGrouping ? (entry) => `${entry.date}\x00${entry.project}` : (entry) => entry.date;
3743
+ const groupedData = groupBy(allEntries, groupingKey);
3744
+ const results = Object.entries(groupedData).map(([groupKey, entries]) => {
3715
3745
  if (entries == null) return void 0;
3746
+ const parts = groupKey.split("\0");
3747
+ const date = parts[0] ?? groupKey;
3748
+ const project = parts.length > 1 ? parts[1] : void 0;
3716
3749
  const modelAggregates = aggregateByModel(entries, (entry) => entry.model, (entry) => entry.data.message.usage, (entry) => entry.cost);
3717
3750
  const modelBreakdowns = createModelBreakdowns(modelAggregates);
3718
3751
  const totals = calculateTotals(entries, (entry) => entry.data.message.usage, (entry) => entry.cost);
@@ -3721,11 +3754,13 @@ async function loadDailyUsageData(options) {
3721
3754
  date: createDailyDate(date),
3722
3755
  ...totals,
3723
3756
  modelsUsed,
3724
- modelBreakdowns
3757
+ modelBreakdowns,
3758
+ ...project != null && { project }
3725
3759
  };
3726
3760
  }).filter((item) => item != null);
3727
- const filtered = filterByDateRange(results, (item) => item.date, options?.since, options?.until);
3728
- return sortByDate(filtered, (item) => item.date, options?.order);
3761
+ const dateFiltered = filterByDateRange(results, (item) => item.date, options?.since, options?.until);
3762
+ const finalFiltered = filterByProject(dateFiltered, (item) => item.project, options?.project);
3763
+ return sortByDate(finalFiltered, (item) => item.date, options?.order);
3729
3764
  } catch (_) {
3730
3765
  _usingCtx.e = _;
3731
3766
  } finally {
@@ -3744,8 +3779,9 @@ async function loadSessionData(options) {
3744
3779
  const claudePaths = toArray(options?.claudePath ?? getClaudePaths());
3745
3780
  const filesWithBase = await globUsageFiles(claudePaths);
3746
3781
  if (filesWithBase.length === 0) return [];
3747
- const fileToBaseMap = new Map(filesWithBase.map((f$1) => [f$1.file, f$1.baseDir]));
3748
- const sortedFilesWithBase = await sortFilesByTimestamp(filesWithBase.map((f$1) => f$1.file)).then((sortedFiles) => sortedFiles.map((file) => ({
3782
+ const projectFilteredWithBase = filterByProject(filesWithBase, (item) => extractProjectFromPath(item.file), options?.project);
3783
+ const fileToBaseMap = new Map(projectFilteredWithBase.map((f$1) => [f$1.file, f$1.baseDir]));
3784
+ const sortedFilesWithBase = await sortFilesByTimestamp(projectFilteredWithBase.map((f$1) => f$1.file)).then((sortedFiles) => sortedFiles.map((file) => ({
3749
3785
  file,
3750
3786
  baseDir: fileToBaseMap.get(file) ?? ""
3751
3787
  })));
@@ -3802,8 +3838,9 @@ async function loadSessionData(options) {
3802
3838
  modelBreakdowns
3803
3839
  };
3804
3840
  }).filter((item) => item != null);
3805
- const filtered = filterByDateRange(results, (item) => item.lastActivity, options?.since, options?.until);
3806
- return sortByDate(filtered, (item) => item.lastActivity, options?.order);
3841
+ const dateFiltered = filterByDateRange(results, (item) => item.lastActivity, options?.since, options?.until);
3842
+ const sessionFiltered = filterByProject(dateFiltered, (item) => item.projectPath, options?.project);
3843
+ return sortByDate(sessionFiltered, (item) => item.lastActivity, options?.order);
3807
3844
  } catch (_) {
3808
3845
  _usingCtx3.e = _;
3809
3846
  } finally {
@@ -3818,10 +3855,15 @@ async function loadSessionData(options) {
3818
3855
  */
3819
3856
  async function loadMonthlyUsageData(options) {
3820
3857
  const dailyData = await loadDailyUsageData(options);
3821
- const groupedByMonth = groupBy(dailyData, (data) => data.date.substring(0, 7));
3858
+ const needsProjectGrouping = options?.groupByProject === true || options?.project != null;
3859
+ const groupingKey = needsProjectGrouping ? (data) => `${data.date.substring(0, 7)}\x00${data.project ?? "unknown"}` : (data) => data.date.substring(0, 7);
3860
+ const groupedByMonth = groupBy(dailyData, groupingKey);
3822
3861
  const monthlyArray = [];
3823
- for (const [month, dailyEntries] of Object.entries(groupedByMonth)) {
3862
+ for (const [groupKey, dailyEntries] of Object.entries(groupedByMonth)) {
3824
3863
  if (dailyEntries == null) continue;
3864
+ const parts = groupKey.split("\0");
3865
+ const month = parts[0] ?? groupKey;
3866
+ const project = parts.length > 1 ? parts[1] : void 0;
3825
3867
  const allBreakdowns = dailyEntries.flatMap((daily) => daily.modelBreakdowns);
3826
3868
  const modelAggregates = aggregateModelBreakdowns(allBreakdowns);
3827
3869
  const modelBreakdowns = createModelBreakdowns(modelAggregates);
@@ -3847,7 +3889,8 @@ async function loadMonthlyUsageData(options) {
3847
3889
  cacheReadTokens: totalCacheReadTokens,
3848
3890
  totalCost,
3849
3891
  modelsUsed: uniq(models),
3850
- modelBreakdowns
3892
+ modelBreakdowns,
3893
+ ...project != null && { project }
3851
3894
  };
3852
3895
  monthlyArray.push(monthlyUsage);
3853
3896
  }
@@ -3873,7 +3916,8 @@ async function loadSessionBlockData(options) {
3873
3916
  allFiles.push(...files);
3874
3917
  }
3875
3918
  if (allFiles.length === 0) return [];
3876
- const sortedFiles = await sortFilesByTimestamp(allFiles);
3919
+ const blocksFilteredFiles = filterByProject(allFiles, (filePath) => extractProjectFromPath(filePath), options?.project);
3920
+ const sortedFiles = await sortFilesByTimestamp(blocksFilteredFiles);
3877
3921
  const mode = options?.mode ?? "auto";
3878
3922
  const fetcher = _usingCtx4.u(mode === "display" ? null : new PricingFetcher(options?.offline));
3879
3923
  const processedHashes = /* @__PURE__ */ new Set();
@@ -3909,17 +3953,17 @@ async function loadSessionBlockData(options) {
3909
3953
  }
3910
3954
  }
3911
3955
  const blocks = identifySessionBlocks(allEntries, options?.sessionDurationHours);
3912
- const filtered = options?.since != null && options.since !== "" || options?.until != null && options.until !== "" ? blocks.filter((block) => {
3956
+ const dateFiltered = options?.since != null && options.since !== "" || options?.until != null && options.until !== "" ? blocks.filter((block) => {
3913
3957
  const blockDateStr = formatDate(block.startTime.toISOString()).replace(/-/g, "");
3914
3958
  if (options.since != null && options.since !== "" && blockDateStr < options.since) return false;
3915
3959
  if (options.until != null && options.until !== "" && blockDateStr > options.until) return false;
3916
3960
  return true;
3917
3961
  }) : blocks;
3918
- return sortByDate(filtered, (block) => block.startTime, options?.order);
3962
+ return sortByDate(dateFiltered, (block) => block.startTime, options?.order);
3919
3963
  } catch (_) {
3920
3964
  _usingCtx4.e = _;
3921
3965
  } finally {
3922
3966
  _usingCtx4.d();
3923
3967
  }
3924
3968
  }
3925
- export { DEFAULT_SESSION_DURATION_HOURS, calculateBurnRate, calculateCostForEntry, createUniqueHash, dailyUsageSchema, filterRecentBlocks, formatDate, formatDateCompact, getClaudePaths, getEarliestTimestamp, getUsageLimitResetTime, glob, globUsageFiles, identifySessionBlocks, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, projectBlockUsage, sessionUsageSchema, sortFilesByTimestamp, uniq, unwrap, usageDataSchema };
3969
+ export { DEFAULT_SESSION_DURATION_HOURS, calculateBurnRate, calculateCostForEntry, createUniqueHash, dailyUsageSchema, extractProjectFromPath, filterRecentBlocks, formatDate, formatDateCompact, getClaudePaths, getEarliestTimestamp, getUsageLimitResetTime, glob, globUsageFiles, identifySessionBlocks, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, projectBlockUsage, sessionUsageSchema, sortFilesByTimestamp, uniq, unwrap, usageDataSchema };
@@ -55,6 +55,12 @@ type SessionBlock = {
55
55
  * @returns Array of valid Claude data directory paths
56
56
  */
57
57
  declare function getClaudePaths(): string[];
58
+ /**
59
+ * Extract project name from Claude JSONL file path
60
+ * @param jsonlPath - Absolute path to JSONL file
61
+ * @returns Project name extracted from path, or "unknown" if malformed
62
+ */
63
+ declare function extractProjectFromPath(jsonlPath: string): string;
58
64
  /**
59
65
  * Zod schema for validating Claude usage data from JSONL files
60
66
  */
@@ -220,6 +226,7 @@ declare const dailyUsageSchema: z.ZodObject<{
220
226
  cacheReadTokens: number;
221
227
  cost: number;
222
228
  }>, "many">;
229
+ project: z.ZodOptional<z.ZodString>;
223
230
  }, "strip", z.ZodTypeAny, {
224
231
  date: string & z.BRAND<"DailyDate">;
225
232
  inputTokens: number;
@@ -236,6 +243,7 @@ declare const dailyUsageSchema: z.ZodObject<{
236
243
  cacheReadTokens: number;
237
244
  cost: number;
238
245
  }[];
246
+ project?: string | undefined;
239
247
  }, {
240
248
  date: string;
241
249
  inputTokens: number;
@@ -252,6 +260,7 @@ declare const dailyUsageSchema: z.ZodObject<{
252
260
  cacheReadTokens: number;
253
261
  cost: number;
254
262
  }[];
263
+ project?: string | undefined;
255
264
  }>;
256
265
  /**
257
266
  * Type definition for daily usage aggregation
@@ -369,6 +378,7 @@ declare const monthlyUsageSchema: z.ZodObject<{
369
378
  cacheReadTokens: number;
370
379
  cost: number;
371
380
  }>, "many">;
381
+ project: z.ZodOptional<z.ZodString>;
372
382
  }, "strip", z.ZodTypeAny, {
373
383
  month: string & z.BRAND<"MonthlyDate">;
374
384
  inputTokens: number;
@@ -385,6 +395,7 @@ declare const monthlyUsageSchema: z.ZodObject<{
385
395
  cacheReadTokens: number;
386
396
  cost: number;
387
397
  }[];
398
+ project?: string | undefined;
388
399
  }, {
389
400
  month: string;
390
401
  inputTokens: number;
@@ -401,6 +412,7 @@ declare const monthlyUsageSchema: z.ZodObject<{
401
412
  cacheReadTokens: number;
402
413
  cost: number;
403
414
  }[];
415
+ project?: string | undefined;
404
416
  }>;
405
417
  /**
406
418
  * Type definition for monthly usage aggregation
@@ -475,6 +487,8 @@ type LoadOptions = {
475
487
  order?: SortOrder; // Sort order for dates
476
488
  offline?: boolean; // Use offline mode for pricing
477
489
  sessionDurationHours?: number; // Session block duration in hours
490
+ groupByProject?: boolean; // Group data by project instead of aggregating
491
+ project?: string; // Filter to specific project name
478
492
  } & DateFilter;
479
493
  /**
480
494
  * Loads and aggregates Claude usage data by day
@@ -505,4 +519,4 @@ declare function loadMonthlyUsageData(options?: LoadOptions): Promise<MonthlyUsa
505
519
  */
506
520
  declare function loadSessionBlockData(options?: LoadOptions): Promise<SessionBlock[]>;
507
521
  //#endregion
508
- export { DailyUsage, DateFilter, GlobResult, LoadOptions, ModelBreakdown, MonthlyUsage, SessionUsage, UsageData, calculateCostForEntry, createUniqueHash, dailyUsageSchema, formatDate, formatDateCompact, getClaudePaths, getEarliestTimestamp, getUsageLimitResetTime, globUsageFiles, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema };
522
+ export { DailyUsage, DateFilter, GlobResult, LoadOptions, ModelBreakdown, MonthlyUsage, SessionUsage, UsageData, calculateCostForEntry, createUniqueHash, dailyUsageSchema, extractProjectFromPath, formatDate, formatDateCompact, getClaudePaths, getEarliestTimestamp, getUsageLimitResetTime, globUsageFiles, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema };
@@ -1,3 +1,3 @@
1
- import { DailyUsage, DateFilter, GlobResult, LoadOptions, ModelBreakdown, MonthlyUsage, SessionUsage, UsageData, calculateCostForEntry, createUniqueHash, dailyUsageSchema, formatDate, formatDateCompact, getClaudePaths, getEarliestTimestamp, getUsageLimitResetTime, globUsageFiles, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema } from "./data-loader-7c4yXbqG.js";
1
+ import { DailyUsage, DateFilter, GlobResult, LoadOptions, ModelBreakdown, MonthlyUsage, SessionUsage, UsageData, calculateCostForEntry, createUniqueHash, dailyUsageSchema, extractProjectFromPath, formatDate, formatDateCompact, getClaudePaths, getEarliestTimestamp, getUsageLimitResetTime, globUsageFiles, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema } from "./data-loader-CgvyDaQD.js";
2
2
  import "./pricing-fetcher-B3SvKOod.js";
3
- export { DailyUsage, DateFilter, GlobResult, LoadOptions, ModelBreakdown, MonthlyUsage, SessionUsage, UsageData, calculateCostForEntry, createUniqueHash, dailyUsageSchema, formatDate, formatDateCompact, getClaudePaths, getEarliestTimestamp, getUsageLimitResetTime, globUsageFiles, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema };
3
+ export { DailyUsage, DateFilter, GlobResult, LoadOptions, ModelBreakdown, MonthlyUsage, SessionUsage, UsageData, calculateCostForEntry, createUniqueHash, dailyUsageSchema, extractProjectFromPath, formatDate, formatDateCompact, getClaudePaths, getEarliestTimestamp, getUsageLimitResetTime, globUsageFiles, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema };
@@ -1,6 +1,6 @@
1
- import "./pricing-fetcher-KIIpXo8L.js";
1
+ import "./pricing-fetcher-eNV76BjF.js";
2
2
  import "./_token-utils-WjkbrjKv.js";
3
3
  import "./_types-BHFM59hI.js";
4
- import { calculateCostForEntry, createUniqueHash, dailyUsageSchema, formatDate, formatDateCompact, getClaudePaths, getEarliestTimestamp, getUsageLimitResetTime, globUsageFiles, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema } from "./data-loader-BNrxyvc9.js";
5
- import "./logger-mXUMXvyg.js";
6
- export { calculateCostForEntry, createUniqueHash, dailyUsageSchema, formatDate, formatDateCompact, getClaudePaths, getEarliestTimestamp, getUsageLimitResetTime, globUsageFiles, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema };
4
+ import { calculateCostForEntry, createUniqueHash, dailyUsageSchema, extractProjectFromPath, formatDate, formatDateCompact, getClaudePaths, getEarliestTimestamp, getUsageLimitResetTime, globUsageFiles, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema } from "./data-loader-BAp4u-7e.js";
5
+ import "./logger-COLgmk2z.js";
6
+ export { calculateCostForEntry, createUniqueHash, dailyUsageSchema, extractProjectFromPath, formatDate, formatDateCompact, getClaudePaths, getEarliestTimestamp, getUsageLimitResetTime, globUsageFiles, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema };
@@ -1,6 +1,6 @@
1
- import { CLAUDE_PROJECTS_DIR_NAME, DEBUG_MATCH_THRESHOLD_PERCENT, PricingFetcher, USAGE_DATA_GLOB_PATTERN, __toESM, isFailure, require_usingCtx, try_ } from "./pricing-fetcher-KIIpXo8L.js";
2
- import { getClaudePaths, glob, unwrap, usageDataSchema } from "./data-loader-BNrxyvc9.js";
3
- import { logger } from "./logger-mXUMXvyg.js";
1
+ import { CLAUDE_PROJECTS_DIR_NAME, DEBUG_MATCH_THRESHOLD_PERCENT, PricingFetcher, USAGE_DATA_GLOB_PATTERN, __toESM, isFailure, require_usingCtx, try_ } from "./pricing-fetcher-eNV76BjF.js";
2
+ import { getClaudePaths, glob, unwrap, usageDataSchema } from "./data-loader-BAp4u-7e.js";
3
+ import { logger } from "./logger-COLgmk2z.js";
4
4
  import { readFile } from "node:fs/promises";
5
5
  import path from "node:path";
6
6
  var import_usingCtx = __toESM(require_usingCtx(), 1);
package/dist/debug.js CHANGED
@@ -1,7 +1,7 @@
1
- import "./pricing-fetcher-KIIpXo8L.js";
1
+ import "./pricing-fetcher-eNV76BjF.js";
2
2
  import "./_token-utils-WjkbrjKv.js";
3
3
  import "./_types-BHFM59hI.js";
4
- import "./data-loader-BNrxyvc9.js";
5
- import "./logger-mXUMXvyg.js";
6
- import { detectMismatches, printMismatchReport } from "./debug-Brn3ODNf.js";
4
+ import "./data-loader-BAp4u-7e.js";
5
+ import "./logger-COLgmk2z.js";
6
+ import { detectMismatches, printMismatchReport } from "./debug-IQBgd8CJ.js";
7
7
  export { detectMismatches, printMismatchReport };
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, PricingFetcher, __commonJSMin, __require, __toESM, isFailure, require_usingCtx, try_ } from "./pricing-fetcher-KIIpXo8L.js";
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, __commonJSMin, __require, __toESM, isFailure, require_usingCtx, try_ } from "./pricing-fetcher-eNV76BjF.js";
3
3
  import { getTotalTokens } from "./_token-utils-WjkbrjKv.js";
4
4
  import { CostModes, SortOrders, filterDateSchema } from "./_types-BHFM59hI.js";
5
5
  import { calculateTotals, createTotalsObject } from "./calculate-cost-BDqO4yWA.js";
6
- import { DEFAULT_SESSION_DURATION_HOURS, calculateBurnRate, calculateCostForEntry, createUniqueHash, filterRecentBlocks, formatDateCompact, getClaudePaths, getEarliestTimestamp, getUsageLimitResetTime, globUsageFiles, identifySessionBlocks, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, projectBlockUsage, sortFilesByTimestamp, uniq, usageDataSchema } from "./data-loader-BNrxyvc9.js";
7
- import { description, log, logger, name, version } from "./logger-mXUMXvyg.js";
8
- import { detectMismatches, printMismatchReport } from "./debug-Brn3ODNf.js";
9
- import { createMcpHttpApp, createMcpServer, startMcpServerStdio } from "./mcp-CQw7snVU.js";
6
+ import { DEFAULT_SESSION_DURATION_HOURS, calculateBurnRate, calculateCostForEntry, createUniqueHash, filterRecentBlocks, formatDateCompact, getClaudePaths, getEarliestTimestamp, getUsageLimitResetTime, globUsageFiles, identifySessionBlocks, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, projectBlockUsage, sortFilesByTimestamp, uniq, usageDataSchema } from "./data-loader-BAp4u-7e.js";
7
+ import { description, log, logger, name, version } from "./logger-COLgmk2z.js";
8
+ import { detectMismatches, printMismatchReport } from "./debug-IQBgd8CJ.js";
9
+ import { createMcpHttpApp, createMcpServer, startMcpServerStdio } from "./mcp-Ta2UK9sc.js";
10
10
  import { readFile } from "node:fs/promises";
11
11
  import process$1 from "node:process";
12
12
  import { createServer } from "node:http";
@@ -3729,13 +3729,6 @@ function formatTokensShort(num) {
3729
3729
  return num.toString();
3730
3730
  }
3731
3731
  /**
3732
- * Column layout constants for detail rows
3733
- */
3734
- const DETAIL_COLUMN_WIDTHS = {
3735
- col1: 46,
3736
- col2: 37
3737
- };
3738
- /**
3739
3732
  * Renders the live display for an active session block
3740
3733
  */
3741
3734
  function renderLiveDisplay(terminal, block, config) {
@@ -3744,6 +3737,9 @@ function renderLiveDisplay(terminal, block, config) {
3744
3737
  const totalTokens = getTotalTokens(block.tokenCounts);
3745
3738
  const elapsed = (now.getTime() - block.startTime.getTime()) / (1e3 * 60);
3746
3739
  const remaining = (block.endTime.getTime() - now.getTime()) / (1e3 * 60);
3740
+ const formatTokenDisplay = (tokens, useShort) => {
3741
+ return useShort ? formatTokensShort(tokens) : formatNumber(tokens);
3742
+ };
3747
3743
  if (width < 60) {
3748
3744
  renderCompactLiveDisplay(terminal, block, config, totalTokens, elapsed, remaining);
3749
3745
  return;
@@ -3751,12 +3747,19 @@ function renderLiveDisplay(terminal, block, config) {
3751
3747
  const boxWidth = Math.min(120, width - 2);
3752
3748
  const boxMargin = Math.floor((width - boxWidth) / 2);
3753
3749
  const marginStr = " ".repeat(boxMargin);
3754
- const labelWidth = 14;
3755
- const percentWidth = 7;
3756
- const shortLabelWidth = 20;
3757
- const barWidth = boxWidth - labelWidth - percentWidth - shortLabelWidth - 4;
3758
3750
  const sessionDuration = elapsed + remaining;
3759
3751
  const sessionPercent = elapsed / sessionDuration * 100;
3752
+ const sessionRightText = `${sessionPercent.toFixed(1).padStart(6)}%`;
3753
+ const tokenPercent = config.tokenLimit != null && config.tokenLimit > 0 ? totalTokens / config.tokenLimit * 100 : 0;
3754
+ const usageRightText = config.tokenLimit != null && config.tokenLimit > 0 ? `${tokenPercent.toFixed(1).padStart(6)}% (${formatTokensShort(totalTokens)}/${formatTokensShort(config.tokenLimit)})` : `(${formatTokensShort(totalTokens)} tokens)`;
3755
+ const projection = projectBlockUsage(block);
3756
+ const projectedPercent = projection != null && config.tokenLimit != null && config.tokenLimit > 0 ? projection.totalTokens / config.tokenLimit * 100 : 0;
3757
+ const projectionRightText = projection != null ? config.tokenLimit != null && config.tokenLimit > 0 ? `${projectedPercent.toFixed(1).padStart(6)}% (${formatTokensShort(projection.totalTokens)}/${formatTokensShort(config.tokenLimit)})` : `(${formatTokensShort(projection.totalTokens)} tokens)` : "";
3758
+ const maxRightTextWidth = Math.max(stringWidth(sessionRightText), stringWidth(usageRightText), projection != null ? stringWidth(projectionRightText) : 0);
3759
+ const labelWidth = 14;
3760
+ const spacing = 4;
3761
+ const boxPadding = 4;
3762
+ const barWidth = boxWidth - labelWidth - maxRightTextWidth - spacing - boxPadding;
3760
3763
  const sessionProgressBar = createProgressBar(elapsed, sessionDuration, barWidth, {
3761
3764
  showPercentage: false,
3762
3765
  fillChar: import_picocolors$5.default.cyan("█"),
@@ -3776,23 +3779,24 @@ function renderLiveDisplay(terminal, block, config) {
3776
3779
  second: "2-digit",
3777
3780
  hour12: true
3778
3781
  });
3782
+ const detailsIndent = 3;
3783
+ const detailsSpacing = 2;
3784
+ const detailsAvailableWidth = boxWidth - 3 - detailsIndent;
3779
3785
  terminal.write(`${marginStr}┌${"─".repeat(boxWidth - 2)}┐\n`);
3780
3786
  terminal.write(`${marginStr}│${import_picocolors$5.default.bold(centerText("CLAUDE CODE - LIVE TOKEN USAGE MONITOR", boxWidth - 2))}│\n`);
3781
3787
  terminal.write(`${marginStr}├${"─".repeat(boxWidth - 2)}┤\n`);
3782
3788
  terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
3783
3789
  const sessionLabel = import_picocolors$5.default.bold("⏱️ SESSION");
3784
3790
  const sessionLabelWidth = stringWidth(sessionLabel);
3785
- const sessionBarStr = `${sessionLabel}${"".padEnd(Math.max(0, labelWidth - sessionLabelWidth))} ${sessionProgressBar} ${sessionPercent.toFixed(1).padStart(6)}%`;
3791
+ const sessionBarStr = `${sessionLabel}${"".padEnd(Math.max(0, labelWidth - sessionLabelWidth))} ${sessionProgressBar} ${sessionRightText}`;
3786
3792
  const sessionBarPadded = sessionBarStr + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(sessionBarStr)));
3787
3793
  terminal.write(`${marginStr}│ ${sessionBarPadded}│\n`);
3788
- const col1 = `${import_picocolors$5.default.gray("Started:")} ${startTime}`;
3789
- const col2 = `${import_picocolors$5.default.gray("Elapsed:")} ${prettyMilliseconds(elapsed * 60 * 1e3, { compact: true })}`;
3790
- const col3 = `${import_picocolors$5.default.gray("Remaining:")} ${prettyMilliseconds(remaining * 60 * 1e3, { compact: true })} (${endTime})`;
3791
- const col1Visible = stringWidth(col1);
3792
- const col2Visible = stringWidth(col2);
3793
- const pad1 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col1 - col1Visible));
3794
- const pad2 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col2 - col2Visible));
3795
- const sessionDetails = ` ${col1}${pad1}${pad2}${col3}`;
3794
+ const sessionCol1 = `${import_picocolors$5.default.gray("Started:")} ${startTime}`;
3795
+ const sessionCol2 = `${import_picocolors$5.default.gray("Elapsed:")} ${prettyMilliseconds(elapsed * 60 * 1e3, { compact: true })}`;
3796
+ const sessionCol3 = `${import_picocolors$5.default.gray("Remaining:")} ${prettyMilliseconds(remaining * 60 * 1e3, { compact: true })} (${endTime})`;
3797
+ let sessionDetails = `${" ".repeat(detailsIndent)}${sessionCol1}${" ".repeat(detailsSpacing)}${sessionCol2}${" ".repeat(detailsSpacing)}${sessionCol3}`;
3798
+ const sessionDetailsWidth = stringWidth(sessionCol1) + stringWidth(sessionCol2) + stringWidth(sessionCol3) + detailsSpacing * 2;
3799
+ if (sessionDetailsWidth > detailsAvailableWidth) sessionDetails = `${" ".repeat(detailsIndent)}${sessionCol1}${" ".repeat(detailsSpacing)}${sessionCol3}`;
3796
3800
  const sessionDetailsPadded = sessionDetails + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(sessionDetails)));
3797
3801
  let usageLimitResetTimePadded = null;
3798
3802
  if (block.usageLimitResetTime !== void 0 && now < block.usageLimitResetTime) {
@@ -3809,7 +3813,6 @@ function renderLiveDisplay(terminal, block, config) {
3809
3813
  terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
3810
3814
  terminal.write(`${marginStr}├${"─".repeat(boxWidth - 2)}┤\n`);
3811
3815
  terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
3812
- const tokenPercent = config.tokenLimit != null && config.tokenLimit > 0 ? totalTokens / config.tokenLimit * 100 : 0;
3813
3816
  let barColor = import_picocolors$5.default.green;
3814
3817
  if (tokenPercent > 100) barColor = import_picocolors$5.default.red;
3815
3818
  else if (tokenPercent > 80) barColor = import_picocolors$5.default.yellow;
@@ -3822,35 +3825,51 @@ function renderLiveDisplay(terminal, block, config) {
3822
3825
  }) : `[${import_picocolors$5.default.green("█".repeat(Math.floor(barWidth * .1)))}${import_picocolors$5.default.gray("░".repeat(barWidth - Math.floor(barWidth * .1)))}]`;
3823
3826
  const burnRate = calculateBurnRate(block);
3824
3827
  const rateIndicator = getRateIndicator(burnRate);
3825
- const rateDisplay = burnRate != null ? `${import_picocolors$5.default.bold("Burn Rate:")} ${Math.round(burnRate.tokensPerMinute)} token/min ${rateIndicator}` : `${import_picocolors$5.default.bold("Burn Rate:")} N/A`;
3828
+ const buildRateDisplay = (useShort) => {
3829
+ if (burnRate == null) return `${import_picocolors$5.default.bold("Burn Rate:")} N/A`;
3830
+ const rateValue = Math.round(burnRate.tokensPerMinute);
3831
+ const formattedRate = useShort ? formatTokensShort(rateValue) : formatNumber(rateValue);
3832
+ return `${import_picocolors$5.default.bold("Burn Rate:")} ${formattedRate} token/min ${rateIndicator}`;
3833
+ };
3826
3834
  const usageLabel = import_picocolors$5.default.bold("🔥 USAGE");
3827
3835
  const usageLabelWidth = stringWidth(usageLabel);
3828
- const { usageBarStr, usageCol1, usageCol2, usageCol3 } = config.tokenLimit != null && config.tokenLimit > 0 ? {
3829
- usageBarStr: `${usageLabel}${"".padEnd(Math.max(0, labelWidth - usageLabelWidth))} ${usageBar} ${tokenPercent.toFixed(1).padStart(6)}% (${formatTokensShort(totalTokens)}/${formatTokensShort(config.tokenLimit)})`,
3830
- usageCol1: `${import_picocolors$5.default.gray("Tokens:")} ${formatNumber(totalTokens)} (${rateDisplay})`,
3831
- usageCol2: `${import_picocolors$5.default.gray("Limit:")} ${formatNumber(config.tokenLimit)} tokens`,
3832
- usageCol3: `${import_picocolors$5.default.gray("Cost:")} ${formatCurrency(block.costUSD)}`
3833
- } : {
3834
- usageBarStr: `${usageLabel}${"".padEnd(Math.max(0, labelWidth - usageLabelWidth))} ${usageBar} (${formatTokensShort(totalTokens)} tokens)`,
3835
- usageCol1: `${import_picocolors$5.default.gray("Tokens:")} ${formatNumber(totalTokens)} (${rateDisplay})`,
3836
- usageCol2: "",
3837
- usageCol3: `${import_picocolors$5.default.gray("Cost:")} ${formatCurrency(block.costUSD)}`
3838
- };
3836
+ const usageBarStr = `${usageLabel}${"".padEnd(Math.max(0, labelWidth - usageLabelWidth))} ${usageBar} ${usageRightText}`;
3837
+ let rateDisplay = buildRateDisplay(false);
3838
+ let usageCol1 = `${import_picocolors$5.default.gray("Tokens:")} ${formatTokenDisplay(totalTokens, false)} (${rateDisplay})`;
3839
+ let usageCol2 = config.tokenLimit != null && config.tokenLimit > 0 ? `${import_picocolors$5.default.gray("Limit:")} ${formatTokenDisplay(config.tokenLimit, false)} tokens` : "";
3840
+ const usageCol3 = `${import_picocolors$5.default.gray("Cost:")} ${formatCurrency(block.costUSD)}`;
3841
+ let totalWidth = stringWidth(usageCol1);
3842
+ if (usageCol2.length > 0) totalWidth += detailsSpacing + stringWidth(usageCol2);
3843
+ totalWidth += detailsSpacing + stringWidth(usageCol3);
3844
+ let useTwoLineLayout = false;
3845
+ if (totalWidth > detailsAvailableWidth) {
3846
+ useTwoLineLayout = true;
3847
+ rateDisplay = buildRateDisplay(true);
3848
+ usageCol1 = `${import_picocolors$5.default.gray("Tokens:")} ${formatTokenDisplay(totalTokens, true)} (${rateDisplay})`;
3849
+ if (usageCol2.length > 0) usageCol2 = `${import_picocolors$5.default.gray("Limit:")} ${formatTokenDisplay(config.tokenLimit, true)} tokens`;
3850
+ }
3839
3851
  const usageBarPadded = usageBarStr + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(usageBarStr)));
3840
3852
  terminal.write(`${marginStr}│ ${usageBarPadded}│\n`);
3841
- const usageCol1Visible = stringWidth(usageCol1);
3842
- const usageCol2Visible = stringWidth(usageCol2);
3843
- const usagePad1 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col1 - usageCol1Visible));
3844
- const usagePad2 = usageCol2.length > 0 ? " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col2 - usageCol2Visible)) : " ".repeat(DETAIL_COLUMN_WIDTHS.col2);
3845
- const usageDetails = ` ${usageCol1}${usagePad1}${usageCol2}${usagePad2}${usageCol3}`;
3846
- const usageDetailsPadded = usageDetails + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(usageDetails)));
3847
- terminal.write(`${marginStr}${usageDetailsPadded}│\n`);
3853
+ if (useTwoLineLayout) {
3854
+ const usageDetailsLine1 = `${" ".repeat(detailsIndent)}${usageCol1}`;
3855
+ const usageDetailsLine1Padded = usageDetailsLine1 + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(usageDetailsLine1)));
3856
+ terminal.write(`${marginStr}│ ${usageDetailsLine1Padded}│\n`);
3857
+ let usageDetailsLine2;
3858
+ if (usageCol2.length > 0) usageDetailsLine2 = `${" ".repeat(detailsIndent)}${usageCol2}${" ".repeat(detailsSpacing)}${usageCol3}`;
3859
+ else usageDetailsLine2 = `${" ".repeat(detailsIndent)}${usageCol3}`;
3860
+ const usageDetailsLine2Padded = usageDetailsLine2 + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(usageDetailsLine2)));
3861
+ terminal.write(`${marginStr}│ ${usageDetailsLine2Padded}│\n`);
3862
+ } else {
3863
+ let usageDetails;
3864
+ if (usageCol2.length > 0) usageDetails = `${" ".repeat(detailsIndent)}${usageCol1}${" ".repeat(detailsSpacing)}${usageCol2}${" ".repeat(detailsSpacing)}${usageCol3}`;
3865
+ else usageDetails = `${" ".repeat(detailsIndent)}${usageCol1}${" ".repeat(detailsSpacing)}${usageCol3}`;
3866
+ const usageDetailsPadded = usageDetails + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(usageDetails)));
3867
+ terminal.write(`${marginStr}│ ${usageDetailsPadded}│\n`);
3868
+ }
3848
3869
  terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
3849
3870
  terminal.write(`${marginStr}├${"─".repeat(boxWidth - 2)}┤\n`);
3850
3871
  terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
3851
- const projection = projectBlockUsage(block);
3852
3872
  if (projection != null) {
3853
- const projectedPercent = config.tokenLimit != null && config.tokenLimit > 0 ? projection.totalTokens / config.tokenLimit * 100 : 0;
3854
3873
  let projBarColor = import_picocolors$5.default.green;
3855
3874
  if (projectedPercent > 100) projBarColor = import_picocolors$5.default.red;
3856
3875
  else if (projectedPercent > 80) projBarColor = import_picocolors$5.default.yellow;
@@ -3864,32 +3883,27 @@ function renderLiveDisplay(terminal, block, config) {
3864
3883
  const limitStatus = config.tokenLimit != null && config.tokenLimit > 0 ? projectedPercent > 100 ? import_picocolors$5.default.red("❌ WILL EXCEED LIMIT") : projectedPercent > 80 ? import_picocolors$5.default.yellow("⚠️ APPROACHING LIMIT") : import_picocolors$5.default.green("✓ WITHIN LIMIT") : import_picocolors$5.default.green("✓ ON TRACK");
3865
3884
  const projLabel = import_picocolors$5.default.bold("📈 PROJECTION");
3866
3885
  const projLabelWidth = stringWidth(projLabel);
3867
- if (config.tokenLimit != null && config.tokenLimit > 0) {
3868
- const projBarStr = `${projLabel}${"".padEnd(Math.max(0, labelWidth - projLabelWidth))} ${projectionBar} ${projectedPercent.toFixed(1).padStart(6)}% (${formatTokensShort(projection.totalTokens)}/${formatTokensShort(config.tokenLimit)})`;
3869
- const projBarPadded = projBarStr + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(projBarStr)));
3870
- terminal.write(`${marginStr} ${projBarPadded}│\n`);
3871
- const col1$1 = `${import_picocolors$5.default.gray("Status:")} ${limitStatus}`;
3872
- const col2$1 = `${import_picocolors$5.default.gray("Tokens:")} ${formatNumber(projection.totalTokens)}`;
3873
- const col3$1 = `${import_picocolors$5.default.gray("Cost:")} ${formatCurrency(projection.totalCost)}`;
3874
- const col1Visible$1 = stringWidth(col1$1);
3875
- const col2Visible$1 = stringWidth(col2$1);
3876
- const pad1$1 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col1 - col1Visible$1));
3877
- const pad2$1 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col2 - col2Visible$1));
3878
- const projDetails = ` ${col1$1}${pad1$1}${col2$1}${pad2$1}${col3$1}`;
3879
- const projDetailsPadded = projDetails + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(projDetails)));
3880
- terminal.write(`${marginStr}${projDetailsPadded}│\n`);
3886
+ const projBarStr = `${projLabel}${"".padEnd(Math.max(0, labelWidth - projLabelWidth))} ${projectionBar} ${projectionRightText}`;
3887
+ const projBarPadded = projBarStr + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(projBarStr)));
3888
+ terminal.write(`${marginStr}│ ${projBarPadded}│\n`);
3889
+ const projCol1 = `${import_picocolors$5.default.gray("Status:")} ${limitStatus}`;
3890
+ let projCol2 = `${import_picocolors$5.default.gray("Tokens:")} ${formatTokenDisplay(projection.totalTokens, false)}`;
3891
+ const projCol3 = `${import_picocolors$5.default.gray("Cost:")} ${formatCurrency(projection.totalCost)}`;
3892
+ const projTotalWidth = stringWidth(projCol1) + stringWidth(projCol2) + stringWidth(projCol3) + detailsSpacing * 2;
3893
+ let projUseTwoLineLayout = false;
3894
+ if (projTotalWidth > detailsAvailableWidth) {
3895
+ projUseTwoLineLayout = true;
3896
+ projCol2 = `${import_picocolors$5.default.gray("Tokens:")} ${formatTokenDisplay(projection.totalTokens, true)}`;
3897
+ }
3898
+ if (projUseTwoLineLayout) {
3899
+ const projDetailsLine1 = `${" ".repeat(detailsIndent)}${projCol1}`;
3900
+ const projDetailsLine1Padded = projDetailsLine1 + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(projDetailsLine1)));
3901
+ terminal.write(`${marginStr}│ ${projDetailsLine1Padded}│\n`);
3902
+ const projDetailsLine2 = `${" ".repeat(detailsIndent)}${projCol2}${" ".repeat(detailsSpacing)}${projCol3}`;
3903
+ const projDetailsLine2Padded = projDetailsLine2 + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(projDetailsLine2)));
3904
+ terminal.write(`${marginStr}│ ${projDetailsLine2Padded}│\n`);
3881
3905
  } else {
3882
- const projBarStr = `${projLabel}${"".padEnd(Math.max(0, labelWidth - projLabelWidth))} ${projectionBar} (${formatTokensShort(projection.totalTokens)} tokens)`;
3883
- const projBarPadded = projBarStr + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(projBarStr)));
3884
- terminal.write(`${marginStr}│ ${projBarPadded}│\n`);
3885
- const col1$1 = `${import_picocolors$5.default.gray("Status:")} ${limitStatus}`;
3886
- const col2$1 = `${import_picocolors$5.default.gray("Tokens:")} ${formatNumber(projection.totalTokens)}`;
3887
- const col3$1 = `${import_picocolors$5.default.gray("Cost:")} ${formatCurrency(projection.totalCost)}`;
3888
- const col1Visible$1 = stringWidth(col1$1);
3889
- const col2Visible$1 = stringWidth(col2$1);
3890
- const pad1$1 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col1 - col1Visible$1));
3891
- const pad2$1 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col2 - col2Visible$1));
3892
- const projDetails = ` ${col1$1}${pad1$1}${col2$1}${pad2$1}${col3$1}`;
3906
+ const projDetails = `${" ".repeat(detailsIndent)}${projCol1}${" ".repeat(detailsSpacing)}${projCol2}${" ".repeat(detailsSpacing)}${projCol3}`;
3893
3907
  const projDetailsPadded = projDetails + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(projDetails)));
3894
3908
  terminal.write(`${marginStr}│ ${projDetailsPadded}│\n`);
3895
3909
  }
@@ -3970,7 +3984,15 @@ async function startLiveMonitoring(config) {
3970
3984
  }
3971
3985
  renderActiveBlock(terminal, activeBlock, config);
3972
3986
  lastRenderTime = Date.now();
3973
- await delayWithAbort(config.refreshInterval, abortController.signal);
3987
+ let resizeEventHandler;
3988
+ try {
3989
+ await Promise.race([delayWithAbort(config.refreshInterval, abortController.signal), new Promise((resolve) => {
3990
+ resizeEventHandler = resolve;
3991
+ process$1.stdout.once("resize", resolve);
3992
+ })]);
3993
+ } finally {
3994
+ if (resizeEventHandler != null) process$1.stdout.removeListener("resize", resizeEventHandler);
3995
+ }
3974
3996
  }
3975
3997
  },
3976
3998
  catch: (error) => error
@@ -4329,11 +4351,162 @@ const blocksCommand = define({
4329
4351
  }
4330
4352
  }
4331
4353
  });
4354
+ /**
4355
+ * Group daily usage data by project for JSON output
4356
+ */
4357
+ function groupByProject(dailyData) {
4358
+ const projects = {};
4359
+ for (const data of dailyData) {
4360
+ const projectName = data.project ?? "unknown";
4361
+ if (projects[projectName] == null) projects[projectName] = [];
4362
+ projects[projectName].push({
4363
+ date: data.date,
4364
+ inputTokens: data.inputTokens,
4365
+ outputTokens: data.outputTokens,
4366
+ cacheCreationTokens: data.cacheCreationTokens,
4367
+ cacheReadTokens: data.cacheReadTokens,
4368
+ totalTokens: getTotalTokens(data),
4369
+ totalCost: data.totalCost,
4370
+ modelsUsed: data.modelsUsed,
4371
+ modelBreakdowns: data.modelBreakdowns
4372
+ });
4373
+ }
4374
+ return projects;
4375
+ }
4376
+ /**
4377
+ * Group daily usage data by project for table display
4378
+ */
4379
+ function groupDataByProject(dailyData) {
4380
+ const projects = {};
4381
+ for (const data of dailyData) {
4382
+ const projectName = data.project ?? "unknown";
4383
+ if (projects[projectName] == null) projects[projectName] = [];
4384
+ projects[projectName].push(data);
4385
+ }
4386
+ return projects;
4387
+ }
4388
+ /**
4389
+ * Cache for parsed aliases to avoid repeated parsing
4390
+ */
4391
+ let aliasCache = null;
4392
+ /**
4393
+ * Parse project aliases from environment variable
4394
+ * @returns Map of raw project names to their aliases
4395
+ */
4396
+ function getProjectAliases() {
4397
+ if (aliasCache !== null) return aliasCache;
4398
+ aliasCache = /* @__PURE__ */ new Map();
4399
+ const aliasEnv = (process$1.env[PROJECT_ALIASES_ENV] ?? "").trim();
4400
+ if (aliasEnv === "") return aliasCache;
4401
+ const pairs = aliasEnv.split(",").map((pair) => pair.trim()).filter((pair) => pair !== "");
4402
+ for (const pair of pairs) {
4403
+ const parts = pair.split("=").map((s) => s.trim());
4404
+ const rawName = parts[0];
4405
+ const alias = parts[1];
4406
+ if (rawName != null && alias != null && rawName !== "" && alias !== "") aliasCache.set(rawName, alias);
4407
+ }
4408
+ return aliasCache;
4409
+ }
4410
+ /**
4411
+ * Extract meaningful project name from directory-style project paths
4412
+ * Uses improved heuristics to handle complex project structures
4413
+ *
4414
+ * @param projectName - Raw project name from directory path
4415
+ * @returns Cleaned and formatted project name
4416
+ *
4417
+ * @example
4418
+ * ```typescript
4419
+ * // Basic cleanup
4420
+ * parseProjectName('-Users-phaedrus-Development-ccusage')
4421
+ * // → 'ccusage'
4422
+ *
4423
+ * // Complex project with feature branch
4424
+ * parseProjectName('-Users-phaedrus-Development-adminifi-edugakko-api--feature-ticket-002-configure-dependabot')
4425
+ * // → 'configure-dependabot'
4426
+ *
4427
+ * // Handle unknown projects
4428
+ * parseProjectName('unknown')
4429
+ * // → 'Unknown Project'
4430
+ * ```
4431
+ */
4432
+ function parseProjectName(projectName) {
4433
+ if (projectName === "unknown" || projectName === "") return "Unknown Project";
4434
+ let cleaned = projectName;
4435
+ if (cleaned.match(/^[A-Z]:\\Users\\|^\\Users\\/) != null) {
4436
+ const segments = cleaned.split("\\");
4437
+ const userIndex = segments.findIndex((seg) => seg === "Users");
4438
+ if (userIndex !== -1 && userIndex + 3 < segments.length) cleaned = segments.slice(userIndex + 3).join("-");
4439
+ }
4440
+ if (cleaned.startsWith("-Users-") || cleaned.startsWith("/Users/")) {
4441
+ const separator = cleaned.startsWith("-Users-") ? "-" : "/";
4442
+ const segments = cleaned.split(separator).filter((s) => s.length > 0);
4443
+ const userIndex = segments.findIndex((seg) => seg === "Users");
4444
+ if (userIndex !== -1 && userIndex + 3 < segments.length) cleaned = segments.slice(userIndex + 3).join("-");
4445
+ }
4446
+ if (cleaned === projectName) cleaned = projectName.replace(/^[/\\-]+|[/\\-]+$/g, "");
4447
+ if (cleaned.match(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/i) != null) {
4448
+ const parts = cleaned.split("-");
4449
+ if (parts.length >= 5) cleaned = parts.slice(-2).join("-");
4450
+ }
4451
+ if (cleaned.includes("--")) {
4452
+ const parts = cleaned.split("--");
4453
+ if (parts.length >= 2 && parts[0] != null) cleaned = parts[0];
4454
+ }
4455
+ if (cleaned.includes("-") && cleaned.length > 20) {
4456
+ const segments = cleaned.split("-");
4457
+ const meaningfulSegments = segments.filter((seg) => seg.length > 2 && seg.match(/^(?:dev|development|feat|feature|fix|bug|test|staging|prod|production|main|master|branch)$/i) == null);
4458
+ if (meaningfulSegments.length >= 2) {
4459
+ const lastSegments = meaningfulSegments.slice(-2);
4460
+ if (lastSegments.join("-").length >= 6) cleaned = lastSegments.join("-");
4461
+ else if (meaningfulSegments.length >= 3) cleaned = meaningfulSegments.slice(-3).join("-");
4462
+ }
4463
+ }
4464
+ cleaned = cleaned.replace(/^[/\\-]+|[/\\-]+$/g, "");
4465
+ return cleaned !== "" ? cleaned : projectName !== "" ? projectName : "Unknown Project";
4466
+ }
4467
+ /**
4468
+ * Format project name for display with custom alias support
4469
+ *
4470
+ * @param projectName - Raw project name from directory path
4471
+ * @returns User-friendly project name with alias support
4472
+ *
4473
+ * @example
4474
+ * ```typescript
4475
+ * // Without aliases
4476
+ * formatProjectName('-Users-phaedrus-Development-ccusage')
4477
+ * // → 'ccusage'
4478
+ *
4479
+ * // With alias (when CCUSAGE_PROJECT_ALIASES="ccusage=Usage Tracker")
4480
+ * formatProjectName('-Users-phaedrus-Development-ccusage')
4481
+ * // → 'Usage Tracker'
4482
+ * ```
4483
+ */
4484
+ function formatProjectName(projectName) {
4485
+ const aliases = getProjectAliases();
4486
+ if (aliases.has(projectName)) return aliases.get(projectName);
4487
+ const parsed = parseProjectName(projectName);
4488
+ if (aliases.has(parsed)) return aliases.get(parsed);
4489
+ return parsed;
4490
+ }
4332
4491
  var import_picocolors$2 = __toESM(require_picocolors(), 1);
4333
4492
  const dailyCommand = define({
4334
4493
  name: "daily",
4335
4494
  description: "Show usage report grouped by date",
4336
4495
  ...sharedCommandConfig,
4496
+ args: {
4497
+ ...sharedCommandConfig.args,
4498
+ instances: {
4499
+ type: "boolean",
4500
+ short: "i",
4501
+ description: "Show usage breakdown by project/instance",
4502
+ default: false
4503
+ },
4504
+ project: {
4505
+ type: "string",
4506
+ short: "p",
4507
+ description: "Filter to specific project name"
4508
+ }
4509
+ },
4337
4510
  async run(ctx) {
4338
4511
  if (ctx.values.json) logger.level = 0;
4339
4512
  const dailyData = await loadDailyUsageData({
@@ -4341,7 +4514,9 @@ const dailyCommand = define({
4341
4514
  until: ctx.values.until,
4342
4515
  mode: ctx.values.mode,
4343
4516
  order: ctx.values.order,
4344
- offline: ctx.values.offline
4517
+ offline: ctx.values.offline,
4518
+ groupByProject: ctx.values.instances,
4519
+ project: ctx.values.project
4345
4520
  });
4346
4521
  if (dailyData.length === 0) {
4347
4522
  if (ctx.values.json) log(JSON.stringify([]));
@@ -4354,7 +4529,10 @@ const dailyCommand = define({
4354
4529
  printMismatchReport(mismatchStats, ctx.values.debugSamples);
4355
4530
  }
4356
4531
  if (ctx.values.json) {
4357
- const jsonOutput = {
4532
+ const jsonOutput = ctx.values.instances && dailyData.some((d) => d.project != null) ? {
4533
+ projects: groupByProject(dailyData),
4534
+ totals: createTotalsObject(totals)
4535
+ } : {
4358
4536
  daily: dailyData.map((data) => ({
4359
4537
  date: data.date,
4360
4538
  inputTokens: data.inputTokens,
@@ -4364,7 +4542,8 @@ const dailyCommand = define({
4364
4542
  totalTokens: getTotalTokens(data),
4365
4543
  totalCost: data.totalCost,
4366
4544
  modelsUsed: data.modelsUsed,
4367
- modelBreakdowns: data.modelBreakdowns
4545
+ modelBreakdowns: data.modelBreakdowns,
4546
+ ...data.project != null && { project: data.project }
4368
4547
  })),
4369
4548
  totals: createTotalsObject(totals)
4370
4549
  };
@@ -4410,7 +4589,46 @@ const dailyCommand = define({
4410
4589
  ],
4411
4590
  compactThreshold: 100
4412
4591
  });
4413
- for (const data of dailyData) {
4592
+ if (ctx.values.instances && dailyData.some((d) => d.project != null)) {
4593
+ const projectGroups = groupDataByProject(dailyData);
4594
+ let isFirstProject = true;
4595
+ for (const [projectName, projectData] of Object.entries(projectGroups)) {
4596
+ if (!isFirstProject) table.push([
4597
+ "",
4598
+ "",
4599
+ "",
4600
+ "",
4601
+ "",
4602
+ "",
4603
+ "",
4604
+ ""
4605
+ ]);
4606
+ table.push([
4607
+ import_picocolors$2.default.cyan(`Project: ${formatProjectName(projectName)}`),
4608
+ "",
4609
+ "",
4610
+ "",
4611
+ "",
4612
+ "",
4613
+ "",
4614
+ ""
4615
+ ]);
4616
+ for (const data of projectData) {
4617
+ table.push([
4618
+ data.date,
4619
+ formatModelsDisplayMultiline(data.modelsUsed),
4620
+ formatNumber(data.inputTokens),
4621
+ formatNumber(data.outputTokens),
4622
+ formatNumber(data.cacheCreationTokens),
4623
+ formatNumber(data.cacheReadTokens),
4624
+ formatNumber(getTotalTokens(data)),
4625
+ formatCurrency(data.totalCost)
4626
+ ]);
4627
+ if (ctx.values.breakdown) pushBreakdownRows(table, data.modelBreakdowns);
4628
+ }
4629
+ isFirstProject = false;
4630
+ }
4631
+ } else for (const data of dailyData) {
4414
4632
  table.push([
4415
4633
  data.date,
4416
4634
  formatModelsDisplayMultiline(data.modelsUsed),
@@ -5072,7 +5290,8 @@ const sessionCommand = define({
5072
5290
  totalCost: data.totalCost,
5073
5291
  lastActivity: data.lastActivity,
5074
5292
  modelsUsed: data.modelsUsed,
5075
- modelBreakdowns: data.modelBreakdowns
5293
+ modelBreakdowns: data.modelBreakdowns,
5294
+ projectPath: data.projectPath
5076
5295
  })),
5077
5296
  totals: createTotalsObject(totals)
5078
5297
  };
@@ -951,7 +951,7 @@ function _getDefaultLogLevel() {
951
951
  }
952
952
  const consola = createConsola$1();
953
953
  var name = "ccusage";
954
- var version = "15.4.0";
954
+ var version = "15.5.1";
955
955
  var description = "Usage analysis tool for Claude Code";
956
956
  /**
957
957
  * Application logger instance with package name tag
package/dist/logger.js CHANGED
@@ -1,2 +1,2 @@
1
- import { log, logger } from "./logger-mXUMXvyg.js";
1
+ import { log, logger } from "./logger-COLgmk2z.js";
2
2
  export { log, logger };
@@ -1,9 +1,9 @@
1
- import { __commonJSMin, __toESM, require_usingCtx } from "./pricing-fetcher-KIIpXo8L.js";
1
+ import { __commonJSMin, __toESM, require_usingCtx } from "./pricing-fetcher-eNV76BjF.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-BHFM59hI.js";
4
4
  import { calculateTotals, createTotalsObject } from "./calculate-cost-BDqO4yWA.js";
5
- import { getClaudePaths, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData } from "./data-loader-BNrxyvc9.js";
6
- import { name, version } from "./logger-mXUMXvyg.js";
5
+ import { getClaudePaths, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData } from "./data-loader-BAp4u-7e.js";
6
+ import { name, version } from "./logger-COLgmk2z.js";
7
7
  import process from "node:process";
8
8
  const LATEST_PROTOCOL_VERSION = "2025-06-18";
9
9
  const SUPPORTED_PROTOCOL_VERSIONS = [
@@ -1605,6 +1605,7 @@ var Protocol = class {
1605
1605
  this._responseHandlers = /* @__PURE__ */ new Map();
1606
1606
  this._progressHandlers = /* @__PURE__ */ new Map();
1607
1607
  this._timeoutInfo = /* @__PURE__ */ new Map();
1608
+ this._pendingDebouncedNotifications = /* @__PURE__ */ new Set();
1608
1609
  this.setNotificationHandler(CancelledNotificationSchema, (notification) => {
1609
1610
  const controller = this._requestHandlerAbortControllers.get(notification.params.requestId);
1610
1611
  controller === null || controller === void 0 || controller.abort(notification.params.reason);
@@ -1679,6 +1680,7 @@ var Protocol = class {
1679
1680
  const responseHandlers = this._responseHandlers;
1680
1681
  this._responseHandlers = /* @__PURE__ */ new Map();
1681
1682
  this._progressHandlers.clear();
1683
+ this._pendingDebouncedNotifications.clear();
1682
1684
  this._transport = void 0;
1683
1685
  (_a = this.onclose) === null || _a === void 0 || _a.call(this);
1684
1686
  const error = new McpError(ErrorCode.ConnectionClosed, "Connection closed");
@@ -1872,8 +1874,26 @@ var Protocol = class {
1872
1874
  * Emits a notification, which is a one-way message that does not expect a response.
1873
1875
  */
1874
1876
  async notification(notification, options) {
1877
+ var _a, _b;
1875
1878
  if (!this._transport) throw new Error("Not connected");
1876
1879
  this.assertNotificationCapability(notification.method);
1880
+ const debouncedMethods = (_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.debouncedNotificationMethods) !== null && _b !== void 0 ? _b : [];
1881
+ const canDebounce = debouncedMethods.includes(notification.method) && !notification.params && !(options === null || options === void 0 ? void 0 : options.relatedRequestId);
1882
+ if (canDebounce) {
1883
+ if (this._pendingDebouncedNotifications.has(notification.method)) return;
1884
+ this._pendingDebouncedNotifications.add(notification.method);
1885
+ Promise.resolve().then(() => {
1886
+ var _a$1;
1887
+ this._pendingDebouncedNotifications.delete(notification.method);
1888
+ if (!this._transport) return;
1889
+ const jsonrpcNotification$1 = {
1890
+ ...notification,
1891
+ jsonrpc: "2.0"
1892
+ };
1893
+ (_a$1 = this._transport) === null || _a$1 === void 0 || _a$1.send(jsonrpcNotification$1, options).catch((error) => this._onerror(error));
1894
+ });
1895
+ return;
1896
+ }
1877
1897
  const jsonrpcNotification = {
1878
1898
  ...notification,
1879
1899
  jsonrpc: "2.0"
package/dist/mcp.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { LoadOptions } from "./data-loader-7c4yXbqG.js";
1
+ import { LoadOptions } from "./data-loader-CgvyDaQD.js";
2
2
  import "./pricing-fetcher-B3SvKOod.js";
3
3
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
4
  import { Hono } from "hono/tiny";
package/dist/mcp.js CHANGED
@@ -1,8 +1,8 @@
1
- import "./pricing-fetcher-KIIpXo8L.js";
1
+ import "./pricing-fetcher-eNV76BjF.js";
2
2
  import "./_token-utils-WjkbrjKv.js";
3
3
  import "./_types-BHFM59hI.js";
4
4
  import "./calculate-cost-BDqO4yWA.js";
5
- import "./data-loader-BNrxyvc9.js";
6
- import "./logger-mXUMXvyg.js";
7
- import { createMcpHttpApp, createMcpServer, startMcpServerStdio } from "./mcp-CQw7snVU.js";
5
+ import "./data-loader-BAp4u-7e.js";
6
+ import "./logger-COLgmk2z.js";
7
+ import { createMcpHttpApp, createMcpServer, startMcpServerStdio } from "./mcp-Ta2UK9sc.js";
8
8
  export { createMcpHttpApp, createMcpServer, startMcpServerStdio };
@@ -1,5 +1,5 @@
1
1
  import { modelPricingSchema } from "./_types-BHFM59hI.js";
2
- import { logger } from "./logger-mXUMXvyg.js";
2
+ import { logger } from "./logger-COLgmk2z.js";
3
3
  import { createRequire } from "node:module";
4
4
  import path from "node:path";
5
5
  import F, { homedir } from "node:os";
@@ -192,6 +192,28 @@ const DEFAULT_CLAUDE_CONFIG_PATH = `${XDG_CONFIG_DIR}/claude`;
192
192
  */
193
193
  const CLAUDE_CONFIG_DIR_ENV = "CLAUDE_CONFIG_DIR";
194
194
  /**
195
+ * Environment variable for custom project name aliases
196
+ *
197
+ * Allows users to configure readable display names for cryptic or long project directory names.
198
+ * This is particularly useful for:
199
+ * - UUID-based project directories generated by Claude Code
200
+ * - Long path-based project names that are hard to read in tables
201
+ * - Custom branding of project names for organizational consistency
202
+ *
203
+ * Format: "raw-name=alias,another-name=other-alias"
204
+ * Example: "a2cd99ed-a586-4fe4-8f59-b0026409ec09=My Project,long-project-name=Short"
205
+ *
206
+ * Environment variable approach is used because:
207
+ * - No config file needed - simple and lightweight configuration
208
+ * - Works in any shell/environment without file management
209
+ * - Easy to set temporarily for specific invocations
210
+ * - Follows Unix conventions for tool configuration
211
+ * - Doesn't require file system permissions or config directory setup
212
+ *
213
+ * Used to override display names for project directories in all output formats.
214
+ */
215
+ const PROJECT_ALIASES_ENV = "CCUSAGE_PROJECT_ALIASES";
216
+ /**
195
217
  * Claude projects directory name within the data directory
196
218
  * Contains subdirectories for each project with usage data
197
219
  */
@@ -538,4 +560,4 @@ var PricingFetcher = class {
538
560
  return cost;
539
561
  }
540
562
  };
541
- export { BLOCKS_COMPACT_WIDTH_THRESHOLD, BLOCKS_DEFAULT_TERMINAL_WIDTH, BLOCKS_WARNING_THRESHOLD, BURN_RATE_THRESHOLDS, CLAUDE_CONFIG_DIR_ENV, CLAUDE_PROJECTS_DIR_NAME, DEBUG_MATCH_THRESHOLD_PERCENT, DEFAULT_CLAUDE_CODE_PATH, DEFAULT_CLAUDE_CONFIG_PATH, DEFAULT_RECENT_DAYS, DEFAULT_REFRESH_INTERVAL_SECONDS, MAX_REFRESH_INTERVAL_SECONDS, MCP_DEFAULT_PORT, MIN_REFRESH_INTERVAL_SECONDS, MIN_RENDER_INTERVAL_MS, PricingFetcher, USAGE_DATA_GLOB_PATTERN, USER_HOME_DIR, __commonJSMin, __require, __toESM, isFailure, isPromise, require_usingCtx, try_ };
563
+ export { BLOCKS_COMPACT_WIDTH_THRESHOLD, BLOCKS_DEFAULT_TERMINAL_WIDTH, BLOCKS_WARNING_THRESHOLD, BURN_RATE_THRESHOLDS, CLAUDE_CONFIG_DIR_ENV, CLAUDE_PROJECTS_DIR_NAME, DEBUG_MATCH_THRESHOLD_PERCENT, DEFAULT_CLAUDE_CODE_PATH, DEFAULT_CLAUDE_CONFIG_PATH, 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, USAGE_DATA_GLOB_PATTERN, USER_HOME_DIR, __commonJSMin, __require, __toESM, isFailure, isPromise, require_usingCtx, try_ };
@@ -1,4 +1,4 @@
1
- import { PricingFetcher } from "./pricing-fetcher-KIIpXo8L.js";
1
+ import { PricingFetcher } from "./pricing-fetcher-eNV76BjF.js";
2
2
  import "./_types-BHFM59hI.js";
3
- import "./logger-mXUMXvyg.js";
3
+ import "./logger-COLgmk2z.js";
4
4
  export { PricingFetcher };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccusage",
3
- "version": "15.4.0",
3
+ "version": "15.5.1",
4
4
  "description": "Usage analysis tool for Claude Code",
5
5
  "homepage": "https://github.com/ryoppippi/ccusage#readme",
6
6
  "bugs": {