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/README.md +26 -1
- package/dist/calculate-cost.d.ts +1 -1
- package/dist/{data-loader-C1n0ww95.d.ts → data-loader-Bll0wMdK.d.ts} +6 -6
- package/dist/{data-loader-BFS_JSqd.js → data-loader-DQftfBz9.js} +15 -5
- package/dist/data-loader.d.ts +1 -1
- package/dist/data-loader.js +3 -3
- package/dist/{debug-DIfzQVQt.js → debug-CXtWvV0a.js} +3 -3
- package/dist/debug.js +4 -4
- package/dist/index.js +772 -15
- package/dist/{logger-CIYlc309.js → logger-r_DhIB_J.js} +1 -1
- package/dist/logger.js +1 -1
- package/dist/{mcp-BI0xh4oM.js → mcp-C-EN2PDc.js} +312 -138
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +4 -4
- package/dist/{pricing-fetcher-XdvIG0to.js → pricing-fetcher-DBzWmU38.js} +17 -2
- package/dist/pricing-fetcher.js +2 -2
- package/package.json +1 -1
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-
|
|
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-
|
|
6
|
-
import { description, log, logger, name, version } from "./logger-
|
|
7
|
-
import { detectMismatches, printMismatchReport } from "./debug-
|
|
8
|
-
import { createMcpHttpApp, createMcpServer, startMcpServerStdio } from "./mcp-
|
|
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$
|
|
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$
|
|
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\"
|
|
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;
|