@ekkos/cli 1.3.5 → 1.3.7
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/commands/dashboard.js +29 -10
- package/dist/commands/gemini.js +12 -1
- package/dist/index.js +101 -26
- package/dist/lib/usage-parser.js +1 -1
- package/package.json +8 -11
- package/dist/cron/index.d.ts +0 -7
- package/dist/cron/index.js +0 -13
- package/dist/cron/promoter.d.ts +0 -70
- package/dist/cron/promoter.js +0 -403
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ekkOS
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -71,7 +71,9 @@ const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12
|
|
|
71
71
|
// ── Pricing ──
|
|
72
72
|
// Pricing per MTok from https://platform.claude.com/docs/en/about-claude/pricing
|
|
73
73
|
const MODEL_PRICING = {
|
|
74
|
+
'claude-opus-4-6-20260514': { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.50 },
|
|
74
75
|
'claude-opus-4-6': { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.50 },
|
|
76
|
+
'claude-sonnet-4-6-20260514': { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
|
|
75
77
|
'claude-opus-4-5-20250620': { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.50 },
|
|
76
78
|
'claude-sonnet-4-6': { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
|
|
77
79
|
'claude-sonnet-4-5-20250929': { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
|
|
@@ -96,12 +98,23 @@ function calculateTurnCost(model, usage) {
|
|
|
96
98
|
(usage.cache_creation_tokens / 1000000) * p.cacheWrite +
|
|
97
99
|
(usage.cache_read_tokens / 1000000) * p.cacheRead);
|
|
98
100
|
}
|
|
99
|
-
function getModelCtxSize(model) {
|
|
100
|
-
|
|
101
|
+
function getModelCtxSize(model, contextTierHint) {
|
|
102
|
+
const normalized = (model || '').toLowerCase();
|
|
103
|
+
if (contextTierHint === '1m')
|
|
104
|
+
return 1000000;
|
|
105
|
+
// Claude 4.6 has GA 1M context in Claude Code/API.
|
|
106
|
+
if (/^claude-(?:opus|sonnet)-4-6(?:$|-)/.test(normalized))
|
|
107
|
+
return 1000000;
|
|
108
|
+
// Keep Gemini on the 1M class in dashboard context math.
|
|
109
|
+
if (normalized.startsWith('gemini-'))
|
|
110
|
+
return 1000000;
|
|
111
|
+
if (normalized.includes('1m'))
|
|
112
|
+
return 1000000;
|
|
113
|
+
if (normalized.includes('opus'))
|
|
101
114
|
return 200000;
|
|
102
|
-
if (
|
|
115
|
+
if (normalized.includes('haiku'))
|
|
103
116
|
return 200000;
|
|
104
|
-
if (
|
|
117
|
+
if (normalized.includes('sonnet'))
|
|
105
118
|
return 200000;
|
|
106
119
|
return 200000; // Default Anthropic context
|
|
107
120
|
}
|
|
@@ -183,12 +196,15 @@ function parseJsonlFile(jsonlPath, sessionName) {
|
|
|
183
196
|
const evictionState = typeof entry.message._ekkos_eviction_state === 'string'
|
|
184
197
|
? entry.message._ekkos_eviction_state
|
|
185
198
|
: (parseCacheHintValue(cacheHint, 'eviction') || 'unknown');
|
|
199
|
+
const contextTierHint = typeof entry.message._ekkos_context_tier === 'string'
|
|
200
|
+
? entry.message._ekkos_context_tier.trim().toLowerCase()
|
|
201
|
+
: undefined;
|
|
186
202
|
const inputTokens = usage.input_tokens || 0;
|
|
187
203
|
const outputTokens = usage.output_tokens || 0;
|
|
188
204
|
const cacheReadTokens = usage.cache_read_input_tokens || 0;
|
|
189
205
|
const cacheCreationTokens = usage.cache_creation_input_tokens || 0;
|
|
190
206
|
const contextTokens = inputTokens + cacheReadTokens + cacheCreationTokens;
|
|
191
|
-
const modelCtxSize = getModelCtxSize(
|
|
207
|
+
const modelCtxSize = getModelCtxSize(routedModel, contextTierHint);
|
|
192
208
|
const contextPct = (contextTokens / modelCtxSize) * 100;
|
|
193
209
|
const ts = entry.timestamp || new Date().toISOString();
|
|
194
210
|
if (!startedAt)
|
|
@@ -215,6 +231,7 @@ function parseJsonlFile(jsonlPath, sessionName) {
|
|
|
215
231
|
const turnData = {
|
|
216
232
|
turn: turnNum,
|
|
217
233
|
contextPct,
|
|
234
|
+
modelContextSize: modelCtxSize,
|
|
218
235
|
input: inputTokens,
|
|
219
236
|
cacheRead: cacheReadTokens,
|
|
220
237
|
cacheCreate: cacheCreationTokens,
|
|
@@ -267,7 +284,9 @@ function parseJsonlFile(jsonlPath, sessionName) {
|
|
|
267
284
|
const currentContextTokens = lastTurn
|
|
268
285
|
? lastTurn.input + lastTurn.cacheRead + lastTurn.cacheCreate
|
|
269
286
|
: 0;
|
|
270
|
-
const modelContextSize =
|
|
287
|
+
const modelContextSize = lastTurn
|
|
288
|
+
? lastTurn.modelContextSize
|
|
289
|
+
: getModelCtxSize(model);
|
|
271
290
|
return {
|
|
272
291
|
sessionName,
|
|
273
292
|
model,
|
|
@@ -1100,8 +1119,8 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1100
1119
|
try {
|
|
1101
1120
|
const ctxPct = Math.min(data.currentContextPct, 100);
|
|
1102
1121
|
const ctxColor = ctxPct < 50 ? 'green' : ctxPct < 80 ? 'yellow' : 'red';
|
|
1103
|
-
const
|
|
1104
|
-
const
|
|
1122
|
+
const tokensLabel = fmtK(data.currentContextTokens);
|
|
1123
|
+
const maxLabel = fmtK(data.modelContextSize);
|
|
1105
1124
|
// Visual progress bar (fills available width)
|
|
1106
1125
|
const contextInnerWidth = Math.max(10, contextBox.width - 2);
|
|
1107
1126
|
// Extend bar slightly closer to mascot while keeping a small visual gap.
|
|
@@ -1121,7 +1140,7 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1121
1140
|
const hitColor = data.cacheHitRate >= 80 ? 'green' : data.cacheHitRate >= 50 ? 'yellow' : 'red';
|
|
1122
1141
|
const cappedMax = Math.min(data.maxContextPct, 100);
|
|
1123
1142
|
contextBox.setContent(` ${bar}\n` +
|
|
1124
|
-
` {${ctxColor}-fg}${ctxPct.toFixed(0)}%{/${ctxColor}-fg} ${
|
|
1143
|
+
` {${ctxColor}-fg}${ctxPct.toFixed(0)}%{/${ctxColor}-fg} ${tokensLabel}/${maxLabel}` +
|
|
1125
1144
|
` {white-fg}Input{/white-fg} $${breakdown.input.toFixed(2)}` +
|
|
1126
1145
|
` {green-fg}Read{/green-fg} $${breakdown.read.toFixed(2)}` +
|
|
1127
1146
|
` {yellow-fg}Write{/yellow-fg} $${breakdown.write.toFixed(2)}` +
|
|
@@ -1766,7 +1785,7 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1766
1785
|
const tier = cachedProfile.organization?.rate_limit_tier ?? 'unknown';
|
|
1767
1786
|
const status = cachedProfile.organization?.subscription_status ?? 'unknown';
|
|
1768
1787
|
const extraUsage = cachedProfile.organization?.has_extra_usage_enabled
|
|
1769
|
-
? ' {green-fg}extra usage on{/green-fg}'
|
|
1788
|
+
? ' {green-fg}extra usage on{/green-fg} {gray-fg}(legacy >200k only){/gray-fg}'
|
|
1770
1789
|
: '';
|
|
1771
1790
|
line2 =
|
|
1772
1791
|
` {bold}Plan:{/bold} ${plan} {bold}Tier:{/bold} ${tier} {bold}Sub:{/bold} ${status}${extraUsage}`;
|
package/dist/commands/gemini.js
CHANGED
|
@@ -154,8 +154,19 @@ async function gemini(options = {}) {
|
|
|
154
154
|
console.log(chalk_1.default.cyan(' 🧠 ekkOS_Continuum Loaded!'));
|
|
155
155
|
}
|
|
156
156
|
console.log('');
|
|
157
|
+
// Extract any trailing arguments meant for the inner CLI
|
|
158
|
+
// We look for 'gemini' in process.argv and pass everything after it.
|
|
159
|
+
// If the user did `ekkos gemini -m gemini-3.1-pro-preview`, we want to pass `-m gemini-3.1-pro-preview`
|
|
160
|
+
let extraArgs = [];
|
|
161
|
+
const geminiIdx = process.argv.indexOf('gemini');
|
|
162
|
+
if (geminiIdx !== -1) {
|
|
163
|
+
extraArgs = process.argv.slice(geminiIdx + 1).filter(a => {
|
|
164
|
+
// Filter out ekkos wrapper options
|
|
165
|
+
return !['--skip-proxy', '-v', '--verbose'].includes(a) && !(a === '-s' || a === '--session' || process.argv[process.argv.indexOf(a) - 1] === '-s' || process.argv[process.argv.indexOf(a) - 1] === '--session');
|
|
166
|
+
});
|
|
167
|
+
}
|
|
157
168
|
// Spawn Gemini CLI — stdio: inherit for full terminal passthrough
|
|
158
|
-
const child = (0, child_process_1.spawn)(geminiPath,
|
|
169
|
+
const child = (0, child_process_1.spawn)(geminiPath, extraArgs, {
|
|
159
170
|
stdio: 'inherit',
|
|
160
171
|
env,
|
|
161
172
|
cwd: process.cwd(),
|
package/dist/index.js
CHANGED
|
@@ -59,6 +59,7 @@ const scan_1 = require("./commands/scan");
|
|
|
59
59
|
const chalk_1 = __importDefault(require("chalk"));
|
|
60
60
|
const fs = __importStar(require("fs"));
|
|
61
61
|
const path = __importStar(require("path"));
|
|
62
|
+
const child_process_1 = require("child_process");
|
|
62
63
|
// Get version from package.json (CommonJS compatible)
|
|
63
64
|
const pkgPath = path.resolve(__dirname, '../package.json');
|
|
64
65
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
@@ -75,16 +76,12 @@ commander_1.program
|
|
|
75
76
|
.addHelpText('after', [
|
|
76
77
|
'',
|
|
77
78
|
chalk_1.default.cyan.bold('Examples:'),
|
|
78
|
-
` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos')} ${chalk_1.default.gray('Start Claude Code with ekkOS memory (default
|
|
79
|
-
` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos
|
|
80
|
-
` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos
|
|
81
|
-
` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos
|
|
82
|
-
` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos
|
|
83
|
-
` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos run
|
|
84
|
-
` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos doctor --fix')} ${chalk_1.default.gray('Check and auto-fix system prerequisites')}`,
|
|
85
|
-
` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos usage daily')} ${chalk_1.default.gray("View today's token usage and costs")}`,
|
|
86
|
-
` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos gemini')} ${chalk_1.default.gray('Launch Gemini CLI with ekkOS memory proxy')}`,
|
|
87
|
-
` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos swarm launch -t "build X"')} ${chalk_1.default.gray('Launch parallel workers on a task')}`,
|
|
79
|
+
` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos')} ${chalk_1.default.gray('Start Claude Code with ekkOS memory (default)')}`,
|
|
80
|
+
` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos codex')} ${chalk_1.default.gray('Start Codex (OpenAI) mode')}`,
|
|
81
|
+
` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos daemon start')} ${chalk_1.default.gray('Start the background mobile sync service')}`,
|
|
82
|
+
` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos connect gemini')} ${chalk_1.default.gray('Securely store your Gemini API key in the cloud')}`,
|
|
83
|
+
` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos agent create --path ./repo')} ${chalk_1.default.gray('Spawn a headless remote agent in a directory')}`,
|
|
84
|
+
` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos run --dashboard')} ${chalk_1.default.gray('Launch Claude with live usage dashboard')}`,
|
|
88
85
|
'',
|
|
89
86
|
chalk_1.default.gray(' Run ') + chalk_1.default.white('ekkos <command> --help') + chalk_1.default.gray(' for detailed options on any command.'),
|
|
90
87
|
'',
|
|
@@ -152,38 +149,48 @@ commander_1.program
|
|
|
152
149
|
title: 'Getting Started',
|
|
153
150
|
icon: '▸',
|
|
154
151
|
commands: [
|
|
155
|
-
{ name: 'init', desc: '
|
|
152
|
+
{ name: 'init', desc: 'First-time setup — authenticate and configure IDEs' },
|
|
156
153
|
{ name: 'scan', desc: 'Scan repo structure and seed ekkOS system registry' },
|
|
157
|
-
{ name: 'status', desc: 'Show
|
|
158
|
-
{ name: '
|
|
159
|
-
{ name: 'doctor', desc: 'Check system prerequisites (Node, PTY, Claude, MCP)' },
|
|
154
|
+
{ name: 'status', desc: 'Show overall system status (Memory, Sync Daemon, Auth)' },
|
|
155
|
+
{ name: 'doctor', desc: 'Check system prerequisites and fix runaway processes' },
|
|
160
156
|
],
|
|
161
157
|
},
|
|
162
158
|
{
|
|
163
|
-
title: '
|
|
159
|
+
title: 'Launch Agents (Local + Mobile Synced)',
|
|
164
160
|
icon: '▸',
|
|
165
161
|
commands: [
|
|
166
|
-
{ name: 'run', desc: 'Launch Claude Code with
|
|
167
|
-
{ name: '
|
|
168
|
-
{ name: '
|
|
169
|
-
{ name: '
|
|
162
|
+
{ name: 'run', desc: 'Launch Claude Code with memory and mobile control', note: 'default' },
|
|
163
|
+
{ name: 'codex', desc: 'Launch Codex (OpenAI) with mobile control' },
|
|
164
|
+
{ name: 'gemini', desc: 'Launch Gemini mode (ACP) with mobile control' },
|
|
165
|
+
{ name: 'acp', desc: 'Launch a generic ACP-compatible agent' },
|
|
170
166
|
],
|
|
171
167
|
},
|
|
172
168
|
{
|
|
173
|
-
title: '
|
|
169
|
+
title: 'Mobile Sync & Cloud (Synk)',
|
|
174
170
|
icon: '▸',
|
|
175
171
|
commands: [
|
|
176
|
-
{ name: '
|
|
177
|
-
{ name: '
|
|
178
|
-
{ name: '
|
|
179
|
-
{ name: '
|
|
172
|
+
{ name: 'daemon', desc: 'Manage background mobile sync service (start, stop, status)' },
|
|
173
|
+
{ name: 'connect', desc: 'Securely store AI vendor API keys in the cloud' },
|
|
174
|
+
{ name: 'sandbox', desc: 'Configure OS-level sandboxing for agent execution' },
|
|
175
|
+
{ name: 'notify', desc: 'Send push notifications to your Synk mobile app' },
|
|
180
176
|
],
|
|
181
177
|
},
|
|
182
178
|
{
|
|
183
|
-
title: '
|
|
179
|
+
title: 'Headless Agents (Remote Control)',
|
|
184
180
|
icon: '▸',
|
|
185
181
|
commands: [
|
|
186
|
-
{ name: '
|
|
182
|
+
{ name: 'agent', desc: 'Manage remote headless agents (create, list, stop)' },
|
|
183
|
+
{ name: 'swarm', desc: 'Parallel workers, Q-learning routing, and swarm dashboard' },
|
|
184
|
+
],
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
title: 'Monitoring & Usage',
|
|
188
|
+
icon: '▸',
|
|
189
|
+
commands: [
|
|
190
|
+
{ name: 'auth', desc: 'Manage authentication for Memory, Agents, and Mobile App' },
|
|
191
|
+
{ name: 'usage', desc: 'Token usage and cost tracking (daily, weekly, monthly, session)' },
|
|
192
|
+
{ name: 'dashboard', desc: 'Live TUI dashboard for session monitoring' },
|
|
193
|
+
{ name: 'sessions', desc: 'List active local CLI sessions' },
|
|
187
194
|
],
|
|
188
195
|
},
|
|
189
196
|
];
|
|
@@ -501,6 +508,74 @@ swarmCmd
|
|
|
501
508
|
(0, swarm_setup_1.swarmSetup)();
|
|
502
509
|
});
|
|
503
510
|
swarmCmd.addCommand(swarm_dashboard_1.swarmDashboardCommand);
|
|
511
|
+
// --- Remote & Agent Wrapper Helpers ---
|
|
512
|
+
function runRemoteCommand(command, ...args) {
|
|
513
|
+
const isAgent = command === 'agent';
|
|
514
|
+
const pkgName = isAgent ? 'ekkos-agent' : 'ekkos-remote';
|
|
515
|
+
const binName = isAgent ? 'ekkos-agent.mjs' : 'ekkos-synk.mjs';
|
|
516
|
+
// Try resolving local monorepo first
|
|
517
|
+
let binPath = path.resolve(__dirname, '../../' + pkgName + '/bin/' + binName);
|
|
518
|
+
// If we are installed globally (dist folder structure), resolve relatively
|
|
519
|
+
if (!fs.existsSync(binPath)) {
|
|
520
|
+
try {
|
|
521
|
+
const globalPath = require.resolve('@ekkos/' + (isAgent ? 'agent' : 'remote') + '/package.json');
|
|
522
|
+
binPath = path.join(path.dirname(globalPath), 'bin', binName);
|
|
523
|
+
}
|
|
524
|
+
catch (e) {
|
|
525
|
+
// Fallback relative to current dist
|
|
526
|
+
binPath = path.resolve(__dirname, '../../' + pkgName + '/bin/' + binName);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
const p = (0, child_process_1.spawn)('node', [binPath, ...args], {
|
|
530
|
+
stdio: 'inherit',
|
|
531
|
+
env: process.env
|
|
532
|
+
});
|
|
533
|
+
p.on('exit', (code) => {
|
|
534
|
+
process.exit(code || 0);
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
// Mobile Sync & Cloud (Synk) Commands
|
|
538
|
+
commander_1.program
|
|
539
|
+
.command('auth')
|
|
540
|
+
.description('Manage authentication for Memory, Agents, and Mobile App')
|
|
541
|
+
.allowUnknownOption()
|
|
542
|
+
.action(() => runRemoteCommand('synk', 'auth', ...process.argv.slice(3)));
|
|
543
|
+
commander_1.program
|
|
544
|
+
.command('codex')
|
|
545
|
+
.description('Launch Codex (OpenAI) with mobile control')
|
|
546
|
+
.allowUnknownOption()
|
|
547
|
+
.action(() => runRemoteCommand('synk', 'codex', ...process.argv.slice(3)));
|
|
548
|
+
commander_1.program
|
|
549
|
+
.command('acp')
|
|
550
|
+
.description('Launch a generic ACP-compatible agent')
|
|
551
|
+
.allowUnknownOption()
|
|
552
|
+
.action(() => runRemoteCommand('synk', 'acp', ...process.argv.slice(3)));
|
|
553
|
+
commander_1.program
|
|
554
|
+
.command('daemon')
|
|
555
|
+
.description('Manage background mobile sync service (start, stop, status)')
|
|
556
|
+
.allowUnknownOption()
|
|
557
|
+
.action(() => runRemoteCommand('synk', 'daemon', ...process.argv.slice(3)));
|
|
558
|
+
commander_1.program
|
|
559
|
+
.command('connect')
|
|
560
|
+
.description('Securely store AI vendor API keys in the cloud')
|
|
561
|
+
.allowUnknownOption()
|
|
562
|
+
.action(() => runRemoteCommand('synk', 'connect', ...process.argv.slice(3)));
|
|
563
|
+
commander_1.program
|
|
564
|
+
.command('sandbox')
|
|
565
|
+
.description('Configure OS-level sandboxing for agent execution')
|
|
566
|
+
.allowUnknownOption()
|
|
567
|
+
.action(() => runRemoteCommand('synk', 'sandbox', ...process.argv.slice(3)));
|
|
568
|
+
commander_1.program
|
|
569
|
+
.command('notify')
|
|
570
|
+
.description('Send push notifications to your Synk mobile app')
|
|
571
|
+
.allowUnknownOption()
|
|
572
|
+
.action(() => runRemoteCommand('synk', 'notify', ...process.argv.slice(3)));
|
|
573
|
+
// Headless Agents (Remote Control)
|
|
574
|
+
commander_1.program
|
|
575
|
+
.command('agent')
|
|
576
|
+
.description('Manage remote headless agents (create, list, stop)')
|
|
577
|
+
.allowUnknownOption()
|
|
578
|
+
.action(() => runRemoteCommand('agent', ...process.argv.slice(3)));
|
|
504
579
|
// Handle `-help` (single dash) — rewrite to `--help` for Commander compatibility
|
|
505
580
|
const helpIdx = process.argv.indexOf('-help');
|
|
506
581
|
if (helpIdx !== -1) {
|
package/dist/lib/usage-parser.js
CHANGED
|
@@ -485,7 +485,7 @@ function calculateContextPercentage(usage) {
|
|
|
485
485
|
*/
|
|
486
486
|
async function fetchPatternMetrics(sessionId) {
|
|
487
487
|
try {
|
|
488
|
-
const response = await fetch(
|
|
488
|
+
const response = await fetch(`${process.env.EKKOS_MCP_URL || 'https://mcp.ekkos.dev'}/api/v1/session/stats?session_id=${sessionId}`, {
|
|
489
489
|
method: 'GET',
|
|
490
490
|
headers: {
|
|
491
491
|
'Content-Type': 'application/json',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ekkos/cli",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.7",
|
|
4
4
|
"description": "Setup ekkOS memory for AI coding assistants (Claude Code, Cursor, Windsurf)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,13 +8,6 @@
|
|
|
8
8
|
"cli": "dist/index.js",
|
|
9
9
|
"ekkos-capture": "dist/cache/capture.js"
|
|
10
10
|
},
|
|
11
|
-
"scripts": {
|
|
12
|
-
"build": "tsc",
|
|
13
|
-
"dev": "tsx src/index.ts",
|
|
14
|
-
"prepack": "node scripts/build-templates.js prepack",
|
|
15
|
-
"postpack": "node scripts/build-templates.js postpack",
|
|
16
|
-
"prepublishOnly": "npm run build"
|
|
17
|
-
},
|
|
18
11
|
"keywords": [
|
|
19
12
|
"ekkos",
|
|
20
13
|
"ai",
|
|
@@ -28,7 +21,6 @@
|
|
|
28
21
|
"author": "ekkOS",
|
|
29
22
|
"license": "MIT",
|
|
30
23
|
"dependencies": {
|
|
31
|
-
"@ekkos/prometheus": "workspace:*",
|
|
32
24
|
"@supabase/supabase-js": "^2.39.8",
|
|
33
25
|
"axios": "^1.7.0",
|
|
34
26
|
"blessed": "^0.1.81",
|
|
@@ -56,7 +48,12 @@
|
|
|
56
48
|
},
|
|
57
49
|
"files": [
|
|
58
50
|
"dist",
|
|
51
|
+
"!dist/cron",
|
|
59
52
|
"templates/CLAUDE.md",
|
|
60
53
|
"templates/skills"
|
|
61
|
-
]
|
|
62
|
-
|
|
54
|
+
],
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsc",
|
|
57
|
+
"dev": "tsx src/index.ts"
|
|
58
|
+
}
|
|
59
|
+
}
|
package/dist/cron/index.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
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';
|
package/dist/cron/index.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
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; } });
|
package/dist/cron/promoter.d.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
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 };
|
package/dist/cron/promoter.js
DELETED
|
@@ -1,403 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
/**
|
|
4
|
-
* ekkOS PROMETHEUS Pattern Promoter
|
|
5
|
-
* ==================================
|
|
6
|
-
*
|
|
7
|
-
* Daily cron job that evaluates patterns for promotion to constitutional memory.
|
|
8
|
-
*
|
|
9
|
-
* Constitutional patterns are injected into the system prompt via @ekkos/patch,
|
|
10
|
-
* making them "instincts" that don't require retrieval.
|
|
11
|
-
*
|
|
12
|
-
* Promotion Criteria (default):
|
|
13
|
-
* - Success rate ≥ 85%
|
|
14
|
-
* - Applications ≥ 5
|
|
15
|
-
* - Skip rate ≤ 10%
|
|
16
|
-
* - Confidence ≥ 70%
|
|
17
|
-
* - Used within last 30 days
|
|
18
|
-
* - Used in ≥ 2 unique sessions
|
|
19
|
-
*
|
|
20
|
-
* Usage:
|
|
21
|
-
* npx ekkos-promote # Run promotion evaluation
|
|
22
|
-
* npx ekkos-promote --dry-run # Preview without applying changes
|
|
23
|
-
* npx ekkos-promote --user <uuid> # Promote for specific user
|
|
24
|
-
*
|
|
25
|
-
* Schedule via launchd (macOS) or cron:
|
|
26
|
-
* 0 6 * * * /path/to/node /path/to/ekkos-promote
|
|
27
|
-
*/
|
|
28
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
29
|
-
if (k2 === undefined) k2 = k;
|
|
30
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
31
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
32
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
33
|
-
}
|
|
34
|
-
Object.defineProperty(o, k2, desc);
|
|
35
|
-
}) : (function(o, m, k, k2) {
|
|
36
|
-
if (k2 === undefined) k2 = k;
|
|
37
|
-
o[k2] = m[k];
|
|
38
|
-
}));
|
|
39
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
40
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
41
|
-
}) : function(o, v) {
|
|
42
|
-
o["default"] = v;
|
|
43
|
-
});
|
|
44
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
45
|
-
var ownKeys = function(o) {
|
|
46
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
47
|
-
var ar = [];
|
|
48
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
49
|
-
return ar;
|
|
50
|
-
};
|
|
51
|
-
return ownKeys(o);
|
|
52
|
-
};
|
|
53
|
-
return function (mod) {
|
|
54
|
-
if (mod && mod.__esModule) return mod;
|
|
55
|
-
var result = {};
|
|
56
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
57
|
-
__setModuleDefault(result, mod);
|
|
58
|
-
return result;
|
|
59
|
-
};
|
|
60
|
-
})();
|
|
61
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
62
|
-
exports.evaluatePromotions = evaluatePromotions;
|
|
63
|
-
exports.queryPatternStats = queryPatternStats;
|
|
64
|
-
exports.writePatchConfig = writePatchConfig;
|
|
65
|
-
const supabase_js_1 = require("@supabase/supabase-js");
|
|
66
|
-
const prometheus_1 = require("@ekkos/prometheus");
|
|
67
|
-
const fs = __importStar(require("fs"));
|
|
68
|
-
const path = __importStar(require("path"));
|
|
69
|
-
const os = __importStar(require("os"));
|
|
70
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
71
|
-
// Pattern Stats Query
|
|
72
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
73
|
-
async function queryPatternStats(supabase, userId) {
|
|
74
|
-
// Query patterns with their application stats
|
|
75
|
-
let query = supabase
|
|
76
|
-
.from('patterns')
|
|
77
|
-
.select(`
|
|
78
|
-
pattern_id,
|
|
79
|
-
title,
|
|
80
|
-
content,
|
|
81
|
-
success_rate,
|
|
82
|
-
applied_count,
|
|
83
|
-
confidence_score,
|
|
84
|
-
created_at,
|
|
85
|
-
updated_at,
|
|
86
|
-
tags,
|
|
87
|
-
user_id
|
|
88
|
-
`)
|
|
89
|
-
.eq('quarantined', false)
|
|
90
|
-
.gte('applied_count', 1); // Only consider applied patterns
|
|
91
|
-
if (userId) {
|
|
92
|
-
query = query.eq('user_id', userId);
|
|
93
|
-
}
|
|
94
|
-
const { data: patterns, error } = await query;
|
|
95
|
-
if (error) {
|
|
96
|
-
throw new Error(`Failed to query patterns: ${error.message}`);
|
|
97
|
-
}
|
|
98
|
-
if (!patterns || patterns.length === 0) {
|
|
99
|
-
return [];
|
|
100
|
-
}
|
|
101
|
-
// Get application details for skip rate and session count
|
|
102
|
-
const patternIds = patterns.map(p => p.pattern_id);
|
|
103
|
-
const { data: applications } = await supabase
|
|
104
|
-
.from('pattern_applications')
|
|
105
|
-
.select('pattern_id, outcome_success, created_at, user_id')
|
|
106
|
-
.in('pattern_id', patternIds);
|
|
107
|
-
const { data: skips } = await supabase
|
|
108
|
-
.from('pattern_skips')
|
|
109
|
-
.select('pattern_id')
|
|
110
|
-
.in('pattern_id', patternIds);
|
|
111
|
-
// Aggregate stats per pattern
|
|
112
|
-
const appsByPattern = new Map();
|
|
113
|
-
const skipsByPattern = new Map();
|
|
114
|
-
for (const app of applications || []) {
|
|
115
|
-
if (!appsByPattern.has(app.pattern_id)) {
|
|
116
|
-
appsByPattern.set(app.pattern_id, []);
|
|
117
|
-
}
|
|
118
|
-
appsByPattern.get(app.pattern_id).push(app);
|
|
119
|
-
}
|
|
120
|
-
for (const skip of skips || []) {
|
|
121
|
-
const count = skipsByPattern.get(skip.pattern_id) || 0;
|
|
122
|
-
skipsByPattern.set(skip.pattern_id, count + 1);
|
|
123
|
-
}
|
|
124
|
-
// Build PatternStats for each pattern
|
|
125
|
-
return patterns.map(p => {
|
|
126
|
-
const apps = appsByPattern.get(p.pattern_id) || [];
|
|
127
|
-
const skipCount = skipsByPattern.get(p.pattern_id) || 0;
|
|
128
|
-
const successCount = apps.filter(a => a.outcome_success === true).length;
|
|
129
|
-
const failureCount = apps.filter(a => a.outcome_success === false).length;
|
|
130
|
-
const totalApplications = successCount + failureCount;
|
|
131
|
-
// Count unique sessions (using created_at date as proxy)
|
|
132
|
-
const uniqueDates = new Set(apps.map(a => new Date(a.created_at).toISOString().split('T')[0]));
|
|
133
|
-
const stats = {
|
|
134
|
-
patternId: p.pattern_id,
|
|
135
|
-
title: p.title || 'Untitled',
|
|
136
|
-
successCount,
|
|
137
|
-
failureCount,
|
|
138
|
-
skipCount,
|
|
139
|
-
totalApplications: totalApplications || p.applied_count || 0,
|
|
140
|
-
uniqueSessions: uniqueDates.size,
|
|
141
|
-
firstApplied: new Date(p.created_at),
|
|
142
|
-
lastApplied: new Date(p.updated_at || p.created_at),
|
|
143
|
-
currentConfidence: p.confidence_score || p.success_rate || 0.5,
|
|
144
|
-
tags: p.tags,
|
|
145
|
-
};
|
|
146
|
-
return stats;
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
150
|
-
// Promotion Logic
|
|
151
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
152
|
-
async function evaluatePromotions(supabase, config) {
|
|
153
|
-
const result = {
|
|
154
|
-
evaluated: 0,
|
|
155
|
-
promoted: 0,
|
|
156
|
-
demoted: 0,
|
|
157
|
-
patterns: [],
|
|
158
|
-
};
|
|
159
|
-
// Get pattern stats
|
|
160
|
-
const patternStats = await queryPatternStats(supabase, config.userId);
|
|
161
|
-
result.evaluated = patternStats.length;
|
|
162
|
-
if (config.verbose) {
|
|
163
|
-
console.log(`[PROMOTER] Evaluating ${patternStats.length} patterns...`);
|
|
164
|
-
}
|
|
165
|
-
// Get currently promoted patterns (from promoted_patterns table or metadata)
|
|
166
|
-
const { data: currentlyPromoted } = await supabase
|
|
167
|
-
.from('patterns')
|
|
168
|
-
.select('pattern_id')
|
|
169
|
-
.eq('evolution_stage', 'constitutional');
|
|
170
|
-
const promotedSet = new Set((currentlyPromoted || []).map(p => p.pattern_id));
|
|
171
|
-
const promotedPatterns = [];
|
|
172
|
-
// Evaluate each pattern
|
|
173
|
-
for (const stats of patternStats) {
|
|
174
|
-
const evaluation = prometheus_1.promotionEvaluator.evaluate(stats);
|
|
175
|
-
const wasPromoted = promotedSet.has(stats.patternId);
|
|
176
|
-
if (evaluation.eligible && !wasPromoted) {
|
|
177
|
-
// PROMOTE: Pattern meets criteria and wasn't promoted before
|
|
178
|
-
result.promoted++;
|
|
179
|
-
result.patterns.push({
|
|
180
|
-
patternId: stats.patternId,
|
|
181
|
-
title: stats.title,
|
|
182
|
-
action: 'promoted',
|
|
183
|
-
score: evaluation.score,
|
|
184
|
-
});
|
|
185
|
-
if (!config.dryRun) {
|
|
186
|
-
// Update pattern to constitutional
|
|
187
|
-
await supabase
|
|
188
|
-
.from('patterns')
|
|
189
|
-
.update({
|
|
190
|
-
evolution_stage: 'constitutional',
|
|
191
|
-
promoted_at: new Date().toISOString(),
|
|
192
|
-
})
|
|
193
|
-
.eq('pattern_id', stats.patternId);
|
|
194
|
-
// Emit PATTERN_PROMOTED event
|
|
195
|
-
prometheus_1.learningEvents.emitPatternPromoted({
|
|
196
|
-
id: stats.patternId,
|
|
197
|
-
title: stats.title,
|
|
198
|
-
confidenceBefore: stats.currentConfidence,
|
|
199
|
-
confidenceAfter: 1.0,
|
|
200
|
-
tier: 'constitutional',
|
|
201
|
-
successRate: stats.successCount / Math.max(stats.totalApplications, 1),
|
|
202
|
-
appliedCount: stats.totalApplications,
|
|
203
|
-
}, {
|
|
204
|
-
daysInEpisodic: Math.floor((Date.now() - stats.firstApplied.getTime()) / (1000 * 60 * 60 * 24)),
|
|
205
|
-
totalApplications: stats.totalApplications,
|
|
206
|
-
finalSuccessRate: stats.successCount / Math.max(stats.totalApplications, 1),
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
// Fetch full pattern for patch config
|
|
210
|
-
const { data: fullPattern } = await supabase
|
|
211
|
-
.from('patterns')
|
|
212
|
-
.select('content, tags')
|
|
213
|
-
.eq('pattern_id', stats.patternId)
|
|
214
|
-
.single();
|
|
215
|
-
if (fullPattern) {
|
|
216
|
-
// Parse problem/solution from content
|
|
217
|
-
const content = fullPattern.content || '';
|
|
218
|
-
const problemMatch = content.match(/## Problem\n([\s\S]*?)(?=\n## Solution|$)/);
|
|
219
|
-
const solutionMatch = content.match(/## Solution\n([\s\S]*?)$/);
|
|
220
|
-
promotedPatterns.push({
|
|
221
|
-
id: stats.patternId,
|
|
222
|
-
title: stats.title,
|
|
223
|
-
problem: problemMatch?.[1]?.trim() || 'N/A',
|
|
224
|
-
solution: solutionMatch?.[1]?.trim() || content,
|
|
225
|
-
promotedAt: new Date().toISOString(),
|
|
226
|
-
successRate: stats.successCount / Math.max(stats.totalApplications, 1),
|
|
227
|
-
appliedCount: stats.totalApplications,
|
|
228
|
-
tags: fullPattern.tags,
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
if (config.verbose) {
|
|
232
|
-
console.log(` ✓ PROMOTE: "${stats.title}" (score: ${evaluation.score.toFixed(2)})`);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
else if (!evaluation.eligible && wasPromoted) {
|
|
236
|
-
// DEMOTE: Pattern no longer meets criteria
|
|
237
|
-
result.demoted++;
|
|
238
|
-
// Find the main blocker for demotion reason
|
|
239
|
-
const reason = evaluation.blockers[0]?.includes('success')
|
|
240
|
-
? 'low_success_rate'
|
|
241
|
-
: evaluation.blockers[0]?.includes('skip')
|
|
242
|
-
? 'high_skip_rate'
|
|
243
|
-
: 'outdated';
|
|
244
|
-
result.patterns.push({
|
|
245
|
-
patternId: stats.patternId,
|
|
246
|
-
title: stats.title,
|
|
247
|
-
action: 'demoted',
|
|
248
|
-
score: evaluation.score,
|
|
249
|
-
reason,
|
|
250
|
-
});
|
|
251
|
-
if (!config.dryRun) {
|
|
252
|
-
// Update pattern back to episodic
|
|
253
|
-
await supabase
|
|
254
|
-
.from('patterns')
|
|
255
|
-
.update({
|
|
256
|
-
evolution_stage: 'episodic',
|
|
257
|
-
demoted_at: new Date().toISOString(),
|
|
258
|
-
})
|
|
259
|
-
.eq('pattern_id', stats.patternId);
|
|
260
|
-
// Emit PATTERN_DEMOTED event
|
|
261
|
-
prometheus_1.learningEvents.emitPatternDemoted({
|
|
262
|
-
id: stats.patternId,
|
|
263
|
-
title: stats.title,
|
|
264
|
-
confidenceBefore: 1.0,
|
|
265
|
-
confidenceAfter: stats.currentConfidence,
|
|
266
|
-
tier: 'episodic',
|
|
267
|
-
successRate: stats.successCount / Math.max(stats.totalApplications, 1),
|
|
268
|
-
appliedCount: stats.totalApplications,
|
|
269
|
-
}, reason);
|
|
270
|
-
}
|
|
271
|
-
if (config.verbose) {
|
|
272
|
-
console.log(` ✗ DEMOTE: "${stats.title}" (${reason})`);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
else {
|
|
276
|
-
result.patterns.push({
|
|
277
|
-
patternId: stats.patternId,
|
|
278
|
-
title: stats.title,
|
|
279
|
-
action: 'unchanged',
|
|
280
|
-
score: evaluation.score,
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
// Also include patterns that are already promoted and still eligible
|
|
285
|
-
for (const stats of patternStats) {
|
|
286
|
-
const evaluation = prometheus_1.promotionEvaluator.evaluate(stats);
|
|
287
|
-
if (evaluation.eligible && promotedSet.has(stats.patternId)) {
|
|
288
|
-
// Fetch and include in patch config
|
|
289
|
-
const { data: fullPattern } = await supabase
|
|
290
|
-
.from('patterns')
|
|
291
|
-
.select('content, tags')
|
|
292
|
-
.eq('pattern_id', stats.patternId)
|
|
293
|
-
.single();
|
|
294
|
-
if (fullPattern) {
|
|
295
|
-
const content = fullPattern.content || '';
|
|
296
|
-
const problemMatch = content.match(/## Problem\n([\s\S]*?)(?=\n## Solution|$)/);
|
|
297
|
-
const solutionMatch = content.match(/## Solution\n([\s\S]*?)$/);
|
|
298
|
-
promotedPatterns.push({
|
|
299
|
-
id: stats.patternId,
|
|
300
|
-
title: stats.title,
|
|
301
|
-
problem: problemMatch?.[1]?.trim() || 'N/A',
|
|
302
|
-
solution: solutionMatch?.[1]?.trim() || content,
|
|
303
|
-
promotedAt: new Date().toISOString(),
|
|
304
|
-
successRate: stats.successCount / Math.max(stats.totalApplications, 1),
|
|
305
|
-
appliedCount: stats.totalApplications,
|
|
306
|
-
tags: fullPattern.tags,
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
// Generate patch config
|
|
312
|
-
result.patchConfig = {
|
|
313
|
-
version: '1.0.0',
|
|
314
|
-
generatedAt: new Date().toISOString(),
|
|
315
|
-
promotedPatterns,
|
|
316
|
-
totalPatterns: promotedPatterns.length,
|
|
317
|
-
};
|
|
318
|
-
return result;
|
|
319
|
-
}
|
|
320
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
321
|
-
// Patch Config Generation
|
|
322
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
323
|
-
function writePatchConfig(config, outputPath) {
|
|
324
|
-
const dir = path.dirname(outputPath);
|
|
325
|
-
if (!fs.existsSync(dir)) {
|
|
326
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
327
|
-
}
|
|
328
|
-
fs.writeFileSync(outputPath, JSON.stringify(config, null, 2));
|
|
329
|
-
console.log(`[PROMOTER] Wrote patch config to: ${outputPath}`);
|
|
330
|
-
}
|
|
331
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
332
|
-
// CLI Entry Point
|
|
333
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
334
|
-
async function main() {
|
|
335
|
-
const args = process.argv.slice(2);
|
|
336
|
-
const config = {
|
|
337
|
-
supabaseUrl: process.env.SUPABASE_URL || process.env.NEXT_PUBLIC_SUPABASE_URL || '',
|
|
338
|
-
supabaseKey: process.env.SUPABASE_SECRET_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY || '',
|
|
339
|
-
dryRun: args.includes('--dry-run'),
|
|
340
|
-
userId: args.includes('--user') ? args[args.indexOf('--user') + 1] : undefined,
|
|
341
|
-
patchConfigPath: path.join(os.homedir(), '.ekkos', 'promoted-patterns.json'),
|
|
342
|
-
verbose: args.includes('--verbose') || args.includes('-v'),
|
|
343
|
-
};
|
|
344
|
-
// Allow custom output path
|
|
345
|
-
if (args.includes('--output')) {
|
|
346
|
-
config.patchConfigPath = args[args.indexOf('--output') + 1];
|
|
347
|
-
}
|
|
348
|
-
console.log('═══════════════════════════════════════════════════════════════');
|
|
349
|
-
console.log(' ekkOS PROMETHEUS Pattern Promoter');
|
|
350
|
-
console.log('═══════════════════════════════════════════════════════════════');
|
|
351
|
-
console.log(` Mode: ${config.dryRun ? 'DRY RUN (no changes)' : 'LIVE'}`);
|
|
352
|
-
console.log(` User: ${config.userId || 'All users'}`);
|
|
353
|
-
console.log(` Output: ${config.patchConfigPath}`);
|
|
354
|
-
console.log('═══════════════════════════════════════════════════════════════');
|
|
355
|
-
console.log('');
|
|
356
|
-
if (!config.supabaseUrl || !config.supabaseKey) {
|
|
357
|
-
console.error('[ERROR] Missing SUPABASE_URL or SUPABASE_SECRET_KEY environment variables');
|
|
358
|
-
process.exit(1);
|
|
359
|
-
}
|
|
360
|
-
const supabase = (0, supabase_js_1.createClient)(config.supabaseUrl, config.supabaseKey);
|
|
361
|
-
try {
|
|
362
|
-
const result = await evaluatePromotions(supabase, config);
|
|
363
|
-
console.log('');
|
|
364
|
-
console.log('═══════════════════════════════════════════════════════════════');
|
|
365
|
-
console.log(' Results');
|
|
366
|
-
console.log('═══════════════════════════════════════════════════════════════');
|
|
367
|
-
console.log(` Patterns evaluated: ${result.evaluated}`);
|
|
368
|
-
console.log(` Promoted: ${result.promoted}`);
|
|
369
|
-
console.log(` Demoted: ${result.demoted}`);
|
|
370
|
-
console.log(` Constitutional total: ${result.patchConfig?.totalPatterns || 0}`);
|
|
371
|
-
console.log('═══════════════════════════════════════════════════════════════');
|
|
372
|
-
// Write patch config
|
|
373
|
-
if (!config.dryRun && result.patchConfig) {
|
|
374
|
-
writePatchConfig(result.patchConfig, config.patchConfigPath);
|
|
375
|
-
}
|
|
376
|
-
else if (config.dryRun && result.patchConfig) {
|
|
377
|
-
console.log('\n[DRY RUN] Would write patch config with:');
|
|
378
|
-
console.log(` - ${result.patchConfig.totalPatterns} promoted patterns`);
|
|
379
|
-
}
|
|
380
|
-
// Summary of changes
|
|
381
|
-
if (result.patterns.filter(p => p.action !== 'unchanged').length > 0) {
|
|
382
|
-
console.log('\nChanges:');
|
|
383
|
-
for (const p of result.patterns) {
|
|
384
|
-
if (p.action === 'promoted') {
|
|
385
|
-
console.log(` 🎓 PROMOTED: "${p.title}" (score: ${p.score.toFixed(2)})`);
|
|
386
|
-
}
|
|
387
|
-
else if (p.action === 'demoted') {
|
|
388
|
-
console.log(` ⬇️ DEMOTED: "${p.title}" (${p.reason})`);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
else {
|
|
393
|
-
console.log('\nNo promotion changes needed.');
|
|
394
|
-
}
|
|
395
|
-
console.log('\n✓ Promotion evaluation complete');
|
|
396
|
-
}
|
|
397
|
-
catch (error) {
|
|
398
|
-
console.error('[ERROR]', error);
|
|
399
|
-
process.exit(1);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
// Run if executed directly
|
|
403
|
-
main().catch(console.error);
|