@ekkos/cli 0.2.17 → 0.3.3
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/LICENSE +21 -0
- package/dist/capture/eviction-client.d.ts +139 -0
- package/dist/capture/eviction-client.js +454 -0
- package/dist/capture/index.d.ts +2 -0
- package/dist/capture/index.js +2 -0
- package/dist/capture/jsonl-rewriter.d.ts +96 -0
- package/dist/capture/jsonl-rewriter.js +1369 -0
- package/dist/capture/transcript-repair.d.ts +50 -0
- package/dist/capture/transcript-repair.js +308 -0
- package/dist/commands/doctor.js +23 -1
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.js +1229 -293
- package/dist/commands/setup.js +62 -16
- package/dist/commands/usage.d.ts +7 -0
- package/dist/commands/usage.js +214 -0
- package/dist/cron/index.d.ts +7 -0
- package/dist/cron/index.js +13 -0
- package/dist/cron/promoter.d.ts +70 -0
- package/dist/cron/promoter.js +403 -0
- package/dist/index.js +24 -3
- package/dist/lib/usage-monitor.d.ts +47 -0
- package/dist/lib/usage-monitor.js +124 -0
- package/dist/lib/usage-parser.d.ts +72 -0
- package/dist/lib/usage-parser.js +238 -0
- package/dist/restore/RestoreOrchestrator.d.ts +4 -0
- package/dist/restore/RestoreOrchestrator.js +118 -30
- package/package.json +12 -12
- 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 +2 -2
- package/templates/hooks/assistant-response.sh +0 -0
- package/templates/hooks/session-start.sh +0 -0
- package/templates/hooks/user-prompt-submit.sh +6 -0
- package/templates/plan-template.md +0 -0
- package/templates/spec-template.md +0 -0
- package/templates/agents/README.md +0 -182
- package/templates/agents/code-reviewer.md +0 -166
- package/templates/agents/debug-detective.md +0 -169
- package/templates/agents/ekkOS_Vercel.md +0 -99
- package/templates/agents/extension-manager.md +0 -229
- package/templates/agents/git-companion.md +0 -185
- package/templates/agents/github-test-agent.md +0 -321
- package/templates/agents/railway-manager.md +0 -215
package/dist/commands/setup.js
CHANGED
|
@@ -10,6 +10,7 @@ const fs_1 = require("fs");
|
|
|
10
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
11
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
12
12
|
const ora_1 = __importDefault(require("ora"));
|
|
13
|
+
const hooks_js_1 = require("./hooks.js");
|
|
13
14
|
const EKKOS_API_URL = 'https://mcp.ekkos.dev';
|
|
14
15
|
const CONFIG_DIR = (0, path_1.join)((0, os_1.homedir)(), '.ekkos');
|
|
15
16
|
const CONFIG_FILE = (0, path_1.join)(CONFIG_DIR, 'config.json');
|
|
@@ -207,24 +208,74 @@ async function setupIDE(ide, apiKey, config) {
|
|
|
207
208
|
async function setupClaudeCode(apiKey) {
|
|
208
209
|
const claudeDir = (0, path_1.join)((0, os_1.homedir)(), '.claude');
|
|
209
210
|
const hooksDir = (0, path_1.join)(claudeDir, 'hooks');
|
|
210
|
-
|
|
211
|
-
|
|
211
|
+
const stateDir = (0, path_1.join)(claudeDir, 'state');
|
|
212
|
+
// Create directories
|
|
213
|
+
(0, fs_1.mkdirSync)(claudeDir, { recursive: true });
|
|
214
|
+
(0, fs_1.mkdirSync)(hooksDir, { recursive: true });
|
|
215
|
+
(0, fs_1.mkdirSync)(stateDir, { recursive: true });
|
|
216
|
+
// Check for existing custom hooks (don't have EKKOS_MANAGED=1 marker)
|
|
217
|
+
const isWindows = (0, os_1.platform)() === 'win32';
|
|
218
|
+
const hookExt = isWindows ? '.ps1' : '.sh';
|
|
219
|
+
const hookFiles = ['user-prompt-submit', 'stop', 'session-start', 'assistant-response'];
|
|
220
|
+
let hasCustomHooks = false;
|
|
221
|
+
for (const hookName of hookFiles) {
|
|
222
|
+
const hookPath = (0, path_1.join)(hooksDir, `${hookName}${hookExt}`);
|
|
223
|
+
if ((0, fs_1.existsSync)(hookPath)) {
|
|
224
|
+
const content = (0, fs_1.readFileSync)(hookPath, 'utf-8');
|
|
225
|
+
if (!content.includes('EKKOS_MANAGED=1')) {
|
|
226
|
+
hasCustomHooks = true;
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (hasCustomHooks) {
|
|
232
|
+
// User has custom hooks - don't overwrite, use minimal approach
|
|
233
|
+
console.log(chalk_1.default.yellow(' Detected custom hooks - preserving your hooks'));
|
|
234
|
+
console.log(chalk_1.default.gray(' Run `ekkos hooks install --global` to upgrade to managed hooks'));
|
|
235
|
+
console.log(chalk_1.default.gray(' (This will overwrite existing hooks with full-featured versions)'));
|
|
236
|
+
// Still save API key for existing hooks to use
|
|
212
237
|
}
|
|
238
|
+
else {
|
|
239
|
+
// No custom hooks OR all managed - safe to install full templates
|
|
240
|
+
try {
|
|
241
|
+
await (0, hooks_js_1.hooksInstall)({ global: true, verbose: false });
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
// Fallback: if manifest-driven install fails, generate basic hooks
|
|
245
|
+
console.log(chalk_1.default.yellow(' Note: Could not install hooks from templates'));
|
|
246
|
+
console.log(chalk_1.default.gray(' Generating basic hooks instead...'));
|
|
247
|
+
await generateBasicHooks(hooksDir, apiKey);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Save API key to config for hooks to use
|
|
251
|
+
const ekkosConfigDir = (0, path_1.join)((0, os_1.homedir)(), '.ekkos');
|
|
252
|
+
(0, fs_1.mkdirSync)(ekkosConfigDir, { recursive: true });
|
|
253
|
+
const configPath = (0, path_1.join)(ekkosConfigDir, 'config.json');
|
|
254
|
+
let existingConfig = {};
|
|
255
|
+
if ((0, fs_1.existsSync)(configPath)) {
|
|
256
|
+
try {
|
|
257
|
+
existingConfig = JSON.parse((0, fs_1.readFileSync)(configPath, 'utf-8'));
|
|
258
|
+
}
|
|
259
|
+
catch { }
|
|
260
|
+
}
|
|
261
|
+
// Update config with API key (hookApiKey for hooks, apiKey for compatibility)
|
|
262
|
+
existingConfig.hookApiKey = apiKey;
|
|
263
|
+
existingConfig.apiKey = apiKey;
|
|
264
|
+
existingConfig.updatedAt = new Date().toISOString();
|
|
265
|
+
(0, fs_1.writeFileSync)(configPath, JSON.stringify(existingConfig, null, 2));
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Generate basic inline hooks as fallback when templates aren't available
|
|
269
|
+
*/
|
|
270
|
+
async function generateBasicHooks(hooksDir, apiKey) {
|
|
213
271
|
const isWindows = (0, os_1.platform)() === 'win32';
|
|
214
272
|
if (isWindows) {
|
|
215
|
-
// Windows: PowerShell hooks
|
|
216
273
|
const promptSubmitHook = generatePromptSubmitHookPS(apiKey);
|
|
217
|
-
|
|
218
|
-
(0, fs_1.writeFileSync)(promptSubmitPath, promptSubmitHook);
|
|
274
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(hooksDir, 'user-prompt-submit.ps1'), promptSubmitHook);
|
|
219
275
|
const stopHook = generateStopHookPS(apiKey);
|
|
220
|
-
|
|
221
|
-
(0, fs_1.writeFileSync)(stopPath, stopHook);
|
|
222
|
-
// Create wrapper batch files for Windows
|
|
223
|
-
(0, fs_1.writeFileSync)((0, path_1.join)(hooksDir, 'user-prompt-submit.cmd'), `@echo off\npowershell -ExecutionPolicy Bypass -File "%~dp0user-prompt-submit.ps1"`);
|
|
224
|
-
(0, fs_1.writeFileSync)((0, path_1.join)(hooksDir, 'stop.cmd'), `@echo off\npowershell -ExecutionPolicy Bypass -File "%~dp0stop.ps1"`);
|
|
276
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(hooksDir, 'stop.ps1'), stopHook);
|
|
225
277
|
}
|
|
226
278
|
else {
|
|
227
|
-
// Unix: Bash hooks
|
|
228
279
|
const promptSubmitHook = generatePromptSubmitHook(apiKey);
|
|
229
280
|
const promptSubmitPath = (0, path_1.join)(hooksDir, 'user-prompt-submit.sh');
|
|
230
281
|
(0, fs_1.writeFileSync)(promptSubmitPath, promptSubmitHook);
|
|
@@ -234,11 +285,6 @@ async function setupClaudeCode(apiKey) {
|
|
|
234
285
|
(0, fs_1.writeFileSync)(stopPath, stopHook);
|
|
235
286
|
(0, fs_1.chmodSync)(stopPath, '755');
|
|
236
287
|
}
|
|
237
|
-
// Create state directory
|
|
238
|
-
const stateDir = (0, path_1.join)(claudeDir, 'state');
|
|
239
|
-
if (!(0, fs_1.existsSync)(stateDir)) {
|
|
240
|
-
(0, fs_1.mkdirSync)(stateDir, { recursive: true });
|
|
241
|
-
}
|
|
242
288
|
}
|
|
243
289
|
async function setupCursor(apiKey) {
|
|
244
290
|
// Cursor uses .cursorrules for system prompt
|
|
@@ -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 };
|