@ekkos/cli 0.3.3 → 1.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 +57 -0
- package/dist/agent/daemon.d.ts +27 -0
- package/dist/agent/daemon.js +254 -29
- package/dist/agent/health-check.d.ts +35 -0
- package/dist/agent/health-check.js +243 -0
- package/dist/agent/pty-runner.d.ts +1 -0
- package/dist/agent/pty-runner.js +6 -1
- package/dist/capture/transcript-repair.d.ts +1 -0
- package/dist/capture/transcript-repair.js +12 -1
- package/dist/commands/agent.d.ts +6 -0
- package/dist/commands/agent.js +244 -0
- package/dist/commands/dashboard.d.ts +25 -0
- package/dist/commands/dashboard.js +1175 -0
- package/dist/commands/run.d.ts +3 -0
- package/dist/commands/run.js +503 -350
- package/dist/commands/setup-remote.js +146 -37
- package/dist/commands/swarm-dashboard.d.ts +20 -0
- package/dist/commands/swarm-dashboard.js +735 -0
- package/dist/commands/swarm-setup.d.ts +10 -0
- package/dist/commands/swarm-setup.js +956 -0
- package/dist/commands/swarm.d.ts +46 -0
- package/dist/commands/swarm.js +441 -0
- package/dist/commands/test-claude.d.ts +16 -0
- package/dist/commands/test-claude.js +156 -0
- package/dist/commands/usage/blocks.d.ts +8 -0
- package/dist/commands/usage/blocks.js +60 -0
- package/dist/commands/usage/daily.d.ts +9 -0
- package/dist/commands/usage/daily.js +96 -0
- package/dist/commands/usage/dashboard.d.ts +8 -0
- package/dist/commands/usage/dashboard.js +104 -0
- package/dist/commands/usage/formatters.d.ts +41 -0
- package/dist/commands/usage/formatters.js +147 -0
- package/dist/commands/usage/index.d.ts +13 -0
- package/dist/commands/usage/index.js +87 -0
- package/dist/commands/usage/monthly.d.ts +8 -0
- package/dist/commands/usage/monthly.js +66 -0
- package/dist/commands/usage/session.d.ts +11 -0
- package/dist/commands/usage/session.js +193 -0
- package/dist/commands/usage/weekly.d.ts +9 -0
- package/dist/commands/usage/weekly.js +61 -0
- package/dist/deploy/instructions.d.ts +5 -2
- package/dist/deploy/instructions.js +11 -8
- package/dist/index.js +256 -20
- package/dist/lib/tmux-scrollbar.d.ts +14 -0
- package/dist/lib/tmux-scrollbar.js +296 -0
- package/dist/lib/usage-parser.d.ts +95 -5
- package/dist/lib/usage-parser.js +416 -71
- package/dist/utils/log-rotate.d.ts +18 -0
- package/dist/utils/log-rotate.js +74 -0
- package/dist/utils/platform.d.ts +2 -0
- package/dist/utils/platform.js +3 -1
- package/dist/utils/session-binding.d.ts +5 -0
- package/dist/utils/session-binding.js +46 -0
- package/dist/utils/state.js +4 -0
- package/dist/utils/verify-remote-terminal.d.ts +10 -0
- package/dist/utils/verify-remote-terminal.js +415 -0
- package/package.json +16 -11
- package/templates/CLAUDE.md +135 -23
- package/templates/cursor-hooks/after-agent-response.sh +0 -0
- package/templates/cursor-hooks/before-submit-prompt.sh +0 -0
- package/templates/cursor-hooks/stop.sh +0 -0
- package/templates/ekkos-manifest.json +5 -5
- package/templates/hooks/assistant-response.sh +0 -0
- package/templates/hooks/lib/contract.sh +43 -31
- package/templates/hooks/lib/count-tokens.cjs +86 -0
- package/templates/hooks/lib/ekkos-reminders.sh +98 -0
- package/templates/hooks/lib/state.sh +53 -1
- package/templates/hooks/session-start.sh +0 -0
- package/templates/hooks/stop.sh +150 -388
- package/templates/hooks/user-prompt-submit.sh +353 -443
- package/templates/plan-template.md +0 -0
- package/templates/spec-template.md +0 -0
- package/templates/windsurf-hooks/README.md +212 -0
- package/templates/windsurf-hooks/hooks.json +9 -2
- package/templates/windsurf-hooks/install.sh +148 -0
- package/templates/windsurf-hooks/lib/contract.sh +2 -0
- package/templates/windsurf-hooks/post-cascade-response.sh +251 -0
- package/templates/windsurf-hooks/pre-user-prompt.sh +435 -0
- package/templates/windsurf-skills/ekkos-memory/SKILL.md +219 -0
- package/LICENSE +0 -21
- package/templates/windsurf-hooks/before-submit-prompt.sh +0 -238
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.blocksCommand = blocksCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const usage_parser_js_1 = require("../../lib/usage-parser.js");
|
|
9
|
+
const formatters_js_1 = require("./formatters.js");
|
|
10
|
+
/**
|
|
11
|
+
* ekkos usage blocks [--json]
|
|
12
|
+
*
|
|
13
|
+
* Show 5-hour billing block analysis
|
|
14
|
+
*/
|
|
15
|
+
async function blocksCommand(options) {
|
|
16
|
+
const data = await (0, usage_parser_js_1.getBucketUsage)();
|
|
17
|
+
if (data.length === 0) {
|
|
18
|
+
if (options.json) {
|
|
19
|
+
console.log(JSON.stringify([]));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
console.log();
|
|
23
|
+
console.log(chalk_1.default.yellow(' Billing blocks not available.'));
|
|
24
|
+
console.log(chalk_1.default.gray(' This may be due to a compatibility issue with ccusage v18.'));
|
|
25
|
+
console.log(chalk_1.default.gray(' Try: npx ccusage blocks'));
|
|
26
|
+
console.log();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// Sort by start time descending
|
|
30
|
+
const sorted = [...data].sort((a, b) => (b.bucketStart || b.start || b.date || '').localeCompare(a.bucketStart || a.start || a.date || ''));
|
|
31
|
+
const recent = sorted.slice(0, 20);
|
|
32
|
+
if (options.json) {
|
|
33
|
+
console.log(JSON.stringify(recent, null, 2));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
(0, formatters_js_1.titleBar)('Billing Blocks (5-Hour Windows)');
|
|
37
|
+
const maxCost = Math.max(...recent.map(b => b.totalCost || b.cost || 0));
|
|
38
|
+
console.log(chalk_1.default.gray(' Block Start'.padEnd(22)) +
|
|
39
|
+
chalk_1.default.gray('│ ') +
|
|
40
|
+
chalk_1.default.gray('Cost'.padStart(10)) +
|
|
41
|
+
chalk_1.default.gray(' │ ') +
|
|
42
|
+
chalk_1.default.gray('Input'.padStart(8)) +
|
|
43
|
+
chalk_1.default.gray(' │ ') +
|
|
44
|
+
chalk_1.default.gray('Output'.padStart(8)) +
|
|
45
|
+
chalk_1.default.gray(' │ ') +
|
|
46
|
+
chalk_1.default.gray('Models'.padEnd(12)) +
|
|
47
|
+
chalk_1.default.gray(' │ ') +
|
|
48
|
+
chalk_1.default.gray('Chart'));
|
|
49
|
+
console.log(chalk_1.default.gray('─'.repeat(80)));
|
|
50
|
+
for (const block of recent) {
|
|
51
|
+
const cost = block.totalCost || block.cost || 0;
|
|
52
|
+
const start = (0, formatters_js_1.formatTimestamp)(block.bucketStart || block.start || block.date || '');
|
|
53
|
+
const bar = (0, formatters_js_1.renderBar)(cost, maxCost, 12);
|
|
54
|
+
const models = (block.modelsUsed || []).length;
|
|
55
|
+
console.log(` ${chalk_1.default.white(start.padEnd(20))} │ ${chalk_1.default.green((0, formatters_js_1.formatCost)(cost).padStart(10))} │ ${(0, formatters_js_1.formatCompact)(block.inputTokens || 0).padStart(8)} │ ${(0, formatters_js_1.formatCompact)(block.outputTokens || 0).padStart(8)} │ ${chalk_1.default.cyan(models.toString().padStart(2))} models │ ${bar}`);
|
|
56
|
+
}
|
|
57
|
+
console.log();
|
|
58
|
+
console.log(chalk_1.default.bold.cyan('═'.repeat(80)));
|
|
59
|
+
console.log();
|
|
60
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.dailyCommand = dailyCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const usage_parser_js_1 = require("../../lib/usage-parser.js");
|
|
9
|
+
const formatters_js_1 = require("./formatters.js");
|
|
10
|
+
/**
|
|
11
|
+
* ekkos usage daily [--days N] [--json]
|
|
12
|
+
*
|
|
13
|
+
* Show daily usage breakdown
|
|
14
|
+
*/
|
|
15
|
+
async function dailyCommand(options) {
|
|
16
|
+
const days = options.days || 14;
|
|
17
|
+
const data = await (0, usage_parser_js_1.getDailyUsage)();
|
|
18
|
+
if (data.length === 0) {
|
|
19
|
+
if (options.json) {
|
|
20
|
+
console.log(JSON.stringify([]));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
(0, formatters_js_1.noData)('daily usage');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
// Sort by date descending (most recent first)
|
|
27
|
+
const sorted = [...data].sort((a, b) => b.date.localeCompare(a.date));
|
|
28
|
+
const recent = sorted.slice(0, days);
|
|
29
|
+
if (options.json) {
|
|
30
|
+
console.log(JSON.stringify(recent, null, 2));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
(0, formatters_js_1.titleBar)('Daily Usage Report', `Last ${Math.min(days, recent.length)} days`);
|
|
34
|
+
// Find max cost for bar chart scaling
|
|
35
|
+
const maxCost = Math.max(...recent.map(d => d.totalCost || d.cost || 0));
|
|
36
|
+
let totalCost = 0;
|
|
37
|
+
let totalInput = 0;
|
|
38
|
+
let totalOutput = 0;
|
|
39
|
+
let totalCacheRead = 0;
|
|
40
|
+
let totalCacheCreation = 0;
|
|
41
|
+
// Table header
|
|
42
|
+
console.log(chalk_1.default.gray(' Date'.padEnd(16)) +
|
|
43
|
+
chalk_1.default.gray('│ ') +
|
|
44
|
+
chalk_1.default.gray('Cost'.padStart(10)) +
|
|
45
|
+
chalk_1.default.gray(' │ ') +
|
|
46
|
+
chalk_1.default.gray('Input'.padStart(8)) +
|
|
47
|
+
chalk_1.default.gray(' │ ') +
|
|
48
|
+
chalk_1.default.gray('Output'.padStart(8)) +
|
|
49
|
+
chalk_1.default.gray(' │ ') +
|
|
50
|
+
chalk_1.default.gray('Cache'.padStart(8)) +
|
|
51
|
+
chalk_1.default.gray(' │ ') +
|
|
52
|
+
chalk_1.default.gray('Chart'));
|
|
53
|
+
console.log(chalk_1.default.gray('─'.repeat(80)));
|
|
54
|
+
for (const day of recent) {
|
|
55
|
+
const cost = day.totalCost || day.cost || 0;
|
|
56
|
+
const date = (0, formatters_js_1.formatDate)(day.date);
|
|
57
|
+
const bar = (0, formatters_js_1.renderBar)(cost, maxCost, 20);
|
|
58
|
+
totalCost += cost;
|
|
59
|
+
totalInput += day.inputTokens || 0;
|
|
60
|
+
totalOutput += day.outputTokens || 0;
|
|
61
|
+
totalCacheRead += day.cacheReadTokens || 0;
|
|
62
|
+
totalCacheCreation += day.cacheCreationTokens || 0;
|
|
63
|
+
// Highlight today
|
|
64
|
+
const isToday = day.date === new Date().toISOString().split('T')[0];
|
|
65
|
+
const dateStr = isToday ? chalk_1.default.bold.cyan(date.padEnd(14)) : chalk_1.default.white(date.padEnd(14));
|
|
66
|
+
console.log(` ${dateStr} │ ${chalk_1.default.green((0, formatters_js_1.formatCost)(cost).padStart(10))} │ ${(0, formatters_js_1.formatCompact)(day.inputTokens || 0).padStart(8)} │ ${(0, formatters_js_1.formatCompact)(day.outputTokens || 0).padStart(8)} │ ${(0, formatters_js_1.formatCompact)(day.cacheReadTokens || 0).padStart(8)} │ ${bar}`);
|
|
67
|
+
}
|
|
68
|
+
console.log(chalk_1.default.gray('─'.repeat(80)));
|
|
69
|
+
console.log(chalk_1.default.bold(' TOTAL'.padEnd(16)) +
|
|
70
|
+
`│ ${chalk_1.default.green.bold((0, formatters_js_1.formatCost)(totalCost).padStart(10))} │ ${(0, formatters_js_1.formatCompact)(totalInput).padStart(8)} │ ${(0, formatters_js_1.formatCompact)(totalOutput).padStart(8)} │ ${(0, formatters_js_1.formatCompact)(totalCacheRead).padStart(8)} │`);
|
|
71
|
+
console.log();
|
|
72
|
+
// Model breakdown for the period
|
|
73
|
+
const allBreakdowns = recent.flatMap(d => d.modelBreakdowns || []);
|
|
74
|
+
if (allBreakdowns.length > 0) {
|
|
75
|
+
// Aggregate by model
|
|
76
|
+
const byModel = new Map();
|
|
77
|
+
for (const b of allBreakdowns) {
|
|
78
|
+
const existing = byModel.get(b.modelName);
|
|
79
|
+
if (existing) {
|
|
80
|
+
existing.inputTokens += b.inputTokens || 0;
|
|
81
|
+
existing.outputTokens += b.outputTokens || 0;
|
|
82
|
+
existing.cacheCreationTokens += b.cacheCreationTokens || 0;
|
|
83
|
+
existing.cacheReadTokens += b.cacheReadTokens || 0;
|
|
84
|
+
existing.cost += b.cost || 0;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
byModel.set(b.modelName, { ...b });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
(0, formatters_js_1.sectionHeader)('Models Used');
|
|
91
|
+
(0, formatters_js_1.printModelBreakdowns)([...byModel.values()].sort((a, b) => b.cost - a.cost));
|
|
92
|
+
console.log();
|
|
93
|
+
}
|
|
94
|
+
console.log(chalk_1.default.bold.cyan('═'.repeat(80)));
|
|
95
|
+
console.log();
|
|
96
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.dashboardCommand = dashboardCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const usage_parser_js_1 = require("../../lib/usage-parser.js");
|
|
9
|
+
const formatters_js_1 = require("./formatters.js");
|
|
10
|
+
/**
|
|
11
|
+
* ekkos usage dashboard [--json]
|
|
12
|
+
*
|
|
13
|
+
* Rich composite dashboard showing today, this week, this month + trends
|
|
14
|
+
*/
|
|
15
|
+
async function dashboardCommand(options) {
|
|
16
|
+
// Load all data in parallel
|
|
17
|
+
const [daily, monthly, sessions] = await Promise.all([
|
|
18
|
+
(0, usage_parser_js_1.getDailyUsage)(),
|
|
19
|
+
(0, usage_parser_js_1.getMonthlyUsage)(),
|
|
20
|
+
(0, usage_parser_js_1.getAllSessions)(),
|
|
21
|
+
]);
|
|
22
|
+
if (daily.length === 0 && monthly.length === 0 && sessions.length === 0) {
|
|
23
|
+
if (options.json) {
|
|
24
|
+
console.log(JSON.stringify({ daily: [], monthly: [], sessions: [] }));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
(0, formatters_js_1.noData)('usage');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (options.json) {
|
|
31
|
+
console.log(JSON.stringify({ daily, monthly, sessions }, null, 2));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const now = new Date();
|
|
35
|
+
const todayStr = now.toISOString().split('T')[0];
|
|
36
|
+
const dateDisplay = now.toLocaleDateString('en-US', {
|
|
37
|
+
weekday: 'long',
|
|
38
|
+
month: 'long',
|
|
39
|
+
day: 'numeric',
|
|
40
|
+
year: 'numeric'
|
|
41
|
+
});
|
|
42
|
+
(0, formatters_js_1.titleBar)('ekkOS Usage Dashboard', dateDisplay);
|
|
43
|
+
// ── TODAY / THIS WEEK / THIS MONTH summary ──
|
|
44
|
+
const sortedDaily = [...daily].sort((a, b) => b.date.localeCompare(a.date));
|
|
45
|
+
const today = sortedDaily.find(d => d.date === todayStr);
|
|
46
|
+
const last7 = sortedDaily.filter(d => {
|
|
47
|
+
const dDate = new Date(d.date);
|
|
48
|
+
const diff = (now.getTime() - dDate.getTime()) / (1000 * 60 * 60 * 24);
|
|
49
|
+
return diff <= 7;
|
|
50
|
+
});
|
|
51
|
+
const thisMonthStr = todayStr.substring(0, 7); // "2026-02"
|
|
52
|
+
const sortedMonthly = [...monthly].sort((a, b) => (b.month || '').localeCompare(a.month || ''));
|
|
53
|
+
const thisMonth = sortedMonthly.find(m => (m.month || '').startsWith(thisMonthStr));
|
|
54
|
+
const todayCost = today ? (today.totalCost || today.cost || 0) : 0;
|
|
55
|
+
const weekCost = last7.reduce((sum, d) => sum + (d.totalCost || d.cost || 0), 0);
|
|
56
|
+
const monthCost = thisMonth ? (thisMonth.totalCost || thisMonth.cost || 0) : 0;
|
|
57
|
+
const todayTokens = today ? (today.inputTokens || 0) + (today.outputTokens || 0) + (today.cacheReadTokens || 0) + (today.cacheCreationTokens || 0) : 0;
|
|
58
|
+
const weekTokens = last7.reduce((sum, d) => sum + (d.inputTokens || 0) + (d.outputTokens || 0) + (d.cacheReadTokens || 0) + (d.cacheCreationTokens || 0), 0);
|
|
59
|
+
const monthTokens = thisMonth ? (thisMonth.inputTokens || 0) + (thisMonth.outputTokens || 0) + (thisMonth.cacheReadTokens || 0) + (thisMonth.cacheCreationTokens || 0) : 0;
|
|
60
|
+
console.log(` ${chalk_1.default.bold('TODAY')} ${chalk_1.default.bold('THIS WEEK')} ${chalk_1.default.bold('THIS MONTH')}`);
|
|
61
|
+
console.log(` ${chalk_1.default.gray('─────')} ${chalk_1.default.gray('─────────')} ${chalk_1.default.gray('──────────')}`);
|
|
62
|
+
console.log(` Cost: ${chalk_1.default.green((0, formatters_js_1.formatCost)(todayCost).padEnd(16))} Cost: ${chalk_1.default.green((0, formatters_js_1.formatCost)(weekCost).padEnd(16))} Cost: ${chalk_1.default.green((0, formatters_js_1.formatCost)(monthCost))}`);
|
|
63
|
+
console.log(` Tokens: ${(0, formatters_js_1.formatCompact)(todayTokens).padEnd(16)} Tokens: ${(0, formatters_js_1.formatCompact)(weekTokens).padEnd(16)} Tokens: ${(0, formatters_js_1.formatCompact)(monthTokens)}`);
|
|
64
|
+
console.log();
|
|
65
|
+
// ── TOP MODELS TODAY ──
|
|
66
|
+
if (today && today.modelBreakdowns && today.modelBreakdowns.length > 0) {
|
|
67
|
+
(0, formatters_js_1.sectionHeader)('Top Models Today');
|
|
68
|
+
(0, formatters_js_1.printModelBreakdowns)([...today.modelBreakdowns].sort((a, b) => b.cost - a.cost).slice(0, 5));
|
|
69
|
+
console.log();
|
|
70
|
+
}
|
|
71
|
+
// ── DAILY TREND (last 7 days) ──
|
|
72
|
+
if (sortedDaily.length > 0) {
|
|
73
|
+
(0, formatters_js_1.sectionHeader)('Daily Trend (7 days)');
|
|
74
|
+
const trend = sortedDaily.slice(0, 7).reverse(); // oldest first for chart
|
|
75
|
+
const maxCost = Math.max(...trend.map(d => d.totalCost || d.cost || 0));
|
|
76
|
+
for (const day of trend) {
|
|
77
|
+
const cost = day.totalCost || day.cost || 0;
|
|
78
|
+
const date = new Date(day.date);
|
|
79
|
+
const dayName = date.toLocaleDateString('en-US', { weekday: 'short' });
|
|
80
|
+
const bar = (0, formatters_js_1.renderBar)(cost, maxCost, 20);
|
|
81
|
+
const isToday = day.date === todayStr;
|
|
82
|
+
const marker = isToday ? chalk_1.default.cyan(' <- today') : '';
|
|
83
|
+
console.log(` ${chalk_1.default.white(dayName)} │ ${chalk_1.default.green((0, formatters_js_1.formatCost)(cost).padStart(8))} ${bar}${marker}`);
|
|
84
|
+
}
|
|
85
|
+
console.log();
|
|
86
|
+
}
|
|
87
|
+
// ── RECENT SESSIONS (top 5 by cost) ──
|
|
88
|
+
if (sessions.length > 0) {
|
|
89
|
+
(0, formatters_js_1.sectionHeader)('Top Sessions by Cost');
|
|
90
|
+
const topSessions = [...sessions]
|
|
91
|
+
.sort((a, b) => (b.total_cost || 0) - (a.total_cost || 0))
|
|
92
|
+
.slice(0, 5);
|
|
93
|
+
for (const s of topSessions) {
|
|
94
|
+
const name = s.session_name || s.session_id;
|
|
95
|
+
const cost = (0, formatters_js_1.formatCost)(s.total_cost);
|
|
96
|
+
const tokens = (0, formatters_js_1.formatCompact)(s.total_tokens);
|
|
97
|
+
const models = s.models_used.slice(0, 2).join(', ');
|
|
98
|
+
console.log(` ${chalk_1.default.bold(name.padEnd(30))} │ ${chalk_1.default.green(cost.padStart(10))} │ ${tokens.padStart(8)} tokens │ ${chalk_1.default.cyan(models)}`);
|
|
99
|
+
}
|
|
100
|
+
console.log();
|
|
101
|
+
}
|
|
102
|
+
console.log(chalk_1.default.bold.cyan('═'.repeat(80)));
|
|
103
|
+
console.log();
|
|
104
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared formatting utilities for all usage subcommands
|
|
3
|
+
*/
|
|
4
|
+
/** Format numbers with commas */
|
|
5
|
+
export declare function formatNumber(num: number): string;
|
|
6
|
+
/** Format as compact number (1.2M, 45K, etc.) */
|
|
7
|
+
export declare function formatCompact(num: number): string;
|
|
8
|
+
/** Format percentage */
|
|
9
|
+
export declare function formatPercentage(pct: number): string;
|
|
10
|
+
/** Format cost in USD */
|
|
11
|
+
export declare function formatCost(cost: number): string;
|
|
12
|
+
/** Format timestamp to readable string */
|
|
13
|
+
export declare function formatTimestamp(ts: string): string;
|
|
14
|
+
/** Format date only */
|
|
15
|
+
export declare function formatDate(ts: string): string;
|
|
16
|
+
/** Get color function for context percentage */
|
|
17
|
+
export declare function getContextColor(pct: number): (str: string) => string;
|
|
18
|
+
/** Render a horizontal bar chart */
|
|
19
|
+
export declare function renderBar(value: number, maxValue: number, width?: number): string;
|
|
20
|
+
/** Print a section header */
|
|
21
|
+
export declare function sectionHeader(title: string): void;
|
|
22
|
+
/** Print the main title bar */
|
|
23
|
+
export declare function titleBar(title: string, subtitle?: string): void;
|
|
24
|
+
/** Format token breakdown inline */
|
|
25
|
+
export declare function formatTokensInline(data: {
|
|
26
|
+
inputTokens: number;
|
|
27
|
+
outputTokens: number;
|
|
28
|
+
cacheCreationTokens: number;
|
|
29
|
+
cacheReadTokens: number;
|
|
30
|
+
}): string;
|
|
31
|
+
/** Print model breakdown table */
|
|
32
|
+
export declare function printModelBreakdowns(breakdowns: {
|
|
33
|
+
modelName: string;
|
|
34
|
+
inputTokens: number;
|
|
35
|
+
outputTokens: number;
|
|
36
|
+
cacheCreationTokens: number;
|
|
37
|
+
cacheReadTokens: number;
|
|
38
|
+
cost: number;
|
|
39
|
+
}[]): void;
|
|
40
|
+
/** Print "no data" message */
|
|
41
|
+
export declare function noData(what: string): void;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.formatNumber = formatNumber;
|
|
7
|
+
exports.formatCompact = formatCompact;
|
|
8
|
+
exports.formatPercentage = formatPercentage;
|
|
9
|
+
exports.formatCost = formatCost;
|
|
10
|
+
exports.formatTimestamp = formatTimestamp;
|
|
11
|
+
exports.formatDate = formatDate;
|
|
12
|
+
exports.getContextColor = getContextColor;
|
|
13
|
+
exports.renderBar = renderBar;
|
|
14
|
+
exports.sectionHeader = sectionHeader;
|
|
15
|
+
exports.titleBar = titleBar;
|
|
16
|
+
exports.formatTokensInline = formatTokensInline;
|
|
17
|
+
exports.printModelBreakdowns = printModelBreakdowns;
|
|
18
|
+
exports.noData = noData;
|
|
19
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
20
|
+
/**
|
|
21
|
+
* Shared formatting utilities for all usage subcommands
|
|
22
|
+
*/
|
|
23
|
+
/** Format numbers with commas */
|
|
24
|
+
function formatNumber(num) {
|
|
25
|
+
if (num === undefined || num === null || isNaN(num))
|
|
26
|
+
return '0';
|
|
27
|
+
return num.toLocaleString('en-US');
|
|
28
|
+
}
|
|
29
|
+
/** Format as compact number (1.2M, 45K, etc.) */
|
|
30
|
+
function formatCompact(num) {
|
|
31
|
+
if (num === undefined || num === null || isNaN(num))
|
|
32
|
+
return '0';
|
|
33
|
+
if (num >= 1000000000)
|
|
34
|
+
return `${(num / 1000000000).toFixed(1)}B`;
|
|
35
|
+
if (num >= 1000000)
|
|
36
|
+
return `${(num / 1000000).toFixed(1)}M`;
|
|
37
|
+
if (num >= 1000)
|
|
38
|
+
return `${(num / 1000).toFixed(1)}K`;
|
|
39
|
+
return num.toString();
|
|
40
|
+
}
|
|
41
|
+
/** Format percentage */
|
|
42
|
+
function formatPercentage(pct) {
|
|
43
|
+
if (pct === undefined || pct === null || isNaN(pct))
|
|
44
|
+
return '0.0%';
|
|
45
|
+
return `${pct.toFixed(1)}%`;
|
|
46
|
+
}
|
|
47
|
+
/** Format cost in USD */
|
|
48
|
+
function formatCost(cost) {
|
|
49
|
+
if (cost === undefined || cost === null || isNaN(cost))
|
|
50
|
+
return '$0.00';
|
|
51
|
+
return `$${cost.toFixed(2)}`;
|
|
52
|
+
}
|
|
53
|
+
/** Format timestamp to readable string */
|
|
54
|
+
function formatTimestamp(ts) {
|
|
55
|
+
try {
|
|
56
|
+
const date = new Date(ts);
|
|
57
|
+
if (isNaN(date.getTime()))
|
|
58
|
+
return ts;
|
|
59
|
+
return date.toLocaleString('en-US', {
|
|
60
|
+
month: 'short',
|
|
61
|
+
day: 'numeric',
|
|
62
|
+
hour: 'numeric',
|
|
63
|
+
minute: '2-digit',
|
|
64
|
+
hour12: true
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return ts;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Format date only */
|
|
72
|
+
function formatDate(ts) {
|
|
73
|
+
try {
|
|
74
|
+
const date = new Date(ts);
|
|
75
|
+
if (isNaN(date.getTime()))
|
|
76
|
+
return ts;
|
|
77
|
+
return date.toLocaleDateString('en-US', {
|
|
78
|
+
weekday: 'short',
|
|
79
|
+
month: 'short',
|
|
80
|
+
day: 'numeric'
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return ts;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** Get color function for context percentage */
|
|
88
|
+
function getContextColor(pct) {
|
|
89
|
+
if (pct < 30)
|
|
90
|
+
return chalk_1.default.green;
|
|
91
|
+
if (pct < 50)
|
|
92
|
+
return chalk_1.default.yellow;
|
|
93
|
+
if (pct < 70)
|
|
94
|
+
return chalk_1.default.hex('#FFA500');
|
|
95
|
+
return chalk_1.default.red;
|
|
96
|
+
}
|
|
97
|
+
/** Render a horizontal bar chart */
|
|
98
|
+
function renderBar(value, maxValue, width = 16) {
|
|
99
|
+
if (maxValue === 0)
|
|
100
|
+
return '░'.repeat(width);
|
|
101
|
+
const filled = Math.round((value / maxValue) * width);
|
|
102
|
+
const empty = width - filled;
|
|
103
|
+
return '█'.repeat(Math.max(0, filled)) + '░'.repeat(Math.max(0, empty));
|
|
104
|
+
}
|
|
105
|
+
/** Print a section header */
|
|
106
|
+
function sectionHeader(title) {
|
|
107
|
+
console.log(chalk_1.default.bold(title));
|
|
108
|
+
console.log(chalk_1.default.gray('─'.repeat(80)));
|
|
109
|
+
}
|
|
110
|
+
/** Print the main title bar */
|
|
111
|
+
function titleBar(title, subtitle) {
|
|
112
|
+
console.log();
|
|
113
|
+
console.log(chalk_1.default.bold.cyan('═'.repeat(80)));
|
|
114
|
+
if (subtitle) {
|
|
115
|
+
console.log(chalk_1.default.bold.cyan(` ${title}`) + chalk_1.default.gray(` ${subtitle}`));
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
console.log(chalk_1.default.bold.cyan(` ${title}`));
|
|
119
|
+
}
|
|
120
|
+
console.log(chalk_1.default.bold.cyan('═'.repeat(80)));
|
|
121
|
+
console.log();
|
|
122
|
+
}
|
|
123
|
+
/** Format token breakdown inline */
|
|
124
|
+
function formatTokensInline(data) {
|
|
125
|
+
return `In: ${formatCompact(data.inputTokens)} Out: ${formatCompact(data.outputTokens)} Cache: ${formatCompact(data.cacheReadTokens)}`;
|
|
126
|
+
}
|
|
127
|
+
/** Print model breakdown table */
|
|
128
|
+
function printModelBreakdowns(breakdowns) {
|
|
129
|
+
if (!breakdowns || breakdowns.length === 0)
|
|
130
|
+
return;
|
|
131
|
+
const totalCost = breakdowns.reduce((sum, b) => sum + (b.cost || 0), 0);
|
|
132
|
+
for (const b of breakdowns) {
|
|
133
|
+
const pct = totalCost > 0 ? (b.cost / totalCost) * 100 : 0;
|
|
134
|
+
const totalTokens = (b.inputTokens || 0) + (b.outputTokens || 0) +
|
|
135
|
+
(b.cacheCreationTokens || 0) + (b.cacheReadTokens || 0);
|
|
136
|
+
const name = (b.modelName || 'unknown').padEnd(32);
|
|
137
|
+
const bar = renderBar(b.cost, totalCost);
|
|
138
|
+
console.log(` ${chalk_1.default.cyan(name)} │ ${formatCompact(totalTokens).padStart(7)} tokens │ ${chalk_1.default.green(formatCost(b.cost).padStart(8))} │ ${bar} ${pct.toFixed(0).padStart(3)}%`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** Print "no data" message */
|
|
142
|
+
function noData(what) {
|
|
143
|
+
console.log();
|
|
144
|
+
console.log(chalk_1.default.yellow(` No ${what} data found.`));
|
|
145
|
+
console.log(chalk_1.default.gray(' Make sure you have Claude Code sessions in ~/.claude/projects/'));
|
|
146
|
+
console.log();
|
|
147
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* Register usage subcommand tree on the main program
|
|
4
|
+
*
|
|
5
|
+
* ekkos usage → dashboard (default)
|
|
6
|
+
* ekkos usage daily → daily report
|
|
7
|
+
* ekkos usage weekly → weekly report
|
|
8
|
+
* ekkos usage monthly → monthly report
|
|
9
|
+
* ekkos usage blocks → 5-hour billing blocks
|
|
10
|
+
* ekkos usage session [id] → session detail / list
|
|
11
|
+
* ekkos usage dashboard → rich composite view
|
|
12
|
+
*/
|
|
13
|
+
export declare function registerUsageCommand(program: Command): void;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerUsageCommand = registerUsageCommand;
|
|
4
|
+
const daily_js_1 = require("./daily.js");
|
|
5
|
+
const weekly_js_1 = require("./weekly.js");
|
|
6
|
+
const monthly_js_1 = require("./monthly.js");
|
|
7
|
+
const blocks_js_1 = require("./blocks.js");
|
|
8
|
+
const dashboard_js_1 = require("./dashboard.js");
|
|
9
|
+
const session_js_1 = require("./session.js");
|
|
10
|
+
/**
|
|
11
|
+
* Register usage subcommand tree on the main program
|
|
12
|
+
*
|
|
13
|
+
* ekkos usage → dashboard (default)
|
|
14
|
+
* ekkos usage daily → daily report
|
|
15
|
+
* ekkos usage weekly → weekly report
|
|
16
|
+
* ekkos usage monthly → monthly report
|
|
17
|
+
* ekkos usage blocks → 5-hour billing blocks
|
|
18
|
+
* ekkos usage session [id] → session detail / list
|
|
19
|
+
* ekkos usage dashboard → rich composite view
|
|
20
|
+
*/
|
|
21
|
+
function registerUsageCommand(program) {
|
|
22
|
+
const usageCmd = program
|
|
23
|
+
.command('usage')
|
|
24
|
+
.description('Track Claude Code token usage and costs (powered by ccusage)')
|
|
25
|
+
.action(async (options) => {
|
|
26
|
+
// Default: show dashboard when no subcommand given
|
|
27
|
+
await (0, dashboard_js_1.dashboardCommand)({ json: options.json });
|
|
28
|
+
})
|
|
29
|
+
.option('-j, --json', 'Output machine-readable JSON');
|
|
30
|
+
// Daily subcommand
|
|
31
|
+
usageCmd
|
|
32
|
+
.command('daily')
|
|
33
|
+
.description('Daily usage breakdown')
|
|
34
|
+
.option('-d, --days <n>', 'Number of days to show', '14')
|
|
35
|
+
.option('-j, --json', 'Output JSON')
|
|
36
|
+
.action(async (options) => {
|
|
37
|
+
await (0, daily_js_1.dailyCommand)({ days: parseInt(options.days, 10), json: options.json });
|
|
38
|
+
});
|
|
39
|
+
// Weekly subcommand
|
|
40
|
+
usageCmd
|
|
41
|
+
.command('weekly')
|
|
42
|
+
.description('Weekly usage breakdown')
|
|
43
|
+
.option('-w, --weeks <n>', 'Number of weeks to show', '8')
|
|
44
|
+
.option('-j, --json', 'Output JSON')
|
|
45
|
+
.action(async (options) => {
|
|
46
|
+
await (0, weekly_js_1.weeklyCommand)({ weeks: parseInt(options.weeks, 10), json: options.json });
|
|
47
|
+
});
|
|
48
|
+
// Monthly subcommand
|
|
49
|
+
usageCmd
|
|
50
|
+
.command('monthly')
|
|
51
|
+
.description('Monthly usage breakdown')
|
|
52
|
+
.option('-j, --json', 'Output JSON')
|
|
53
|
+
.action(async (options) => {
|
|
54
|
+
await (0, monthly_js_1.monthlyCommand)({ json: options.json });
|
|
55
|
+
});
|
|
56
|
+
// Blocks subcommand (5-hour billing windows)
|
|
57
|
+
usageCmd
|
|
58
|
+
.command('blocks')
|
|
59
|
+
.description('5-hour billing block analysis')
|
|
60
|
+
.option('-j, --json', 'Output JSON')
|
|
61
|
+
.action(async (options) => {
|
|
62
|
+
await (0, blocks_js_1.blocksCommand)({ json: options.json });
|
|
63
|
+
});
|
|
64
|
+
// Session subcommand
|
|
65
|
+
usageCmd
|
|
66
|
+
.command('session [session-id]')
|
|
67
|
+
.description('Session usage detail or list all sessions')
|
|
68
|
+
.option('-l, --list', 'List all sessions')
|
|
69
|
+
.option('-i, --instance <id>', 'Instance ID (project path)')
|
|
70
|
+
.option('-j, --json', 'Output JSON')
|
|
71
|
+
.action(async (sessionId, options) => {
|
|
72
|
+
await (0, session_js_1.sessionCommand)({
|
|
73
|
+
sessionId,
|
|
74
|
+
list: options.list,
|
|
75
|
+
instance: options.instance,
|
|
76
|
+
json: options.json,
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
// Dashboard subcommand (explicit)
|
|
80
|
+
usageCmd
|
|
81
|
+
.command('dashboard')
|
|
82
|
+
.description('Rich composite usage dashboard')
|
|
83
|
+
.option('-j, --json', 'Output JSON')
|
|
84
|
+
.action(async (options) => {
|
|
85
|
+
await (0, dashboard_js_1.dashboardCommand)({ json: options.json });
|
|
86
|
+
});
|
|
87
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.monthlyCommand = monthlyCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const usage_parser_js_1 = require("../../lib/usage-parser.js");
|
|
9
|
+
const formatters_js_1 = require("./formatters.js");
|
|
10
|
+
/**
|
|
11
|
+
* ekkos usage monthly [--json]
|
|
12
|
+
*
|
|
13
|
+
* Show monthly usage breakdown
|
|
14
|
+
*/
|
|
15
|
+
async function monthlyCommand(options) {
|
|
16
|
+
const data = await (0, usage_parser_js_1.getMonthlyUsage)();
|
|
17
|
+
if (data.length === 0) {
|
|
18
|
+
if (options.json) {
|
|
19
|
+
console.log(JSON.stringify([]));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
(0, formatters_js_1.noData)('monthly usage');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
// Sort by month descending
|
|
26
|
+
const sorted = [...data].sort((a, b) => (b.month || '').localeCompare(a.month || ''));
|
|
27
|
+
if (options.json) {
|
|
28
|
+
console.log(JSON.stringify(sorted, null, 2));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
(0, formatters_js_1.titleBar)('Monthly Usage Report');
|
|
32
|
+
const maxCost = Math.max(...sorted.map(m => m.totalCost || m.cost || 0));
|
|
33
|
+
let grandTotal = 0;
|
|
34
|
+
console.log(chalk_1.default.gray(' Month'.padEnd(16)) +
|
|
35
|
+
chalk_1.default.gray('│ ') +
|
|
36
|
+
chalk_1.default.gray('Cost'.padStart(10)) +
|
|
37
|
+
chalk_1.default.gray(' │ ') +
|
|
38
|
+
chalk_1.default.gray('Input'.padStart(8)) +
|
|
39
|
+
chalk_1.default.gray(' │ ') +
|
|
40
|
+
chalk_1.default.gray('Output'.padStart(8)) +
|
|
41
|
+
chalk_1.default.gray(' │ ') +
|
|
42
|
+
chalk_1.default.gray('Cache Read'.padStart(10)) +
|
|
43
|
+
chalk_1.default.gray(' │ ') +
|
|
44
|
+
chalk_1.default.gray('Chart'));
|
|
45
|
+
console.log(chalk_1.default.gray('─'.repeat(80)));
|
|
46
|
+
for (const month of sorted) {
|
|
47
|
+
const cost = month.totalCost || month.cost || 0;
|
|
48
|
+
grandTotal += cost;
|
|
49
|
+
const label = month.month || 'unknown';
|
|
50
|
+
const bar = (0, formatters_js_1.renderBar)(cost, maxCost, 14);
|
|
51
|
+
console.log(` ${chalk_1.default.white(label.padEnd(14))} │ ${chalk_1.default.green((0, formatters_js_1.formatCost)(cost).padStart(10))} │ ${(0, formatters_js_1.formatCompact)(month.inputTokens || 0).padStart(8)} │ ${(0, formatters_js_1.formatCompact)(month.outputTokens || 0).padStart(8)} │ ${(0, formatters_js_1.formatCompact)(month.cacheReadTokens || 0).padStart(10)} │ ${bar}`);
|
|
52
|
+
// Show model breakdown per month
|
|
53
|
+
if (month.modelBreakdowns && month.modelBreakdowns.length > 0) {
|
|
54
|
+
for (const b of month.modelBreakdowns.sort((a, b) => b.cost - a.cost)) {
|
|
55
|
+
const name = (b.modelName || 'unknown').substring(0, 28);
|
|
56
|
+
console.log(chalk_1.default.gray(` ${name.padEnd(30)} ${(0, formatters_js_1.formatCost)(b.cost).padStart(10)}`));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
console.log(chalk_1.default.gray('─'.repeat(80)));
|
|
61
|
+
console.log(chalk_1.default.bold(' GRAND TOTAL'.padEnd(16)) +
|
|
62
|
+
`│ ${chalk_1.default.green.bold((0, formatters_js_1.formatCost)(grandTotal).padStart(10))} │`);
|
|
63
|
+
console.log();
|
|
64
|
+
console.log(chalk_1.default.bold.cyan('═'.repeat(80)));
|
|
65
|
+
console.log();
|
|
66
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ekkos usage session [id] [--list] [--instance id] [--json]
|
|
3
|
+
*
|
|
4
|
+
* Accepts ekkOS 3-word names (lit-lex-zip) or ccusage project paths.
|
|
5
|
+
*/
|
|
6
|
+
export declare function sessionCommand(options: {
|
|
7
|
+
sessionId?: string;
|
|
8
|
+
list?: boolean;
|
|
9
|
+
instance?: string;
|
|
10
|
+
json?: boolean;
|
|
11
|
+
}): Promise<void>;
|