ccusage 14.1.2 → 15.0.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/dist/index.js CHANGED
@@ -1,11 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import { BLOCKS_COMPACT_WIDTH_THRESHOLD, BLOCKS_DEFAULT_TERMINAL_WIDTH, BLOCKS_WARNING_THRESHOLD, DEFAULT_RECENT_DAYS, MCP_DEFAULT_PORT, __commonJSMin, __require, __toESM } from "./pricing-fetcher-XdvIG0to.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, PricingFetcher, USAGE_DATA_GLOB_PATTERN, __commonJSMin, __require, __toESM, require_usingCtx } from "./pricing-fetcher-DBzWmU38.js";
3
3
  import { CostModes, SortOrders, dateSchema } from "./_types-Cr2YEzKm.js";
4
4
  import { calculateTotals, createTotalsObject, getTotalTokens } from "./calculate-cost-CoS7we68.js";
5
- import { DEFAULT_SESSION_DURATION_HOURS, calculateBurnRate, filterRecentBlocks, formatDateCompact, getDefaultClaudePath, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, projectBlockUsage, uniq } from "./data-loader-BFS_JSqd.js";
6
- import { description, log, logger, name, version } from "./logger-CIYlc309.js";
7
- import { detectMismatches, printMismatchReport } from "./debug-DIfzQVQt.js";
8
- import { createMcpHttpApp, createMcpServer, startMcpServerStdio } from "./mcp-BI0xh4oM.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-DQftfBz9.js";
6
+ import { description, log, logger, name, version } from "./logger-r_DhIB_J.js";
7
+ import { detectMismatches, printMismatchReport } from "./debug-CXtWvV0a.js";
8
+ import { createMcpHttpApp, createMcpServer, startMcpServerStdio } from "./mcp-C-EN2PDc.js";
9
+ import { readFile } from "node:fs/promises";
10
+ import path from "node:path";
9
11
  import process$1 from "node:process";
10
12
  import { createServer } from "node:http";
11
13
  import { Http2ServerRequest } from "node:http2";
@@ -1589,19 +1591,19 @@ var require_utils = __commonJSMin((exports, module) => {
1589
1591
  function hyperlink(url, text) {
1590
1592
  const OSC = "\x1B]";
1591
1593
  const BEL = "\x07";
1592
- const SEP = ";";
1594
+ const SEP$1 = ";";
1593
1595
  return [
1594
1596
  OSC,
1595
1597
  "8",
1596
- SEP,
1597
- SEP,
1598
+ SEP$1,
1599
+ SEP$1,
1598
1600
  url || text,
1599
1601
  BEL,
1600
1602
  text,
1601
1603
  OSC,
1602
1604
  "8",
1603
- SEP,
1604
- SEP,
1605
+ SEP$1,
1606
+ SEP$1,
1605
1607
  BEL
1606
1608
  ].join("");
1607
1609
  }
@@ -2558,8 +2560,8 @@ var require_cell = __commonJSMin((exports, module) => {
2558
2560
  for (let i = 1; i < span; i++) ret += 1 + dimensionTable[startingIndex + i];
2559
2561
  return ret;
2560
2562
  }
2561
- function sumPlusOne(a, b) {
2562
- return a + b + 1;
2563
+ function sumPlusOne(a$1, b) {
2564
+ return a$1 + b + 1;
2563
2565
  }
2564
2566
  let CHAR_NAMES = [
2565
2567
  "top",
@@ -2929,7 +2931,7 @@ function stringWidth(string, options = {}) {
2929
2931
  return width;
2930
2932
  }
2931
2933
  var import_cli_table3 = __toESM(require_cli_table3(), 1);
2932
- var import_picocolors$4 = __toESM(require_picocolors(), 1);
2934
+ var import_picocolors$5 = __toESM(require_picocolors(), 1);
2933
2935
  /**
2934
2936
  * Responsive table class that adapts column widths based on terminal size
2935
2937
  * Automatically adjusts formatting and layout for different screen sizes
@@ -3133,6 +3135,16 @@ function formatModelName(modelName) {
3133
3135
  return modelName;
3134
3136
  }
3135
3137
  /**
3138
+ * Formats an array of model names for display as a comma-separated string
3139
+ * Removes duplicates and sorts alphabetically
3140
+ * @param models - Array of model names
3141
+ * @returns Formatted string with unique, sorted model names separated by commas
3142
+ */
3143
+ function formatModelsDisplay(models) {
3144
+ const uniqueModels = uniq(models.map(formatModelName));
3145
+ return uniqueModels.sort().join(", ");
3146
+ }
3147
+ /**
3136
3148
  * Formats an array of model names for display with each model on a new line
3137
3149
  * Removes duplicates and sorts alphabetically
3138
3150
  * @param models - Array of model names
@@ -3155,11 +3167,727 @@ function pushBreakdownRows(table, breakdowns, extraColumns = 1, trailingColumns
3155
3167
  const row = [` └─ ${formatModelName(breakdown.modelName)}`];
3156
3168
  for (let i = 0; i < extraColumns; i++) row.push("");
3157
3169
  const totalTokens = breakdown.inputTokens + breakdown.outputTokens + breakdown.cacheCreationTokens + breakdown.cacheReadTokens;
3158
- row.push(import_picocolors$4.default.gray(formatNumber(breakdown.inputTokens)), import_picocolors$4.default.gray(formatNumber(breakdown.outputTokens)), import_picocolors$4.default.gray(formatNumber(breakdown.cacheCreationTokens)), import_picocolors$4.default.gray(formatNumber(breakdown.cacheReadTokens)), import_picocolors$4.default.gray(formatNumber(totalTokens)), import_picocolors$4.default.gray(formatCurrency(breakdown.cost)));
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)));
3159
3171
  for (let i = 0; i < trailingColumns; i++) row.push("");
3160
3172
  table.push(row);
3161
3173
  }
3162
3174
  }
3175
+ /** Options for {@linkcode delay}. */
3176
+ /**
3177
+ * Resolve a {@linkcode Promise} after a given amount of milliseconds.
3178
+ *
3179
+ * @throws {DOMException} If the optional signal is aborted before the delay
3180
+ * duration, and `signal.reason` is undefined.
3181
+ * @param ms Duration in milliseconds for how long the delay should last.
3182
+ * @param options Additional options.
3183
+ *
3184
+ * @example Basic usage
3185
+ * ```ts no-assert
3186
+ * import { delay } from "@std/async/delay";
3187
+ *
3188
+ * // ...
3189
+ * const delayedPromise = delay(100);
3190
+ * const result = await delayedPromise;
3191
+ * // ...
3192
+ * ```
3193
+ *
3194
+ * @example Disable persistence
3195
+ *
3196
+ * Setting `persistent` to `false` will allow the process to continue to run as
3197
+ * long as the timer exists.
3198
+ *
3199
+ * ```ts no-assert ignore
3200
+ * import { delay } from "@std/async/delay";
3201
+ *
3202
+ * // ...
3203
+ * await delay(100, { persistent: false });
3204
+ * // ...
3205
+ * ```
3206
+ */ function delay(ms, options = {}) {
3207
+ const { signal, persistent = true } = options;
3208
+ if (signal?.aborted) return Promise.reject(signal.reason);
3209
+ return new Promise((resolve, reject) => {
3210
+ const abort = () => {
3211
+ clearTimeout(i);
3212
+ reject(signal?.reason);
3213
+ };
3214
+ const done = () => {
3215
+ signal?.removeEventListener("abort", abort);
3216
+ resolve();
3217
+ };
3218
+ const i = setTimeout(done, ms);
3219
+ signal?.addEventListener("abort", abort, { once: true });
3220
+ if (persistent === false) try {
3221
+ Deno.unrefTimer(i);
3222
+ } catch (error) {
3223
+ if (!(error instanceof ReferenceError)) throw error;
3224
+ console.error("`persistent` option is only available in Deno");
3225
+ }
3226
+ });
3227
+ }
3228
+ const toZeroIfInfinity = (value) => Number.isFinite(value) ? value : 0;
3229
+ function parseNumber(milliseconds) {
3230
+ return {
3231
+ days: Math.trunc(milliseconds / 864e5),
3232
+ hours: Math.trunc(milliseconds / 36e5 % 24),
3233
+ minutes: Math.trunc(milliseconds / 6e4 % 60),
3234
+ seconds: Math.trunc(milliseconds / 1e3 % 60),
3235
+ milliseconds: Math.trunc(milliseconds % 1e3),
3236
+ microseconds: Math.trunc(toZeroIfInfinity(milliseconds * 1e3) % 1e3),
3237
+ nanoseconds: Math.trunc(toZeroIfInfinity(milliseconds * 1e6) % 1e3)
3238
+ };
3239
+ }
3240
+ function parseBigint(milliseconds) {
3241
+ return {
3242
+ days: milliseconds / 86400000n,
3243
+ hours: milliseconds / 3600000n % 24n,
3244
+ minutes: milliseconds / 60000n % 60n,
3245
+ seconds: milliseconds / 1000n % 60n,
3246
+ milliseconds: milliseconds % 1000n,
3247
+ microseconds: 0n,
3248
+ nanoseconds: 0n
3249
+ };
3250
+ }
3251
+ function parseMilliseconds(milliseconds) {
3252
+ switch (typeof milliseconds) {
3253
+ case "number": {
3254
+ if (Number.isFinite(milliseconds)) return parseNumber(milliseconds);
3255
+ break;
3256
+ }
3257
+ case "bigint": return parseBigint(milliseconds);
3258
+ }
3259
+ throw new TypeError("Expected a finite number or bigint");
3260
+ }
3261
+ const isZero = (value) => value === 0 || value === 0n;
3262
+ const pluralize = (word, count) => count === 1 || count === 1n ? word : `${word}s`;
3263
+ const SECOND_ROUNDING_EPSILON = 1e-7;
3264
+ const ONE_DAY_IN_MILLISECONDS = 24n * 60n * 60n * 1000n;
3265
+ function prettyMilliseconds(milliseconds, options) {
3266
+ const isBigInt = typeof milliseconds === "bigint";
3267
+ if (!isBigInt && !Number.isFinite(milliseconds)) throw new TypeError("Expected a finite number or bigint");
3268
+ options = { ...options };
3269
+ const sign = milliseconds < 0 ? "-" : "";
3270
+ milliseconds = milliseconds < 0 ? -milliseconds : milliseconds;
3271
+ if (options.colonNotation) {
3272
+ options.compact = false;
3273
+ options.formatSubMilliseconds = false;
3274
+ options.separateMilliseconds = false;
3275
+ options.verbose = false;
3276
+ }
3277
+ if (options.compact) {
3278
+ options.unitCount = 1;
3279
+ options.secondsDecimalDigits = 0;
3280
+ options.millisecondsDecimalDigits = 0;
3281
+ }
3282
+ let result = [];
3283
+ const floorDecimals = (value, decimalDigits) => {
3284
+ const flooredInterimValue = Math.floor(value * 10 ** decimalDigits + SECOND_ROUNDING_EPSILON);
3285
+ const flooredValue = Math.round(flooredInterimValue) / 10 ** decimalDigits;
3286
+ return flooredValue.toFixed(decimalDigits);
3287
+ };
3288
+ const add = (value, long, short, valueString) => {
3289
+ if ((result.length === 0 || !options.colonNotation) && isZero(value) && !(options.colonNotation && short === "m")) return;
3290
+ valueString ??= String(value);
3291
+ if (options.colonNotation) {
3292
+ const wholeDigits = valueString.includes(".") ? valueString.split(".")[0].length : valueString.length;
3293
+ const minLength = result.length > 0 ? 2 : 1;
3294
+ valueString = "0".repeat(Math.max(0, minLength - wholeDigits)) + valueString;
3295
+ } else valueString += options.verbose ? " " + pluralize(long, value) : short;
3296
+ result.push(valueString);
3297
+ };
3298
+ const parsed = parseMilliseconds(milliseconds);
3299
+ const days = BigInt(parsed.days);
3300
+ if (options.hideYearAndDays) add(BigInt(days) * 24n + BigInt(parsed.hours), "hour", "h");
3301
+ else {
3302
+ if (options.hideYear) add(days, "day", "d");
3303
+ else {
3304
+ add(days / 365n, "year", "y");
3305
+ add(days % 365n, "day", "d");
3306
+ }
3307
+ add(Number(parsed.hours), "hour", "h");
3308
+ }
3309
+ add(Number(parsed.minutes), "minute", "m");
3310
+ if (!options.hideSeconds) if (options.separateMilliseconds || options.formatSubMilliseconds || !options.colonNotation && milliseconds < 1e3) {
3311
+ const seconds = Number(parsed.seconds);
3312
+ const milliseconds$1 = Number(parsed.milliseconds);
3313
+ const microseconds = Number(parsed.microseconds);
3314
+ const nanoseconds = Number(parsed.nanoseconds);
3315
+ add(seconds, "second", "s");
3316
+ if (options.formatSubMilliseconds) {
3317
+ add(milliseconds$1, "millisecond", "ms");
3318
+ add(microseconds, "microsecond", "µs");
3319
+ add(nanoseconds, "nanosecond", "ns");
3320
+ } else {
3321
+ const millisecondsAndBelow = milliseconds$1 + microseconds / 1e3 + nanoseconds / 1e6;
3322
+ const millisecondsDecimalDigits = typeof options.millisecondsDecimalDigits === "number" ? options.millisecondsDecimalDigits : 0;
3323
+ const roundedMilliseconds = millisecondsAndBelow >= 1 ? Math.round(millisecondsAndBelow) : Math.ceil(millisecondsAndBelow);
3324
+ const millisecondsString = millisecondsDecimalDigits ? millisecondsAndBelow.toFixed(millisecondsDecimalDigits) : roundedMilliseconds;
3325
+ add(Number.parseFloat(millisecondsString), "millisecond", "ms", millisecondsString);
3326
+ }
3327
+ } else {
3328
+ const seconds = (isBigInt ? Number(milliseconds % ONE_DAY_IN_MILLISECONDS) : milliseconds) / 1e3 % 60;
3329
+ const secondsDecimalDigits = typeof options.secondsDecimalDigits === "number" ? options.secondsDecimalDigits : 1;
3330
+ const secondsFixed = floorDecimals(seconds, secondsDecimalDigits);
3331
+ const secondsString = options.keepDecimalsOnWholeSeconds ? secondsFixed : secondsFixed.replace(/\.0+$/, "");
3332
+ add(Number.parseFloat(secondsString), "second", "s", secondsString);
3333
+ }
3334
+ if (result.length === 0) return sign + "0" + (options.verbose ? " milliseconds" : "ms");
3335
+ const separator = options.colonNotation ? ":" : " ";
3336
+ if (typeof options.unitCount === "number") result = result.slice(0, Math.max(options.unitCount, 1));
3337
+ return sign + result.join(separator);
3338
+ }
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
+ };
3483
+ /**
3484
+ * Manages terminal state for live updates
3485
+ */
3486
+ var TerminalManager = class {
3487
+ stream;
3488
+ cursorHidden = false;
3489
+ originalWrite;
3490
+ constructor(stream = process$1.stdout) {
3491
+ this.stream = stream;
3492
+ this.originalWrite = stream.write.bind(stream);
3493
+ }
3494
+ /**
3495
+ * Hides the terminal cursor
3496
+ */
3497
+ hideCursor() {
3498
+ if (!this.cursorHidden && this.stream.isTTY) {
3499
+ this.stream.write(TERMINAL_CONTROL.HIDE_CURSOR);
3500
+ this.cursorHidden = true;
3501
+ }
3502
+ }
3503
+ /**
3504
+ * Shows the terminal cursor
3505
+ */
3506
+ showCursor() {
3507
+ if (this.cursorHidden && this.stream.isTTY) {
3508
+ this.stream.write(TERMINAL_CONTROL.SHOW_CURSOR);
3509
+ this.cursorHidden = false;
3510
+ }
3511
+ }
3512
+ /**
3513
+ * Clears the entire screen and moves cursor to top
3514
+ */
3515
+ clearScreen() {
3516
+ if (this.stream.isTTY) {
3517
+ this.stream.write(TERMINAL_CONTROL.CLEAR_SCREEN);
3518
+ this.stream.write(TERMINAL_CONTROL.MOVE_TO_TOP);
3519
+ }
3520
+ }
3521
+ /**
3522
+ * Clears the current line
3523
+ */
3524
+ clearLine() {
3525
+ if (this.stream.isTTY) this.stream.write(TERMINAL_CONTROL.CLEAR_LINE);
3526
+ }
3527
+ /**
3528
+ * Moves cursor up by n lines
3529
+ */
3530
+ moveUp(lines) {
3531
+ if (this.stream.isTTY && lines > 0) this.stream.write(TERMINAL_CONTROL.MOVE_UP(lines));
3532
+ }
3533
+ /**
3534
+ * Moves cursor to beginning of line
3535
+ */
3536
+ moveToLineStart() {
3537
+ if (this.stream.isTTY) this.stream.write(TERMINAL_CONTROL.MOVE_TO_COLUMN(1));
3538
+ }
3539
+ /**
3540
+ * Writes text to the stream
3541
+ */
3542
+ write(text) {
3543
+ this.stream.write(text);
3544
+ }
3545
+ /**
3546
+ * Gets terminal width
3547
+ */
3548
+ get width() {
3549
+ return this.stream.columns || 80;
3550
+ }
3551
+ /**
3552
+ * Gets terminal height
3553
+ */
3554
+ get height() {
3555
+ return this.stream.rows || 24;
3556
+ }
3557
+ /**
3558
+ * Checks if the stream is a TTY
3559
+ */
3560
+ get isTTY() {
3561
+ return this.stream.isTTY ?? false;
3562
+ }
3563
+ /**
3564
+ * Ensures cursor is shown on cleanup
3565
+ */
3566
+ cleanup() {
3567
+ this.showCursor();
3568
+ }
3569
+ };
3570
+ /**
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
3577
+ * @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
3588
+ */
3589
+ function createProgressBar(value, max, width, options = {}) {
3590
+ const { showPercentage = true, showValues = false, fillChar = "█", emptyChar = "░", leftBracket = "[", rightBracket = "]", colors: colors$2 = {} } = options;
3591
+ const percentage = max > 0 ? Math.min(100, value / max * 100) : 0;
3592
+ const fillWidth = Math.round(percentage / 100 * width);
3593
+ const emptyWidth = width - fillWidth;
3594
+ let color = "";
3595
+ if (colors$2.critical != null && percentage >= 90) color = colors$2.critical;
3596
+ else if (colors$2.high != null && percentage >= 80) color = colors$2.high;
3597
+ else if (colors$2.medium != null && percentage >= 50) color = colors$2.medium;
3598
+ else if (colors$2.low != null) color = colors$2.low;
3599
+ let bar = leftBracket;
3600
+ if (color !== "") bar += color;
3601
+ bar += fillChar.repeat(fillWidth);
3602
+ bar += emptyChar.repeat(emptyWidth);
3603
+ if (color !== "") bar += "\x1B[0m";
3604
+ bar += rightBracket;
3605
+ if (showPercentage) bar += ` ${percentage.toFixed(1)}%`;
3606
+ if (showValues) bar += ` (${value}/${max})`;
3607
+ return bar;
3608
+ }
3609
+ /**
3610
+ * Centers text within a given width
3611
+ * @param text - Text to center
3612
+ * @param width - Total width
3613
+ * @returns Centered text with padding
3614
+ */
3615
+ function centerText(text, width) {
3616
+ const textLength = stringWidth(text);
3617
+ if (textLength >= width) return text;
3618
+ const leftPadding = Math.floor((width - textLength) / 2);
3619
+ const rightPadding = width - textLength - leftPadding;
3620
+ return " ".repeat(leftPadding) + text + " ".repeat(rightPadding);
3621
+ }
3622
+ var import_picocolors$4 = __toESM(require_picocolors(), 1);
3623
+ var import_usingCtx = __toESM(require_usingCtx(), 1);
3624
+ /**
3625
+ * Format token counts with K suffix for display
3626
+ * @param num - Number of tokens
3627
+ * @returns Formatted string like "12.3k" or "999"
3628
+ */
3629
+ function formatTokensShort(num) {
3630
+ if (num >= 1e3) return `${(num / 1e3).toFixed(1)}k`;
3631
+ return num.toString();
3632
+ }
3633
+ /**
3634
+ * Column layout constants for detail rows
3635
+ */
3636
+ const DETAIL_COLUMN_WIDTHS = {
3637
+ col1: 46,
3638
+ col2: 37
3639
+ };
3640
+ /**
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
+ * Renders the live display for an active session block
3705
+ */
3706
+ function renderLiveDisplay(terminal, block, config) {
3707
+ const width = terminal.width;
3708
+ const now = /* @__PURE__ */ new Date();
3709
+ const totalTokens = block.tokenCounts.inputTokens + block.tokenCounts.outputTokens;
3710
+ const elapsed = (now.getTime() - block.startTime.getTime()) / (1e3 * 60);
3711
+ const remaining = (block.endTime.getTime() - now.getTime()) / (1e3 * 60);
3712
+ if (width < 60) {
3713
+ renderCompactLiveDisplay(terminal, block, config, totalTokens, elapsed, remaining);
3714
+ return;
3715
+ }
3716
+ terminal.clearScreen();
3717
+ const boxWidth = Math.min(120, width - 2);
3718
+ const boxMargin = Math.floor((width - boxWidth) / 2);
3719
+ const marginStr = " ".repeat(boxMargin);
3720
+ const labelWidth = 14;
3721
+ const percentWidth = 7;
3722
+ const shortLabelWidth = 20;
3723
+ const barWidth = boxWidth - labelWidth - percentWidth - shortLabelWidth - 4;
3724
+ const sessionDuration = elapsed + remaining;
3725
+ const sessionPercent = elapsed / sessionDuration * 100;
3726
+ const sessionProgressBar = createProgressBar(elapsed, sessionDuration, barWidth, {
3727
+ showPercentage: false,
3728
+ fillChar: import_picocolors$4.default.cyan("█"),
3729
+ emptyChar: import_picocolors$4.default.gray("░"),
3730
+ leftBracket: "[",
3731
+ rightBracket: "]"
3732
+ });
3733
+ const startTime = block.startTime.toLocaleTimeString(void 0, {
3734
+ hour: "2-digit",
3735
+ minute: "2-digit",
3736
+ second: "2-digit",
3737
+ hour12: true
3738
+ });
3739
+ const endTime = block.endTime.toLocaleTimeString(void 0, {
3740
+ hour: "2-digit",
3741
+ minute: "2-digit",
3742
+ second: "2-digit",
3743
+ hour12: true
3744
+ });
3745
+ 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`);
3747
+ terminal.write(`${marginStr}├${"─".repeat(boxWidth - 2)}┤\n`);
3748
+ terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
3749
+ const sessionLabel = import_picocolors$4.default.bold("⏱️ SESSION");
3750
+ const sessionLabelWidth = stringWidth(sessionLabel);
3751
+ const sessionBarStr = `${sessionLabel}${"".padEnd(Math.max(0, labelWidth - sessionLabelWidth))} ${sessionProgressBar} ${sessionPercent.toFixed(1).padStart(6)}%`;
3752
+ const sessionBarPadded = sessionBarStr + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(sessionBarStr)));
3753
+ 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})`;
3757
+ const col1Visible = stringWidth(col1);
3758
+ const col2Visible = stringWidth(col2);
3759
+ const pad1 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col1 - col1Visible));
3760
+ const pad2 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col2 - col2Visible));
3761
+ const sessionDetails = ` ${col1}${pad1}${col2}${pad2}${col3}`;
3762
+ const sessionDetailsPadded = sessionDetails + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(sessionDetails)));
3763
+ terminal.write(`${marginStr}│ ${sessionDetailsPadded}│\n`);
3764
+ terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
3765
+ terminal.write(`${marginStr}├${"─".repeat(boxWidth - 2)}┤\n`);
3766
+ terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
3767
+ 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;
3771
+ const usageBar = config.tokenLimit != null && config.tokenLimit > 0 ? createProgressBar(totalTokens, config.tokenLimit, barWidth, {
3772
+ showPercentage: false,
3773
+ fillChar: barColor("█"),
3774
+ emptyChar: import_picocolors$4.default.gray("░"),
3775
+ leftBracket: "[",
3776
+ rightBracket: "]"
3777
+ }) : `[${import_picocolors$4.default.green("█".repeat(Math.floor(barWidth * .1)))}${import_picocolors$4.default.gray("░".repeat(barWidth - Math.floor(barWidth * .1)))}]`;
3778
+ 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");
3782
+ 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
+ }
3810
+ terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
3811
+ terminal.write(`${marginStr}├${"─".repeat(boxWidth - 2)}┤\n`);
3812
+ terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
3813
+ const projection = projectBlockUsage(block);
3814
+ if (projection != null) {
3815
+ 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;
3819
+ const projectionBar = config.tokenLimit != null && config.tokenLimit > 0 ? createProgressBar(projection.totalTokens, config.tokenLimit, barWidth, {
3820
+ showPercentage: false,
3821
+ fillChar: projBarColor("█"),
3822
+ emptyChar: import_picocolors$4.default.gray("░"),
3823
+ leftBracket: "[",
3824
+ 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");
3828
+ const projLabelWidth = stringWidth(projLabel);
3829
+ if (config.tokenLimit != null && config.tokenLimit > 0) {
3830
+ const projBarStr = `${projLabel}${"".padEnd(Math.max(0, labelWidth - projLabelWidth))} ${projectionBar} ${projectedPercent.toFixed(1).padStart(6)}% (${formatTokensShort(projection.totalTokens)}/${formatTokensShort(config.tokenLimit)})`;
3831
+ const projBarPadded = projBarStr + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(projBarStr)));
3832
+ 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)}`;
3836
+ const col1Visible$1 = stringWidth(col1$1);
3837
+ const col2Visible$1 = stringWidth(col2$1);
3838
+ const pad1$1 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col1 - col1Visible$1));
3839
+ const pad2$1 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col2 - col2Visible$1));
3840
+ const projDetails = ` ${col1$1}${pad1$1}${col2$1}${pad2$1}${col3$1}`;
3841
+ const projDetailsPadded = projDetails + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(projDetails)));
3842
+ terminal.write(`${marginStr}│ ${projDetailsPadded}│\n`);
3843
+ } else {
3844
+ const projBarStr = `${projLabel}${"".padEnd(Math.max(0, labelWidth - projLabelWidth))} ${projectionBar} (${formatTokensShort(projection.totalTokens)} tokens)`;
3845
+ const projBarPadded = projBarStr + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(projBarStr)));
3846
+ 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)}`;
3850
+ const col1Visible$1 = stringWidth(col1$1);
3851
+ const col2Visible$1 = stringWidth(col2$1);
3852
+ const pad1$1 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col1 - col1Visible$1));
3853
+ const pad2$1 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col2 - col2Visible$1));
3854
+ const projDetails = ` ${col1$1}${pad1$1}${col2$1}${pad2$1}${col3$1}`;
3855
+ const projDetailsPadded = projDetails + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(projDetails)));
3856
+ terminal.write(`${marginStr}│ ${projDetailsPadded}│\n`);
3857
+ }
3858
+ terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
3859
+ }
3860
+ if (block.models.length > 0) {
3861
+ terminal.write(`${marginStr}├${"─".repeat(boxWidth - 2)}┤\n`);
3862
+ const modelsLine = `⚙️ Models: ${formatModelsDisplay(block.models)}`;
3863
+ const modelsLinePadded = modelsLine + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(modelsLine)));
3864
+ terminal.write(`${marginStr}│ ${modelsLinePadded}│\n`);
3865
+ }
3866
+ terminal.write(`${marginStr}├${"─".repeat(boxWidth - 2)}┤\n`);
3867
+ 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`);
3869
+ terminal.write(`${marginStr}└${"─".repeat(boxWidth - 2)}┘\n`);
3870
+ }
3871
+ /**
3872
+ * Renders a compact live display for narrow terminals
3873
+ */
3874
+ function renderCompactLiveDisplay(terminal, block, config, totalTokens, elapsed, remaining) {
3875
+ const width = terminal.width;
3876
+ terminal.write(`${import_picocolors$4.default.bold(centerText("LIVE MONITOR", width))}\n`);
3877
+ terminal.write(`${"─".repeat(width)}\n`);
3878
+ const sessionPercent = elapsed / (elapsed + remaining) * 100;
3879
+ terminal.write(`Session: ${sessionPercent.toFixed(1)}% (${Math.floor(elapsed / 60)}h ${Math.floor(elapsed % 60)}m)\n`);
3880
+ if (config.tokenLimit != null && config.tokenLimit > 0) {
3881
+ 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");
3883
+ terminal.write(`Tokens: ${formatNumber(totalTokens)}/${formatNumber(config.tokenLimit)} ${status}\n`);
3884
+ } else terminal.write(`Tokens: ${formatNumber(totalTokens)}\n`);
3885
+ terminal.write(`Cost: ${formatCurrency(block.costUSD)}\n`);
3886
+ const burnRate = calculateBurnRate(block);
3887
+ if (burnRate != null) terminal.write(`Rate: ${formatNumber(burnRate.tokensPerMinute)}/min\n`);
3888
+ terminal.write(`${"─".repeat(width)}\n`);
3889
+ terminal.write(import_picocolors$4.default.gray(`Refresh: ${config.refreshInterval / 1e3}s | Ctrl+C: stop\n`));
3890
+ }
3163
3891
  var import_picocolors$3 = __toESM(require_picocolors(), 1);
3164
3892
  /**
3165
3893
  * Formats the time display for a session block
@@ -3241,13 +3969,23 @@ const blocksCommand = define({
3241
3969
  tokenLimit: {
3242
3970
  type: "string",
3243
3971
  short: "t",
3244
- description: "Token limit for quota warnings (e.g., 500000 or \"max\" for highest previous block)"
3972
+ description: "Token limit for quota warnings (e.g., 500000 or \"max\")"
3245
3973
  },
3246
3974
  sessionLength: {
3247
3975
  type: "number",
3248
3976
  short: "l",
3249
3977
  description: `Session block duration in hours (default: ${DEFAULT_SESSION_DURATION_HOURS})`,
3250
3978
  default: DEFAULT_SESSION_DURATION_HOURS
3979
+ },
3980
+ live: {
3981
+ type: "boolean",
3982
+ description: "Live monitoring mode with real-time updates",
3983
+ default: false
3984
+ },
3985
+ refreshInterval: {
3986
+ type: "number",
3987
+ description: `Refresh interval in seconds for live mode (default: ${DEFAULT_REFRESH_INTERVAL_SECONDS})`,
3988
+ default: DEFAULT_REFRESH_INTERVAL_SECONDS
3251
3989
  }
3252
3990
  },
3253
3991
  toKebab: true,
@@ -3290,6 +4028,25 @@ const blocksCommand = define({
3290
4028
  process$1.exit(0);
3291
4029
  }
3292
4030
  }
4031
+ if (ctx.values.live && !ctx.values.json) {
4032
+ if (!ctx.values.active) logger.info("Live mode automatically shows only active blocks.");
4033
+ let tokenLimitValue = ctx.values.tokenLimit;
4034
+ if (tokenLimitValue == null || tokenLimitValue === "") {
4035
+ tokenLimitValue = "max";
4036
+ if (maxTokensFromAll > 0) logger.info(`No token limit specified, using max from previous sessions: ${formatNumber(maxTokensFromAll)}`);
4037
+ }
4038
+ const refreshInterval = Math.max(MIN_REFRESH_INTERVAL_SECONDS, Math.min(MAX_REFRESH_INTERVAL_SECONDS, ctx.values.refreshInterval));
4039
+ if (refreshInterval !== ctx.values.refreshInterval) logger.warn(`Refresh interval adjusted to ${refreshInterval} seconds (valid range: ${MIN_REFRESH_INTERVAL_SECONDS}-${MAX_REFRESH_INTERVAL_SECONDS})`);
4040
+ await startLiveMonitoring({
4041
+ claudePath: getDefaultClaudePath(),
4042
+ tokenLimit: parseTokenLimit(tokenLimitValue, maxTokensFromAll),
4043
+ refreshInterval: refreshInterval * 1e3,
4044
+ sessionDurationHours: ctx.values.sessionLength,
4045
+ mode: ctx.values.mode,
4046
+ order: ctx.values.order
4047
+ });
4048
+ return;
4049
+ }
3293
4050
  if (ctx.values.json) {
3294
4051
  const jsonOutput = { blocks: blocks.map((block) => {
3295
4052
  const burnRate = block.isActive ? calculateBurnRate(block) : null;