ccusage 14.1.2 → 15.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/dist/calculate-cost.d.ts +1 -1
- package/dist/{data-loader-BFS_JSqd.js → data-loader-B_ymgjWR.js} +3 -3
- package/dist/{data-loader-C1n0ww95.d.ts → data-loader-Bll0wMdK.d.ts} +6 -6
- package/dist/data-loader.d.ts +1 -1
- package/dist/data-loader.js +3 -3
- package/dist/{debug-DIfzQVQt.js → debug-2jwq04IT.js} +3 -3
- package/dist/debug.js +4 -4
- package/dist/index.js +772 -15
- package/dist/{logger-CIYlc309.js → logger-6I_KKR-u.js} +1 -1
- package/dist/logger.js +1 -1
- package/dist/{mcp-BI0xh4oM.js → mcp-COnbPauf.js} +3 -3
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +4 -4
- package/dist/{pricing-fetcher-XdvIG0to.js → pricing-fetcher-CMyU5JUX.js} +17 -2
- package/dist/pricing-fetcher.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
<img src="https://cdn.jsdelivr.net/gh/ryoppippi/ccusage@main/docs/screenshot.png">
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
|
+
<div align="center">
|
|
19
|
+
<img src="https://cdn.jsdelivr.net/gh/ryoppippi/ccusage@main/docs/blocks-live.png">
|
|
20
|
+
</div>
|
|
21
|
+
|
|
18
22
|
> **ccusage(claude-code-usage)**
|
|
19
23
|
|
|
20
24
|
A CLI tool for analyzing Claude Code usage from local JSONL files.
|
|
@@ -48,6 +52,7 @@ This tool helps you understand the value you're getting from your subscription b
|
|
|
48
52
|
- 📅 **Monthly Report**: View token usage and costs aggregated by month
|
|
49
53
|
- 💬 **Session Report**: View usage grouped by conversation sessions
|
|
50
54
|
- ⏰ **5-Hour Blocks Report**: Track usage within Claude's billing windows with active block monitoring
|
|
55
|
+
- 📈 **Live Monitoring**: Real-time dashboard showing active session progress, token burn rate, and cost projections with `blocks --live`
|
|
51
56
|
- 🤖 **Model Tracking**: See which Claude models you're using (Opus, Sonnet, etc.)
|
|
52
57
|
- 📊 **Model Breakdown**: View per-model cost breakdown with `--breakdown` flag
|
|
53
58
|
- 📅 **Date Filtering**: Filter reports by date range using `--since` and `--until`
|
|
@@ -284,6 +289,13 @@ ccusage blocks -t 500000
|
|
|
284
289
|
# Use the highest previous block as the token limit
|
|
285
290
|
ccusage blocks -t max
|
|
286
291
|
|
|
292
|
+
# Live monitoring dashboard with real-time updates
|
|
293
|
+
ccusage blocks --live # automatically uses highest previous session as token limit (-t max)
|
|
294
|
+
ccusage blocks --live -t 500000 # with explicit token limit (500,000 tokens)
|
|
295
|
+
ccusage blocks --live -t max # explicitly use highest previous session limit
|
|
296
|
+
ccusage blocks --live --refresh-interval 5 # update every 5 seconds (default: 1s)
|
|
297
|
+
ccusage blocks --live -t 1000000 --refresh-interval 2 # 1M token limit, 2s refresh
|
|
298
|
+
|
|
287
299
|
# Combine options
|
|
288
300
|
ccusage blocks --recent -t max
|
|
289
301
|
|
|
@@ -307,11 +319,23 @@ The blocks report helps you understand Claude Code's 5-hour rolling session wind
|
|
|
307
319
|
- Helps track if you're approaching token limits within a session
|
|
308
320
|
- The `-t max` option automatically uses your highest previous block as the limit
|
|
309
321
|
|
|
322
|
+
**Live Monitoring Features:**
|
|
323
|
+
|
|
324
|
+
- Real-time dashboard updates every 1 second (configurable 1-60s)
|
|
325
|
+
- Automatic token limit detection from your usage history
|
|
326
|
+
- Session progress bar with time remaining and burn rate
|
|
327
|
+
- Cost projections based on current usage patterns
|
|
328
|
+
- Colorful progress indicators (green/yellow/red for quota warnings)
|
|
329
|
+
- Graceful shutdown with Ctrl+C
|
|
330
|
+
|
|
310
331
|
#### Blocks-specific options
|
|
311
332
|
|
|
312
333
|
- `-t, --token-limit <number|max>`: Set token limit for quota warnings (use "max" for highest previous block)
|
|
313
334
|
- `-a, --active`: Show only active block with detailed projections
|
|
314
335
|
- `-r, --recent`: Show blocks from last 3 days (including active)
|
|
336
|
+
- `-l, --session-length <hours>`: Session block duration in hours (default: 5)
|
|
337
|
+
- `--live`: Live monitoring mode with real-time dashboard (automatically uses `-t max` and shows active block only)
|
|
338
|
+
- `--refresh-interval <seconds>`: Refresh interval for live mode (default: 1, range: 1-60)
|
|
315
339
|
|
|
316
340
|
### Options
|
|
317
341
|
|
|
@@ -565,6 +589,8 @@ some projects use `ccusage` internally and provide additional features:
|
|
|
565
589
|
|
|
566
590
|
- [claude-usage-tracker-for-mac](https://github.com/penicillin0/claude-usage-tracker-for-mac) – macOS menu bar app to visualize Claude Code usage costs by [@penicillin0](https://github.com/penicillin0).
|
|
567
591
|
- [ccusage Raycast Extension](https://www.raycast.com/nyatinte/ccusage) – Raycast extension to view Claude Code usage reports in Raycast by [@nyatinte](https://github.com/nyatinte).
|
|
592
|
+
- [claude-code-usage-monitor](https://github.com/Maciek-roboblog/Claude-Code-Usage-Monitor) – A real-time terminal-based tool for monitoring Claude Code token usage. It displays live token consumption, burn rate, and depletion predictions, with session-aware analytics, visual progress bars, and support for multiple subscription plans by [@Maciek-roboblog](https://github.com/Maciek-roboblog).
|
|
593
|
+
- [ClaudeCode_Dashboard](https://github.com/m-sigepon/ClaudeCode_Dashboard) – Web dashboard to visualize Claude Code usage with charts and USD/JPY conversion by [@m-sigepon](https://github.com/m-sigepon).
|
|
568
594
|
|
|
569
595
|
## Acknowledgments
|
|
570
596
|
|
package/dist/calculate-cost.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { CLAUDE_CONFIG_DIR_ENV, CLAUDE_PROJECTS_DIR_NAME, DEFAULT_CLAUDE_CODE_PATH, DEFAULT_CLAUDE_CONFIG_PATH, DEFAULT_RECENT_DAYS, PricingFetcher, USAGE_DATA_GLOB_PATTERN, USER_HOME_DIR, __commonJSMin, __require, __toESM, require_usingCtx } from "./pricing-fetcher-
|
|
1
|
+
import { CLAUDE_CONFIG_DIR_ENV, CLAUDE_PROJECTS_DIR_NAME, DEFAULT_CLAUDE_CODE_PATH, DEFAULT_CLAUDE_CONFIG_PATH, DEFAULT_RECENT_DAYS, PricingFetcher, USAGE_DATA_GLOB_PATTERN, USER_HOME_DIR, __commonJSMin, __require, __toESM, require_usingCtx } from "./pricing-fetcher-CMyU5JUX.js";
|
|
2
2
|
import { activityDateSchema, arrayType, createDailyDate, createMonthlyDate, createProjectPath, createSessionId, dailyDateSchema, isoTimestampSchema, messageIdSchema, modelNameSchema, monthlyDateSchema, numberType, objectType, projectPathSchema, requestIdSchema, sessionIdSchema, versionSchema } from "./_types-Cr2YEzKm.js";
|
|
3
|
-
import { logger } from "./logger-
|
|
3
|
+
import { logger } from "./logger-6I_KKR-u.js";
|
|
4
4
|
import a, { readFile } from "node:fs/promises";
|
|
5
5
|
import F, { homedir } from "node:os";
|
|
6
6
|
import path, { posix } from "node:path";
|
|
@@ -3869,4 +3869,4 @@ async function loadSessionBlockData(options) {
|
|
|
3869
3869
|
_usingCtx4.d();
|
|
3870
3870
|
}
|
|
3871
3871
|
}
|
|
3872
|
-
export { DEFAULT_SESSION_DURATION_HOURS, calculateBurnRate, calculateCostForEntry, createUniqueHash, dailyUsageSchema, filterRecentBlocks, formatDate, formatDateCompact, getClaudePaths, getDefaultClaudePath, getEarliestTimestamp, glob, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, projectBlockUsage, sessionUsageSchema, sortFilesByTimestamp, uniq, usageDataSchema };
|
|
3872
|
+
export { DEFAULT_SESSION_DURATION_HOURS, calculateBurnRate, calculateCostForEntry, createUniqueHash, dailyUsageSchema, filterRecentBlocks, formatDate, formatDateCompact, getClaudePaths, getDefaultClaudePath, getEarliestTimestamp, glob, identifySessionBlocks, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, projectBlockUsage, sessionUsageSchema, sortFilesByTimestamp, uniq, usageDataSchema };
|
|
@@ -91,8 +91,8 @@ declare const usageDataSchema: z.ZodObject<{
|
|
|
91
91
|
cache_creation_input_tokens?: number | undefined;
|
|
92
92
|
cache_read_input_tokens?: number | undefined;
|
|
93
93
|
};
|
|
94
|
-
model?: string & z.BRAND<"ModelName"> | undefined;
|
|
95
|
-
id?: string & z.BRAND<"MessageId"> | undefined;
|
|
94
|
+
model?: (string & z.BRAND<"ModelName">) | undefined;
|
|
95
|
+
id?: (string & z.BRAND<"MessageId">) | undefined;
|
|
96
96
|
}, {
|
|
97
97
|
usage: {
|
|
98
98
|
input_tokens: number;
|
|
@@ -107,7 +107,7 @@ declare const usageDataSchema: z.ZodObject<{
|
|
|
107
107
|
requestId: z.ZodOptional<z.ZodBranded<z.ZodString, "RequestId">>;
|
|
108
108
|
}, "strip", z.ZodTypeAny, {
|
|
109
109
|
timestamp: string & z.BRAND<"ISOTimestamp">;
|
|
110
|
-
version?: string & z.BRAND<"Version"> | undefined;
|
|
110
|
+
version?: (string & z.BRAND<"Version">) | undefined;
|
|
111
111
|
message: {
|
|
112
112
|
usage: {
|
|
113
113
|
input_tokens: number;
|
|
@@ -115,11 +115,11 @@ declare const usageDataSchema: z.ZodObject<{
|
|
|
115
115
|
cache_creation_input_tokens?: number | undefined;
|
|
116
116
|
cache_read_input_tokens?: number | undefined;
|
|
117
117
|
};
|
|
118
|
-
model?: string & z.BRAND<"ModelName"> | undefined;
|
|
119
|
-
id?: string & z.BRAND<"MessageId"> | undefined;
|
|
118
|
+
model?: (string & z.BRAND<"ModelName">) | undefined;
|
|
119
|
+
id?: (string & z.BRAND<"MessageId">) | undefined;
|
|
120
120
|
};
|
|
121
121
|
costUSD?: number | undefined;
|
|
122
|
-
requestId?: string & z.BRAND<"RequestId"> | undefined;
|
|
122
|
+
requestId?: (string & z.BRAND<"RequestId">) | undefined;
|
|
123
123
|
}, {
|
|
124
124
|
timestamp: string;
|
|
125
125
|
version?: string | undefined;
|
package/dist/data-loader.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import "./pricing-fetcher-CXnYw4TA.js";
|
|
2
|
-
import { DailyUsage, DateFilter, LoadOptions, ModelBreakdown, MonthlyUsage, SessionUsage, UsageData, calculateCostForEntry, createUniqueHash, dailyUsageSchema, formatDate, formatDateCompact, getClaudePaths, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema } from "./data-loader-
|
|
2
|
+
import { DailyUsage, DateFilter, LoadOptions, ModelBreakdown, MonthlyUsage, SessionUsage, UsageData, calculateCostForEntry, createUniqueHash, dailyUsageSchema, formatDate, formatDateCompact, getClaudePaths, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema } from "./data-loader-Bll0wMdK.js";
|
|
3
3
|
export { DailyUsage, DateFilter, LoadOptions, ModelBreakdown, MonthlyUsage, SessionUsage, UsageData, calculateCostForEntry, createUniqueHash, dailyUsageSchema, formatDate, formatDateCompact, getClaudePaths, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema };
|
package/dist/data-loader.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import "./pricing-fetcher-
|
|
1
|
+
import "./pricing-fetcher-CMyU5JUX.js";
|
|
2
2
|
import "./_types-Cr2YEzKm.js";
|
|
3
|
-
import { calculateCostForEntry, createUniqueHash, dailyUsageSchema, formatDate, formatDateCompact, getClaudePaths, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema } from "./data-loader-
|
|
4
|
-
import "./logger-
|
|
3
|
+
import { calculateCostForEntry, createUniqueHash, dailyUsageSchema, formatDate, formatDateCompact, getClaudePaths, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema } from "./data-loader-B_ymgjWR.js";
|
|
4
|
+
import "./logger-6I_KKR-u.js";
|
|
5
5
|
export { calculateCostForEntry, createUniqueHash, dailyUsageSchema, formatDate, formatDateCompact, getClaudePaths, getDefaultClaudePath, getEarliestTimestamp, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData, modelBreakdownSchema, monthlyUsageSchema, sessionUsageSchema, sortFilesByTimestamp, usageDataSchema };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { CLAUDE_PROJECTS_DIR_NAME, DEBUG_MATCH_THRESHOLD_PERCENT, PricingFetcher, USAGE_DATA_GLOB_PATTERN, __toESM, require_usingCtx } from "./pricing-fetcher-
|
|
2
|
-
import { getDefaultClaudePath, glob, usageDataSchema } from "./data-loader-
|
|
3
|
-
import { logger } from "./logger-
|
|
1
|
+
import { CLAUDE_PROJECTS_DIR_NAME, DEBUG_MATCH_THRESHOLD_PERCENT, PricingFetcher, USAGE_DATA_GLOB_PATTERN, __toESM, require_usingCtx } from "./pricing-fetcher-CMyU5JUX.js";
|
|
2
|
+
import { getDefaultClaudePath, glob, usageDataSchema } from "./data-loader-B_ymgjWR.js";
|
|
3
|
+
import { logger } from "./logger-6I_KKR-u.js";
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
var import_usingCtx = __toESM(require_usingCtx(), 1);
|
package/dist/debug.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import "./pricing-fetcher-
|
|
1
|
+
import "./pricing-fetcher-CMyU5JUX.js";
|
|
2
2
|
import "./_types-Cr2YEzKm.js";
|
|
3
|
-
import "./data-loader-
|
|
4
|
-
import "./logger-
|
|
5
|
-
import { detectMismatches, printMismatchReport } from "./debug-
|
|
3
|
+
import "./data-loader-B_ymgjWR.js";
|
|
4
|
+
import "./logger-6I_KKR-u.js";
|
|
5
|
+
import { detectMismatches, printMismatchReport } from "./debug-2jwq04IT.js";
|
|
6
6
|
export { detectMismatches, printMismatchReport };
|
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-CMyU5JUX.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-B_ymgjWR.js";
|
|
6
|
+
import { description, log, logger, name, version } from "./logger-6I_KKR-u.js";
|
|
7
|
+
import { detectMismatches, printMismatchReport } from "./debug-2jwq04IT.js";
|
|
8
|
+
import { createMcpHttpApp, createMcpServer, startMcpServerStdio } from "./mcp-COnbPauf.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)} per 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;
|
|
@@ -951,7 +951,7 @@ function _getDefaultLogLevel() {
|
|
|
951
951
|
}
|
|
952
952
|
const consola = createConsola$1();
|
|
953
953
|
var name = "ccusage";
|
|
954
|
-
var version = "
|
|
954
|
+
var version = "15.0.0";
|
|
955
955
|
var description = "Usage analysis tool for Claude Code";
|
|
956
956
|
/**
|
|
957
957
|
* Application logger instance with package name tag
|
package/dist/logger.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { log, logger } from "./logger-
|
|
1
|
+
import { log, logger } from "./logger-6I_KKR-u.js";
|
|
2
2
|
export { log, logger };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { __commonJSMin, __toESM, require_usingCtx } from "./pricing-fetcher-
|
|
1
|
+
import { __commonJSMin, __toESM, require_usingCtx } from "./pricing-fetcher-CMyU5JUX.js";
|
|
2
2
|
import { ZodFirstPartyTypeKind, ZodOptional, ZodType, arrayType, booleanType, dateSchema, discriminatedUnionType, enumType, literalType, numberType, objectType, optionalType, recordType, stringType, unionType, unknownType } from "./_types-Cr2YEzKm.js";
|
|
3
|
-
import { getDefaultClaudePath, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData } from "./data-loader-
|
|
4
|
-
import { name, version } from "./logger-
|
|
3
|
+
import { getDefaultClaudePath, loadDailyUsageData, loadMonthlyUsageData, loadSessionBlockData, loadSessionData } from "./data-loader-B_ymgjWR.js";
|
|
4
|
+
import { name, version } from "./logger-6I_KKR-u.js";
|
|
5
5
|
import process from "node:process";
|
|
6
6
|
const LATEST_PROTOCOL_VERSION = "2025-03-26";
|
|
7
7
|
const SUPPORTED_PROTOCOL_VERSIONS = [
|
package/dist/mcp.d.ts
CHANGED
package/dist/mcp.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import "./pricing-fetcher-
|
|
1
|
+
import "./pricing-fetcher-CMyU5JUX.js";
|
|
2
2
|
import "./_types-Cr2YEzKm.js";
|
|
3
|
-
import "./data-loader-
|
|
4
|
-
import "./logger-
|
|
5
|
-
import { createMcpHttpApp, createMcpServer, startMcpServerStdio } from "./mcp-
|
|
3
|
+
import "./data-loader-B_ymgjWR.js";
|
|
4
|
+
import "./logger-6I_KKR-u.js";
|
|
5
|
+
import { createMcpHttpApp, createMcpServer, startMcpServerStdio } from "./mcp-COnbPauf.js";
|
|
6
6
|
export { createMcpHttpApp, createMcpServer, startMcpServerStdio };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { modelPricingSchema } from "./_types-Cr2YEzKm.js";
|
|
2
|
-
import { logger } from "./logger-
|
|
2
|
+
import { logger } from "./logger-6I_KKR-u.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import F, { homedir } from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
@@ -105,6 +105,21 @@ const USAGE_DATA_GLOB_PATTERN = "**/*.jsonl";
|
|
|
105
105
|
* Used when no port is specified for MCP server communication
|
|
106
106
|
*/
|
|
107
107
|
const MCP_DEFAULT_PORT = 8080;
|
|
108
|
+
/**
|
|
109
|
+
* Default refresh interval in seconds for live monitoring mode
|
|
110
|
+
* Used in blocks command for real-time updates
|
|
111
|
+
*/
|
|
112
|
+
const DEFAULT_REFRESH_INTERVAL_SECONDS = 1;
|
|
113
|
+
/**
|
|
114
|
+
* Minimum refresh interval in seconds for live monitoring mode
|
|
115
|
+
* Prevents too-frequent updates that could impact performance
|
|
116
|
+
*/
|
|
117
|
+
const MIN_REFRESH_INTERVAL_SECONDS = 1;
|
|
118
|
+
/**
|
|
119
|
+
* Maximum refresh interval in seconds for live monitoring mode
|
|
120
|
+
* Prevents too-slow updates that reduce monitoring effectiveness
|
|
121
|
+
*/
|
|
122
|
+
const MAX_REFRESH_INTERVAL_SECONDS = 60;
|
|
108
123
|
var require_usingCtx = __commonJSMin((exports, module) => {
|
|
109
124
|
function _usingCtx() {
|
|
110
125
|
var r = "function" == typeof SuppressedError ? SuppressedError : function(r$1, e$1) {
|
|
@@ -389,4 +404,4 @@ var PricingFetcher = class {
|
|
|
389
404
|
return cost;
|
|
390
405
|
}
|
|
391
406
|
};
|
|
392
|
-
export { BLOCKS_COMPACT_WIDTH_THRESHOLD, BLOCKS_DEFAULT_TERMINAL_WIDTH, BLOCKS_WARNING_THRESHOLD, CLAUDE_CONFIG_DIR_ENV, CLAUDE_PROJECTS_DIR_NAME, DEBUG_MATCH_THRESHOLD_PERCENT, DEFAULT_CLAUDE_CODE_PATH, DEFAULT_CLAUDE_CONFIG_PATH, DEFAULT_RECENT_DAYS, MCP_DEFAULT_PORT, PricingFetcher, USAGE_DATA_GLOB_PATTERN, USER_HOME_DIR, __commonJSMin, __require, __toESM, require_usingCtx };
|
|
407
|
+
export { BLOCKS_COMPACT_WIDTH_THRESHOLD, BLOCKS_DEFAULT_TERMINAL_WIDTH, BLOCKS_WARNING_THRESHOLD, CLAUDE_CONFIG_DIR_ENV, CLAUDE_PROJECTS_DIR_NAME, DEBUG_MATCH_THRESHOLD_PERCENT, DEFAULT_CLAUDE_CODE_PATH, DEFAULT_CLAUDE_CONFIG_PATH, DEFAULT_RECENT_DAYS, DEFAULT_REFRESH_INTERVAL_SECONDS, MAX_REFRESH_INTERVAL_SECONDS, MCP_DEFAULT_PORT, MIN_REFRESH_INTERVAL_SECONDS, PricingFetcher, USAGE_DATA_GLOB_PATTERN, USER_HOME_DIR, __commonJSMin, __require, __toESM, require_usingCtx };
|
package/dist/pricing-fetcher.js
CHANGED