@ekkos/cli 0.2.18 → 1.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.
Files changed (98) hide show
  1. package/README.md +57 -0
  2. package/dist/agent/daemon.d.ts +27 -0
  3. package/dist/agent/daemon.js +254 -29
  4. package/dist/agent/health-check.d.ts +35 -0
  5. package/dist/agent/health-check.js +243 -0
  6. package/dist/agent/pty-runner.d.ts +1 -0
  7. package/dist/agent/pty-runner.js +6 -1
  8. package/dist/capture/eviction-client.d.ts +139 -0
  9. package/dist/capture/eviction-client.js +454 -0
  10. package/dist/capture/index.d.ts +2 -0
  11. package/dist/capture/index.js +2 -0
  12. package/dist/capture/jsonl-rewriter.d.ts +96 -0
  13. package/dist/capture/jsonl-rewriter.js +1369 -0
  14. package/dist/capture/transcript-repair.d.ts +51 -0
  15. package/dist/capture/transcript-repair.js +319 -0
  16. package/dist/commands/agent.d.ts +6 -0
  17. package/dist/commands/agent.js +244 -0
  18. package/dist/commands/dashboard.d.ts +25 -0
  19. package/dist/commands/dashboard.js +1175 -0
  20. package/dist/commands/doctor.js +23 -1
  21. package/dist/commands/run.d.ts +5 -0
  22. package/dist/commands/run.js +1605 -516
  23. package/dist/commands/setup-remote.js +146 -37
  24. package/dist/commands/swarm-dashboard.d.ts +20 -0
  25. package/dist/commands/swarm-dashboard.js +735 -0
  26. package/dist/commands/swarm-setup.d.ts +10 -0
  27. package/dist/commands/swarm-setup.js +956 -0
  28. package/dist/commands/swarm.d.ts +46 -0
  29. package/dist/commands/swarm.js +441 -0
  30. package/dist/commands/test-claude.d.ts +16 -0
  31. package/dist/commands/test-claude.js +156 -0
  32. package/dist/commands/usage/blocks.d.ts +8 -0
  33. package/dist/commands/usage/blocks.js +60 -0
  34. package/dist/commands/usage/daily.d.ts +9 -0
  35. package/dist/commands/usage/daily.js +96 -0
  36. package/dist/commands/usage/dashboard.d.ts +8 -0
  37. package/dist/commands/usage/dashboard.js +104 -0
  38. package/dist/commands/usage/formatters.d.ts +41 -0
  39. package/dist/commands/usage/formatters.js +147 -0
  40. package/dist/commands/usage/index.d.ts +13 -0
  41. package/dist/commands/usage/index.js +87 -0
  42. package/dist/commands/usage/monthly.d.ts +8 -0
  43. package/dist/commands/usage/monthly.js +66 -0
  44. package/dist/commands/usage/session.d.ts +11 -0
  45. package/dist/commands/usage/session.js +193 -0
  46. package/dist/commands/usage/weekly.d.ts +9 -0
  47. package/dist/commands/usage/weekly.js +61 -0
  48. package/dist/commands/usage.d.ts +7 -0
  49. package/dist/commands/usage.js +214 -0
  50. package/dist/cron/index.d.ts +7 -0
  51. package/dist/cron/index.js +13 -0
  52. package/dist/cron/promoter.d.ts +70 -0
  53. package/dist/cron/promoter.js +403 -0
  54. package/dist/deploy/instructions.d.ts +5 -2
  55. package/dist/deploy/instructions.js +11 -8
  56. package/dist/index.js +262 -5
  57. package/dist/lib/tmux-scrollbar.d.ts +14 -0
  58. package/dist/lib/tmux-scrollbar.js +296 -0
  59. package/dist/lib/usage-monitor.d.ts +47 -0
  60. package/dist/lib/usage-monitor.js +124 -0
  61. package/dist/lib/usage-parser.d.ts +162 -0
  62. package/dist/lib/usage-parser.js +583 -0
  63. package/dist/restore/RestoreOrchestrator.d.ts +4 -0
  64. package/dist/restore/RestoreOrchestrator.js +118 -30
  65. package/dist/utils/log-rotate.d.ts +18 -0
  66. package/dist/utils/log-rotate.js +74 -0
  67. package/dist/utils/platform.d.ts +2 -0
  68. package/dist/utils/platform.js +3 -1
  69. package/dist/utils/session-binding.d.ts +5 -0
  70. package/dist/utils/session-binding.js +46 -0
  71. package/dist/utils/state.js +4 -0
  72. package/dist/utils/verify-remote-terminal.d.ts +10 -0
  73. package/dist/utils/verify-remote-terminal.js +415 -0
  74. package/package.json +9 -2
  75. package/templates/CLAUDE.md +135 -23
  76. package/templates/ekkos-manifest.json +5 -5
  77. package/templates/hooks/lib/contract.sh +43 -31
  78. package/templates/hooks/lib/count-tokens.cjs +86 -0
  79. package/templates/hooks/lib/ekkos-reminders.sh +98 -0
  80. package/templates/hooks/lib/state.sh +53 -1
  81. package/templates/hooks/stop.sh +150 -388
  82. package/templates/hooks/user-prompt-submit.sh +353 -443
  83. package/templates/windsurf-hooks/README.md +212 -0
  84. package/templates/windsurf-hooks/hooks.json +9 -2
  85. package/templates/windsurf-hooks/install.sh +148 -0
  86. package/templates/windsurf-hooks/lib/contract.sh +2 -0
  87. package/templates/windsurf-hooks/post-cascade-response.sh +251 -0
  88. package/templates/windsurf-hooks/pre-user-prompt.sh +435 -0
  89. package/templates/windsurf-skills/ekkos-memory/SKILL.md +219 -0
  90. package/templates/agents/README.md +0 -182
  91. package/templates/agents/code-reviewer.md +0 -166
  92. package/templates/agents/debug-detective.md +0 -169
  93. package/templates/agents/ekkOS_Vercel.md +0 -99
  94. package/templates/agents/extension-manager.md +0 -229
  95. package/templates/agents/git-companion.md +0 -185
  96. package/templates/agents/github-test-agent.md +0 -321
  97. package/templates/agents/railway-manager.md +0 -215
  98. 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,9 @@
1
+ /**
2
+ * ekkos usage daily [--days N] [--json]
3
+ *
4
+ * Show daily usage breakdown
5
+ */
6
+ export declare function dailyCommand(options: {
7
+ days?: number;
8
+ json?: boolean;
9
+ }): Promise<void>;
@@ -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,8 @@
1
+ /**
2
+ * ekkos usage dashboard [--json]
3
+ *
4
+ * Rich composite dashboard showing today, this week, this month + trends
5
+ */
6
+ export declare function dashboardCommand(options: {
7
+ json?: boolean;
8
+ }): Promise<void>;
@@ -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,8 @@
1
+ /**
2
+ * ekkos usage monthly [--json]
3
+ *
4
+ * Show monthly usage breakdown
5
+ */
6
+ export declare function monthlyCommand(options: {
7
+ json?: boolean;
8
+ }): Promise<void>;
@@ -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>;