ccusage 15.1.0 → 15.2.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,11 @@
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, require_usingCtx } from "./pricing-fetcher-fT0o6CKK.js";
3
+ import { CostModes, SortOrders, filterDateSchema } from "./_types-CmSE0O0q.js";
4
+ import { calculateTotals, createTotalsObject, getTotalTokens } from "./calculate-cost-B0RYn0Vm.js";
5
+ import { DEFAULT_SESSION_DURATION_HOURS, calculateBurnRate, calculateCostForEntry, createUniqueHash, filterRecentBlocks, formatDateCompact, getClaudePaths, getEarliestTimestamp, glob, identifySessionBlocks, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, projectBlockUsage, sortFilesByTimestamp, uniq, usageDataSchema } from "./data-loader-CzOPffdg.js";
6
+ import { description, log, logger, name, version } from "./logger-CeR-gFvq.js";
7
+ import { detectMismatches, printMismatchReport } from "./debug-CCsUo8-n.js";
8
+ import { createMcpHttpApp, createMcpServer, startMcpServerStdio } from "./mcp-DGCqhgBz.js";
9
9
  import { readFile } from "node:fs/promises";
10
10
  import path from "node:path";
11
11
  import process$1 from "node:process";
@@ -1194,7 +1194,7 @@ var require_picocolors = __commonJSMin((exports, module) => {
1194
1194
  * @throws TypeError if date format is invalid
1195
1195
  */
1196
1196
  function parseDateArg(value) {
1197
- const result = dateSchema.safeParse(value);
1197
+ const result = filterDateSchema.safeParse(value);
1198
1198
  if (!result.success) throw new TypeError(result.error.issues[0]?.message ?? "Invalid date format");
1199
1199
  return result.data;
1200
1200
  }
@@ -2931,7 +2931,7 @@ function stringWidth(string, options = {}) {
2931
2931
  return width;
2932
2932
  }
2933
2933
  var import_cli_table3 = __toESM(require_cli_table3(), 1);
2934
- var import_picocolors$5 = __toESM(require_picocolors(), 1);
2934
+ var import_picocolors$6 = __toESM(require_picocolors(), 1);
2935
2935
  /**
2936
2936
  * Responsive table class that adapts column widths based on terminal size
2937
2937
  * Automatically adjusts formatting and layout for different screen sizes
@@ -3167,11 +3167,92 @@ function pushBreakdownRows(table, breakdowns, extraColumns = 1, trailingColumns
3167
3167
  const row = [` └─ ${formatModelName(breakdown.modelName)}`];
3168
3168
  for (let i = 0; i < extraColumns; i++) row.push("");
3169
3169
  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)));
3170
+ 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
3171
  for (let i = 0; i < trailingColumns; i++) row.push("");
3172
3172
  table.push(row);
3173
3173
  }
3174
3174
  }
3175
+ /**
3176
+ * Manages live monitoring of Claude usage with efficient data reloading
3177
+ */
3178
+ var LiveMonitor = class {
3179
+ config;
3180
+ fetcher = null;
3181
+ lastFileTimestamps = /* @__PURE__ */ new Map();
3182
+ processedHashes = /* @__PURE__ */ new Set();
3183
+ allEntries = [];
3184
+ constructor(config) {
3185
+ this.config = config;
3186
+ if (config.mode !== "display") this.fetcher = new PricingFetcher();
3187
+ }
3188
+ /**
3189
+ * Implements Disposable interface
3190
+ */
3191
+ [Symbol.dispose]() {
3192
+ this.fetcher?.[Symbol.dispose]();
3193
+ }
3194
+ /**
3195
+ * Gets the current active session block with minimal file reading
3196
+ * Only reads new or modified files since last check
3197
+ */
3198
+ async getActiveBlock() {
3199
+ const claudeDir = path.join(this.config.claudePath, CLAUDE_PROJECTS_DIR_NAME);
3200
+ const files = await glob([USAGE_DATA_GLOB_PATTERN], {
3201
+ cwd: claudeDir,
3202
+ absolute: true
3203
+ });
3204
+ if (files.length === 0) return null;
3205
+ const filesToRead = [];
3206
+ for (const file of files) {
3207
+ const timestamp = await getEarliestTimestamp(file);
3208
+ const lastTimestamp = this.lastFileTimestamps.get(file);
3209
+ if (timestamp != null && (lastTimestamp == null || timestamp.getTime() > lastTimestamp)) {
3210
+ filesToRead.push(file);
3211
+ this.lastFileTimestamps.set(file, timestamp.getTime());
3212
+ }
3213
+ }
3214
+ if (filesToRead.length > 0) {
3215
+ const sortedFiles = await sortFilesByTimestamp(filesToRead);
3216
+ for (const file of sortedFiles) {
3217
+ const content = await readFile(file, "utf-8");
3218
+ const lines = content.trim().split("\n").filter((line) => line.length > 0);
3219
+ for (const line of lines) try {
3220
+ const parsed = JSON.parse(line);
3221
+ const result = usageDataSchema.safeParse(parsed);
3222
+ if (!result.success) continue;
3223
+ const data = result.data;
3224
+ const uniqueHash = createUniqueHash(data);
3225
+ if (uniqueHash != null && this.processedHashes.has(uniqueHash)) continue;
3226
+ if (uniqueHash != null) this.processedHashes.add(uniqueHash);
3227
+ const costUSD = await (this.config.mode === "display" ? Promise.resolve(data.costUSD ?? 0) : calculateCostForEntry(data, this.config.mode, this.fetcher));
3228
+ this.allEntries.push({
3229
+ timestamp: new Date(data.timestamp),
3230
+ usage: {
3231
+ inputTokens: data.message.usage.input_tokens ?? 0,
3232
+ outputTokens: data.message.usage.output_tokens ?? 0,
3233
+ cacheCreationInputTokens: data.message.usage.cache_creation_input_tokens ?? 0,
3234
+ cacheReadInputTokens: data.message.usage.cache_read_input_tokens ?? 0
3235
+ },
3236
+ costUSD,
3237
+ model: data.message.model ?? "<synthetic>",
3238
+ version: data.version
3239
+ });
3240
+ } catch {}
3241
+ }
3242
+ }
3243
+ const blocks = identifySessionBlocks(this.allEntries, this.config.sessionDurationHours);
3244
+ const sortedBlocks = this.config.order === "asc" ? blocks : blocks.reverse();
3245
+ return sortedBlocks.find((block) => block.isActive) ?? null;
3246
+ }
3247
+ /**
3248
+ * Clears all cached data to force a full reload
3249
+ */
3250
+ clearCache() {
3251
+ this.lastFileTimestamps.clear();
3252
+ this.processedHashes.clear();
3253
+ this.allEntries = [];
3254
+ }
3255
+ };
3175
3256
  /** Options for {@linkcode delay}. */
3176
3257
  /**
3177
3258
  * Resolve a {@linkcode Promise} after a given amount of milliseconds.
@@ -3225,6 +3306,54 @@ function pushBreakdownRows(table, breakdowns, extraColumns = 1, trailingColumns
3225
3306
  }
3226
3307
  });
3227
3308
  }
3309
+ const isBrowser = globalThis.window?.document !== void 0;
3310
+ const isNode = globalThis.process?.versions?.node !== void 0;
3311
+ const isBun = globalThis.process?.versions?.bun !== void 0;
3312
+ const isDeno = globalThis.Deno?.version?.deno !== void 0;
3313
+ const isElectron = globalThis.process?.versions?.electron !== void 0;
3314
+ const isJsDom = globalThis.navigator?.userAgent?.includes("jsdom") === true;
3315
+ const isWebWorker = typeof WorkerGlobalScope !== "undefined" && globalThis instanceof WorkerGlobalScope;
3316
+ const isDedicatedWorker = typeof DedicatedWorkerGlobalScope !== "undefined" && globalThis instanceof DedicatedWorkerGlobalScope;
3317
+ const isSharedWorker = typeof SharedWorkerGlobalScope !== "undefined" && globalThis instanceof SharedWorkerGlobalScope;
3318
+ const isServiceWorker = typeof ServiceWorkerGlobalScope !== "undefined" && globalThis instanceof ServiceWorkerGlobalScope;
3319
+ const platform = globalThis.navigator?.userAgentData?.platform;
3320
+ const isMacOs = platform === "macOS" || globalThis.navigator?.platform === "MacIntel" || globalThis.navigator?.userAgent?.includes(" Mac ") === true || globalThis.process?.platform === "darwin";
3321
+ const isWindows$1 = platform === "Windows" || globalThis.navigator?.platform === "Win32" || globalThis.process?.platform === "win32";
3322
+ const isLinux = platform === "Linux" || globalThis.navigator?.platform?.startsWith("Linux") === true || globalThis.navigator?.userAgent?.includes(" Linux ") === true || globalThis.process?.platform === "linux";
3323
+ const isIos = platform === "iOS" || globalThis.navigator?.platform === "MacIntel" && globalThis.navigator?.maxTouchPoints > 1 || /iPad|iPhone|iPod/.test(globalThis.navigator?.platform);
3324
+ const isAndroid = platform === "Android" || globalThis.navigator?.platform === "Android" || globalThis.navigator?.userAgent?.includes(" Android ") === true || globalThis.process?.platform === "android";
3325
+ const ESC = "\x1B[";
3326
+ const SEP = ";";
3327
+ const isTerminalApp = !isBrowser && process$1.env.TERM_PROGRAM === "Apple_Terminal";
3328
+ const isWindows = !isBrowser && process$1.platform === "win32";
3329
+ const cwdFunction = isBrowser ? () => {
3330
+ throw new Error("`process.cwd()` only works in Node.js, not the browser.");
3331
+ } : process$1.cwd;
3332
+ const cursorTo = (x, y) => {
3333
+ if (typeof x !== "number") throw new TypeError("The `x` argument is required");
3334
+ if (typeof y !== "number") return ESC + (x + 1) + "G";
3335
+ return ESC + (y + 1) + SEP + (x + 1) + "H";
3336
+ };
3337
+ const cursorLeft = ESC + "G";
3338
+ const cursorSavePosition = isTerminalApp ? "\x1B7" : ESC + "s";
3339
+ const cursorRestorePosition = isTerminalApp ? "\x1B8" : ESC + "u";
3340
+ const cursorGetPosition = ESC + "6n";
3341
+ const cursorNextLine = ESC + "E";
3342
+ const cursorPrevLine = ESC + "F";
3343
+ const cursorHide = ESC + "?25l";
3344
+ const cursorShow = ESC + "?25h";
3345
+ const eraseEndLine = ESC + "K";
3346
+ const eraseStartLine = ESC + "1K";
3347
+ const eraseLine = ESC + "2K";
3348
+ const eraseDown = ESC + "J";
3349
+ const eraseUp = ESC + "1J";
3350
+ const eraseScreen = ESC + "2J";
3351
+ const scrollUp = ESC + "S";
3352
+ const scrollDown = ESC + "T";
3353
+ const clearScreen = "\x1Bc";
3354
+ const clearTerminal = isWindows ? `${eraseScreen}${ESC}0f` : `${eraseScreen}${ESC}3J${ESC}H`;
3355
+ const enterAlternativeScreen = ESC + "?1049h";
3356
+ const exitAlternativeScreen = ESC + "?1049l";
3228
3357
  const toZeroIfInfinity = (value) => Number.isFinite(value) ? value : 0;
3229
3358
  function parseNumber(milliseconds) {
3230
3359
  return {
@@ -3336,255 +3465,170 @@ function prettyMilliseconds(milliseconds, options) {
3336
3465
  if (typeof options.unitCount === "number") result = result.slice(0, Math.max(options.unitCount, 1));
3337
3466
  return sign + result.join(separator);
3338
3467
  }
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
- };
3468
+ const SYNC_START = "\x1B[?2026h";
3469
+ const SYNC_END = "\x1B[?2026l";
3470
+ const DISABLE_LINE_WRAP = "\x1B[?7l";
3471
+ const ENABLE_LINE_WRAP = "\x1B[?7h";
3472
+ const ANSI_RESET = "\x1B[0m";
3483
3473
  /**
3484
3474
  * Manages terminal state for live updates
3475
+ * Provides a clean interface for terminal operations with automatic TTY checking
3476
+ * and cursor state management for live monitoring displays
3485
3477
  */
3486
3478
  var TerminalManager = class {
3487
3479
  stream;
3488
3480
  cursorHidden = false;
3489
- originalWrite;
3481
+ buffer = [];
3482
+ useBuffering = false;
3483
+ alternateScreenActive = false;
3484
+ syncMode = false;
3490
3485
  constructor(stream = process$1.stdout) {
3491
3486
  this.stream = stream;
3492
- this.originalWrite = stream.write.bind(stream);
3493
3487
  }
3494
3488
  /**
3495
- * Hides the terminal cursor
3489
+ * Hides the terminal cursor for cleaner live updates
3490
+ * Only works in TTY environments (real terminals)
3496
3491
  */
3497
3492
  hideCursor() {
3498
3493
  if (!this.cursorHidden && this.stream.isTTY) {
3499
- this.stream.write(TERMINAL_CONTROL.HIDE_CURSOR);
3494
+ this.stream.write(cursorHide);
3500
3495
  this.cursorHidden = true;
3501
3496
  }
3502
3497
  }
3503
3498
  /**
3504
3499
  * Shows the terminal cursor
3500
+ * Should be called during cleanup to restore normal terminal behavior
3505
3501
  */
3506
3502
  showCursor() {
3507
3503
  if (this.cursorHidden && this.stream.isTTY) {
3508
- this.stream.write(TERMINAL_CONTROL.SHOW_CURSOR);
3504
+ this.stream.write(cursorShow);
3509
3505
  this.cursorHidden = false;
3510
3506
  }
3511
3507
  }
3512
3508
  /**
3513
- * Clears the entire screen and moves cursor to top
3509
+ * Clears the entire screen and moves cursor to top-left corner
3510
+ * Essential for live monitoring displays that need to refresh completely
3514
3511
  */
3515
3512
  clearScreen() {
3516
3513
  if (this.stream.isTTY) {
3517
- this.stream.write(TERMINAL_CONTROL.CLEAR_SCREEN);
3518
- this.stream.write(TERMINAL_CONTROL.MOVE_TO_TOP);
3514
+ this.stream.write(clearScreen);
3515
+ this.stream.write(cursorTo(0, 0));
3519
3516
  }
3520
3517
  }
3521
3518
  /**
3522
- * Clears the current line
3519
+ * Writes text to the terminal stream
3520
+ * Supports buffering mode for performance optimization
3523
3521
  */
3524
- clearLine() {
3525
- if (this.stream.isTTY) this.stream.write(TERMINAL_CONTROL.CLEAR_LINE);
3522
+ write(text) {
3523
+ if (this.useBuffering) this.buffer.push(text);
3524
+ else this.stream.write(text);
3526
3525
  }
3527
3526
  /**
3528
- * Moves cursor up by n lines
3527
+ * Enables buffering mode - collects all writes in memory instead of sending immediately
3528
+ * This prevents flickering when doing many rapid updates
3529
3529
  */
3530
- moveUp(lines) {
3531
- if (this.stream.isTTY && lines > 0) this.stream.write(TERMINAL_CONTROL.MOVE_UP(lines));
3530
+ startBuffering() {
3531
+ this.useBuffering = true;
3532
+ this.buffer = [];
3532
3533
  }
3533
3534
  /**
3534
- * Moves cursor to beginning of line
3535
+ * Sends all buffered content to terminal at once
3536
+ * This creates smooth, atomic updates without flickering
3535
3537
  */
3536
- moveToLineStart() {
3537
- if (this.stream.isTTY) this.stream.write(TERMINAL_CONTROL.MOVE_TO_COLUMN(1));
3538
+ flush() {
3539
+ if (this.useBuffering && this.buffer.length > 0) {
3540
+ if (this.syncMode && this.stream.isTTY) this.stream.write(SYNC_START + this.buffer.join("") + SYNC_END);
3541
+ else this.stream.write(this.buffer.join(""));
3542
+ this.buffer = [];
3543
+ }
3544
+ this.useBuffering = false;
3538
3545
  }
3539
3546
  /**
3540
- * Writes text to the stream
3547
+ * Switches to alternate screen buffer (like vim/less does)
3548
+ * This preserves what was on screen before and allows full-screen apps
3541
3549
  */
3542
- write(text) {
3543
- this.stream.write(text);
3550
+ enterAlternateScreen() {
3551
+ if (!this.alternateScreenActive && this.stream.isTTY) {
3552
+ this.stream.write(enterAlternativeScreen);
3553
+ this.stream.write(DISABLE_LINE_WRAP);
3554
+ this.alternateScreenActive = true;
3555
+ }
3544
3556
  }
3545
3557
  /**
3546
- * Gets terminal width
3558
+ * Returns to normal screen, restoring what was there before
3559
+ */
3560
+ exitAlternateScreen() {
3561
+ if (this.alternateScreenActive && this.stream.isTTY) {
3562
+ this.stream.write(ENABLE_LINE_WRAP);
3563
+ this.stream.write(exitAlternativeScreen);
3564
+ this.alternateScreenActive = false;
3565
+ }
3566
+ }
3567
+ /**
3568
+ * Enables sync mode - terminal will wait for END signal before showing updates
3569
+ * Prevents the user from seeing partial/torn screen updates
3570
+ */
3571
+ enableSyncMode() {
3572
+ this.syncMode = true;
3573
+ }
3574
+ /**
3575
+ * Disables synchronized output mode
3576
+ */
3577
+ disableSyncMode() {
3578
+ this.syncMode = false;
3579
+ }
3580
+ /**
3581
+ * Gets terminal width in columns
3582
+ * Falls back to 80 columns if detection fails
3547
3583
  */
3548
3584
  get width() {
3549
3585
  return this.stream.columns || 80;
3550
3586
  }
3551
3587
  /**
3552
- * Gets terminal height
3588
+ * Gets terminal height in rows
3589
+ * Falls back to 24 rows if detection fails
3553
3590
  */
3554
3591
  get height() {
3555
3592
  return this.stream.rows || 24;
3556
3593
  }
3557
3594
  /**
3558
- * Checks if the stream is a TTY
3595
+ * Returns true if output goes to a real terminal (not a file or pipe)
3596
+ * We only send fancy ANSI codes to real terminals
3559
3597
  */
3560
3598
  get isTTY() {
3561
3599
  return this.stream.isTTY ?? false;
3562
3600
  }
3563
3601
  /**
3564
- * Ensures cursor is shown on cleanup
3602
+ * Restores terminal to normal state - MUST call before program exits
3603
+ * Otherwise user's terminal might be left in a broken state
3565
3604
  */
3566
3605
  cleanup() {
3567
3606
  this.showCursor();
3607
+ this.exitAlternateScreen();
3608
+ this.disableSyncMode();
3568
3609
  }
3569
3610
  };
3570
3611
  /**
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
3612
+ * Creates a progress bar string with customizable appearance
3613
+ *
3614
+ * Example: createProgressBar(75, 100, 20) -> "[████████████████░░░░] 75.0%"
3615
+ *
3616
+ * @param value - Current progress value
3617
+ * @param max - Maximum value (100% point)
3618
+ * @param width - Character width of the progress bar (excluding brackets and text)
3619
+ * @param options - Customization options for appearance and display
3620
+ * @param options.showPercentage - Whether to show percentage after the bar
3577
3621
  * @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
3622
+ * @param options.fillChar - Character for filled portion (default: '█')
3623
+ * @param options.emptyChar - Character for empty portion (default: '░')
3624
+ * @param options.leftBracket - Left bracket character (default: '[')
3625
+ * @param options.rightBracket - Right bracket character (default: ']')
3626
+ * @param options.colors - Color configuration for different thresholds
3627
+ * @param options.colors.low - Color for low percentage values
3628
+ * @param options.colors.medium - Color for medium percentage values
3629
+ * @param options.colors.high - Color for high percentage values
3630
+ * @param options.colors.critical - Color for critical percentage values
3631
+ * @returns Formatted progress bar string with optional percentage/values
3588
3632
  */
3589
3633
  function createProgressBar(value, max, width, options = {}) {
3590
3634
  const { showPercentage = true, showValues = false, fillChar = "█", emptyChar = "░", leftBracket = "[", rightBracket = "]", colors: colors$2 = {} } = options;
@@ -3600,17 +3644,23 @@ function createProgressBar(value, max, width, options = {}) {
3600
3644
  if (color !== "") bar += color;
3601
3645
  bar += fillChar.repeat(fillWidth);
3602
3646
  bar += emptyChar.repeat(emptyWidth);
3603
- if (color !== "") bar += "\x1B[0m";
3647
+ if (color !== "") bar += ANSI_RESET;
3604
3648
  bar += rightBracket;
3605
3649
  if (showPercentage) bar += ` ${percentage.toFixed(1)}%`;
3606
3650
  if (showValues) bar += ` (${value}/${max})`;
3607
3651
  return bar;
3608
3652
  }
3609
3653
  /**
3610
- * Centers text within a given width
3611
- * @param text - Text to center
3612
- * @param width - Total width
3613
- * @returns Centered text with padding
3654
+ * Centers text within a specified width using spaces for padding
3655
+ *
3656
+ * Uses string-width to handle Unicode characters and ANSI escape codes properly.
3657
+ * If text is longer than width, returns original text without truncation.
3658
+ *
3659
+ * Example: centerText("Hello", 10) -> " Hello "
3660
+ *
3661
+ * @param text - Text to center (may contain ANSI color codes)
3662
+ * @param width - Total character width including padding
3663
+ * @returns Text with spaces added for centering
3614
3664
  */
3615
3665
  function centerText(text, width) {
3616
3666
  const textLength = stringWidth(text);
@@ -3619,12 +3669,40 @@ function centerText(text, width) {
3619
3669
  const rightPadding = width - textLength - leftPadding;
3620
3670
  return " ".repeat(leftPadding) + text + " ".repeat(rightPadding);
3621
3671
  }
3622
- var import_picocolors$4 = __toESM(require_picocolors(), 1);
3623
- var import_usingCtx = __toESM(require_usingCtx(), 1);
3672
+ var import_picocolors$5 = __toESM(require_picocolors(), 1);
3673
+ /**
3674
+ * Delay with AbortSignal support and graceful error handling
3675
+ */
3676
+ async function delayWithAbort(ms, signal) {
3677
+ await delay(ms, { signal });
3678
+ }
3679
+ /**
3680
+ * Shows waiting message when no Claude session is active
3681
+ * Uses efficient cursor positioning instead of full screen clear
3682
+ */
3683
+ async function renderWaitingState(terminal, config, signal) {
3684
+ terminal.startBuffering();
3685
+ terminal.write(cursorTo(0, 0));
3686
+ terminal.write(eraseDown);
3687
+ terminal.write(import_picocolors$5.default.yellow("No active session block found. Waiting...\n"));
3688
+ terminal.write(cursorHide);
3689
+ terminal.flush();
3690
+ await delayWithAbort(config.refreshInterval, signal);
3691
+ }
3692
+ /**
3693
+ * Displays the live monitoring dashboard for active Claude session
3694
+ * Uses buffering and sync mode to prevent screen flickering
3695
+ */
3696
+ function renderActiveBlock(terminal, activeBlock, config) {
3697
+ terminal.startBuffering();
3698
+ terminal.write(cursorTo(0, 0));
3699
+ terminal.write(eraseDown);
3700
+ renderLiveDisplay(terminal, activeBlock, config);
3701
+ terminal.write(cursorHide);
3702
+ terminal.flush();
3703
+ }
3624
3704
  /**
3625
3705
  * Format token counts with K suffix for display
3626
- * @param num - Number of tokens
3627
- * @returns Formatted string like "12.3k" or "999"
3628
3706
  */
3629
3707
  function formatTokensShort(num) {
3630
3708
  if (num >= 1e3) return `${(num / 1e3).toFixed(1)}k`;
@@ -3638,69 +3716,6 @@ const DETAIL_COLUMN_WIDTHS = {
3638
3716
  col2: 37
3639
3717
  };
3640
3718
  /**
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
3719
  * Renders the live display for an active session block
3705
3720
  */
3706
3721
  function renderLiveDisplay(terminal, block, config) {
@@ -3713,7 +3728,6 @@ function renderLiveDisplay(terminal, block, config) {
3713
3728
  renderCompactLiveDisplay(terminal, block, config, totalTokens, elapsed, remaining);
3714
3729
  return;
3715
3730
  }
3716
- terminal.clearScreen();
3717
3731
  const boxWidth = Math.min(120, width - 2);
3718
3732
  const boxMargin = Math.floor((width - boxWidth) / 2);
3719
3733
  const marginStr = " ".repeat(boxMargin);
@@ -3725,8 +3739,8 @@ function renderLiveDisplay(terminal, block, config) {
3725
3739
  const sessionPercent = elapsed / sessionDuration * 100;
3726
3740
  const sessionProgressBar = createProgressBar(elapsed, sessionDuration, barWidth, {
3727
3741
  showPercentage: false,
3728
- fillChar: import_picocolors$4.default.cyan("█"),
3729
- emptyChar: import_picocolors$4.default.gray("░"),
3742
+ fillChar: import_picocolors$5.default.cyan("█"),
3743
+ emptyChar: import_picocolors$5.default.gray("░"),
3730
3744
  leftBracket: "[",
3731
3745
  rightBracket: "]"
3732
3746
  });
@@ -3743,96 +3757,89 @@ function renderLiveDisplay(terminal, block, config) {
3743
3757
  hour12: true
3744
3758
  });
3745
3759
  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`);
3760
+ terminal.write(`${marginStr}│${import_picocolors$5.default.bold(centerText("CLAUDE CODE - LIVE TOKEN USAGE MONITOR", boxWidth - 2))}│\n`);
3747
3761
  terminal.write(`${marginStr}├${"─".repeat(boxWidth - 2)}┤\n`);
3748
3762
  terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
3749
- const sessionLabel = import_picocolors$4.default.bold("⏱️ SESSION");
3763
+ const sessionLabel = import_picocolors$5.default.bold("⏱️ SESSION");
3750
3764
  const sessionLabelWidth = stringWidth(sessionLabel);
3751
3765
  const sessionBarStr = `${sessionLabel}${"".padEnd(Math.max(0, labelWidth - sessionLabelWidth))} ${sessionProgressBar} ${sessionPercent.toFixed(1).padStart(6)}%`;
3752
3766
  const sessionBarPadded = sessionBarStr + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(sessionBarStr)));
3753
3767
  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})`;
3768
+ const col1 = `${import_picocolors$5.default.gray("Started:")} ${startTime}`;
3769
+ const col2 = `${import_picocolors$5.default.gray("Elapsed:")} ${prettyMilliseconds(elapsed * 60 * 1e3, { compact: true })}`;
3770
+ const col3 = `${import_picocolors$5.default.gray("Remaining:")} ${prettyMilliseconds(remaining * 60 * 1e3, { compact: true })} (${endTime})`;
3757
3771
  const col1Visible = stringWidth(col1);
3758
3772
  const col2Visible = stringWidth(col2);
3759
3773
  const pad1 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col1 - col1Visible));
3760
3774
  const pad2 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col2 - col2Visible));
3761
- const sessionDetails = ` ${col1}${pad1}${col2}${pad2}${col3}`;
3775
+ const sessionDetails = ` ${col1}${pad1}${pad2}${col3}`;
3762
3776
  const sessionDetailsPadded = sessionDetails + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(sessionDetails)));
3763
3777
  terminal.write(`${marginStr}│ ${sessionDetailsPadded}│\n`);
3764
3778
  terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
3765
3779
  terminal.write(`${marginStr}├${"─".repeat(boxWidth - 2)}┤\n`);
3766
3780
  terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
3767
3781
  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;
3782
+ let barColor = import_picocolors$5.default.green;
3783
+ if (tokenPercent > 100) barColor = import_picocolors$5.default.red;
3784
+ else if (tokenPercent > 80) barColor = import_picocolors$5.default.yellow;
3771
3785
  const usageBar = config.tokenLimit != null && config.tokenLimit > 0 ? createProgressBar(totalTokens, config.tokenLimit, barWidth, {
3772
3786
  showPercentage: false,
3773
3787
  fillChar: barColor("█"),
3774
- emptyChar: import_picocolors$4.default.gray("░"),
3788
+ emptyChar: import_picocolors$5.default.gray("░"),
3775
3789
  leftBracket: "[",
3776
3790
  rightBracket: "]"
3777
- }) : `[${import_picocolors$4.default.green("█".repeat(Math.floor(barWidth * .1)))}${import_picocolors$4.default.gray("░".repeat(barWidth - Math.floor(barWidth * .1)))}]`;
3791
+ }) : `[${import_picocolors$5.default.green("█".repeat(Math.floor(barWidth * .1)))}${import_picocolors$5.default.gray("░".repeat(barWidth - Math.floor(barWidth * .1)))}]`;
3778
3792
  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");
3793
+ 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") : "";
3794
+ 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`;
3795
+ const usageLabel = import_picocolors$5.default.bold("🔥 USAGE");
3782
3796
  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
- }
3797
+ const { usageBarStr, usageCol1, usageCol2, usageCol3 } = config.tokenLimit != null && config.tokenLimit > 0 ? {
3798
+ usageBarStr: `${usageLabel}${"".padEnd(Math.max(0, labelWidth - usageLabelWidth))} ${usageBar} ${tokenPercent.toFixed(1).padStart(6)}% (${formatTokensShort(totalTokens)}/${formatTokensShort(config.tokenLimit)})`,
3799
+ usageCol1: `${import_picocolors$5.default.gray("Tokens:")} ${formatNumber(totalTokens)} (${rateDisplay})`,
3800
+ usageCol2: `${import_picocolors$5.default.gray("Limit:")} ${formatNumber(config.tokenLimit)} tokens`,
3801
+ usageCol3: `${import_picocolors$5.default.gray("Cost:")} ${formatCurrency(block.costUSD)}`
3802
+ } : {
3803
+ usageBarStr: `${usageLabel}${"".padEnd(Math.max(0, labelWidth - usageLabelWidth))} ${usageBar} (${formatTokensShort(totalTokens)} tokens)`,
3804
+ usageCol1: `${import_picocolors$5.default.gray("Tokens:")} ${formatNumber(totalTokens)} (${rateDisplay})`,
3805
+ usageCol2: "",
3806
+ usageCol3: `${import_picocolors$5.default.gray("Cost:")} ${formatCurrency(block.costUSD)}`
3807
+ };
3808
+ const usageBarPadded = usageBarStr + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(usageBarStr)));
3809
+ terminal.write(`${marginStr}│ ${usageBarPadded}│\n`);
3810
+ const usageCol1Visible = stringWidth(usageCol1);
3811
+ const usageCol2Visible = stringWidth(usageCol2);
3812
+ const usagePad1 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col1 - usageCol1Visible));
3813
+ const usagePad2 = usageCol2.length > 0 ? " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col2 - usageCol2Visible)) : " ".repeat(DETAIL_COLUMN_WIDTHS.col2);
3814
+ const usageDetails = ` ${usageCol1}${usagePad1}${usageCol2}${usagePad2}${usageCol3}`;
3815
+ const usageDetailsPadded = usageDetails + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(usageDetails)));
3816
+ terminal.write(`${marginStr} ${usageDetailsPadded}│\n`);
3810
3817
  terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
3811
3818
  terminal.write(`${marginStr}├${"─".repeat(boxWidth - 2)}┤\n`);
3812
3819
  terminal.write(`${marginStr}│${" ".repeat(boxWidth - 2)}│\n`);
3813
3820
  const projection = projectBlockUsage(block);
3814
3821
  if (projection != null) {
3815
3822
  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;
3823
+ let projBarColor = import_picocolors$5.default.green;
3824
+ if (projectedPercent > 100) projBarColor = import_picocolors$5.default.red;
3825
+ else if (projectedPercent > 80) projBarColor = import_picocolors$5.default.yellow;
3819
3826
  const projectionBar = config.tokenLimit != null && config.tokenLimit > 0 ? createProgressBar(projection.totalTokens, config.tokenLimit, barWidth, {
3820
3827
  showPercentage: false,
3821
3828
  fillChar: projBarColor("█"),
3822
- emptyChar: import_picocolors$4.default.gray("░"),
3829
+ emptyChar: import_picocolors$5.default.gray("░"),
3823
3830
  leftBracket: "[",
3824
3831
  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");
3832
+ }) : `[${import_picocolors$5.default.green("█".repeat(Math.floor(barWidth * .15)))}${import_picocolors$5.default.gray("░".repeat(barWidth - Math.floor(barWidth * .15)))}]`;
3833
+ 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");
3834
+ const projLabel = import_picocolors$5.default.bold("📈 PROJECTION");
3828
3835
  const projLabelWidth = stringWidth(projLabel);
3829
3836
  if (config.tokenLimit != null && config.tokenLimit > 0) {
3830
3837
  const projBarStr = `${projLabel}${"".padEnd(Math.max(0, labelWidth - projLabelWidth))} ${projectionBar} ${projectedPercent.toFixed(1).padStart(6)}% (${formatTokensShort(projection.totalTokens)}/${formatTokensShort(config.tokenLimit)})`;
3831
3838
  const projBarPadded = projBarStr + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(projBarStr)));
3832
3839
  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)}`;
3840
+ const col1$1 = `${import_picocolors$5.default.gray("Status:")} ${limitStatus}`;
3841
+ const col2$1 = `${import_picocolors$5.default.gray("Tokens:")} ${formatNumber(projection.totalTokens)}`;
3842
+ const col3$1 = `${import_picocolors$5.default.gray("Cost:")} ${formatCurrency(projection.totalCost)}`;
3836
3843
  const col1Visible$1 = stringWidth(col1$1);
3837
3844
  const col2Visible$1 = stringWidth(col2$1);
3838
3845
  const pad1$1 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col1 - col1Visible$1));
@@ -3844,9 +3851,9 @@ function renderLiveDisplay(terminal, block, config) {
3844
3851
  const projBarStr = `${projLabel}${"".padEnd(Math.max(0, labelWidth - projLabelWidth))} ${projectionBar} (${formatTokensShort(projection.totalTokens)} tokens)`;
3845
3852
  const projBarPadded = projBarStr + " ".repeat(Math.max(0, boxWidth - 3 - stringWidth(projBarStr)));
3846
3853
  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)}`;
3854
+ const col1$1 = `${import_picocolors$5.default.gray("Status:")} ${limitStatus}`;
3855
+ const col2$1 = `${import_picocolors$5.default.gray("Tokens:")} ${formatNumber(projection.totalTokens)}`;
3856
+ const col3$1 = `${import_picocolors$5.default.gray("Cost:")} ${formatCurrency(projection.totalCost)}`;
3850
3857
  const col1Visible$1 = stringWidth(col1$1);
3851
3858
  const col2Visible$1 = stringWidth(col2$1);
3852
3859
  const pad1$1 = " ".repeat(Math.max(0, DETAIL_COLUMN_WIDTHS.col1 - col1Visible$1));
@@ -3865,7 +3872,7 @@ function renderLiveDisplay(terminal, block, config) {
3865
3872
  }
3866
3873
  terminal.write(`${marginStr}├${"─".repeat(boxWidth - 2)}┤\n`);
3867
3874
  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`);
3875
+ terminal.write(`${marginStr}│${import_picocolors$5.default.gray(centerText(refreshText, boxWidth - 2))}│\n`);
3869
3876
  terminal.write(`${marginStr}└${"─".repeat(boxWidth - 2)}┘\n`);
3870
3877
  }
3871
3878
  /**
@@ -3873,20 +3880,81 @@ function renderLiveDisplay(terminal, block, config) {
3873
3880
  */
3874
3881
  function renderCompactLiveDisplay(terminal, block, config, totalTokens, elapsed, remaining) {
3875
3882
  const width = terminal.width;
3876
- terminal.write(`${import_picocolors$4.default.bold(centerText("LIVE MONITOR", width))}\n`);
3883
+ terminal.write(`${import_picocolors$5.default.bold(centerText("LIVE MONITOR", width))}\n`);
3877
3884
  terminal.write(`${"─".repeat(width)}\n`);
3878
3885
  const sessionPercent = elapsed / (elapsed + remaining) * 100;
3879
3886
  terminal.write(`Session: ${sessionPercent.toFixed(1)}% (${Math.floor(elapsed / 60)}h ${Math.floor(elapsed % 60)}m)\n`);
3880
3887
  if (config.tokenLimit != null && config.tokenLimit > 0) {
3881
3888
  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");
3889
+ 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
3890
  terminal.write(`Tokens: ${formatNumber(totalTokens)}/${formatNumber(config.tokenLimit)} ${status}\n`);
3884
3891
  } else terminal.write(`Tokens: ${formatNumber(totalTokens)}\n`);
3885
3892
  terminal.write(`Cost: ${formatCurrency(block.costUSD)}\n`);
3886
3893
  const burnRate = calculateBurnRate(block);
3887
3894
  if (burnRate != null) terminal.write(`Rate: ${formatNumber(burnRate.tokensPerMinute)}/min\n`);
3888
3895
  terminal.write(`${"─".repeat(width)}\n`);
3889
- terminal.write(import_picocolors$4.default.gray(`Refresh: ${config.refreshInterval / 1e3}s | Ctrl+C: stop\n`));
3896
+ terminal.write(import_picocolors$5.default.gray(`Refresh: ${config.refreshInterval / 1e3}s | Ctrl+C: stop\n`));
3897
+ }
3898
+ var import_picocolors$4 = __toESM(require_picocolors(), 1);
3899
+ var import_usingCtx = __toESM(require_usingCtx(), 1);
3900
+ async function startLiveMonitoring(config) {
3901
+ try {
3902
+ var _usingCtx = (0, import_usingCtx.default)();
3903
+ const terminal = new TerminalManager();
3904
+ const abortController = new AbortController();
3905
+ let lastRenderTime = 0;
3906
+ const cleanup = () => {
3907
+ abortController.abort();
3908
+ terminal.cleanup();
3909
+ terminal.clearScreen();
3910
+ logger.info("Live monitoring stopped.");
3911
+ if (process$1.exitCode == null) process$1.exit(0);
3912
+ };
3913
+ process$1.on("SIGINT", cleanup);
3914
+ process$1.on("SIGTERM", cleanup);
3915
+ terminal.enterAlternateScreen();
3916
+ terminal.enableSyncMode();
3917
+ terminal.clearScreen();
3918
+ terminal.hideCursor();
3919
+ const monitor = _usingCtx.u(new LiveMonitor({
3920
+ claudePath: config.claudePath,
3921
+ sessionDurationHours: config.sessionDurationHours,
3922
+ mode: config.mode,
3923
+ order: config.order
3924
+ }));
3925
+ try {
3926
+ while (!abortController.signal.aborted) {
3927
+ const now = Date.now();
3928
+ const timeSinceLastRender = now - lastRenderTime;
3929
+ if (timeSinceLastRender < MIN_RENDER_INTERVAL_MS) {
3930
+ await delayWithAbort(MIN_RENDER_INTERVAL_MS - timeSinceLastRender, abortController.signal);
3931
+ continue;
3932
+ }
3933
+ const activeBlock = await monitor.getActiveBlock();
3934
+ monitor.clearCache();
3935
+ if (activeBlock == null) {
3936
+ await renderWaitingState(terminal, config, abortController.signal);
3937
+ continue;
3938
+ }
3939
+ renderActiveBlock(terminal, activeBlock, config);
3940
+ lastRenderTime = Date.now();
3941
+ await delayWithAbort(config.refreshInterval, abortController.signal);
3942
+ }
3943
+ } catch (error) {
3944
+ if ((error instanceof DOMException || error instanceof Error) && error.name === "AbortError") return;
3945
+ const errorMessage = error instanceof Error ? error.message : String(error);
3946
+ terminal.startBuffering();
3947
+ terminal.clearScreen();
3948
+ terminal.write(import_picocolors$4.default.red(`Error: ${errorMessage}\n`));
3949
+ terminal.flush();
3950
+ logger.error(`Live monitoring error: ${errorMessage}`);
3951
+ await delayWithAbort(config.refreshInterval, abortController.signal).catch(() => {});
3952
+ }
3953
+ } catch (_) {
3954
+ _usingCtx.e = _;
3955
+ } finally {
3956
+ _usingCtx.d();
3957
+ }
3890
3958
  }
3891
3959
  var import_picocolors$3 = __toESM(require_picocolors(), 1);
3892
3960
  /**
@@ -4037,8 +4105,13 @@ const blocksCommand = define({
4037
4105
  }
4038
4106
  const refreshInterval = Math.max(MIN_REFRESH_INTERVAL_SECONDS, Math.min(MAX_REFRESH_INTERVAL_SECONDS, ctx.values.refreshInterval));
4039
4107
  if (refreshInterval !== ctx.values.refreshInterval) logger.warn(`Refresh interval adjusted to ${refreshInterval} seconds (valid range: ${MIN_REFRESH_INTERVAL_SECONDS}-${MAX_REFRESH_INTERVAL_SECONDS})`);
4108
+ const paths = getClaudePaths();
4109
+ if (paths.length === 0) {
4110
+ logger.error("No valid Claude data directory found");
4111
+ throw new Error("No valid Claude data directory found");
4112
+ }
4040
4113
  await startLiveMonitoring({
4041
- claudePath: getDefaultClaudePath(),
4114
+ claudePath: paths[0],
4042
4115
  tokenLimit: parseTokenLimit(tokenLimitValue, maxTokensFromAll),
4043
4116
  refreshInterval: refreshInterval * 1e3,
4044
4117
  sessionDurationHours: ctx.values.sessionLength,
@@ -4732,8 +4805,13 @@ const mcpCommand = define({
4732
4805
  async run(ctx) {
4733
4806
  const { type, mode, port } = ctx.values;
4734
4807
  if (type === "stdio") logger.level = 0;
4808
+ const paths = getClaudePaths();
4809
+ if (paths.length === 0) {
4810
+ logger.error("No valid Claude data directory found");
4811
+ throw new Error("No valid Claude data directory found");
4812
+ }
4735
4813
  const options = {
4736
- claudePath: getDefaultClaudePath(),
4814
+ claudePath: paths[0],
4737
4815
  mode
4738
4816
  };
4739
4817
  if (type === "stdio") {