@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,193 @@
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.sessionCommand = sessionCommand;
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 session [id] [--list] [--instance id] [--json]
12
+ *
13
+ * Accepts ekkOS 3-word names (lit-lex-zip) or ccusage project paths.
14
+ */
15
+ async function sessionCommand(options) {
16
+ const instanceId = options.instance;
17
+ if (options.list) {
18
+ await listSessions(instanceId, options.json);
19
+ return;
20
+ }
21
+ if (!options.sessionId) {
22
+ await listSessions(instanceId, options.json);
23
+ return;
24
+ }
25
+ try {
26
+ let usage = null;
27
+ // Detect ekkOS 3-word session names and resolve via active-sessions.json
28
+ if ((0, usage_parser_js_1.isEkkosSessionName)(options.sessionId)) {
29
+ usage = await (0, usage_parser_js_1.getSessionUsageByName)(options.sessionId);
30
+ if (!usage) {
31
+ if (options.json) {
32
+ console.log(JSON.stringify({ error: `ekkOS session "${options.sessionId}" not found` }));
33
+ return;
34
+ }
35
+ console.log(chalk_1.default.yellow(` ekkOS session "${options.sessionId}" not found.`));
36
+ console.log(chalk_1.default.gray(' Check available sessions with: ekkos usage session -l'));
37
+ return;
38
+ }
39
+ }
40
+ else {
41
+ // Fallback to ccusage project-path lookup
42
+ usage = await (0, usage_parser_js_1.getSessionUsage)(options.sessionId, instanceId);
43
+ }
44
+ if (!usage) {
45
+ if (options.json) {
46
+ console.log(JSON.stringify({ error: 'Session not found' }));
47
+ return;
48
+ }
49
+ console.log(chalk_1.default.yellow(` No usage data found for session: ${options.sessionId}`));
50
+ return;
51
+ }
52
+ if (options.json) {
53
+ console.log(JSON.stringify(usage, null, 2));
54
+ return;
55
+ }
56
+ displaySessionUsage(usage);
57
+ }
58
+ catch (err) {
59
+ console.log(chalk_1.default.red(` Error: ${err.message}`));
60
+ }
61
+ }
62
+ /** Display session usage in formatted table */
63
+ function displaySessionUsage(usage) {
64
+ (0, formatters_js_1.titleBar)(`Session: ${usage.session_name}`, usage.session_id);
65
+ (0, formatters_js_1.sectionHeader)('Summary');
66
+ console.log(` ${chalk_1.default.bold('Session ID:')} ${usage.session_id}`);
67
+ console.log(` ${chalk_1.default.bold('Turn Count:')} ${chalk_1.default.green(usage.turn_count.toString())}`);
68
+ console.log(` ${chalk_1.default.bold('Total Tokens:')} ${(0, formatters_js_1.formatNumber)(usage.total_tokens)}`);
69
+ console.log(` ${chalk_1.default.bold('Avg Context:')} ${(0, formatters_js_1.formatPercentage)(usage.avg_context_percentage)}`);
70
+ console.log(` ${chalk_1.default.bold('Max Context:')} ${(0, formatters_js_1.formatPercentage)(usage.max_context_percentage)}`);
71
+ console.log(` ${chalk_1.default.bold('Started:')} ${(0, formatters_js_1.formatTimestamp)(usage.started_at)}`);
72
+ console.log(` ${chalk_1.default.bold('Last Activity:')} ${(0, formatters_js_1.formatTimestamp)(usage.last_activity)}`);
73
+ console.log();
74
+ (0, formatters_js_1.sectionHeader)('Token Breakdown');
75
+ console.log(` ${chalk_1.default.bold('Input Tokens:')} ${(0, formatters_js_1.formatNumber)(usage.total_input_tokens)}`);
76
+ console.log(` ${chalk_1.default.bold('Output Tokens:')} ${(0, formatters_js_1.formatNumber)(usage.total_output_tokens)}`);
77
+ console.log(` ${chalk_1.default.bold('Cache Read:')} ${(0, formatters_js_1.formatNumber)(usage.total_cache_read_tokens)} ${chalk_1.default.gray('(90% discount)')}`);
78
+ console.log(` ${chalk_1.default.bold('Cache Creation:')} ${(0, formatters_js_1.formatNumber)(usage.total_cache_creation_tokens)} ${chalk_1.default.gray('(25% premium)')}`);
79
+ console.log();
80
+ (0, formatters_js_1.sectionHeader)('Cost');
81
+ console.log(` ${chalk_1.default.bold('Total Cost:')} ${chalk_1.default.green((0, formatters_js_1.formatCost)(usage.total_cost))}`);
82
+ console.log(` ${chalk_1.default.bold('Models Used:')} ${chalk_1.default.cyan(usage.models_used.join(', '))}`);
83
+ console.log();
84
+ // ekkOS pattern metrics
85
+ if (usage.patterns_retrieved !== undefined) {
86
+ (0, formatters_js_1.sectionHeader)('ekkOS Pattern Metrics');
87
+ console.log(` ${chalk_1.default.bold('Patterns Retrieved:')} ${chalk_1.default.cyan(usage.patterns_retrieved.toString())}`);
88
+ console.log(` ${chalk_1.default.bold('Patterns Applied:')} ${chalk_1.default.green(usage.patterns_applied?.toString() || '0')}`);
89
+ console.log(` ${chalk_1.default.bold('Patterns Learned:')} ${chalk_1.default.yellow(usage.patterns_learned?.toString() || '0')}`);
90
+ console.log(` ${chalk_1.default.bold('Confidence Gain:')} ${chalk_1.default.magenta(`+${((usage.confidence_gain || 0) * 100).toFixed(1)}%`)}`);
91
+ console.log();
92
+ }
93
+ // Turn-by-turn breakdown
94
+ if (usage.turns.length > 0) {
95
+ (0, formatters_js_1.sectionHeader)('Turn-by-Turn Breakdown');
96
+ console.log(chalk_1.default.gray(' Turn │ Context % │ Input │ Output │ Cache Read │ Total'));
97
+ console.log(chalk_1.default.gray('─'.repeat(80)));
98
+ for (const turn of usage.turns) {
99
+ const turnStr = turn.turn_number.toString().padStart(4);
100
+ const contextStr = (0, formatters_js_1.formatPercentage)(turn.context_percentage).padStart(10);
101
+ const inputStr = (0, formatters_js_1.formatNumber)(turn.input_tokens).padStart(7);
102
+ const outputStr = (0, formatters_js_1.formatNumber)(turn.output_tokens).padStart(7);
103
+ const cacheStr = (0, formatters_js_1.formatNumber)(turn.cache_read_tokens).padStart(11);
104
+ const totalStr = (0, formatters_js_1.formatNumber)(turn.total_tokens).padStart(8);
105
+ const contextColor = (0, formatters_js_1.getContextColor)(turn.context_percentage);
106
+ console.log(` ${turnStr} │ ${contextColor(contextStr)} │ ${inputStr} │ ${outputStr} │ ${cacheStr} │ ${totalStr}`);
107
+ }
108
+ console.log();
109
+ }
110
+ // VM validation
111
+ if (usage.turn_count >= 5) {
112
+ const contextValues = usage.turns.map(t => t.context_percentage);
113
+ const range = Math.max(...contextValues) - Math.min(...contextValues);
114
+ if (range < 15) {
115
+ console.log(chalk_1.default.green.bold(' VM Working: Context staying in constant band'));
116
+ }
117
+ else {
118
+ console.log(chalk_1.default.yellow.bold(' VM Warning: Context growing linearly (check proxy eviction)'));
119
+ }
120
+ console.log();
121
+ }
122
+ console.log(chalk_1.default.bold.cyan('═'.repeat(80)));
123
+ console.log();
124
+ }
125
+ /** List available sessions - shows ekkOS sessions by default, falls back to ccusage */
126
+ async function listSessions(instanceId, json) {
127
+ // Try ekkOS sessions first (individual sessions with 3-word names)
128
+ const ekkosSessions = await (0, usage_parser_js_1.listEkkosSessions)(30);
129
+ if (ekkosSessions.length > 0) {
130
+ if (json) {
131
+ console.log(JSON.stringify(ekkosSessions, null, 2));
132
+ return;
133
+ }
134
+ (0, formatters_js_1.titleBar)('ekkOS Sessions', `${ekkosSessions.length} recent`);
135
+ // Find max cost for bar rendering
136
+ const maxCost = Math.max(...ekkosSessions.map(s => s.cost), 0.01);
137
+ console.log(chalk_1.default.gray(' Session Name │ Turns │ Cost │ Tokens │ Models │ Started'));
138
+ console.log(chalk_1.default.gray(' ' + '─'.repeat(100)));
139
+ for (const s of ekkosSessions) {
140
+ const name = chalk_1.default.bold.cyan(s.name.padEnd(20));
141
+ const turns = s.turnCount.toString().padStart(5);
142
+ const cost = chalk_1.default.green((0, formatters_js_1.formatCost)(s.cost).padStart(7));
143
+ const tokens = (0, formatters_js_1.formatCompact)(s.tokens).padStart(8);
144
+ const models = chalk_1.default.gray(s.models
145
+ .filter(m => !m.includes('synthetic'))
146
+ .map(m => {
147
+ if (m.includes('opus-4-6'))
148
+ return 'opus-4.6';
149
+ if (m.includes('opus-4-5'))
150
+ return 'opus-4.5';
151
+ if (m.includes('sonnet-4-5'))
152
+ return 'sonnet-4.5';
153
+ if (m.includes('haiku-4-5'))
154
+ return 'haiku-4.5';
155
+ return m;
156
+ }).join(', ').padEnd(24));
157
+ const started = chalk_1.default.gray((0, formatters_js_1.formatTimestamp)(s.startedAt));
158
+ const bar = (0, formatters_js_1.renderBar)(s.cost, maxCost, 8);
159
+ console.log(` ${name}│ ${turns} │ ${cost} │ ${tokens} │ ${models}│ ${started} ${bar}`);
160
+ }
161
+ console.log();
162
+ console.log(chalk_1.default.gray(' Use: ekkos usage session <name> for detailed breakdown'));
163
+ console.log(chalk_1.default.bold.cyan('═'.repeat(80)));
164
+ console.log();
165
+ return;
166
+ }
167
+ // Fallback to ccusage project-level sessions
168
+ const sessions = await (0, usage_parser_js_1.getAllSessions)(instanceId);
169
+ if (sessions.length === 0) {
170
+ if (json) {
171
+ console.log(JSON.stringify([]));
172
+ return;
173
+ }
174
+ (0, formatters_js_1.noData)('session');
175
+ return;
176
+ }
177
+ if (json) {
178
+ console.log(JSON.stringify(sessions, null, 2));
179
+ return;
180
+ }
181
+ (0, formatters_js_1.titleBar)('Sessions', `${sessions.length} found`);
182
+ const sorted = [...sessions].sort((a, b) => (b.last_activity || '').localeCompare(a.last_activity || ''));
183
+ for (const session of sorted) {
184
+ console.log(` ${chalk_1.default.bold(session.session_id)}`);
185
+ console.log(` ${chalk_1.default.gray('Cost:')} ${chalk_1.default.green((0, formatters_js_1.formatCost)(session.total_cost))}` +
186
+ ` ${chalk_1.default.gray('Tokens:')} ${(0, formatters_js_1.formatCompact)(session.total_tokens)}` +
187
+ ` ${chalk_1.default.gray('Last:')} ${session.last_activity}` +
188
+ ` ${chalk_1.default.gray('Models:')} ${chalk_1.default.cyan(session.models_used.join(', '))}`);
189
+ console.log();
190
+ }
191
+ console.log(chalk_1.default.bold.cyan('═'.repeat(80)));
192
+ console.log();
193
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * ekkos usage weekly [--weeks N] [--json]
3
+ *
4
+ * Show weekly usage breakdown
5
+ */
6
+ export declare function weeklyCommand(options: {
7
+ weeks?: number;
8
+ json?: boolean;
9
+ }): Promise<void>;
@@ -0,0 +1,61 @@
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.weeklyCommand = weeklyCommand;
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 weekly [--weeks N] [--json]
12
+ *
13
+ * Show weekly usage breakdown
14
+ */
15
+ async function weeklyCommand(options) {
16
+ const weeks = options.weeks || 8;
17
+ const data = await (0, usage_parser_js_1.getWeeklyUsage)();
18
+ if (data.length === 0) {
19
+ if (options.json) {
20
+ console.log(JSON.stringify([]));
21
+ return;
22
+ }
23
+ (0, formatters_js_1.noData)('weekly usage');
24
+ return;
25
+ }
26
+ // Sort by week date descending
27
+ const sorted = [...data].sort((a, b) => (b.week || '').localeCompare(a.week || ''));
28
+ const recent = sorted.slice(0, weeks);
29
+ if (options.json) {
30
+ console.log(JSON.stringify(recent, null, 2));
31
+ return;
32
+ }
33
+ (0, formatters_js_1.titleBar)('Weekly Usage Report', `Last ${Math.min(weeks, recent.length)} weeks`);
34
+ const maxCost = Math.max(...recent.map(w => w.totalCost || 0));
35
+ let totalCost = 0;
36
+ console.log(chalk_1.default.gray(' Week of'.padEnd(22)) +
37
+ chalk_1.default.gray('│ ') +
38
+ chalk_1.default.gray('Cost'.padStart(10)) +
39
+ chalk_1.default.gray(' │ ') +
40
+ chalk_1.default.gray('Input'.padStart(8)) +
41
+ chalk_1.default.gray(' │ ') +
42
+ chalk_1.default.gray('Output'.padStart(8)) +
43
+ chalk_1.default.gray(' │ ') +
44
+ chalk_1.default.gray('Cache'.padStart(8)) +
45
+ chalk_1.default.gray(' │ ') +
46
+ chalk_1.default.gray('Chart'));
47
+ console.log(chalk_1.default.gray('─'.repeat(80)));
48
+ for (const week of recent) {
49
+ const cost = week.totalCost || 0;
50
+ totalCost += cost;
51
+ const weekDate = (0, formatters_js_1.formatDate)(week.week || '');
52
+ const bar = (0, formatters_js_1.renderBar)(cost, maxCost, 14);
53
+ console.log(` ${chalk_1.default.white(weekDate.padEnd(20))} │ ${chalk_1.default.green((0, formatters_js_1.formatCost)(cost).padStart(10))} │ ${(0, formatters_js_1.formatCompact)(week.inputTokens || 0).padStart(8)} │ ${(0, formatters_js_1.formatCompact)(week.outputTokens || 0).padStart(8)} │ ${(0, formatters_js_1.formatCompact)(week.cacheReadTokens || 0).padStart(8)} │ ${bar}`);
54
+ }
55
+ console.log(chalk_1.default.gray('─'.repeat(80)));
56
+ console.log(chalk_1.default.bold(' TOTAL'.padEnd(22)) +
57
+ `│ ${chalk_1.default.green.bold((0, formatters_js_1.formatCost)(totalCost).padStart(10))} │`);
58
+ console.log();
59
+ console.log(chalk_1.default.bold.cyan('═'.repeat(80)));
60
+ console.log();
61
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * ekkos usage [session-id] [--instance instance-id]
3
+ *
4
+ * Track Claude Code session token usage and context percentage
5
+ * Powered by ccusage (https://ccusage.com)
6
+ */
7
+ export declare function usageCommand(args: string[]): Promise<void>;
@@ -0,0 +1,214 @@
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.usageCommand = usageCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const usage_parser_js_1 = require("../lib/usage-parser.js");
9
+ /**
10
+ * ekkos usage [session-id] [--instance instance-id]
11
+ *
12
+ * Track Claude Code session token usage and context percentage
13
+ * Powered by ccusage (https://ccusage.com)
14
+ */
15
+ async function usageCommand(args) {
16
+ const sessionIdArg = args[0];
17
+ const instanceIdFlag = args.indexOf('--instance');
18
+ const listFlag = args.includes('--list');
19
+ // Default instance ID (current project)
20
+ let instanceId = '-Volumes-MacMiniPort-DEV-EKKOS'; // Default to current project
21
+ if (instanceIdFlag !== -1 && args[instanceIdFlag + 1]) {
22
+ instanceId = args[instanceIdFlag + 1];
23
+ }
24
+ // List available sessions
25
+ if (listFlag) {
26
+ await listAvailableSessions(instanceId);
27
+ return;
28
+ }
29
+ // Show usage for specific session
30
+ if (!sessionIdArg) {
31
+ console.log(chalk_1.default.red('❌ Session ID required'));
32
+ console.log(chalk_1.default.gray('\nUsage:'));
33
+ console.log(chalk_1.default.gray(' ekkos usage <session-id> Show usage for session'));
34
+ console.log(chalk_1.default.gray(' ekkos usage --list List available sessions'));
35
+ console.log(chalk_1.default.gray(' ekkos usage <id> --instance <inst> Use specific instance'));
36
+ process.exit(1);
37
+ }
38
+ try {
39
+ console.log(chalk_1.default.gray('Fetching usage data from ccusage...'));
40
+ const usage = await (0, usage_parser_js_1.getSessionUsage)(sessionIdArg, instanceId);
41
+ if (!usage) {
42
+ console.log(chalk_1.default.yellow('⚠️ No usage data found for session'));
43
+ return;
44
+ }
45
+ displaySessionUsage(usage);
46
+ }
47
+ catch (err) {
48
+ console.log(chalk_1.default.red(`❌ Error: ${err.message}`));
49
+ process.exit(1);
50
+ }
51
+ }
52
+ /**
53
+ * Display session usage in formatted table
54
+ */
55
+ function displaySessionUsage(usage) {
56
+ console.log();
57
+ console.log(chalk_1.default.bold.cyan('═'.repeat(80)));
58
+ console.log(chalk_1.default.bold.cyan(` Session Usage: ${usage.session_name}`));
59
+ console.log(chalk_1.default.bold.cyan('═'.repeat(80)));
60
+ console.log();
61
+ // Summary section
62
+ console.log(chalk_1.default.bold('📊 Summary'));
63
+ console.log(chalk_1.default.gray('─'.repeat(80)));
64
+ console.log(` ${chalk_1.default.bold('Session ID:')} ${usage.session_id}`);
65
+ console.log(` ${chalk_1.default.bold('Turn Count:')} ${chalk_1.default.green(usage.turn_count.toString())}`);
66
+ console.log(` ${chalk_1.default.bold('Total Tokens:')} ${formatNumber(usage.total_tokens)}`);
67
+ console.log(` ${chalk_1.default.bold('Avg Context:')} ${formatPercentage(usage.avg_context_percentage)}`);
68
+ console.log(` ${chalk_1.default.bold('Max Context:')} ${formatPercentage(usage.max_context_percentage)}`);
69
+ console.log(` ${chalk_1.default.bold('Started:')} ${formatTimestamp(usage.started_at)}`);
70
+ console.log(` ${chalk_1.default.bold('Last Activity:')} ${formatTimestamp(usage.last_activity)}`);
71
+ console.log();
72
+ // Token breakdown
73
+ console.log(chalk_1.default.bold('🔢 Token Breakdown'));
74
+ console.log(chalk_1.default.gray('─'.repeat(80)));
75
+ console.log(` ${chalk_1.default.bold('Input Tokens:')} ${formatNumber(usage.total_input_tokens)}`);
76
+ console.log(` ${chalk_1.default.bold('Output Tokens:')} ${formatNumber(usage.total_output_tokens)}`);
77
+ console.log(` ${chalk_1.default.bold('Cache Read:')} ${formatNumber(usage.total_cache_read_tokens)} ${chalk_1.default.gray('(90% discount)')}`);
78
+ console.log(` ${chalk_1.default.bold('Cache Creation:')} ${formatNumber(usage.total_cache_creation_tokens)} ${chalk_1.default.gray('(25% premium)')}`);
79
+ console.log();
80
+ // Cost breakdown
81
+ console.log(chalk_1.default.bold('💰 Cost'));
82
+ console.log(chalk_1.default.gray('─'.repeat(80)));
83
+ console.log(` ${chalk_1.default.bold('Total Cost:')} ${chalk_1.default.green('$' + usage.total_cost.toFixed(2))}`);
84
+ console.log(` ${chalk_1.default.bold('Models Used:')} ${chalk_1.default.cyan(usage.models_used.join(', '))}`);
85
+ console.log();
86
+ // ekkOS pattern metrics (if available)
87
+ if (usage.patterns_retrieved !== undefined) {
88
+ console.log(chalk_1.default.bold('🧠 ekkOS Pattern Metrics'));
89
+ console.log(chalk_1.default.gray('─'.repeat(80)));
90
+ console.log(` ${chalk_1.default.bold('Patterns Retrieved:')} ${chalk_1.default.cyan(usage.patterns_retrieved.toString())}`);
91
+ console.log(` ${chalk_1.default.bold('Patterns Applied:')} ${chalk_1.default.green(usage.patterns_applied?.toString() || '0')}`);
92
+ console.log(` ${chalk_1.default.bold('Patterns Learned:')} ${chalk_1.default.yellow(usage.patterns_learned?.toString() || '0')}`);
93
+ console.log(` ${chalk_1.default.bold('Confidence Gain:')} ${chalk_1.default.magenta(`+${((usage.confidence_gain || 0) * 100).toFixed(1)}%`)}`);
94
+ console.log();
95
+ }
96
+ // Turn-by-turn breakdown (if available)
97
+ if (usage.turns.length > 0) {
98
+ console.log(chalk_1.default.bold('📈 Turn-by-Turn Breakdown'));
99
+ console.log(chalk_1.default.gray('─'.repeat(80)));
100
+ console.log(chalk_1.default.gray(' Turn') +
101
+ chalk_1.default.gray(' │ ') +
102
+ chalk_1.default.gray('Context %') +
103
+ chalk_1.default.gray(' │ ') +
104
+ chalk_1.default.gray('Input') +
105
+ chalk_1.default.gray(' │ ') +
106
+ chalk_1.default.gray('Output') +
107
+ chalk_1.default.gray(' │ ') +
108
+ chalk_1.default.gray('Cache Read') +
109
+ chalk_1.default.gray(' │ ') +
110
+ chalk_1.default.gray('Total'));
111
+ console.log(chalk_1.default.gray('─'.repeat(80)));
112
+ for (const turn of usage.turns) {
113
+ const turnStr = turn.turn_number.toString().padStart(4);
114
+ const contextStr = formatPercentage(turn.context_percentage).padStart(10);
115
+ const inputStr = formatNumber(turn.input_tokens).padStart(7);
116
+ const outputStr = formatNumber(turn.output_tokens).padStart(7);
117
+ const cacheStr = formatNumber(turn.cache_read_tokens).padStart(11);
118
+ const totalStr = formatNumber(turn.total_tokens).padStart(8);
119
+ // Color code context percentage
120
+ const contextColor = getContextColor(turn.context_percentage);
121
+ console.log(` ${turnStr} │ ${contextColor(contextStr)} │ ${inputStr} │ ${outputStr} │ ${cacheStr} │ ${totalStr}`);
122
+ }
123
+ console.log();
124
+ }
125
+ // VM validation status
126
+ const isVmWorking = validateVmBehavior(usage);
127
+ if (isVmWorking) {
128
+ console.log(chalk_1.default.green.bold('✅ VM Working: Context staying in constant band (good!)'));
129
+ }
130
+ else {
131
+ console.log(chalk_1.default.yellow.bold('⚠️ VM Warning: Context growing linearly (check proxy eviction)'));
132
+ }
133
+ console.log();
134
+ console.log(chalk_1.default.bold.cyan('═'.repeat(80)));
135
+ console.log();
136
+ }
137
+ /**
138
+ * List available sessions for an instance
139
+ */
140
+ async function listAvailableSessions(instanceId) {
141
+ console.log();
142
+ console.log(chalk_1.default.bold.cyan('📋 Available Sessions (via ccusage)'));
143
+ console.log(chalk_1.default.gray('─'.repeat(80)));
144
+ console.log(chalk_1.default.gray('Fetching sessions...'));
145
+ const sessions = await (0, usage_parser_js_1.getAllSessions)(instanceId);
146
+ if (sessions.length === 0) {
147
+ console.log(chalk_1.default.yellow(' No sessions found'));
148
+ console.log();
149
+ return;
150
+ }
151
+ for (const session of sessions) {
152
+ console.log(` ${chalk_1.default.bold(session.session_id)}`);
153
+ console.log(` ${chalk_1.default.gray('Turns:')} ${chalk_1.default.green(session.turn_count.toString())}, ${chalk_1.default.gray('Cost:')} ${chalk_1.default.green('$' + session.total_cost.toFixed(2))}, ${chalk_1.default.gray('Context:')} ${formatPercentage(session.avg_context_percentage)}`);
154
+ console.log(` ${chalk_1.default.gray('Last:')} ${session.last_activity}, ${chalk_1.default.gray('Models:')} ${chalk_1.default.cyan(session.models_used.join(', '))}`);
155
+ console.log();
156
+ }
157
+ }
158
+ /**
159
+ * Validate VM behavior based on context growth pattern
160
+ */
161
+ function validateVmBehavior(usage) {
162
+ if (usage.turn_count < 5) {
163
+ return true; // Too few turns to judge
164
+ }
165
+ // Check if context is staying in a constant band (±10%)
166
+ const contextValues = usage.turns.map(t => t.context_percentage);
167
+ const min = Math.min(...contextValues);
168
+ const max = Math.max(...contextValues);
169
+ const range = max - min;
170
+ // If range is small (< 15%), VM is working
171
+ return range < 15;
172
+ }
173
+ /**
174
+ * Format numbers with commas
175
+ */
176
+ function formatNumber(num) {
177
+ return num.toLocaleString('en-US');
178
+ }
179
+ /**
180
+ * Format percentage
181
+ */
182
+ function formatPercentage(pct) {
183
+ return `${pct.toFixed(1)}%`;
184
+ }
185
+ /**
186
+ * Format timestamp
187
+ */
188
+ function formatTimestamp(ts) {
189
+ try {
190
+ const date = new Date(ts);
191
+ return date.toLocaleString('en-US', {
192
+ month: 'short',
193
+ day: 'numeric',
194
+ hour: 'numeric',
195
+ minute: '2-digit',
196
+ hour12: true
197
+ });
198
+ }
199
+ catch {
200
+ return ts;
201
+ }
202
+ }
203
+ /**
204
+ * Get color for context percentage
205
+ */
206
+ function getContextColor(pct) {
207
+ if (pct < 30)
208
+ return chalk_1.default.green;
209
+ if (pct < 50)
210
+ return chalk_1.default.yellow;
211
+ if (pct < 70)
212
+ return chalk_1.default.hex('#FFA500'); // Orange
213
+ return chalk_1.default.red;
214
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * ekkOS Cron Jobs
3
+ * ================
4
+ *
5
+ * Background jobs for ekkOS maintenance and evolution.
6
+ */
7
+ export { evaluatePromotions, queryPatternStats, writePatchConfig, type PromoterConfig, type PromotionResult, type PromotedPatchConfig, type PromotedPattern, } from './promoter.js';
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ /**
3
+ * ekkOS Cron Jobs
4
+ * ================
5
+ *
6
+ * Background jobs for ekkOS maintenance and evolution.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.writePatchConfig = exports.queryPatternStats = exports.evaluatePromotions = void 0;
10
+ var promoter_js_1 = require("./promoter.js");
11
+ Object.defineProperty(exports, "evaluatePromotions", { enumerable: true, get: function () { return promoter_js_1.evaluatePromotions; } });
12
+ Object.defineProperty(exports, "queryPatternStats", { enumerable: true, get: function () { return promoter_js_1.queryPatternStats; } });
13
+ Object.defineProperty(exports, "writePatchConfig", { enumerable: true, get: function () { return promoter_js_1.writePatchConfig; } });
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ekkOS PROMETHEUS Pattern Promoter
4
+ * ==================================
5
+ *
6
+ * Daily cron job that evaluates patterns for promotion to constitutional memory.
7
+ *
8
+ * Constitutional patterns are injected into the system prompt via @ekkos/patch,
9
+ * making them "instincts" that don't require retrieval.
10
+ *
11
+ * Promotion Criteria (default):
12
+ * - Success rate ≥ 85%
13
+ * - Applications ≥ 5
14
+ * - Skip rate ≤ 10%
15
+ * - Confidence ≥ 70%
16
+ * - Used within last 30 days
17
+ * - Used in ≥ 2 unique sessions
18
+ *
19
+ * Usage:
20
+ * npx ekkos-promote # Run promotion evaluation
21
+ * npx ekkos-promote --dry-run # Preview without applying changes
22
+ * npx ekkos-promote --user <uuid> # Promote for specific user
23
+ *
24
+ * Schedule via launchd (macOS) or cron:
25
+ * 0 6 * * * /path/to/node /path/to/ekkos-promote
26
+ */
27
+ import { SupabaseClient } from '@supabase/supabase-js';
28
+ import { type PatternStats } from '@ekkos/prometheus';
29
+ interface PromoterConfig {
30
+ supabaseUrl: string;
31
+ supabaseKey: string;
32
+ dryRun: boolean;
33
+ userId?: string;
34
+ patchConfigPath: string;
35
+ verbose: boolean;
36
+ }
37
+ interface PromotionResult {
38
+ evaluated: number;
39
+ promoted: number;
40
+ demoted: number;
41
+ patterns: Array<{
42
+ patternId: string;
43
+ title: string;
44
+ action: 'promoted' | 'demoted' | 'unchanged';
45
+ score: number;
46
+ reason?: string;
47
+ }>;
48
+ patchConfig?: PromotedPatchConfig;
49
+ }
50
+ interface PromotedPattern {
51
+ id: string;
52
+ title: string;
53
+ problem: string;
54
+ solution: string;
55
+ promotedAt: string;
56
+ successRate: number;
57
+ appliedCount: number;
58
+ tags?: string[];
59
+ }
60
+ interface PromotedPatchConfig {
61
+ version: string;
62
+ generatedAt: string;
63
+ promotedPatterns: PromotedPattern[];
64
+ totalPatterns: number;
65
+ }
66
+ declare function queryPatternStats(supabase: SupabaseClient, userId?: string): Promise<PatternStats[]>;
67
+ declare function evaluatePromotions(supabase: SupabaseClient, config: PromoterConfig): Promise<PromotionResult>;
68
+ declare function writePatchConfig(config: PromotedPatchConfig, outputPath: string): void;
69
+ export { evaluatePromotions, queryPatternStats, writePatchConfig };
70
+ export type { PromoterConfig, PromotionResult, PromotedPatchConfig, PromotedPattern };