ccusage 15.5.1 → 15.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,6 +9,7 @@
9
9
  <a href="https://packagephobia.com/result?p=ccusage"><img src="https://packagephobia.com/badge?p=ccusage" alt="install size" /></a>
10
10
  <a href="https://deepwiki.com/ryoppippi/ccusage"><img src="https://img.shields.io/badge/DeepWiki-ryoppippi%2Fccusage-blue.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAyCAYAAAAnWDnqAAAAAXNSR0IArs4c6QAAA05JREFUaEPtmUtyEzEQhtWTQyQLHNak2AB7ZnyXZMEjXMGeK/AIi+QuHrMnbChYY7MIh8g01fJoopFb0uhhEqqcbWTp06/uv1saEDv4O3n3dV60RfP947Mm9/SQc0ICFQgzfc4CYZoTPAswgSJCCUJUnAAoRHOAUOcATwbmVLWdGoH//PB8mnKqScAhsD0kYP3j/Yt5LPQe2KvcXmGvRHcDnpxfL2zOYJ1mFwrryWTz0advv1Ut4CJgf5uhDuDj5eUcAUoahrdY/56ebRWeraTjMt/00Sh3UDtjgHtQNHwcRGOC98BJEAEymycmYcWwOprTgcB6VZ5JK5TAJ+fXGLBm3FDAmn6oPPjR4rKCAoJCal2eAiQp2x0vxTPB3ALO2CRkwmDy5WohzBDwSEFKRwPbknEggCPB/imwrycgxX2NzoMCHhPkDwqYMr9tRcP5qNrMZHkVnOjRMWwLCcr8ohBVb1OMjxLwGCvjTikrsBOiA6fNyCrm8V1rP93iVPpwaE+gO0SsWmPiXB+jikdf6SizrT5qKasx5j8ABbHpFTx+vFXp9EnYQmLx02h1QTTrl6eDqxLnGjporxl3NL3agEvXdT0WmEost648sQOYAeJS9Q7bfUVoMGnjo4AZdUMQku50McDcMWcBPvr0SzbTAFDfvJqwLzgxwATnCgnp4wDl6Aa+Ax283gghmj+vj7feE2KBBRMW3FzOpLOADl0Isb5587h/U4gGvkt5v60Z1VLG8BhYjbzRwyQZemwAd6cCR5/XFWLYZRIMpX39AR0tjaGGiGzLVyhse5C9RKC6ai42ppWPKiBagOvaYk8lO7DajerabOZP46Lby5wKjw1HCRx7p9sVMOWGzb/vA1hwiWc6jm3MvQDTogQkiqIhJV0nBQBTU+3okKCFDy9WwferkHjtxib7t3xIUQtHxnIwtx4mpg26/HfwVNVDb4oI9RHmx5WGelRVlrtiw43zboCLaxv46AZeB3IlTkwouebTr1y2NjSpHz68WNFjHvupy3q8TFn3Hos2IAk4Ju5dCo8B3wP7VPr/FGaKiG+T+v+TQqIrOqMTL1VdWV1DdmcbO8KXBz6esmYWYKPwDL5b5FA1a0hwapHiom0r/cKaoqr+27/XcrS5UwSMbQAAAABJRU5ErkJggg==" alt="DeepWiki"></a>
11
11
  <!-- DeepWiki badge generated by https://deepwiki.ryoppippi.com/ -->
12
+ <a href="https://claudelog.com/"><img src="https://claudelog.com/img/claude_log_badge.svg" alt="ClaudeLog - A comprehensive knowledge base for Claude." /></a>
12
13
  <a href="https://github.com/hesreallyhim/awesome-claude-code"><img src="https://awesome.re/mentioned-badge.svg" alt="Mentioned in Awesome Claude Code" /></a>
13
14
  </p>
14
15
 
@@ -113,6 +114,16 @@ Check out these [47 Claude Code ProTips from Greg Baugues.](https://www.youtube.
113
114
  </a>
114
115
  </p>
115
116
 
117
+ ## Claude Code Resources
118
+
119
+ [`ClaudeLog`](https://claudelog.com) &nbsp; by &nbsp; [InventorBlack](https://www.reddit.com/user/inventor_black/)
120
+ A comprehensive knowledge base with detailed breakdowns of advanced topics, including:
121
+
122
+ - Advanced [mechanics](https://claudelog.com/mechanics/you-are-the-main-thread/) and [CLAUDE.md best practices](https://claudelog.com/mechanics/claude-md-supremacy).
123
+ - Practical technique guides for [plan mode](https://claudelog.com/mechanics/plan-mode), [ultrathink](https://claudelog.com/faqs/what-is-ultrathink/), and [sub-agents](https://claudelog.com/mechanics/task-agent-tools/).
124
+ - Concepts like [agent-first design](https://claudelog.com/mechanics/agent-first-design/), [agent engineering](https://claudelog.com/mechanics/agent-engineering/), and [humanizing agents](https://claudelog.com/mechanics/humanising-agents/).
125
+ - [Configuration guides](https://claudelog.com/configuration).
126
+
116
127
  ## Star History
117
128
 
118
129
  <a href="https://www.star-history.com/#ryoppippi/ccusage&Date">
@@ -725,7 +725,7 @@ const ipv6CidrRegex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1
725
725
  const base64Regex = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/;
726
726
  const base64urlRegex = /^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_]{3}(=)?))?$/;
727
727
  const dateRegexSource = `((\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-((0[13578]|1[02])-(0[1-9]|[12]\\d|3[01])|(0[469]|11)-(0[1-9]|[12]\\d|30)|(02)-(0[1-9]|1\\d|2[0-8])))`;
728
- const dateRegex = /* @__PURE__ */ new RegExp(`^${dateRegexSource}$`);
728
+ const dateRegex = new RegExp(`^${dateRegexSource}$`);
729
729
  function timeRegexSource(args) {
730
730
  let secondsRegexSource = `[0-5]\\d`;
731
731
  if (args.precision) secondsRegexSource = `${secondsRegexSource}\\.\\d{${args.precision}}`;
@@ -734,7 +734,7 @@ function timeRegexSource(args) {
734
734
  return `([01]\\d|2[0-3]):[0-5]\\d(:${secondsRegexSource})${secondsQuantifier}`;
735
735
  }
736
736
  function timeRegex(args) {
737
- return /* @__PURE__ */ new RegExp(`^${timeRegexSource(args)}$`);
737
+ return new RegExp(`^${timeRegexSource(args)}$`);
738
738
  }
739
739
  function datetimeRegex(args) {
740
740
  let regex = `${dateRegexSource}T${timeRegexSource(args)}`;
@@ -742,7 +742,7 @@ function datetimeRegex(args) {
742
742
  opts.push(args.local ? `Z?` : `Z`);
743
743
  if (args.offset) opts.push(`([+-]\\d{2}:?\\d{2})`);
744
744
  regex = `${regex}(${opts.join("|")})`;
745
- return /* @__PURE__ */ new RegExp(`^${regex}$`);
745
+ return new RegExp(`^${regex}$`);
746
746
  }
747
747
  function isValidIP(ip, version) {
748
748
  if ((version === "v4" || !version) && ipv4Regex.test(ip)) return true;
@@ -2492,7 +2492,7 @@ var ZodDiscriminatedUnion = class ZodDiscriminatedUnion extends ZodType {
2492
2492
  * @param params
2493
2493
  */
2494
2494
  static create(discriminator, options, params) {
2495
- const optionsMap = /* @__PURE__ */ new Map();
2495
+ const optionsMap = new Map();
2496
2496
  for (const type of options) {
2497
2497
  const discriminatorValues = getDiscriminator(type.shape[discriminator]);
2498
2498
  if (!discriminatorValues.length) throw new Error(`A discriminator value for key \`${discriminator}\` could not be extracted from all schema options`);
@@ -2730,7 +2730,7 @@ var ZodMap = class extends ZodType {
2730
2730
  };
2731
2731
  });
2732
2732
  if (ctx.common.async) {
2733
- const finalMap = /* @__PURE__ */ new Map();
2733
+ const finalMap = new Map();
2734
2734
  return Promise.resolve().then(async () => {
2735
2735
  for (const pair of pairs) {
2736
2736
  const key = await pair.key;
@@ -2745,7 +2745,7 @@ var ZodMap = class extends ZodType {
2745
2745
  };
2746
2746
  });
2747
2747
  } else {
2748
- const finalMap = /* @__PURE__ */ new Map();
2748
+ const finalMap = new Map();
2749
2749
  for (const pair of pairs) {
2750
2750
  const key = pair.key;
2751
2751
  const value = pair.value;
@@ -2808,7 +2808,7 @@ var ZodSet = class ZodSet extends ZodType {
2808
2808
  }
2809
2809
  const valueType = this._def.valueType;
2810
2810
  function finalizeSet(elements$1) {
2811
- const parsedSet = /* @__PURE__ */ new Set();
2811
+ const parsedSet = new Set();
2812
2812
  for (const element of elements$1) {
2813
2813
  if (element.status === "aborted") return INVALID;
2814
2814
  if (element.status === "dirty") status.dirty();
@@ -3573,13 +3573,19 @@ const isoTimestampSchema = stringType().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d
3573
3573
  const dailyDateSchema = stringType().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format").brand();
3574
3574
  const activityDateSchema = stringType().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format").brand();
3575
3575
  const monthlyDateSchema = stringType().regex(/^\d{4}-\d{2}$/, "Date must be in YYYY-MM format").brand();
3576
+ const weeklyDateSchema = stringType().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format").brand();
3576
3577
  const filterDateSchema = stringType().regex(/^\d{8}$/, "Date must be in YYYYMMDD format").brand();
3577
3578
  const projectPathSchema = stringType().min(1, "Project path cannot be empty").brand();
3578
3579
  const versionSchema = stringType().regex(/^\d+\.\d+\.\d+/, "Invalid version format").brand();
3579
3580
  const createSessionId = (value) => sessionIdSchema.parse(value);
3580
3581
  const createDailyDate = (value) => dailyDateSchema.parse(value);
3581
3582
  const createMonthlyDate = (value) => monthlyDateSchema.parse(value);
3583
+ const createWeeklyDate = (value) => weeklyDateSchema.parse(value);
3582
3584
  const createProjectPath = (value) => projectPathSchema.parse(value);
3585
+ function createBucket(value) {
3586
+ if (weeklyDateSchema.safeParse(value).success) return createWeeklyDate(value);
3587
+ return createMonthlyDate(value);
3588
+ }
3583
3589
  /**
3584
3590
  * Available cost calculation modes
3585
3591
  * - auto: Use pre-calculated costs when available, otherwise calculate from tokens
@@ -3604,4 +3610,4 @@ const modelPricingSchema = objectType({
3604
3610
  cache_creation_input_token_cost: numberType().optional(),
3605
3611
  cache_read_input_token_cost: numberType().optional()
3606
3612
  });
3607
- export { CostModes, SortOrders, ZodFirstPartyTypeKind, ZodOptional, ZodType, activityDateSchema, arrayType, booleanType, createDailyDate, createMonthlyDate, createProjectPath, createSessionId, dailyDateSchema, discriminatedUnionType, enumType, filterDateSchema, isoTimestampSchema, literalType, messageIdSchema, modelNameSchema, modelPricingSchema, monthlyDateSchema, numberType, objectType, optionalType, projectPathSchema, recordType, requestIdSchema, sessionIdSchema, stringType, unionType, unknownType, versionSchema };
3613
+ export { CostModes, SortOrders, ZodFirstPartyTypeKind, ZodOptional, ZodType, activityDateSchema, arrayType, booleanType, createBucket, createDailyDate, createMonthlyDate, createProjectPath, createSessionId, createWeeklyDate, dailyDateSchema, discriminatedUnionType, enumType, filterDateSchema, isoTimestampSchema, literalType, messageIdSchema, modelNameSchema, modelPricingSchema, monthlyDateSchema, numberType, objectType, optionalType, projectPathSchema, recordType, requestIdSchema, sessionIdSchema, stringType, unionType, unknownType, versionSchema, weeklyDateSchema };
@@ -1,5 +1,5 @@
1
- import { DailyUsage, MonthlyUsage, SessionUsage } from "./data-loader-CgvyDaQD.js";
2
- import "./pricing-fetcher-B3SvKOod.js";
1
+ import "./pricing-fetcher-AYfCy7g-.js";
2
+ import { DailyUsage, MonthlyUsage, SessionUsage, WeeklyUsage } from "./data-loader-DZMUuqrf.js";
3
3
 
4
4
  //#region src/_token-utils.d.ts
5
5
 
@@ -63,7 +63,7 @@ type TotalsObject = TokenTotals & {
63
63
  * @param data - Array of daily, monthly, or session usage data
64
64
  * @returns Aggregated token totals and cost
65
65
  */
66
- declare function calculateTotals(data: Array<DailyUsage | MonthlyUsage | SessionUsage>): TokenTotals;
66
+ declare function calculateTotals(data: Array<DailyUsage | MonthlyUsage | WeeklyUsage | SessionUsage>): TokenTotals;
67
67
  // Re-export getTotalTokens from shared utilities for backward compatibility
68
68
 
69
69
  /**
@@ -1,4 +1,4 @@
1
1
  import { getTotalTokens } from "./_token-utils-WjkbrjKv.js";
2
- import "./_types-BHFM59hI.js";
2
+ import "./_types-ed8-0BH6.js";
3
3
  import { calculateTotals, createTotalsObject } from "./calculate-cost-BDqO4yWA.js";
4
4
  export { calculateTotals, createTotalsObject, getTotalTokens };
@@ -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-eNV76BjF.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-CNjC0nXU.js";
2
2
  import { getTotalTokens } from "./_token-utils-WjkbrjKv.js";
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-COLgmk2z.js";
3
+ import { activityDateSchema, arrayType, booleanType, createBucket, createDailyDate, createMonthlyDate, createProjectPath, createSessionId, createWeeklyDate, dailyDateSchema, isoTimestampSchema, messageIdSchema, modelNameSchema, monthlyDateSchema, numberType, objectType, projectPathSchema, requestIdSchema, sessionIdSchema, stringType, unionType, versionSchema, weeklyDateSchema } from "./_types-ed8-0BH6.js";
4
+ import { logger } from "./logger-C35JCduT.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";
@@ -935,7 +935,7 @@ var require_walker = __commonJSMin((exports) => {
935
935
  counts: new counter_1.Counter(),
936
936
  options,
937
937
  queue: new queue_1.Queue((error, state) => this.callbackInvoker(state, error, callback$1)),
938
- symlinks: /* @__PURE__ */ new Map(),
938
+ symlinks: new Map(),
939
939
  visited: [""].slice(0, 0),
940
940
  controller: new AbortController()
941
941
  };
@@ -2931,7 +2931,7 @@ function isDynamicPattern(pattern, options) {
2931
2931
  return scan$2.isGlob || scan$2.negated;
2932
2932
  }
2933
2933
  function log(...tasks) {
2934
- console.log(`[tinyglobby ${(/* @__PURE__ */ new Date()).toLocaleTimeString("es")}]`, ...tasks);
2934
+ console.log(`[tinyglobby ${new Date().toLocaleTimeString("es")}]`, ...tasks);
2935
2935
  }
2936
2936
  const PARENT_DIRECTORY = /^(\/?\.\.)+/;
2937
2937
  const ESCAPING_BACKSLASHES = /\\(?=[()[\]{}!*+?@|])/g;
@@ -3118,7 +3118,7 @@ function identifySessionBlocks(entries, sessionDurationHours = DEFAULT_SESSION_D
3118
3118
  const sortedEntries = [...entries].sort((a$1, b$1) => a$1.timestamp.getTime() - b$1.timestamp.getTime());
3119
3119
  let currentBlockStart = null;
3120
3120
  let currentBlockEntries = [];
3121
- const now = /* @__PURE__ */ new Date();
3121
+ const now = new Date();
3122
3122
  for (const entry of sortedEntries) {
3123
3123
  const entryTime = entry.timestamp;
3124
3124
  if (currentBlockStart == null) {
@@ -3255,7 +3255,7 @@ function projectBlockUsage(block) {
3255
3255
  if (!block.isActive || (block.isGap ?? false)) return null;
3256
3256
  const burnRate = calculateBurnRate(block);
3257
3257
  if (burnRate == null) return null;
3258
- const now = /* @__PURE__ */ new Date();
3258
+ const now = new Date();
3259
3259
  const remainingTime = block.endTime.getTime() - now.getTime();
3260
3260
  const remainingMinutes = Math.max(0, remainingTime / (1e3 * 60));
3261
3261
  const currentTokens = getTotalTokens(block.tokenCounts);
@@ -3276,21 +3276,22 @@ function projectBlockUsage(block) {
3276
3276
  * @returns Filtered array of recent or active blocks
3277
3277
  */
3278
3278
  function filterRecentBlocks(blocks, days = DEFAULT_RECENT_DAYS) {
3279
- const now = /* @__PURE__ */ new Date();
3280
- const cutoffTime = /* @__PURE__ */ new Date(now.getTime() - days * 24 * 60 * 60 * 1e3);
3279
+ const now = new Date();
3280
+ const cutoffTime = new Date(now.getTime() - days * 24 * 60 * 60 * 1e3);
3281
3281
  return blocks.filter((block) => {
3282
3282
  return block.startTime >= cutoffTime || block.isActive;
3283
3283
  });
3284
3284
  }
3285
3285
  var import_usingCtx = __toESM(require_usingCtx(), 1);
3286
3286
  /**
3287
- * Get all Claude data directories to search for usage data
3288
- * Supports multiple paths: environment variable (comma-separated), new default, and old default
3287
+ * Get Claude data directories to search for usage data
3288
+ * When CLAUDE_CONFIG_DIR is set: uses only those paths
3289
+ * When not set: uses default paths (~/.config/claude and ~/.claude)
3289
3290
  * @returns Array of valid Claude data directory paths
3290
3291
  */
3291
3292
  function getClaudePaths() {
3292
3293
  const paths = [];
3293
- const normalizedPaths = /* @__PURE__ */ new Set();
3294
+ const normalizedPaths = new Set();
3294
3295
  const envPaths = (process$1.env[CLAUDE_CONFIG_DIR_ENV] ?? "").trim();
3295
3296
  if (envPaths !== "") {
3296
3297
  const envPathList = envPaths.split(",").map((p) => p.trim()).filter((p) => p !== "");
@@ -3306,6 +3307,9 @@ function getClaudePaths() {
3306
3307
  }
3307
3308
  }
3308
3309
  }
3310
+ if (paths.length > 0) return paths;
3311
+ throw new Error(`No valid Claude data directories found in CLAUDE_CONFIG_DIR. Please ensure the following exists:
3312
+ - ${envPaths}/${CLAUDE_PROJECTS_DIR_NAME}`.trim());
3309
3313
  }
3310
3314
  const defaultPaths = [DEFAULT_CLAUDE_CONFIG_PATH, path.join(USER_HOME_DIR, DEFAULT_CLAUDE_CODE_PATH)];
3311
3315
  for (const defaultPath of defaultPaths) {
@@ -3416,10 +3420,38 @@ const monthlyUsageSchema = objectType({
3416
3420
  project: stringType().optional()
3417
3421
  });
3418
3422
  /**
3423
+ * Zod schema for weekly usage aggregation data
3424
+ */
3425
+ const weeklyUsageSchema = objectType({
3426
+ week: weeklyDateSchema,
3427
+ inputTokens: numberType(),
3428
+ outputTokens: numberType(),
3429
+ cacheCreationTokens: numberType(),
3430
+ cacheReadTokens: numberType(),
3431
+ totalCost: numberType(),
3432
+ modelsUsed: arrayType(modelNameSchema),
3433
+ modelBreakdowns: arrayType(modelBreakdownSchema),
3434
+ project: stringType().optional()
3435
+ });
3436
+ /**
3437
+ * Zod schema for bucket usage aggregation data
3438
+ */
3439
+ const bucketUsageSchema = objectType({
3440
+ bucket: unionType([weeklyDateSchema, monthlyDateSchema]),
3441
+ inputTokens: numberType(),
3442
+ outputTokens: numberType(),
3443
+ cacheCreationTokens: numberType(),
3444
+ cacheReadTokens: numberType(),
3445
+ totalCost: numberType(),
3446
+ modelsUsed: arrayType(modelNameSchema),
3447
+ modelBreakdowns: arrayType(modelBreakdownSchema),
3448
+ project: stringType().optional()
3449
+ });
3450
+ /**
3419
3451
  * Aggregates token counts and costs by model name
3420
3452
  */
3421
3453
  function aggregateByModel(entries, getModel, getUsage, getCost) {
3422
- const modelAggregates = /* @__PURE__ */ new Map();
3454
+ const modelAggregates = new Map();
3423
3455
  const defaultStats = {
3424
3456
  inputTokens: 0,
3425
3457
  outputTokens: 0,
@@ -3447,7 +3479,7 @@ function aggregateByModel(entries, getModel, getUsage, getCost) {
3447
3479
  * Aggregates model breakdowns from multiple sources
3448
3480
  */
3449
3481
  function aggregateModelBreakdowns(breakdowns) {
3450
- const modelAggregates = /* @__PURE__ */ new Map();
3482
+ const modelAggregates = new Map();
3451
3483
  const defaultStats = {
3452
3484
  inputTokens: 0,
3453
3485
  outputTokens: 0,
@@ -3543,34 +3575,45 @@ function extractUniqueModels(entries, getModel) {
3543
3575
  return uniq(entries.map(getModel).filter((m$1) => m$1 != null && m$1 !== "<synthetic>"));
3544
3576
  }
3545
3577
  /**
3546
- * Shared method for formatting dates with proper timezone handling
3547
- * @param dateStr - Input date string
3548
- * @param twoLine - Whether to format as two lines (true) or single line (false)
3549
- * @returns Formatted date string
3578
+ * Date formatter using Intl.DateTimeFormat for consistent formatting
3579
+ * Using UTC to avoid timezone issues
3550
3580
  */
3551
- function formatDateInternal(dateStr, twoLine) {
3552
- const date = new Date(dateStr);
3553
- const hasTimezone = /Z|[+-]\d{2}:\d{2}/.test(dateStr);
3554
- const year = hasTimezone ? date.getUTCFullYear() : date.getFullYear();
3555
- const month = String(hasTimezone ? date.getUTCMonth() + 1 : date.getMonth() + 1).padStart(2, "0");
3556
- const day = String(hasTimezone ? date.getUTCDate() : date.getDate()).padStart(2, "0");
3557
- return twoLine ? `${year}\n${month}-${day}` : `${year}-${month}-${day}`;
3558
- }
3581
+ const dateFormatter = new Intl.DateTimeFormat("en-CA", {
3582
+ timeZone: "UTC",
3583
+ year: "numeric",
3584
+ month: "2-digit",
3585
+ day: "2-digit"
3586
+ });
3559
3587
  /**
3560
3588
  * Formats a date string to YYYY-MM-DD format
3561
3589
  * @param dateStr - Input date string
3562
3590
  * @returns Formatted date string in YYYY-MM-DD format
3563
3591
  */
3564
3592
  function formatDate(dateStr) {
3565
- return formatDateInternal(dateStr, false);
3593
+ const date = new Date(dateStr);
3594
+ return dateFormatter.format(date);
3566
3595
  }
3567
3596
  /**
3597
+ * Date parts formatter for extracting year, month, and day separately
3598
+ */
3599
+ const datePartsFormatter = new Intl.DateTimeFormat("en", {
3600
+ timeZone: "UTC",
3601
+ year: "numeric",
3602
+ month: "2-digit",
3603
+ day: "2-digit"
3604
+ });
3605
+ /**
3568
3606
  * Formats a date string to compact format with year on first line and month-day on second
3569
3607
  * @param dateStr - Input date string
3570
3608
  * @returns Formatted date string with newline separator (YYYY\nMM-DD)
3571
3609
  */
3572
3610
  function formatDateCompact(dateStr) {
3573
- return formatDateInternal(dateStr, true);
3611
+ const date = new Date(dateStr);
3612
+ const parts = datePartsFormatter.formatToParts(date);
3613
+ const year = parts.find((p) => p.type === "year")?.value ?? "";
3614
+ const month = parts.find((p) => p.type === "month")?.value ?? "";
3615
+ const day = parts.find((p) => p.type === "day")?.value ?? "";
3616
+ return `${year}\n${month}-${day}`;
3574
3617
  }
3575
3618
  /**
3576
3619
  * Generic function to sort items by date based on sort order
@@ -3672,7 +3715,7 @@ function getUsageLimitResetTime(data) {
3672
3715
  const timestampMatch = data.message?.content?.find((c) => c.text != null && c.text.includes("Claude AI usage limit reached"))?.text?.match(/\|(\d+)/) ?? null;
3673
3716
  if (timestampMatch?.[1] != null) {
3674
3717
  const resetTimestamp = Number.parseInt(timestampMatch[1]);
3675
- resetTime = resetTimestamp > 0 ? /* @__PURE__ */ new Date(resetTimestamp * 1e3) : null;
3718
+ resetTime = resetTimestamp > 0 ? new Date(resetTimestamp * 1e3) : null;
3676
3719
  }
3677
3720
  }
3678
3721
  return resetTime;
@@ -3713,7 +3756,7 @@ async function loadDailyUsageData(options) {
3713
3756
  const sortedFiles = await sortFilesByTimestamp(projectFilteredFiles);
3714
3757
  const mode = options?.mode ?? "auto";
3715
3758
  const fetcher = _usingCtx.u(mode === "display" ? null : new PricingFetcher(options?.offline));
3716
- const processedHashes = /* @__PURE__ */ new Set();
3759
+ const processedHashes = new Set();
3717
3760
  const allEntries = [];
3718
3761
  for (const file of sortedFiles) {
3719
3762
  const content = await readFile(file, "utf-8");
@@ -3787,7 +3830,7 @@ async function loadSessionData(options) {
3787
3830
  })));
3788
3831
  const mode = options?.mode ?? "auto";
3789
3832
  const fetcher = _usingCtx3.u(mode === "display" ? null : new PricingFetcher(options?.offline));
3790
- const processedHashes = /* @__PURE__ */ new Set();
3833
+ const processedHashes = new Set();
3791
3834
  const allEntries = [];
3792
3835
  for (const { file, baseDir } of sortedFilesWithBase) {
3793
3836
  const relativePath = path.relative(baseDir, file);
@@ -3854,15 +3897,55 @@ async function loadSessionData(options) {
3854
3897
  * @returns Array of monthly usage summaries sorted by month
3855
3898
  */
3856
3899
  async function loadMonthlyUsageData(options) {
3900
+ return loadBucketUsageData((data) => createMonthlyDate(data.date.substring(0, 7)), options).then((usages) => usages.map(({ bucket,...rest }) => ({
3901
+ month: createMonthlyDate(bucket.toString()),
3902
+ ...rest
3903
+ })));
3904
+ }
3905
+ /**
3906
+ * @param date - The date to get the week for
3907
+ * @param startDay - The day to start the week on (0 = Sunday, 1 = Monday, ..., 6 = Saturday)
3908
+ * @returns The date of the first day of the week for the given date
3909
+ */
3910
+ function getDateWeek(date, startDay) {
3911
+ const d$1 = new Date(date);
3912
+ const day = d$1.getDay();
3913
+ const shift = (day - startDay + 7) % 7;
3914
+ d$1.setDate(d$1.getDate() - shift);
3915
+ return createWeeklyDate(d$1.toISOString().substring(0, 10));
3916
+ }
3917
+ /**
3918
+ * Convert day name to number (0 = Sunday, 1 = Monday, ..., 6 = Saturday)
3919
+ */
3920
+ function getDayNumber(day) {
3921
+ const dayMap = {
3922
+ sunday: 0,
3923
+ monday: 1,
3924
+ tuesday: 2,
3925
+ wednesday: 3,
3926
+ thursday: 4,
3927
+ friday: 5,
3928
+ saturday: 6
3929
+ };
3930
+ return dayMap[day];
3931
+ }
3932
+ async function loadWeeklyUsageData(options) {
3933
+ const startDay = options?.startOfWeek != null ? getDayNumber(options.startOfWeek) : getDayNumber("sunday");
3934
+ return loadBucketUsageData((data) => getDateWeek(new Date(data.date), startDay), options).then((usages) => usages.map(({ bucket,...rest }) => ({
3935
+ week: createWeeklyDate(bucket.toString()),
3936
+ ...rest
3937
+ })));
3938
+ }
3939
+ async function loadBucketUsageData(groupingFn, options) {
3857
3940
  const dailyData = await loadDailyUsageData(options);
3858
3941
  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);
3861
- const monthlyArray = [];
3862
- for (const [groupKey, dailyEntries] of Object.entries(groupedByMonth)) {
3942
+ const groupingKey = needsProjectGrouping ? (data) => `${groupingFn(data)}\x00${data.project ?? "unknown"}` : (data) => `${groupingFn(data)}`;
3943
+ const grouped = groupBy(dailyData, groupingKey);
3944
+ const buckets = [];
3945
+ for (const [groupKey, dailyEntries] of Object.entries(grouped)) {
3863
3946
  if (dailyEntries == null) continue;
3864
3947
  const parts = groupKey.split("\0");
3865
- const month = parts[0] ?? groupKey;
3948
+ const bucket = createBucket(parts[0] ?? groupKey);
3866
3949
  const project = parts.length > 1 ? parts[1] : void 0;
3867
3950
  const allBreakdowns = dailyEntries.flatMap((daily) => daily.modelBreakdowns);
3868
3951
  const modelAggregates = aggregateModelBreakdowns(allBreakdowns);
@@ -3881,8 +3964,8 @@ async function loadMonthlyUsageData(options) {
3881
3964
  totalCacheReadTokens += daily.cacheReadTokens;
3882
3965
  totalCost += daily.totalCost;
3883
3966
  }
3884
- const monthlyUsage = {
3885
- month: createMonthlyDate(month),
3967
+ const bucketUsage = {
3968
+ bucket,
3886
3969
  inputTokens: totalInputTokens,
3887
3970
  outputTokens: totalOutputTokens,
3888
3971
  cacheCreationTokens: totalCacheCreationTokens,
@@ -3892,9 +3975,9 @@ async function loadMonthlyUsageData(options) {
3892
3975
  modelBreakdowns,
3893
3976
  ...project != null && { project }
3894
3977
  };
3895
- monthlyArray.push(monthlyUsage);
3978
+ buckets.push(bucketUsage);
3896
3979
  }
3897
- return sortByDate(monthlyArray, (item) => `${item.month}-01`, options?.order);
3980
+ return sortByDate(buckets, (item) => item.bucket, options?.order);
3898
3981
  }
3899
3982
  /**
3900
3983
  * Loads usage data and organizes it into session blocks (typically 5-hour billing periods)
@@ -3920,7 +4003,7 @@ async function loadSessionBlockData(options) {
3920
4003
  const sortedFiles = await sortFilesByTimestamp(blocksFilteredFiles);
3921
4004
  const mode = options?.mode ?? "auto";
3922
4005
  const fetcher = _usingCtx4.u(mode === "display" ? null : new PricingFetcher(options?.offline));
3923
- const processedHashes = /* @__PURE__ */ new Set();
4006
+ const processedHashes = new Set();
3924
4007
  const allEntries = [];
3925
4008
  for (const file of sortedFiles) {
3926
4009
  const content = await readFile(file, "utf-8");
@@ -3966,4 +4049,4 @@ async function loadSessionBlockData(options) {
3966
4049
  _usingCtx4.d();
3967
4050
  }
3968
4051
  }
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 };
4052
+ export { DEFAULT_SESSION_DURATION_HOURS, bucketUsageSchema, calculateBurnRate, calculateCostForEntry, createUniqueHash, dailyUsageSchema, extractProjectFromPath, filterRecentBlocks, formatDate, formatDateCompact, getClaudePaths, getEarliestTimestamp, getUsageLimitResetTime, glob, globUsageFiles, identifySessionBlocks, loadBucketUsageData, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, loadWeeklyUsageData, modelBreakdownSchema, monthlyUsageSchema, projectBlockUsage, sessionUsageSchema, sortFilesByTimestamp, uniq, unwrap, usageDataSchema, weeklyUsageSchema };