ccusage 15.1.0 → 15.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { BLOCKS_COMPACT_WIDTH_THRESHOLD, BLOCKS_DEFAULT_TERMINAL_WIDTH, BLOCKS_WARNING_THRESHOLD, CLAUDE_PROJECTS_DIR_NAME, DEFAULT_RECENT_DAYS, DEFAULT_REFRESH_INTERVAL_SECONDS, MAX_REFRESH_INTERVAL_SECONDS, MCP_DEFAULT_PORT, MIN_REFRESH_INTERVAL_SECONDS, PricingFetcher, USAGE_DATA_GLOB_PATTERN, __commonJSMin, __require, __toESM, require_usingCtx } from "./pricing-fetcher-Dm8hcn_h.js";
3
- import { CostModes, SortOrders, dateSchema } from "./_types-Cr2YEzKm.js";
4
- import { calculateTotals, createTotalsObject, getTotalTokens } from "./calculate-cost-CoS7we68.js";
5
- import { DEFAULT_SESSION_DURATION_HOURS, calculateBurnRate, calculateCostForEntry, createUniqueHash, filterRecentBlocks, formatDateCompact, getDefaultClaudePath, getEarliestTimestamp, glob, identifySessionBlocks, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, projectBlockUsage, sortFilesByTimestamp, uniq, usageDataSchema } from "./data-loader-BeaFK_sH.js";
6
- import { description, log, logger, name, version } from "./logger-Cke8hliP.js";
7
- import { detectMismatches, printMismatchReport } from "./debug-BmJuGBXC.js";
8
- import { createMcpHttpApp, createMcpServer, startMcpServerStdio } from "./mcp-DKqp_F9c.js";
2
+ import { BLOCKS_COMPACT_WIDTH_THRESHOLD, BLOCKS_DEFAULT_TERMINAL_WIDTH, BLOCKS_WARNING_THRESHOLD, CLAUDE_PROJECTS_DIR_NAME, 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, __commonJSMin, __require, __toESM, isFailure, require_usingCtx, try_ } from "./pricing-fetcher-DaK2jizg.js";
3
+ import { getTotalTokens } from "./_token-utils-WjkbrjKv.js";
4
+ import { CostModes, SortOrders, filterDateSchema } from "./_types-BHFM59hI.js";
5
+ import { calculateTotals, createTotalsObject } from "./calculate-cost-BDqO4yWA.js";
6
+ import { DEFAULT_SESSION_DURATION_HOURS, calculateBurnRate, calculateCostForEntry, createUniqueHash, filterRecentBlocks, formatDateCompact, getClaudePaths, getEarliestTimestamp, getUsageLimitResetTime, glob, identifySessionBlocks, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, projectBlockUsage, sortFilesByTimestamp, uniq, usageDataSchema } from "./data-loader-R9pMvoQp.js";
7
+ import { description, log, logger, name, version } from "./logger-DeTONwj8.js";
8
+ import { detectMismatches, printMismatchReport } from "./debug-Cyr1LS0T.js";
9
+ import { createMcpHttpApp, createMcpServer, startMcpServerStdio } from "./mcp-Kff7A2dF.js";
9
10
  import { readFile } from "node:fs/promises";
10
11
  import path from "node:path";
11
12
  import process$1 from "node:process";
@@ -1194,7 +1195,7 @@ var require_picocolors = __commonJSMin((exports, module) => {
1194
1195
  * @throws TypeError if date format is invalid
1195
1196
  */
1196
1197
  function parseDateArg(value) {
1197
- const result = dateSchema.safeParse(value);
1198
+ const result = filterDateSchema.safeParse(value);
1198
1199
  if (!result.success) throw new TypeError(result.error.issues[0]?.message ?? "Invalid date format");
1199
1200
  return result.data;
1200
1201
  }
@@ -1257,6 +1258,14 @@ const sharedArgs = {
1257
1258
  short: "O",
1258
1259
  description: "Use cached pricing data for Claude models instead of fetching from API",
1259
1260
  default: false
1261
+ },
1262
+ color: {
1263
+ type: "boolean",
1264
+ description: "Enable colored output (default: auto). FORCE_COLOR=1 has the same effect."
1265
+ },
1266
+ noColor: {
1267
+ type: "boolean",
1268
+ description: "Disable colored output (default: auto). NO_COLOR=1 has the same effect."
1260
1269
  }
1261
1270
  };
1262
1271
  /**
@@ -2149,7 +2158,7 @@ var require_colors = __commonJSMin((exports, module) => {
2149
2158
  var util = __require("node:util");
2150
2159
  var ansiStyles = colors$1.styles = require_styles();
2151
2160
  var defineProps = Object.defineProperties;
2152
- var newLineRegex = new RegExp(/[\r\n]+/g);
2161
+ var newLineRegex = /* @__PURE__ */ new RegExp(/[\r\n]+/g);
2153
2162
  colors$1.supportsColor = require_supports_colors().supportsColor;
2154
2163
  if (typeof colors$1.enabled === "undefined") colors$1.enabled = colors$1.supportsColor() !== false;
2155
2164
  colors$1.enable = function() {
@@ -2931,7 +2940,7 @@ function stringWidth(string, options = {}) {
2931
2940
  return width;
2932
2941
  }
2933
2942
  var import_cli_table3 = __toESM(require_cli_table3(), 1);
2934
- var import_picocolors$5 = __toESM(require_picocolors(), 1);
2943
+ var import_picocolors$6 = __toESM(require_picocolors(), 1);
2935
2944
  /**
2936
2945
  * Responsive table class that adapts column widths based on terminal size
2937
2946
  * Automatically adjusts formatting and layout for different screen sizes
@@ -3167,11 +3176,96 @@ function pushBreakdownRows(table, breakdowns, extraColumns = 1, trailingColumns
3167
3176
  const row = [` └─ ${formatModelName(breakdown.modelName)}`];
3168
3177
  for (let i = 0; i < extraColumns; i++) row.push("");
3169
3178
  const totalTokens = breakdown.inputTokens + breakdown.outputTokens + breakdown.cacheCreationTokens + breakdown.cacheReadTokens;
3170
- row.push(import_picocolors$5.default.gray(formatNumber(breakdown.inputTokens)), import_picocolors$5.default.gray(formatNumber(breakdown.outputTokens)), import_picocolors$5.default.gray(formatNumber(breakdown.cacheCreationTokens)), import_picocolors$5.default.gray(formatNumber(breakdown.cacheReadTokens)), import_picocolors$5.default.gray(formatNumber(totalTokens)), import_picocolors$5.default.gray(formatCurrency(breakdown.cost)));
3179
+ row.push(import_picocolors$6.default.gray(formatNumber(breakdown.inputTokens)), import_picocolors$6.default.gray(formatNumber(breakdown.outputTokens)), import_picocolors$6.default.gray(formatNumber(breakdown.cacheCreationTokens)), import_picocolors$6.default.gray(formatNumber(breakdown.cacheReadTokens)), import_picocolors$6.default.gray(formatNumber(totalTokens)), import_picocolors$6.default.gray(formatCurrency(breakdown.cost)));
3171
3180
  for (let i = 0; i < trailingColumns; i++) row.push("");
3172
3181
  table.push(row);
3173
3182
  }
3174
3183
  }
3184
+ /**
3185
+ * Manages live monitoring of Claude usage with efficient data reloading
3186
+ */
3187
+ var LiveMonitor = class {
3188
+ config;
3189
+ fetcher = null;
3190
+ lastFileTimestamps = /* @__PURE__ */ new Map();
3191
+ processedHashes = /* @__PURE__ */ new Set();
3192
+ allEntries = [];
3193
+ constructor(config) {
3194
+ this.config = config;
3195
+ if (config.mode !== "display") this.fetcher = new PricingFetcher();
3196
+ }
3197
+ /**
3198
+ * Implements Disposable interface
3199
+ */
3200
+ [Symbol.dispose]() {
3201
+ this.fetcher?.[Symbol.dispose]();
3202
+ }
3203
+ /**
3204
+ * Gets the current active session block with minimal file reading
3205
+ * Only reads new or modified files since last check
3206
+ */
3207
+ async getActiveBlock() {
3208
+ const claudeDir = path.join(this.config.claudePath, CLAUDE_PROJECTS_DIR_NAME);
3209
+ const files = await glob([USAGE_DATA_GLOB_PATTERN], {
3210
+ cwd: claudeDir,
3211
+ absolute: true
3212
+ });
3213
+ if (files.length === 0) return null;
3214
+ const filesToRead = [];
3215
+ for (const file of files) {
3216
+ const timestamp = await getEarliestTimestamp(file);
3217
+ const lastTimestamp = this.lastFileTimestamps.get(file);
3218
+ if (timestamp != null && (lastTimestamp == null || timestamp.getTime() > lastTimestamp)) {
3219
+ filesToRead.push(file);
3220
+ this.lastFileTimestamps.set(file, timestamp.getTime());
3221
+ }
3222
+ }
3223
+ if (filesToRead.length > 0) {
3224
+ const sortedFiles = await sortFilesByTimestamp(filesToRead);
3225
+ for (const file of sortedFiles) {
3226
+ const content = await readFile(file, "utf-8").catch(() => {
3227
+ return "";
3228
+ });
3229
+ const lines = content.trim().split("\n").filter((line) => line.length > 0);
3230
+ for (const line of lines) try {
3231
+ const parsed = JSON.parse(line);
3232
+ const result = usageDataSchema.safeParse(parsed);
3233
+ if (!result.success) continue;
3234
+ const data = result.data;
3235
+ const uniqueHash = createUniqueHash(data);
3236
+ if (uniqueHash != null && this.processedHashes.has(uniqueHash)) continue;
3237
+ if (uniqueHash != null) this.processedHashes.add(uniqueHash);
3238
+ const costUSD = await (this.config.mode === "display" ? Promise.resolve(data.costUSD ?? 0) : calculateCostForEntry(data, this.config.mode, this.fetcher));
3239
+ const usageLimitResetTime = getUsageLimitResetTime(data);
3240
+ this.allEntries.push({
3241
+ timestamp: new Date(data.timestamp),
3242
+ usage: {
3243
+ inputTokens: data.message.usage.input_tokens ?? 0,
3244
+ outputTokens: data.message.usage.output_tokens ?? 0,
3245
+ cacheCreationInputTokens: data.message.usage.cache_creation_input_tokens ?? 0,
3246
+ cacheReadInputTokens: data.message.usage.cache_read_input_tokens ?? 0
3247
+ },
3248
+ costUSD,
3249
+ model: data.message.model ?? "<synthetic>",
3250
+ version: data.version,
3251
+ usageLimitResetTime: usageLimitResetTime ?? void 0
3252
+ });
3253
+ } catch {}
3254
+ }
3255
+ }
3256
+ const blocks = identifySessionBlocks(this.allEntries, this.config.sessionDurationHours);
3257
+ const sortedBlocks = this.config.order === "asc" ? blocks : blocks.reverse();
3258
+ return sortedBlocks.find((block) => block.isActive) ?? null;
3259
+ }
3260
+ /**
3261
+ * Clears all cached data to force a full reload
3262
+ */
3263
+ clearCache() {
3264
+ this.lastFileTimestamps.clear();
3265
+ this.processedHashes.clear();
3266
+ this.allEntries = [];
3267
+ }
3268
+ };
3175
3269
  /** Options for {@linkcode delay}. */
3176
3270
  /**
3177
3271
  * Resolve a {@linkcode Promise} after a given amount of milliseconds.
@@ -3225,6 +3319,54 @@ function pushBreakdownRows(table, breakdowns, extraColumns = 1, trailingColumns
3225
3319
  }
3226
3320
  });
3227
3321
  }
3322
+ const isBrowser = globalThis.window?.document !== void 0;
3323
+ const isNode = globalThis.process?.versions?.node !== void 0;
3324
+ const isBun = globalThis.process?.versions?.bun !== void 0;
3325
+ const isDeno = globalThis.Deno?.version?.deno !== void 0;
3326
+ const isElectron = globalThis.process?.versions?.electron !== void 0;
3327
+ const isJsDom = globalThis.navigator?.userAgent?.includes("jsdom") === true;
3328
+ const isWebWorker = typeof WorkerGlobalScope !== "undefined" && globalThis instanceof WorkerGlobalScope;
3329
+ const isDedicatedWorker = typeof DedicatedWorkerGlobalScope !== "undefined" && globalThis instanceof DedicatedWorkerGlobalScope;
3330
+ const isSharedWorker = typeof SharedWorkerGlobalScope !== "undefined" && globalThis instanceof SharedWorkerGlobalScope;
3331
+ const isServiceWorker = typeof ServiceWorkerGlobalScope !== "undefined" && globalThis instanceof ServiceWorkerGlobalScope;
3332
+ const platform = globalThis.navigator?.userAgentData?.platform;
3333
+ const isMacOs = platform === "macOS" || globalThis.navigator?.platform === "MacIntel" || globalThis.navigator?.userAgent?.includes(" Mac ") === true || globalThis.process?.platform === "darwin";
3334
+ const isWindows$1 = platform === "Windows" || globalThis.navigator?.platform === "Win32" || globalThis.process?.platform === "win32";
3335
+ const isLinux = platform === "Linux" || globalThis.navigator?.platform?.startsWith("Linux") === true || globalThis.navigator?.userAgent?.includes(" Linux ") === true || globalThis.process?.platform === "linux";
3336
+ const isIos = platform === "iOS" || globalThis.navigator?.platform === "MacIntel" && globalThis.navigator?.maxTouchPoints > 1 || /iPad|iPhone|iPod/.test(globalThis.navigator?.platform);
3337
+ const isAndroid = platform === "Android" || globalThis.navigator?.platform === "Android" || globalThis.navigator?.userAgent?.includes(" Android ") === true || globalThis.process?.platform === "android";
3338
+ const ESC = "\x1B[";
3339
+ const SEP = ";";
3340
+ const isTerminalApp = !isBrowser && process$1.env.TERM_PROGRAM === "Apple_Terminal";
3341
+ const isWindows = !isBrowser && process$1.platform === "win32";
3342
+ const cwdFunction = isBrowser ? () => {
3343
+ throw new Error("`process.cwd()` only works in Node.js, not the browser.");
3344
+ } : process$1.cwd;
3345
+ const cursorTo = (x, y) => {
3346
+ if (typeof x !== "number") throw new TypeError("The `x` argument is required");
3347
+ if (typeof y !== "number") return ESC + (x + 1) + "G";
3348
+ return ESC + (y + 1) + SEP + (x + 1) + "H";
3349
+ };
3350
+ const cursorLeft = ESC + "G";
3351
+ const cursorSavePosition = isTerminalApp ? "\x1B7" : ESC + "s";
3352
+ const cursorRestorePosition = isTerminalApp ? "\x1B8" : ESC + "u";
3353
+ const cursorGetPosition = ESC + "6n";
3354
+ const cursorNextLine = ESC + "E";
3355
+ const cursorPrevLine = ESC + "F";
3356
+ const cursorHide = ESC + "?25l";
3357
+ const cursorShow = ESC + "?25h";
3358
+ const eraseEndLine = ESC + "K";
3359
+ const eraseStartLine = ESC + "1K";
3360
+ const eraseLine = ESC + "2K";
3361
+ const eraseDown = ESC + "J";
3362
+ const eraseUp = ESC + "1J";
3363
+ const eraseScreen = ESC + "2J";
3364
+ const scrollUp = ESC + "S";
3365
+ const scrollDown = ESC + "T";
3366
+ const clearScreen = "\x1Bc";
3367
+ const clearTerminal = isWindows ? `${eraseScreen}${ESC}0f` : `${eraseScreen}${ESC}3J${ESC}H`;
3368
+ const enterAlternativeScreen = ESC + "?1049h";
3369
+ const exitAlternativeScreen = ESC + "?1049l";
3228
3370
  const toZeroIfInfinity = (value) => Number.isFinite(value) ? value : 0;
3229
3371
  function parseNumber(milliseconds) {
3230
3372
  return {
@@ -3336,255 +3478,170 @@ function prettyMilliseconds(milliseconds, options) {
3336
3478
  if (typeof options.unitCount === "number") result = result.slice(0, Math.max(options.unitCount, 1));
3337
3479
  return sign + result.join(separator);
3338
3480
  }
3339
- /**
3340
- * Manages live monitoring of Claude usage with efficient data reloading
3341
- */
3342
- var LiveMonitor = class {
3343
- config;
3344
- fetcher = null;
3345
- lastFileTimestamps = /* @__PURE__ */ new Map();
3346
- processedHashes = /* @__PURE__ */ new Set();
3347
- allEntries = [];
3348
- constructor(config) {
3349
- this.config = config;
3350
- if (config.mode !== "display") this.fetcher = new PricingFetcher();
3351
- }
3352
- /**
3353
- * Implements Disposable interface
3354
- */
3355
- [Symbol.dispose]() {
3356
- this.fetcher?.[Symbol.dispose]();
3357
- }
3358
- /**
3359
- * Gets the current active session block with minimal file reading
3360
- * Only reads new or modified files since last check
3361
- */
3362
- async getActiveBlock() {
3363
- const claudeDir = path.join(this.config.claudePath, CLAUDE_PROJECTS_DIR_NAME);
3364
- const files = await glob([USAGE_DATA_GLOB_PATTERN], {
3365
- cwd: claudeDir,
3366
- absolute: true
3367
- });
3368
- if (files.length === 0) return null;
3369
- const filesToRead = [];
3370
- for (const file of files) {
3371
- const timestamp = await getEarliestTimestamp(file);
3372
- const lastTimestamp = this.lastFileTimestamps.get(file);
3373
- if (timestamp != null && (lastTimestamp == null || timestamp.getTime() > lastTimestamp)) {
3374
- filesToRead.push(file);
3375
- this.lastFileTimestamps.set(file, timestamp.getTime());
3376
- }
3377
- }
3378
- if (filesToRead.length > 0) {
3379
- const sortedFiles = await sortFilesByTimestamp(filesToRead);
3380
- for (const file of sortedFiles) {
3381
- const content = await readFile(file, "utf-8");
3382
- const lines = content.trim().split("\n").filter((line) => line.length > 0);
3383
- for (const line of lines) try {
3384
- const parsed = JSON.parse(line);
3385
- const result = usageDataSchema.safeParse(parsed);
3386
- if (!result.success) continue;
3387
- const data = result.data;
3388
- const uniqueHash = createUniqueHash(data);
3389
- if (uniqueHash != null && this.processedHashes.has(uniqueHash)) continue;
3390
- if (uniqueHash != null) this.processedHashes.add(uniqueHash);
3391
- const costUSD = await (this.config.mode === "display" ? Promise.resolve(data.costUSD ?? 0) : calculateCostForEntry(data, this.config.mode, this.fetcher));
3392
- this.allEntries.push({
3393
- timestamp: new Date(data.timestamp),
3394
- usage: {
3395
- inputTokens: data.message.usage.input_tokens ?? 0,
3396
- outputTokens: data.message.usage.output_tokens ?? 0,
3397
- cacheCreationInputTokens: data.message.usage.cache_creation_input_tokens ?? 0,
3398
- cacheReadInputTokens: data.message.usage.cache_read_input_tokens ?? 0
3399
- },
3400
- costUSD,
3401
- model: data.message.model ?? "<synthetic>",
3402
- version: data.version
3403
- });
3404
- } catch {}
3405
- }
3406
- }
3407
- const blocks = identifySessionBlocks(this.allEntries, this.config.sessionDurationHours);
3408
- const sortedBlocks = this.config.order === "asc" ? blocks : blocks.reverse();
3409
- return sortedBlocks.find((block) => block.isActive) ?? null;
3410
- }
3411
- /**
3412
- * Clears all cached data to force a full reload
3413
- */
3414
- clearCache() {
3415
- this.lastFileTimestamps.clear();
3416
- this.processedHashes.clear();
3417
- this.allEntries = [];
3418
- }
3419
- };
3420
- const isBrowser = globalThis.window?.document !== void 0;
3421
- const isNode = globalThis.process?.versions?.node !== void 0;
3422
- const isBun = globalThis.process?.versions?.bun !== void 0;
3423
- const isDeno = globalThis.Deno?.version?.deno !== void 0;
3424
- const isElectron = globalThis.process?.versions?.electron !== void 0;
3425
- const isJsDom = globalThis.navigator?.userAgent?.includes("jsdom") === true;
3426
- const isWebWorker = typeof WorkerGlobalScope !== "undefined" && globalThis instanceof WorkerGlobalScope;
3427
- const isDedicatedWorker = typeof DedicatedWorkerGlobalScope !== "undefined" && globalThis instanceof DedicatedWorkerGlobalScope;
3428
- const isSharedWorker = typeof SharedWorkerGlobalScope !== "undefined" && globalThis instanceof SharedWorkerGlobalScope;
3429
- const isServiceWorker = typeof ServiceWorkerGlobalScope !== "undefined" && globalThis instanceof ServiceWorkerGlobalScope;
3430
- const platform = globalThis.navigator?.userAgentData?.platform;
3431
- const isMacOs = platform === "macOS" || globalThis.navigator?.platform === "MacIntel" || globalThis.navigator?.userAgent?.includes(" Mac ") === true || globalThis.process?.platform === "darwin";
3432
- const isWindows$1 = platform === "Windows" || globalThis.navigator?.platform === "Win32" || globalThis.process?.platform === "win32";
3433
- const isLinux = platform === "Linux" || globalThis.navigator?.platform?.startsWith("Linux") === true || globalThis.navigator?.userAgent?.includes(" Linux ") === true || globalThis.process?.platform === "linux";
3434
- const isIos = platform === "iOS" || globalThis.navigator?.platform === "MacIntel" && globalThis.navigator?.maxTouchPoints > 1 || /iPad|iPhone|iPod/.test(globalThis.navigator?.platform);
3435
- const isAndroid = platform === "Android" || globalThis.navigator?.platform === "Android" || globalThis.navigator?.userAgent?.includes(" Android ") === true || globalThis.process?.platform === "android";
3436
- const ESC = "\x1B[";
3437
- const SEP = ";";
3438
- const isTerminalApp = !isBrowser && process$1.env.TERM_PROGRAM === "Apple_Terminal";
3439
- const isWindows = !isBrowser && process$1.platform === "win32";
3440
- const cwdFunction = isBrowser ? () => {
3441
- throw new Error("`process.cwd()` only works in Node.js, not the browser.");
3442
- } : process$1.cwd;
3443
- const cursorTo = (x, y) => {
3444
- if (typeof x !== "number") throw new TypeError("The `x` argument is required");
3445
- if (typeof y !== "number") return ESC + (x + 1) + "G";
3446
- return ESC + (y + 1) + SEP + (x + 1) + "H";
3447
- };
3448
- const cursorUp = (count = 1) => ESC + count + "A";
3449
- const cursorDown = (count = 1) => ESC + count + "B";
3450
- const cursorLeft = ESC + "G";
3451
- const cursorSavePosition = isTerminalApp ? "\x1B7" : ESC + "s";
3452
- const cursorRestorePosition = isTerminalApp ? "\x1B8" : ESC + "u";
3453
- const cursorGetPosition = ESC + "6n";
3454
- const cursorNextLine = ESC + "E";
3455
- const cursorPrevLine = ESC + "F";
3456
- const cursorHide = ESC + "?25l";
3457
- const cursorShow = ESC + "?25h";
3458
- const eraseEndLine = ESC + "K";
3459
- const eraseStartLine = ESC + "1K";
3460
- const eraseLine = ESC + "2K";
3461
- const eraseDown = ESC + "J";
3462
- const eraseUp = ESC + "1J";
3463
- const eraseScreen = ESC + "2J";
3464
- const scrollUp = ESC + "S";
3465
- const scrollDown = ESC + "T";
3466
- const clearScreen = "\x1Bc";
3467
- const clearTerminal = isWindows ? `${eraseScreen}${ESC}0f` : `${eraseScreen}${ESC}3J${ESC}H`;
3468
- const enterAlternativeScreen = ESC + "?1049h";
3469
- const exitAlternativeScreen = ESC + "?1049l";
3470
- /**
3471
- * Terminal control sequences for live display updates
3472
- */
3473
- const TERMINAL_CONTROL = {
3474
- HIDE_CURSOR: cursorHide,
3475
- SHOW_CURSOR: cursorShow,
3476
- CLEAR_SCREEN: clearScreen,
3477
- CLEAR_LINE: eraseLine,
3478
- MOVE_TO_TOP: cursorTo(0, 0),
3479
- MOVE_UP: (n) => cursorUp(n),
3480
- MOVE_DOWN: (n) => cursorDown(n),
3481
- MOVE_TO_COLUMN: (n) => cursorTo(n - 1, void 0)
3482
- };
3481
+ const SYNC_START = "\x1B[?2026h";
3482
+ const SYNC_END = "\x1B[?2026l";
3483
+ const DISABLE_LINE_WRAP = "\x1B[?7l";
3484
+ const ENABLE_LINE_WRAP = "\x1B[?7h";
3485
+ const ANSI_RESET = "\x1B[0m";
3483
3486
  /**
3484
3487
  * Manages terminal state for live updates
3488
+ * Provides a clean interface for terminal operations with automatic TTY checking
3489
+ * and cursor state management for live monitoring displays
3485
3490
  */
3486
3491
  var TerminalManager = class {
3487
3492
  stream;
3488
3493
  cursorHidden = false;
3489
- originalWrite;
3494
+ buffer = [];
3495
+ useBuffering = false;
3496
+ alternateScreenActive = false;
3497
+ syncMode = false;
3490
3498
  constructor(stream = process$1.stdout) {
3491
3499
  this.stream = stream;
3492
- this.originalWrite = stream.write.bind(stream);
3493
3500
  }
3494
3501
  /**
3495
- * Hides the terminal cursor
3502
+ * Hides the terminal cursor for cleaner live updates
3503
+ * Only works in TTY environments (real terminals)
3496
3504
  */
3497
3505
  hideCursor() {
3498
3506
  if (!this.cursorHidden && this.stream.isTTY) {
3499
- this.stream.write(TERMINAL_CONTROL.HIDE_CURSOR);
3507
+ this.stream.write(cursorHide);
3500
3508
  this.cursorHidden = true;
3501
3509
  }
3502
3510
  }
3503
3511
  /**
3504
3512
  * Shows the terminal cursor
3513
+ * Should be called during cleanup to restore normal terminal behavior
3505
3514
  */
3506
3515
  showCursor() {
3507
3516
  if (this.cursorHidden && this.stream.isTTY) {
3508
- this.stream.write(TERMINAL_CONTROL.SHOW_CURSOR);
3517
+ this.stream.write(cursorShow);
3509
3518
  this.cursorHidden = false;
3510
3519
  }
3511
3520
  }
3512
3521
  /**
3513
- * Clears the entire screen and moves cursor to top
3522
+ * Clears the entire screen and moves cursor to top-left corner
3523
+ * Essential for live monitoring displays that need to refresh completely
3514
3524
  */
3515
3525
  clearScreen() {
3516
3526
  if (this.stream.isTTY) {
3517
- this.stream.write(TERMINAL_CONTROL.CLEAR_SCREEN);
3518
- this.stream.write(TERMINAL_CONTROL.MOVE_TO_TOP);
3527
+ this.stream.write(clearScreen);
3528
+ this.stream.write(cursorTo(0, 0));
3529
+ }
3530
+ }
3531
+ /**
3532
+ * Writes text to the terminal stream
3533
+ * Supports buffering mode for performance optimization
3534
+ */
3535
+ write(text) {
3536
+ if (this.useBuffering) this.buffer.push(text);
3537
+ else this.stream.write(text);
3538
+ }
3539
+ /**
3540
+ * Enables buffering mode - collects all writes in memory instead of sending immediately
3541
+ * This prevents flickering when doing many rapid updates
3542
+ */
3543
+ startBuffering() {
3544
+ this.useBuffering = true;
3545
+ this.buffer = [];
3546
+ }
3547
+ /**
3548
+ * Sends all buffered content to terminal at once
3549
+ * This creates smooth, atomic updates without flickering
3550
+ */
3551
+ flush() {
3552
+ if (this.useBuffering && this.buffer.length > 0) {
3553
+ if (this.syncMode && this.stream.isTTY) this.stream.write(SYNC_START + this.buffer.join("") + SYNC_END);
3554
+ else this.stream.write(this.buffer.join(""));
3555
+ this.buffer = [];
3519
3556
  }
3557
+ this.useBuffering = false;
3520
3558
  }
3521
3559
  /**
3522
- * Clears the current line
3560
+ * Switches to alternate screen buffer (like vim/less does)
3561
+ * This preserves what was on screen before and allows full-screen apps
3523
3562
  */
3524
- clearLine() {
3525
- if (this.stream.isTTY) this.stream.write(TERMINAL_CONTROL.CLEAR_LINE);
3563
+ enterAlternateScreen() {
3564
+ if (!this.alternateScreenActive && this.stream.isTTY) {
3565
+ this.stream.write(enterAlternativeScreen);
3566
+ this.stream.write(DISABLE_LINE_WRAP);
3567
+ this.alternateScreenActive = true;
3568
+ }
3526
3569
  }
3527
3570
  /**
3528
- * Moves cursor up by n lines
3571
+ * Returns to normal screen, restoring what was there before
3529
3572
  */
3530
- moveUp(lines) {
3531
- if (this.stream.isTTY && lines > 0) this.stream.write(TERMINAL_CONTROL.MOVE_UP(lines));
3573
+ exitAlternateScreen() {
3574
+ if (this.alternateScreenActive && this.stream.isTTY) {
3575
+ this.stream.write(ENABLE_LINE_WRAP);
3576
+ this.stream.write(exitAlternativeScreen);
3577
+ this.alternateScreenActive = false;
3578
+ }
3532
3579
  }
3533
3580
  /**
3534
- * Moves cursor to beginning of line
3581
+ * Enables sync mode - terminal will wait for END signal before showing updates
3582
+ * Prevents the user from seeing partial/torn screen updates
3535
3583
  */
3536
- moveToLineStart() {
3537
- if (this.stream.isTTY) this.stream.write(TERMINAL_CONTROL.MOVE_TO_COLUMN(1));
3584
+ enableSyncMode() {
3585
+ this.syncMode = true;
3538
3586
  }
3539
3587
  /**
3540
- * Writes text to the stream
3588
+ * Disables synchronized output mode
3541
3589
  */
3542
- write(text) {
3543
- this.stream.write(text);
3590
+ disableSyncMode() {
3591
+ this.syncMode = false;
3544
3592
  }
3545
3593
  /**
3546
- * Gets terminal width
3594
+ * Gets terminal width in columns
3595
+ * Falls back to 80 columns if detection fails
3547
3596
  */
3548
3597
  get width() {
3549
3598
  return this.stream.columns || 80;
3550
3599
  }
3551
3600
  /**
3552
- * Gets terminal height
3601
+ * Gets terminal height in rows
3602
+ * Falls back to 24 rows if detection fails
3553
3603
  */
3554
3604
  get height() {
3555
3605
  return this.stream.rows || 24;
3556
3606
  }
3557
3607
  /**
3558
- * Checks if the stream is a TTY
3608
+ * Returns true if output goes to a real terminal (not a file or pipe)
3609
+ * We only send fancy ANSI codes to real terminals
3559
3610
  */
3560
3611
  get isTTY() {
3561
3612
  return this.stream.isTTY ?? false;
3562
3613
  }
3563
3614
  /**
3564
- * Ensures cursor is shown on cleanup
3615
+ * Restores terminal to normal state - MUST call before program exits
3616
+ * Otherwise user's terminal might be left in a broken state
3565
3617
  */
3566
3618
  cleanup() {
3567
3619
  this.showCursor();
3620
+ this.exitAlternateScreen();
3621
+ this.disableSyncMode();
3568
3622
  }
3569
3623
  };
3570
3624
  /**
3571
- * Creates a progress bar string
3572
- * @param value - Current value
3573
- * @param max - Maximum value
3574
- * @param width - Width of the progress bar
3575
- * @param options - Display options
3576
- * @param options.showPercentage - Whether to show percentage
3625
+ * Creates a progress bar string with customizable appearance
3626
+ *
3627
+ * Example: createProgressBar(75, 100, 20) -> "[████████████████░░░░] 75.0%"
3628
+ *
3629
+ * @param value - Current progress value
3630
+ * @param max - Maximum value (100% point)
3631
+ * @param width - Character width of the progress bar (excluding brackets and text)
3632
+ * @param options - Customization options for appearance and display
3633
+ * @param options.showPercentage - Whether to show percentage after the bar
3577
3634
  * @param options.showValues - Whether to show current/max values
3578
- * @param options.fillChar - Character for filled portion
3579
- * @param options.emptyChar - Character for empty portion
3580
- * @param options.leftBracket - Left bracket character
3581
- * @param options.rightBracket - Right bracket character
3582
- * @param options.colors - Color configuration
3583
- * @param options.colors.low - Color for low percentage
3584
- * @param options.colors.medium - Color for medium percentage
3585
- * @param options.colors.high - Color for high percentage
3586
- * @param options.colors.critical - Color for critical percentage
3587
- * @returns Formatted progress bar string
3635
+ * @param options.fillChar - Character for filled portion (default: '█')
3636
+ * @param options.emptyChar - Character for empty portion (default: '░')
3637
+ * @param options.leftBracket - Left bracket character (default: '[')
3638
+ * @param options.rightBracket - Right bracket character (default: ']')
3639
+ * @param options.colors - Color configuration for different thresholds
3640
+ * @param options.colors.low - Color for low percentage values
3641
+ * @param options.colors.medium - Color for medium percentage values
3642
+ * @param options.colors.high - Color for high percentage values
3643
+ * @param options.colors.critical - Color for critical percentage values
3644
+ * @returns Formatted progress bar string with optional percentage/values
3588
3645
  */
3589
3646
  function createProgressBar(value, max, width, options = {}) {
3590
3647
  const { showPercentage = true, showValues = false, fillChar = "█", emptyChar = "░", leftBracket = "[", rightBracket = "]", colors: colors$2 = {} } = options;
@@ -3600,17 +3657,23 @@ function createProgressBar(value, max, width, options = {}) {
3600
3657
  if (color !== "") bar += color;
3601
3658
  bar += fillChar.repeat(fillWidth);
3602
3659
  bar += emptyChar.repeat(emptyWidth);
3603
- if (color !== "") bar += "\x1B[0m";
3660
+ if (color !== "") bar += ANSI_RESET;
3604
3661
  bar += rightBracket;
3605
3662
  if (showPercentage) bar += ` ${percentage.toFixed(1)}%`;
3606
3663
  if (showValues) bar += ` (${value}/${max})`;
3607
3664
  return bar;
3608
3665
  }
3609
3666
  /**
3610
- * Centers text within a given width
3611
- * @param text - Text to center
3612
- * @param width - Total width
3613
- * @returns Centered text with padding
3667
+ * Centers text within a specified width using spaces for padding
3668
+ *
3669
+ * Uses string-width to handle Unicode characters and ANSI escape codes properly.
3670
+ * If text is longer than width, returns original text without truncation.
3671
+ *
3672
+ * Example: centerText("Hello", 10) -> " Hello "
3673
+ *
3674
+ * @param text - Text to center (may contain ANSI color codes)
3675
+ * @param width - Total character width including padding
3676
+ * @returns Text with spaces added for centering
3614
3677
  */
3615
3678
  function centerText(text, width) {
3616
3679
  const textLength = stringWidth(text);
@@ -3619,12 +3682,40 @@ function centerText(text, width) {
3619
3682
  const rightPadding = width - textLength - leftPadding;
3620
3683
  return " ".repeat(leftPadding) + text + " ".repeat(rightPadding);
3621
3684
  }
3622
- var import_picocolors$4 = __toESM(require_picocolors(), 1);
3623
- var import_usingCtx = __toESM(require_usingCtx(), 1);
3685
+ var import_picocolors$5 = __toESM(require_picocolors(), 1);
3686
+ /**
3687
+ * Delay with AbortSignal support and graceful error handling
3688
+ */
3689
+ async function delayWithAbort(ms, signal) {
3690
+ await delay(ms, { signal });
3691
+ }
3692
+ /**
3693
+ * Shows waiting message when no Claude session is active
3694
+ * Uses efficient cursor positioning instead of full screen clear
3695
+ */
3696
+ async function renderWaitingState(terminal, config, signal) {
3697
+ terminal.startBuffering();
3698
+ terminal.write(cursorTo(0, 0));
3699
+ terminal.write(eraseDown);
3700
+ terminal.write(import_picocolors$5.default.yellow("No active session block found. Waiting...\n"));
3701
+ terminal.write(cursorHide);
3702
+ terminal.flush();
3703
+ await delayWithAbort(config.refreshInterval, signal);
3704
+ }
3705
+ /**
3706
+ * Displays the live monitoring dashboard for active Claude session
3707
+ * Uses buffering and sync mode to prevent screen flickering
3708
+ */
3709
+ function renderActiveBlock(terminal, activeBlock, config) {
3710
+ terminal.startBuffering();
3711
+ terminal.write(cursorTo(0, 0));
3712
+ terminal.write(eraseDown);
3713
+ renderLiveDisplay(terminal, activeBlock, config);
3714
+ terminal.write(cursorHide);
3715
+ terminal.flush();
3716
+ }
3624
3717
  /**
3625
3718
  * Format token counts with K suffix for display
3626
- * @param num - Number of tokens
3627
- * @returns Formatted string like "12.3k" or "999"
3628
3719
  */
3629
3720
  function formatTokensShort(num) {
3630
3721
  if (num >= 1e3) return `${(num / 1e3).toFixed(1)}k`;
@@ -3638,69 +3729,6 @@ const DETAIL_COLUMN_WIDTHS = {
3638
3729
  col2: 37
3639
3730
  };
3640
3731
  /**
3641
- * Starts live monitoring of the active session block
3642
- */
3643
- async function startLiveMonitoring(config) {
3644
- try {
3645
- var _usingCtx = (0, import_usingCtx.default)();
3646
- const terminal = new TerminalManager();
3647
- const abortController = new AbortController();
3648
- const cleanup = () => {
3649
- abortController.abort();
3650
- terminal.cleanup();
3651
- terminal.clearScreen();
3652
- logger.info("Live monitoring stopped.");
3653
- if (process$1.exitCode == null) process$1.exit(0);
3654
- };
3655
- process$1.on("SIGINT", cleanup);
3656
- process$1.on("SIGTERM", cleanup);
3657
- terminal.hideCursor();
3658
- const monitor = _usingCtx.u(new LiveMonitor({
3659
- claudePath: config.claudePath,
3660
- sessionDurationHours: config.sessionDurationHours,
3661
- mode: config.mode,
3662
- order: config.order
3663
- }));
3664
- try {
3665
- while (!abortController.signal.aborted) {
3666
- const activeBlock = await monitor.getActiveBlock();
3667
- if (activeBlock == null) {
3668
- terminal.clearScreen();
3669
- terminal.write(import_picocolors$4.default.yellow("No active session block found. Waiting...\n"));
3670
- try {
3671
- await delay(config.refreshInterval, { signal: abortController.signal });
3672
- } catch (error) {
3673
- if ((error instanceof DOMException || error instanceof Error) && error.name === "AbortError") break;
3674
- throw error;
3675
- }
3676
- continue;
3677
- }
3678
- terminal.clearScreen();
3679
- renderLiveDisplay(terminal, activeBlock, config);
3680
- try {
3681
- await delay(config.refreshInterval, { signal: abortController.signal });
3682
- } catch (error) {
3683
- if ((error instanceof DOMException || error instanceof Error) && error.name === "AbortError") break;
3684
- throw error;
3685
- }
3686
- }
3687
- } catch (error) {
3688
- if ((error instanceof DOMException || error instanceof Error) && error.name === "AbortError") return;
3689
- terminal.clearScreen();
3690
- const errorMessage = error instanceof Error ? error.message : String(error);
3691
- terminal.write(import_picocolors$4.default.red(`Error: ${errorMessage}\n`));
3692
- logger.error(`Live monitoring error: ${errorMessage}`);
3693
- try {
3694
- await delay(config.refreshInterval, { signal: abortController.signal });
3695
- } catch {}
3696
- }
3697
- } catch (_) {
3698
- _usingCtx.e = _;
3699
- } finally {
3700
- _usingCtx.d();
3701
- }
3702
- }
3703
- /**
3704
3732
  * Renders the live display for an active session block
3705
3733
  */
3706
3734
  function renderLiveDisplay(terminal, block, config) {
@@ -3713,7 +3741,6 @@ function renderLiveDisplay(terminal, block, config) {
3713
3741
  renderCompactLiveDisplay(terminal, block, config, totalTokens, elapsed, remaining);
3714
3742
  return;
3715
3743
  }
3716
- terminal.clearScreen();
3717
3744
  const boxWidth = Math.min(120, width - 2);
3718
3745
  const boxMargin = Math.floor((width - boxWidth) / 2);
3719
3746
  const marginStr = " ".repeat(boxMargin);
@@ -3725,8 +3752,8 @@ function renderLiveDisplay(terminal, block, config) {
3725
3752
  const sessionPercent = elapsed / sessionDuration * 100;
3726
3753
  const sessionProgressBar = createProgressBar(elapsed, sessionDuration, barWidth, {
3727
3754
  showPercentage: false,
3728
- fillChar: import_picocolors$4.default.cyan("█"),
3729
- emptyChar: import_picocolors$4.default.gray("░"),
3755
+ fillChar: import_picocolors$5.default.cyan("█"),
3756
+ emptyChar: import_picocolors$5.default.gray("░"),
3730
3757
  leftBracket: "[",
3731
3758
  rightBracket: "]"
3732
3759
  });
@@ -3743,96 +3770,100 @@ function renderLiveDisplay(terminal, block, config) {
3743
3770
  hour12: true
3744
3771
  });
3745
3772
  terminal.write(`${marginStr}┌${"─".repeat(boxWidth - 2)}┐\n`);
3746
- terminal.write(`${marginStr}│${import_picocolors$4.default.bold(centerText("CLAUDE CODE - LIVE TOKEN USAGE MONITOR", boxWidth - 2))}│\n`);
3773
+ terminal.write(`${marginStr}│${import_picocolors$5.default.bold(centerText("CLAUDE CODE - LIVE TOKEN USAGE MONITOR", boxWidth - 2))}│\n`);
3747
3774
  terminal.write(`${marginStr}├${"─".repeat(boxWidth - 2)}┤\n`);
3748
3775
  terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
3749
- const sessionLabel = import_picocolors$4.default.bold("⏱️ SESSION");
3776
+ const sessionLabel = import_picocolors$5.default.bold("⏱️ SESSION");
3750
3777
  const sessionLabelWidth = stringWidth(sessionLabel);
3751
3778
  const sessionBarStr = `${sessionLabel}${"".padEnd(Math.max(0, labelWidth - sessionLabelWidth))} ${sessionProgressBar} ${sessionPercent.toFixed(1).padStart(6)}%`;
3752
3779
  const sessionBarPadded = sessionBarStr + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(sessionBarStr)));
3753
3780
  terminal.write(`${marginStr}│ ${sessionBarPadded}│\n`);
3754
- const col1 = `${import_picocolors$4.default.gray("Started:")} ${startTime}`;
3755
- const col2 = `${import_picocolors$4.default.gray("Elapsed:")} ${prettyMilliseconds(elapsed * 60 * 1e3, { compact: true })}`;
3756
- const col3 = `${import_picocolors$4.default.gray("Remaining:")} ${prettyMilliseconds(remaining * 60 * 1e3, { compact: true })} (${endTime})`;
3781
+ const col1 = `${import_picocolors$5.default.gray("Started:")} ${startTime}`;
3782
+ const col2 = `${import_picocolors$5.default.gray("Elapsed:")} ${prettyMilliseconds(elapsed * 60 * 1e3, { compact: true })}`;
3783
+ const col3 = `${import_picocolors$5.default.gray("Remaining:")} ${prettyMilliseconds(remaining * 60 * 1e3, { compact: true })} (${endTime})`;
3757
3784
  const col1Visible = stringWidth(col1);
3758
3785
  const col2Visible = stringWidth(col2);
3759
3786
  const pad1 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col1 - col1Visible));
3760
3787
  const pad2 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col2 - col2Visible));
3761
- const sessionDetails = ` ${col1}${pad1}${col2}${pad2}${col3}`;
3788
+ const sessionDetails = ` ${col1}${pad1}${pad2}${col3}`;
3762
3789
  const sessionDetailsPadded = sessionDetails + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(sessionDetails)));
3790
+ let usageLimitResetTimePadded = null;
3791
+ if (block.usageLimitResetTime !== void 0 && now < block.usageLimitResetTime) {
3792
+ const resetTime = block.usageLimitResetTime?.toLocaleTimeString(void 0, {
3793
+ hour: "2-digit",
3794
+ minute: "2-digit",
3795
+ hour12: true
3796
+ }) ?? null;
3797
+ const usageLimitResetTime = resetTime !== null ? import_picocolors$5.default.red(`❌ USAGE LIMIT. RESET AT ${resetTime}`) : "";
3798
+ usageLimitResetTimePadded = resetTime !== null ? usageLimitResetTime + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(usageLimitResetTime))) : null;
3799
+ }
3763
3800
  terminal.write(`${marginStr}│ ${sessionDetailsPadded}│\n`);
3801
+ if (usageLimitResetTimePadded !== null) terminal.write(`${marginStr}│ ${usageLimitResetTimePadded}│\n`);
3764
3802
  terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
3765
3803
  terminal.write(`${marginStr}├${"─".repeat(boxWidth - 2)}┤\n`);
3766
3804
  terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
3767
3805
  const tokenPercent = config.tokenLimit != null && config.tokenLimit > 0 ? totalTokens / config.tokenLimit * 100 : 0;
3768
- let barColor = import_picocolors$4.default.green;
3769
- if (tokenPercent > 100) barColor = import_picocolors$4.default.red;
3770
- else if (tokenPercent > 80) barColor = import_picocolors$4.default.yellow;
3806
+ let barColor = import_picocolors$5.default.green;
3807
+ if (tokenPercent > 100) barColor = import_picocolors$5.default.red;
3808
+ else if (tokenPercent > 80) barColor = import_picocolors$5.default.yellow;
3771
3809
  const usageBar = config.tokenLimit != null && config.tokenLimit > 0 ? createProgressBar(totalTokens, config.tokenLimit, barWidth, {
3772
3810
  showPercentage: false,
3773
3811
  fillChar: barColor("█"),
3774
- emptyChar: import_picocolors$4.default.gray("░"),
3812
+ emptyChar: import_picocolors$5.default.gray("░"),
3775
3813
  leftBracket: "[",
3776
3814
  rightBracket: "]"
3777
- }) : `[${import_picocolors$4.default.green("█".repeat(Math.floor(barWidth * .1)))}${import_picocolors$4.default.gray("░".repeat(barWidth - Math.floor(barWidth * .1)))}]`;
3815
+ }) : `[${import_picocolors$5.default.green("█".repeat(Math.floor(barWidth * .1)))}${import_picocolors$5.default.gray("░".repeat(barWidth - Math.floor(barWidth * .1)))}]`;
3778
3816
  const burnRate = calculateBurnRate(block);
3779
- const rateIndicator = burnRate != null ? burnRate.tokensPerMinute > 1e3 ? import_picocolors$4.default.red("⚡ HIGH") : burnRate.tokensPerMinute > 500 ? import_picocolors$4.default.yellow("⚡ MODERATE") : import_picocolors$4.default.green("✓ NORMAL") : "";
3780
- const rateDisplay = burnRate != null ? `${import_picocolors$4.default.bold("Burn Rate:")} ${Math.round(burnRate.tokensPerMinute)} token/min ${rateIndicator}` : `${import_picocolors$4.default.bold("Burn Rate:")} N/A`;
3781
- const usageLabel = import_picocolors$4.default.bold("🔥 USAGE");
3817
+ const rateIndicator = burnRate != null ? burnRate.tokensPerMinute > 1e3 ? import_picocolors$5.default.red("⚡ HIGH") : burnRate.tokensPerMinute > 500 ? import_picocolors$5.default.yellow("⚡ MODERATE") : import_picocolors$5.default.green("✓ NORMAL") : "";
3818
+ 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`;
3819
+ const usageLabel = import_picocolors$5.default.bold("🔥 USAGE");
3782
3820
  const usageLabelWidth = stringWidth(usageLabel);
3783
- if (config.tokenLimit != null && config.tokenLimit > 0) {
3784
- const usageBarStr = `${usageLabel}${"".padEnd(Math.max(0, labelWidth - usageLabelWidth))} ${usageBar} ${tokenPercent.toFixed(1).padStart(6)}% (${formatTokensShort(totalTokens)}/${formatTokensShort(config.tokenLimit)})`;
3785
- const usageBarPadded = usageBarStr + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(usageBarStr)));
3786
- terminal.write(`${marginStr} ${usageBarPadded}│\n`);
3787
- const col1$1 = `${import_picocolors$4.default.gray("Tokens:")} ${formatNumber(totalTokens)} (${rateDisplay})`;
3788
- const col2$1 = `${import_picocolors$4.default.gray("Limit:")} ${formatNumber(config.tokenLimit)} tokens`;
3789
- const col3$1 = `${import_picocolors$4.default.gray("Cost:")} ${formatCurrency(block.costUSD)}`;
3790
- const col1Visible$1 = stringWidth(col1$1);
3791
- const col2Visible$1 = stringWidth(col2$1);
3792
- const pad1$1 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col1 - col1Visible$1));
3793
- const pad2$1 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col2 - col2Visible$1));
3794
- const usageDetails = ` ${col1$1}${pad1$1}${col2$1}${pad2$1}${col3$1}`;
3795
- const usageDetailsPadded = usageDetails + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(usageDetails)));
3796
- terminal.write(`${marginStr}│ ${usageDetailsPadded}│\n`);
3797
- } else {
3798
- const usageBarStr = `${usageLabel}${"".padEnd(Math.max(0, labelWidth - usageLabelWidth))} ${usageBar} (${formatTokensShort(totalTokens)} tokens)`;
3799
- const usageBarPadded = usageBarStr + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(usageBarStr)));
3800
- terminal.write(`${marginStr}${usageBarPadded}│\n`);
3801
- const col1$1 = `${import_picocolors$4.default.gray("Tokens:")} ${formatNumber(totalTokens)} (${rateDisplay})`;
3802
- const col3$1 = `${import_picocolors$4.default.gray("Cost:")} ${formatCurrency(block.costUSD)}`;
3803
- const col1Visible$1 = stringWidth(col1$1);
3804
- const pad1$1 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col1 - col1Visible$1));
3805
- const pad2$1 = " ".repeat(DETAIL_COLUMN_WIDTHS.col2);
3806
- const usageDetails = ` ${col1$1}${pad1$1}${pad2$1}${col3$1}`;
3807
- const usageDetailsPadded = usageDetails + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(usageDetails)));
3808
- terminal.write(`${marginStr}│ ${usageDetailsPadded}│\n`);
3809
- }
3821
+ const { usageBarStr, usageCol1, usageCol2, usageCol3 } = config.tokenLimit != null && config.tokenLimit > 0 ? {
3822
+ usageBarStr: `${usageLabel}${"".padEnd(Math.max(0, labelWidth - usageLabelWidth))} ${usageBar} ${tokenPercent.toFixed(1).padStart(6)}% (${formatTokensShort(totalTokens)}/${formatTokensShort(config.tokenLimit)})`,
3823
+ usageCol1: `${import_picocolors$5.default.gray("Tokens:")} ${formatNumber(totalTokens)} (${rateDisplay})`,
3824
+ usageCol2: `${import_picocolors$5.default.gray("Limit:")} ${formatNumber(config.tokenLimit)} tokens`,
3825
+ usageCol3: `${import_picocolors$5.default.gray("Cost:")} ${formatCurrency(block.costUSD)}`
3826
+ } : {
3827
+ usageBarStr: `${usageLabel}${"".padEnd(Math.max(0, labelWidth - usageLabelWidth))} ${usageBar} (${formatTokensShort(totalTokens)} tokens)`,
3828
+ usageCol1: `${import_picocolors$5.default.gray("Tokens:")} ${formatNumber(totalTokens)} (${rateDisplay})`,
3829
+ usageCol2: "",
3830
+ usageCol3: `${import_picocolors$5.default.gray("Cost:")} ${formatCurrency(block.costUSD)}`
3831
+ };
3832
+ const usageBarPadded = usageBarStr + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(usageBarStr)));
3833
+ terminal.write(`${marginStr}│ ${usageBarPadded}│\n`);
3834
+ const usageCol1Visible = stringWidth(usageCol1);
3835
+ const usageCol2Visible = stringWidth(usageCol2);
3836
+ const usagePad1 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col1 - usageCol1Visible));
3837
+ const usagePad2 = usageCol2.length > 0 ? " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col2 - usageCol2Visible)) : " ".repeat(DETAIL_COLUMN_WIDTHS.col2);
3838
+ const usageDetails = ` ${usageCol1}${usagePad1}${usageCol2}${usagePad2}${usageCol3}`;
3839
+ const usageDetailsPadded = usageDetails + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(usageDetails)));
3840
+ terminal.write(`${marginStr} ${usageDetailsPadded}│\n`);
3810
3841
  terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
3811
3842
  terminal.write(`${marginStr}├${"─".repeat(boxWidth - 2)}┤\n`);
3812
3843
  terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
3813
3844
  const projection = projectBlockUsage(block);
3814
3845
  if (projection != null) {
3815
3846
  const projectedPercent = config.tokenLimit != null && config.tokenLimit > 0 ? projection.totalTokens / config.tokenLimit * 100 : 0;
3816
- let projBarColor = import_picocolors$4.default.green;
3817
- if (projectedPercent > 100) projBarColor = import_picocolors$4.default.red;
3818
- else if (projectedPercent > 80) projBarColor = import_picocolors$4.default.yellow;
3847
+ let projBarColor = import_picocolors$5.default.green;
3848
+ if (projectedPercent > 100) projBarColor = import_picocolors$5.default.red;
3849
+ else if (projectedPercent > 80) projBarColor = import_picocolors$5.default.yellow;
3819
3850
  const projectionBar = config.tokenLimit != null && config.tokenLimit > 0 ? createProgressBar(projection.totalTokens, config.tokenLimit, barWidth, {
3820
3851
  showPercentage: false,
3821
3852
  fillChar: projBarColor("█"),
3822
- emptyChar: import_picocolors$4.default.gray("░"),
3853
+ emptyChar: import_picocolors$5.default.gray("░"),
3823
3854
  leftBracket: "[",
3824
3855
  rightBracket: "]"
3825
- }) : `[${import_picocolors$4.default.green("█".repeat(Math.floor(barWidth * .15)))}${import_picocolors$4.default.gray("░".repeat(barWidth - Math.floor(barWidth * .15)))}]`;
3826
- const limitStatus = config.tokenLimit != null && config.tokenLimit > 0 ? projectedPercent > 100 ? import_picocolors$4.default.red("❌ WILL EXCEED LIMIT") : projectedPercent > 80 ? import_picocolors$4.default.yellow("⚠️ APPROACHING LIMIT") : import_picocolors$4.default.green("✓ WITHIN LIMIT") : import_picocolors$4.default.green("✓ ON TRACK");
3827
- const projLabel = import_picocolors$4.default.bold("📈 PROJECTION");
3856
+ }) : `[${import_picocolors$5.default.green("█".repeat(Math.floor(barWidth * .15)))}${import_picocolors$5.default.gray("░".repeat(barWidth - Math.floor(barWidth * .15)))}]`;
3857
+ 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");
3858
+ const projLabel = import_picocolors$5.default.bold("📈 PROJECTION");
3828
3859
  const projLabelWidth = stringWidth(projLabel);
3829
3860
  if (config.tokenLimit != null && config.tokenLimit > 0) {
3830
3861
  const projBarStr = `${projLabel}${"".padEnd(Math.max(0, labelWidth - projLabelWidth))} ${projectionBar} ${projectedPercent.toFixed(1).padStart(6)}% (${formatTokensShort(projection.totalTokens)}/${formatTokensShort(config.tokenLimit)})`;
3831
3862
  const projBarPadded = projBarStr + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(projBarStr)));
3832
3863
  terminal.write(`${marginStr}│ ${projBarPadded}│\n`);
3833
- const col1$1 = `${import_picocolors$4.default.gray("Status:")} ${limitStatus}`;
3834
- const col2$1 = `${import_picocolors$4.default.gray("Tokens:")} ${formatNumber(projection.totalTokens)}`;
3835
- const col3$1 = `${import_picocolors$4.default.gray("Cost:")} ${formatCurrency(projection.totalCost)}`;
3864
+ const col1$1 = `${import_picocolors$5.default.gray("Status:")} ${limitStatus}`;
3865
+ const col2$1 = `${import_picocolors$5.default.gray("Tokens:")} ${formatNumber(projection.totalTokens)}`;
3866
+ const col3$1 = `${import_picocolors$5.default.gray("Cost:")} ${formatCurrency(projection.totalCost)}`;
3836
3867
  const col1Visible$1 = stringWidth(col1$1);
3837
3868
  const col2Visible$1 = stringWidth(col2$1);
3838
3869
  const pad1$1 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col1 - col1Visible$1));
@@ -3844,9 +3875,9 @@ function renderLiveDisplay(terminal, block, config) {
3844
3875
  const projBarStr = `${projLabel}${"".padEnd(Math.max(0, labelWidth - projLabelWidth))} ${projectionBar} (${formatTokensShort(projection.totalTokens)} tokens)`;
3845
3876
  const projBarPadded = projBarStr + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(projBarStr)));
3846
3877
  terminal.write(`${marginStr}│ ${projBarPadded}│\n`);
3847
- const col1$1 = `${import_picocolors$4.default.gray("Status:")} ${limitStatus}`;
3848
- const col2$1 = `${import_picocolors$4.default.gray("Tokens:")} ${formatNumber(projection.totalTokens)}`;
3849
- const col3$1 = `${import_picocolors$4.default.gray("Cost:")} ${formatCurrency(projection.totalCost)}`;
3878
+ const col1$1 = `${import_picocolors$5.default.gray("Status:")} ${limitStatus}`;
3879
+ const col2$1 = `${import_picocolors$5.default.gray("Tokens:")} ${formatNumber(projection.totalTokens)}`;
3880
+ const col3$1 = `${import_picocolors$5.default.gray("Cost:")} ${formatCurrency(projection.totalCost)}`;
3850
3881
  const col1Visible$1 = stringWidth(col1$1);
3851
3882
  const col2Visible$1 = stringWidth(col2$1);
3852
3883
  const pad1$1 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col1 - col1Visible$1));
@@ -3865,7 +3896,7 @@ function renderLiveDisplay(terminal, block, config) {
3865
3896
  }
3866
3897
  terminal.write(`${marginStr}├${"─".repeat(boxWidth - 2)}┤\n`);
3867
3898
  const refreshText = `↻ Refreshing every ${config.refreshInterval / 1e3}s • Press Ctrl+C to stop`;
3868
- terminal.write(`${marginStr}│${import_picocolors$4.default.gray(centerText(refreshText, boxWidth - 2))}│\n`);
3899
+ terminal.write(`${marginStr}│${import_picocolors$5.default.gray(centerText(refreshText, boxWidth - 2))}│\n`);
3869
3900
  terminal.write(`${marginStr}└${"─".repeat(boxWidth - 2)}┘\n`);
3870
3901
  }
3871
3902
  /**
@@ -3873,20 +3904,86 @@ function renderLiveDisplay(terminal, block, config) {
3873
3904
  */
3874
3905
  function renderCompactLiveDisplay(terminal, block, config, totalTokens, elapsed, remaining) {
3875
3906
  const width = terminal.width;
3876
- terminal.write(`${import_picocolors$4.default.bold(centerText("LIVE MONITOR", width))}\n`);
3907
+ terminal.write(`${import_picocolors$5.default.bold(centerText("LIVE MONITOR", width))}\n`);
3877
3908
  terminal.write(`${"─".repeat(width)}\n`);
3878
3909
  const sessionPercent = elapsed / (elapsed + remaining) * 100;
3879
3910
  terminal.write(`Session: ${sessionPercent.toFixed(1)}% (${Math.floor(elapsed / 60)}h ${Math.floor(elapsed % 60)}m)\n`);
3880
3911
  if (config.tokenLimit != null && config.tokenLimit > 0) {
3881
3912
  const tokenPercent = totalTokens / config.tokenLimit * 100;
3882
- const status = tokenPercent > 100 ? import_picocolors$4.default.red("OVER") : tokenPercent > 80 ? import_picocolors$4.default.yellow("WARN") : import_picocolors$4.default.green("OK");
3913
+ const status = tokenPercent > 100 ? import_picocolors$5.default.red("OVER") : tokenPercent > 80 ? import_picocolors$5.default.yellow("WARN") : import_picocolors$5.default.green("OK");
3883
3914
  terminal.write(`Tokens: ${formatNumber(totalTokens)}/${formatNumber(config.tokenLimit)} ${status}\n`);
3884
3915
  } else terminal.write(`Tokens: ${formatNumber(totalTokens)}\n`);
3885
3916
  terminal.write(`Cost: ${formatCurrency(block.costUSD)}\n`);
3886
3917
  const burnRate = calculateBurnRate(block);
3887
3918
  if (burnRate != null) terminal.write(`Rate: ${formatNumber(burnRate.tokensPerMinute)}/min\n`);
3888
3919
  terminal.write(`${"─".repeat(width)}\n`);
3889
- terminal.write(import_picocolors$4.default.gray(`Refresh: ${config.refreshInterval / 1e3}s | Ctrl+C: stop\n`));
3920
+ terminal.write(import_picocolors$5.default.gray(`Refresh: ${config.refreshInterval / 1e3}s | Ctrl+C: stop\n`));
3921
+ }
3922
+ var import_picocolors$4 = __toESM(require_picocolors(), 1);
3923
+ var import_usingCtx = __toESM(require_usingCtx(), 1);
3924
+ async function startLiveMonitoring(config) {
3925
+ try {
3926
+ var _usingCtx = (0, import_usingCtx.default)();
3927
+ const terminal = new TerminalManager();
3928
+ const abortController = new AbortController();
3929
+ let lastRenderTime = 0;
3930
+ const cleanup = () => {
3931
+ abortController.abort();
3932
+ terminal.cleanup();
3933
+ terminal.clearScreen();
3934
+ logger.info("Live monitoring stopped.");
3935
+ if (process$1.exitCode == null) process$1.exit(0);
3936
+ };
3937
+ process$1.on("SIGINT", cleanup);
3938
+ process$1.on("SIGTERM", cleanup);
3939
+ terminal.enterAlternateScreen();
3940
+ terminal.enableSyncMode();
3941
+ terminal.clearScreen();
3942
+ terminal.hideCursor();
3943
+ const monitor = _usingCtx.u(new LiveMonitor({
3944
+ claudePath: config.claudePath,
3945
+ sessionDurationHours: config.sessionDurationHours,
3946
+ mode: config.mode,
3947
+ order: config.order
3948
+ }));
3949
+ const monitoringResult = await try_({
3950
+ try: async () => {
3951
+ while (!abortController.signal.aborted) {
3952
+ const now = Date.now();
3953
+ const timeSinceLastRender = now - lastRenderTime;
3954
+ if (timeSinceLastRender < MIN_RENDER_INTERVAL_MS) {
3955
+ await delayWithAbort(MIN_RENDER_INTERVAL_MS - timeSinceLastRender, abortController.signal);
3956
+ continue;
3957
+ }
3958
+ const activeBlock = await monitor.getActiveBlock();
3959
+ monitor.clearCache();
3960
+ if (activeBlock == null) {
3961
+ await renderWaitingState(terminal, config, abortController.signal);
3962
+ continue;
3963
+ }
3964
+ renderActiveBlock(terminal, activeBlock, config);
3965
+ lastRenderTime = Date.now();
3966
+ await delayWithAbort(config.refreshInterval, abortController.signal);
3967
+ }
3968
+ },
3969
+ catch: (error) => error
3970
+ })();
3971
+ if (isFailure(monitoringResult)) {
3972
+ const error = monitoringResult.error;
3973
+ if ((error instanceof DOMException || error instanceof Error) && error.name === "AbortError") return;
3974
+ const errorMessage = error instanceof Error ? error.message : String(error);
3975
+ terminal.startBuffering();
3976
+ terminal.clearScreen();
3977
+ terminal.write(import_picocolors$4.default.red(`Error: ${errorMessage}\n`));
3978
+ terminal.flush();
3979
+ logger.error(`Live monitoring error: ${errorMessage}`);
3980
+ await delayWithAbort(config.refreshInterval, abortController.signal).catch(() => {});
3981
+ }
3982
+ } catch (_) {
3983
+ _usingCtx.e = _;
3984
+ } finally {
3985
+ _usingCtx.d();
3986
+ }
3890
3987
  }
3891
3988
  var import_picocolors$3 = __toESM(require_picocolors(), 1);
3892
3989
  /**
@@ -4011,7 +4108,7 @@ const blocksCommand = define({
4011
4108
  let maxTokensFromAll = 0;
4012
4109
  if (ctx.values.tokenLimit === "max") {
4013
4110
  for (const block of blocks) if (!(block.isGap ?? false) && !block.isActive) {
4014
- const blockTokens = block.tokenCounts.inputTokens + block.tokenCounts.outputTokens;
4111
+ const blockTokens = getTotalTokens(block.tokenCounts);
4015
4112
  if (blockTokens > maxTokensFromAll) maxTokensFromAll = blockTokens;
4016
4113
  }
4017
4114
  if (!ctx.values.json && maxTokensFromAll > 0) logger.info(`Using max tokens from previous sessions: ${formatNumber(maxTokensFromAll)}`);
@@ -4037,8 +4134,13 @@ const blocksCommand = define({
4037
4134
  }
4038
4135
  const refreshInterval = Math.max(MIN_REFRESH_INTERVAL_SECONDS, Math.min(MAX_REFRESH_INTERVAL_SECONDS, ctx.values.refreshInterval));
4039
4136
  if (refreshInterval !== ctx.values.refreshInterval) logger.warn(`Refresh interval adjusted to ${refreshInterval} seconds (valid range: ${MIN_REFRESH_INTERVAL_SECONDS}-${MAX_REFRESH_INTERVAL_SECONDS})`);
4137
+ const paths = getClaudePaths();
4138
+ if (paths.length === 0) {
4139
+ logger.error("No valid Claude data directory found");
4140
+ throw new Error("No valid Claude data directory found");
4141
+ }
4040
4142
  await startLiveMonitoring({
4041
- claudePath: getDefaultClaudePath(),
4143
+ claudePath: paths[0],
4042
4144
  tokenLimit: parseTokenLimit(tokenLimitValue, maxTokensFromAll),
4043
4145
  refreshInterval: refreshInterval * 1e3,
4044
4146
  sessionDurationHours: ctx.values.sessionLength,
@@ -4073,7 +4175,8 @@ const blocksCommand = define({
4073
4175
  percentUsed: projection.totalTokens / limit * 100,
4074
4176
  status: projection.totalTokens > limit ? "exceeds" : projection.totalTokens > limit * BLOCKS_WARNING_THRESHOLD ? "warning" : "ok"
4075
4177
  } : void 0;
4076
- })() : void 0
4178
+ })() : void 0,
4179
+ usageLimitResetTime: block.usageLimitResetTime
4077
4180
  };
4078
4181
  }) };
4079
4182
  log(JSON.stringify(jsonOutput, null, 2));
@@ -4107,7 +4210,7 @@ const blocksCommand = define({
4107
4210
  if (ctx.values.tokenLimit != null) {
4108
4211
  const limit = parseTokenLimit(ctx.values.tokenLimit, maxTokensFromAll);
4109
4212
  if (limit != null && limit > 0) {
4110
- const currentTokens = block.tokenCounts.inputTokens + block.tokenCounts.outputTokens;
4213
+ const currentTokens = getTotalTokens(block.tokenCounts);
4111
4214
  const remainingTokens = Math.max(0, limit - currentTokens);
4112
4215
  const percentUsed = projection.totalTokens / limit * 100;
4113
4216
  const status = percentUsed > 100 ? import_picocolors$3.default.red("EXCEEDS LIMIT") : percentUsed > BLOCKS_WARNING_THRESHOLD * 100 ? import_picocolors$3.default.yellow("WARNING") : import_picocolors$3.default.green("OK");
@@ -4158,7 +4261,7 @@ const blocksCommand = define({
4158
4261
  gapRow.push(import_picocolors$3.default.gray("-"));
4159
4262
  table.push(gapRow);
4160
4263
  } else {
4161
- const totalTokens = block.tokenCounts.inputTokens + block.tokenCounts.outputTokens;
4264
+ const totalTokens = getTotalTokens(block.tokenCounts);
4162
4265
  const status = block.isActive ? import_picocolors$3.default.green("ACTIVE") : "";
4163
4266
  const row = [
4164
4267
  formatBlockTime(block, useCompactFormat),
@@ -4175,7 +4278,7 @@ const blocksCommand = define({
4175
4278
  table.push(row);
4176
4279
  if (block.isActive) {
4177
4280
  if (actualTokenLimit != null && actualTokenLimit > 0) {
4178
- const currentTokens = block.tokenCounts.inputTokens + block.tokenCounts.outputTokens;
4281
+ const currentTokens = getTotalTokens(block.tokenCounts);
4179
4282
  const remainingTokens = Math.max(0, actualTokenLimit - currentTokens);
4180
4283
  const remainingText = remainingTokens > 0 ? formatNumber(remainingTokens) : import_picocolors$3.default.red("0");
4181
4284
  const remainingPercent = (actualTokenLimit - currentTokens) / actualTokenLimit * 100;
@@ -4732,8 +4835,13 @@ const mcpCommand = define({
4732
4835
  async run(ctx) {
4733
4836
  const { type, mode, port } = ctx.values;
4734
4837
  if (type === "stdio") logger.level = 0;
4838
+ const paths = getClaudePaths();
4839
+ if (paths.length === 0) {
4840
+ logger.error("No valid Claude data directory found");
4841
+ throw new Error("No valid Claude data directory found");
4842
+ }
4735
4843
  const options = {
4736
- claudePath: getDefaultClaudePath(),
4844
+ claudePath: paths[0],
4737
4845
  mode
4738
4846
  };
4739
4847
  if (type === "stdio") {