@ekkos/cli 1.2.18 → 1.3.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.
- package/dist/cache/capture.js +0 -0
- package/dist/commands/dashboard.js +57 -49
- package/dist/commands/hooks.d.ts +25 -36
- package/dist/commands/hooks.js +43 -615
- package/dist/commands/init.js +7 -23
- package/dist/commands/run.js +90 -3
- package/dist/commands/setup.js +10 -352
- package/dist/deploy/hooks.d.ts +8 -5
- package/dist/deploy/hooks.js +12 -105
- package/dist/deploy/settings.d.ts +8 -2
- package/dist/deploy/settings.js +22 -51
- package/dist/index.js +17 -39
- package/dist/utils/state.js +7 -2
- package/package.json +1 -1
- package/templates/CLAUDE.md +82 -292
- package/templates/cursor-rules/ekkos-memory.md +48 -108
- package/templates/windsurf-rules/ekkos-memory.md +62 -64
- package/templates/cursor-hooks/after-agent-response.sh +0 -117
- package/templates/cursor-hooks/before-submit-prompt.sh +0 -419
- package/templates/cursor-hooks/hooks.json +0 -20
- package/templates/cursor-hooks/lib/contract.sh +0 -320
- package/templates/cursor-hooks/stop.sh +0 -75
- package/templates/hooks/assistant-response.ps1 +0 -256
- package/templates/hooks/assistant-response.sh +0 -160
- package/templates/hooks/hooks.json +0 -40
- package/templates/hooks/lib/contract.sh +0 -332
- package/templates/hooks/lib/count-tokens.cjs +0 -86
- package/templates/hooks/lib/ekkos-reminders.sh +0 -98
- package/templates/hooks/lib/state.sh +0 -210
- package/templates/hooks/session-start.ps1 +0 -146
- package/templates/hooks/session-start.sh +0 -353
- package/templates/hooks/stop.ps1 +0 -349
- package/templates/hooks/stop.sh +0 -382
- package/templates/hooks/user-prompt-submit.ps1 +0 -419
- package/templates/hooks/user-prompt-submit.sh +0 -516
- package/templates/project-stubs/session-start.ps1 +0 -63
- package/templates/project-stubs/session-start.sh +0 -55
- package/templates/project-stubs/stop.ps1 +0 -63
- package/templates/project-stubs/stop.sh +0 -55
- package/templates/project-stubs/user-prompt-submit.ps1 +0 -63
- package/templates/project-stubs/user-prompt-submit.sh +0 -55
- package/templates/windsurf-hooks/README.md +0 -212
- package/templates/windsurf-hooks/hooks.json +0 -17
- package/templates/windsurf-hooks/install.sh +0 -148
- package/templates/windsurf-hooks/lib/contract.sh +0 -322
- package/templates/windsurf-hooks/post-cascade-response.sh +0 -251
- package/templates/windsurf-hooks/pre-user-prompt.sh +0 -435
package/dist/commands/init.js
CHANGED
|
@@ -11,8 +11,9 @@ const ora_1 = __importDefault(require("ora"));
|
|
|
11
11
|
const open_1 = __importDefault(require("open"));
|
|
12
12
|
const platform_1 = require("../utils/platform");
|
|
13
13
|
const mcp_1 = require("../deploy/mcp");
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// DEPRECATED: Hooks removed in hookless architecture migration
|
|
15
|
+
// import { deployClaudeSettings } from '../deploy/settings';
|
|
16
|
+
// import { deployHooks } from '../deploy/hooks';
|
|
16
17
|
const skills_1 = require("../deploy/skills");
|
|
17
18
|
const agents_1 = require("../deploy/agents");
|
|
18
19
|
const plugins_1 = require("../deploy/plugins");
|
|
@@ -251,27 +252,10 @@ async function deployForClaude(apiKey, userId, options) {
|
|
|
251
252
|
catch (error) {
|
|
252
253
|
spinner.fail('MCP server configuration failed');
|
|
253
254
|
}
|
|
254
|
-
//
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
result.settings = true;
|
|
259
|
-
spinner.succeed('Hooks configuration');
|
|
260
|
-
}
|
|
261
|
-
catch (error) {
|
|
262
|
-
spinner.fail('Hooks configuration failed');
|
|
263
|
-
}
|
|
264
|
-
// Hook scripts
|
|
265
|
-
if (!options.skipHooks) {
|
|
266
|
-
spinner = (0, ora_1.default)('Deploying hook scripts...').start();
|
|
267
|
-
try {
|
|
268
|
-
result.hooks = (0, hooks_1.deployHooks)(apiKey);
|
|
269
|
-
spinner.succeed(`Hook scripts (${result.hooks.count} files)`);
|
|
270
|
-
}
|
|
271
|
-
catch (error) {
|
|
272
|
-
spinner.fail('Hook scripts failed');
|
|
273
|
-
}
|
|
274
|
-
}
|
|
255
|
+
// DEPRECATED: Hooks removed in hookless architecture migration
|
|
256
|
+
// Settings.json hook registration and hook script deployment are no longer needed.
|
|
257
|
+
// Use `ekkos run` instead.
|
|
258
|
+
result.settings = true; // Mark as satisfied so downstream logic is unaffected
|
|
275
259
|
// Skills
|
|
276
260
|
if (!options.skipSkills) {
|
|
277
261
|
spinner = (0, ora_1.default)('Deploying skills...').start();
|
package/dist/commands/run.js
CHANGED
|
@@ -498,7 +498,8 @@ function getEkkosEnv() {
|
|
|
498
498
|
// Project path is base64-encoded to handle special chars safely
|
|
499
499
|
const projectPath = process.cwd();
|
|
500
500
|
const projectPathEncoded = Buffer.from(projectPath).toString('base64url');
|
|
501
|
-
const
|
|
501
|
+
const userTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
502
|
+
const proxyUrl = `${EKKOS_PROXY_URL}/proxy/${encodeURIComponent(userId)}/${encodeURIComponent(cliSessionName)}?project=${projectPathEncoded}&sid=${encodeURIComponent(cliSessionId)}&tz=${encodeURIComponent(userTz)}`;
|
|
502
503
|
env.ANTHROPIC_BASE_URL = proxyUrl;
|
|
503
504
|
// Proxy URL contains userId + project path — don't leak to terminal
|
|
504
505
|
}
|
|
@@ -797,6 +798,38 @@ function cleanupInstanceFile(instanceId) {
|
|
|
797
798
|
// Ignore cleanup errors
|
|
798
799
|
}
|
|
799
800
|
}
|
|
801
|
+
/**
|
|
802
|
+
* Write session state files that hooks used to write.
|
|
803
|
+
* Keeps ~/.ekkos/current-session.json, ~/.claude/state/current-session.json,
|
|
804
|
+
* and ~/.ekkos/session-hint.json in sync whenever the session is known.
|
|
805
|
+
*/
|
|
806
|
+
function writeSessionFiles(sessionId, sessionName) {
|
|
807
|
+
try {
|
|
808
|
+
const ekkosDir = path.join(os.homedir(), '.ekkos');
|
|
809
|
+
const claudeStateDir = path.join(os.homedir(), '.claude', 'state');
|
|
810
|
+
// Ensure directories exist
|
|
811
|
+
if (!fs.existsSync(ekkosDir))
|
|
812
|
+
fs.mkdirSync(ekkosDir, { recursive: true });
|
|
813
|
+
if (!fs.existsSync(claudeStateDir))
|
|
814
|
+
fs.mkdirSync(claudeStateDir, { recursive: true });
|
|
815
|
+
const now = new Date().toISOString();
|
|
816
|
+
// 1. ~/.ekkos/current-session.json
|
|
817
|
+
fs.writeFileSync(path.join(ekkosDir, 'current-session.json'), JSON.stringify({ session_id: sessionId, session_name: sessionName, timestamp: now }, null, 2));
|
|
818
|
+
// 2. ~/.claude/state/current-session.json
|
|
819
|
+
fs.writeFileSync(path.join(claudeStateDir, 'current-session.json'), JSON.stringify({ session_id: sessionId, session_name: sessionName, timestamp: now }, null, 2));
|
|
820
|
+
// 3. ~/.ekkos/session-hint.json (dashboard discovery)
|
|
821
|
+
fs.writeFileSync(path.join(ekkosDir, 'session-hint.json'), JSON.stringify({
|
|
822
|
+
session_name: sessionName,
|
|
823
|
+
session_id: sessionId,
|
|
824
|
+
project_path: process.cwd(),
|
|
825
|
+
timestamp: now,
|
|
826
|
+
pid: process.pid
|
|
827
|
+
}, null, 2));
|
|
828
|
+
}
|
|
829
|
+
catch {
|
|
830
|
+
// Ignore state file write errors — non-critical
|
|
831
|
+
}
|
|
832
|
+
}
|
|
800
833
|
/**
|
|
801
834
|
* Launch ekkos run + dashboard in isolated tmux panes (60/40 split)
|
|
802
835
|
*/
|
|
@@ -1370,6 +1403,7 @@ async function run(options) {
|
|
|
1370
1403
|
// ════════════════════════════════════════════════════════════════════════════
|
|
1371
1404
|
(0, state_1.registerActiveSession)('pending', // Session ID not yet known
|
|
1372
1405
|
currentSession || 'initializing', process.cwd());
|
|
1406
|
+
writeSessionFiles(cliSessionId || 'pending', currentSession || 'initializing');
|
|
1373
1407
|
dlog(`Registered active session (PID ${process.pid})`);
|
|
1374
1408
|
// Show active sessions count if verbose
|
|
1375
1409
|
if (verbose) {
|
|
@@ -1381,6 +1415,13 @@ async function run(options) {
|
|
|
1381
1415
|
let isAutoClearInProgress = false;
|
|
1382
1416
|
let transcriptPath = null;
|
|
1383
1417
|
let currentSessionId = null;
|
|
1418
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
1419
|
+
// PER-TURN BANNER STATE
|
|
1420
|
+
// Tracks idle→active transitions to print the session banner once per turn
|
|
1421
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
1422
|
+
let wasIdle = true; // Start as idle (no prompt submitted yet)
|
|
1423
|
+
let _turnCount = 0; // Incremented each time a new turn banner prints (unused beyond banner)
|
|
1424
|
+
let lastBannerTime = 0; // Epoch ms of last banner print (debounce guard)
|
|
1384
1425
|
// Stream tailer for mid-turn context capture (must be declared before polling code)
|
|
1385
1426
|
let streamTailer = null;
|
|
1386
1427
|
const streamCacheDir = path.join(os.homedir(), '.ekkos', 'cache', 'sessions', instanceId);
|
|
@@ -1524,9 +1565,13 @@ async function run(options) {
|
|
|
1524
1565
|
const sessionId = path.basename(file).replace('.jsonl', '');
|
|
1525
1566
|
transcriptPath = fullPath;
|
|
1526
1567
|
currentSessionId = sessionId;
|
|
1527
|
-
|
|
1568
|
+
// Keep cliSessionName if set (proxy mode) — it's already sent to the proxy
|
|
1569
|
+
// and shown in "Continuum Loaded". Re-deriving from JSONL UUID produces a
|
|
1570
|
+
// different name since Claude Code's UUID ≠ the CLI-generated UUID.
|
|
1571
|
+
currentSession = cliSessionName || (0, state_1.uuidToWords)(sessionId);
|
|
1528
1572
|
(0, state_1.updateCurrentProcessSession)(currentSessionId, currentSession);
|
|
1529
1573
|
(0, state_1.updateState)({ sessionId: currentSessionId, sessionName: currentSession });
|
|
1574
|
+
writeSessionFiles(currentSessionId, currentSession);
|
|
1530
1575
|
bindRealSessionToProxy(currentSession, 'fast-transcript', currentSessionId);
|
|
1531
1576
|
dlog(`[TRANSCRIPT] FAST DETECT: New transcript found! ${fullPath}`);
|
|
1532
1577
|
evictionDebugLog('TRANSCRIPT_SET', 'Fast poll detected new file', {
|
|
@@ -2306,6 +2351,45 @@ async function run(options) {
|
|
|
2306
2351
|
outputBuffer = outputBuffer.slice(-2000);
|
|
2307
2352
|
}
|
|
2308
2353
|
// ══════════════════════════════════════════════════════════════════════════
|
|
2354
|
+
// PER-TURN BANNER (replaces hook-based terminal header)
|
|
2355
|
+
// Prints once per turn when transitioning from idle → active (AI responding).
|
|
2356
|
+
// Guards: proxy mode only, not during auto-clear, 3 s debounce, substantial output.
|
|
2357
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
2358
|
+
if (proxyModeEnabled && wasIdle && !isAutoClearInProgress) {
|
|
2359
|
+
const cleanChunk = stripAnsi(data);
|
|
2360
|
+
const trimmed = cleanChunk.trim();
|
|
2361
|
+
// Require non-trivial output that isn't just a prompt character
|
|
2362
|
+
const isSubstantialOutput = trimmed.length > 2 &&
|
|
2363
|
+
!trimmed.startsWith('>') &&
|
|
2364
|
+
!trimmed.startsWith('❯') &&
|
|
2365
|
+
!trimmed.startsWith('%'); // zsh prompt artefact
|
|
2366
|
+
if (isSubstantialOutput && Date.now() - lastBannerTime > 3000) {
|
|
2367
|
+
_turnCount++;
|
|
2368
|
+
const now = new Date();
|
|
2369
|
+
const timeStr = now.toLocaleTimeString('en-US', {
|
|
2370
|
+
hour: 'numeric',
|
|
2371
|
+
minute: '2-digit',
|
|
2372
|
+
hour12: true,
|
|
2373
|
+
timeZone: 'America/New_York',
|
|
2374
|
+
});
|
|
2375
|
+
const dateStr = now.toLocaleDateString('en-US', {
|
|
2376
|
+
year: 'numeric',
|
|
2377
|
+
month: '2-digit',
|
|
2378
|
+
day: '2-digit',
|
|
2379
|
+
timeZone: 'America/New_York',
|
|
2380
|
+
});
|
|
2381
|
+
const sessionLabel = currentSession || cliSessionName || 'initializing';
|
|
2382
|
+
process.stderr.write(`${chalk_1.default.cyan.bold('🧠 ekkOS Memory')} ${chalk_1.default.dim(`| ${sessionLabel} | ${dateStr} ${timeStr} EST`)}\n`);
|
|
2383
|
+
lastBannerTime = Date.now();
|
|
2384
|
+
wasIdle = false;
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
// Track idle state for the banner (works in both proxy and local mode).
|
|
2388
|
+
// We check the raw outputBuffer (stripped) so the idle regex fires reliably.
|
|
2389
|
+
if (IDLE_PROMPT_REGEX.test(stripAnsi(outputBuffer))) {
|
|
2390
|
+
wasIdle = true;
|
|
2391
|
+
}
|
|
2392
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
2309
2393
|
// ORPHAN TOOL_RESULT DETECTION (LOCAL MODE ONLY)
|
|
2310
2394
|
// ccDNA validate mode emits [ekkOS] ORPHAN_TOOL_RESULT when it detects
|
|
2311
2395
|
// tool_results without matching tool_uses. This triggers automatic repair.
|
|
@@ -2364,11 +2448,13 @@ async function run(options) {
|
|
|
2364
2448
|
const sessionMatch = cleanData.match(/session[_\s]?(?:id)?[:\s]+([a-f0-9-]{36})/i);
|
|
2365
2449
|
if (sessionMatch) {
|
|
2366
2450
|
currentSessionId = sessionMatch[1];
|
|
2367
|
-
|
|
2451
|
+
// Keep cliSessionName if set (proxy mode) — JSONL UUID differs from CLI UUID
|
|
2452
|
+
currentSession = cliSessionName || (0, state_1.uuidToWords)(currentSessionId);
|
|
2368
2453
|
// Update THIS process's session entry (not global state.json)
|
|
2369
2454
|
(0, state_1.updateCurrentProcessSession)(currentSessionId, currentSession);
|
|
2370
2455
|
// Also update global state for backwards compatibility
|
|
2371
2456
|
(0, state_1.updateState)({ sessionId: currentSessionId, sessionName: currentSession });
|
|
2457
|
+
writeSessionFiles(currentSessionId, currentSession);
|
|
2372
2458
|
dlog(`Session detected from UUID: ${currentSession}`);
|
|
2373
2459
|
resolveTranscriptFromSessionId('session-id-from-output');
|
|
2374
2460
|
bindRealSessionToProxy(currentSession, 'session-id-from-output', currentSessionId || undefined);
|
|
@@ -2404,6 +2490,7 @@ async function run(options) {
|
|
|
2404
2490
|
(0, state_1.updateCurrentProcessSession)(currentSessionId || 'unknown', currentSession);
|
|
2405
2491
|
// Also update global state for backwards compatibility
|
|
2406
2492
|
(0, state_1.updateState)({ sessionName: currentSession });
|
|
2493
|
+
writeSessionFiles(currentSessionId || 'unknown', currentSession);
|
|
2407
2494
|
dlog(`Session detected from status line: ${currentSession} (observedSessionThisRun=true)`);
|
|
2408
2495
|
bindRealSessionToProxy(currentSession, 'status-line', currentSessionId || undefined);
|
|
2409
2496
|
resolveTranscriptFromSessionId('status-line');
|
package/dist/commands/setup.js
CHANGED
|
@@ -10,7 +10,8 @@ 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
|
-
|
|
13
|
+
// DEPRECATED: Hooks removed in hookless architecture migration
|
|
14
|
+
// import { hooksInstall } from './hooks.js';
|
|
14
15
|
const EKKOS_API_URL = 'https://mcp.ekkos.dev';
|
|
15
16
|
const CONFIG_DIR = (0, path_1.join)((0, os_1.homedir)(), '.ekkos');
|
|
16
17
|
const CONFIG_FILE = (0, path_1.join)(CONFIG_DIR, 'config.json');
|
|
@@ -207,47 +208,9 @@ async function setupIDE(ide, apiKey, config) {
|
|
|
207
208
|
}
|
|
208
209
|
async function setupClaudeCode(apiKey) {
|
|
209
210
|
const claudeDir = (0, path_1.join)((0, os_1.homedir)(), '.claude');
|
|
210
|
-
|
|
211
|
-
const stateDir = (0, path_1.join)(claudeDir, 'state');
|
|
212
|
-
// Create directories
|
|
211
|
+
// Create .claude directory (hooks directory is no longer created — hookless architecture)
|
|
213
212
|
(0, fs_1.mkdirSync)(claudeDir, { recursive: true });
|
|
214
|
-
|
|
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
|
|
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
|
|
213
|
+
// Save API key to ekkOS config
|
|
251
214
|
const ekkosConfigDir = (0, path_1.join)((0, os_1.homedir)(), '.ekkos');
|
|
252
215
|
(0, fs_1.mkdirSync)(ekkosConfigDir, { recursive: true });
|
|
253
216
|
const configPath = (0, path_1.join)(ekkosConfigDir, 'config.json');
|
|
@@ -258,34 +221,12 @@ async function setupClaudeCode(apiKey) {
|
|
|
258
221
|
}
|
|
259
222
|
catch { }
|
|
260
223
|
}
|
|
261
|
-
// Update config with API key (hookApiKey for hooks, apiKey for compatibility)
|
|
262
|
-
existingConfig.hookApiKey = apiKey;
|
|
263
224
|
existingConfig.apiKey = apiKey;
|
|
264
225
|
existingConfig.updatedAt = new Date().toISOString();
|
|
265
226
|
(0, fs_1.writeFileSync)(configPath, JSON.stringify(existingConfig, null, 2));
|
|
266
227
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
*/
|
|
270
|
-
async function generateBasicHooks(hooksDir, apiKey) {
|
|
271
|
-
const isWindows = (0, os_1.platform)() === 'win32';
|
|
272
|
-
if (isWindows) {
|
|
273
|
-
const promptSubmitHook = generatePromptSubmitHookPS(apiKey);
|
|
274
|
-
(0, fs_1.writeFileSync)((0, path_1.join)(hooksDir, 'user-prompt-submit.ps1'), promptSubmitHook);
|
|
275
|
-
const stopHook = generateStopHookPS(apiKey);
|
|
276
|
-
(0, fs_1.writeFileSync)((0, path_1.join)(hooksDir, 'stop.ps1'), stopHook);
|
|
277
|
-
}
|
|
278
|
-
else {
|
|
279
|
-
const promptSubmitHook = generatePromptSubmitHook(apiKey);
|
|
280
|
-
const promptSubmitPath = (0, path_1.join)(hooksDir, 'user-prompt-submit.sh');
|
|
281
|
-
(0, fs_1.writeFileSync)(promptSubmitPath, promptSubmitHook);
|
|
282
|
-
(0, fs_1.chmodSync)(promptSubmitPath, '755');
|
|
283
|
-
const stopHook = generateStopHook(apiKey);
|
|
284
|
-
const stopPath = (0, path_1.join)(hooksDir, 'stop.sh');
|
|
285
|
-
(0, fs_1.writeFileSync)(stopPath, stopHook);
|
|
286
|
-
(0, fs_1.chmodSync)(stopPath, '755');
|
|
287
|
-
}
|
|
288
|
-
}
|
|
228
|
+
// DEPRECATED: Hooks removed in hookless architecture migration
|
|
229
|
+
// generateBasicHooks() removed — hook generation is no longer performed.
|
|
289
230
|
async function setupCursor(apiKey) {
|
|
290
231
|
// Cursor uses .cursorrules for system prompt
|
|
291
232
|
// and MCP servers for tools
|
|
@@ -397,143 +338,8 @@ async function setupClaudeDesktop(apiKey) {
|
|
|
397
338
|
(0, fs_1.writeFileSync)(configPath, JSON.stringify(config, null, 2));
|
|
398
339
|
console.log(chalk_1.default.yellow(' Note: Restart Claude Desktop to load the MCP server'));
|
|
399
340
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
# ekkOS Hook: UserPromptSubmit
|
|
403
|
-
# Golden Loop: RETRIEVE → INJECT
|
|
404
|
-
|
|
405
|
-
set -e
|
|
406
|
-
|
|
407
|
-
EKKOS_API_KEY="${apiKey}"
|
|
408
|
-
MEMORY_API_URL="https://mcp.ekkos.dev"
|
|
409
|
-
STATE_DIR="$HOME/.claude/state"
|
|
410
|
-
mkdir -p "$STATE_DIR"
|
|
411
|
-
|
|
412
|
-
INPUT=$(cat)
|
|
413
|
-
USER_QUERY=$(echo "$INPUT" | jq -r '.query // .message // .prompt // ""')
|
|
414
|
-
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
|
|
415
|
-
MODEL_INFO=$(echo "$INPUT" | jq -r '.model // "claude-sonnet-4-5"')
|
|
416
|
-
|
|
417
|
-
if [ -z "$USER_QUERY" ] || [ "$USER_QUERY" = "null" ]; then
|
|
418
|
-
exit 0
|
|
419
|
-
fi
|
|
420
|
-
|
|
421
|
-
TASK_ID="task-\${SESSION_ID}-$(date +%s)"
|
|
422
|
-
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
423
|
-
|
|
424
|
-
echo ""
|
|
425
|
-
echo "[ekkOS_RETRIEVE] Searching memory..."
|
|
426
|
-
|
|
427
|
-
JSON_PAYLOAD=$(cat << EOF
|
|
428
|
-
{
|
|
429
|
-
"query": $(echo "$USER_QUERY" | jq -R -s .),
|
|
430
|
-
"k": 5,
|
|
431
|
-
"model_used": "$MODEL_INFO"
|
|
432
|
-
}
|
|
433
|
-
EOF
|
|
434
|
-
)
|
|
435
|
-
|
|
436
|
-
API_RESPONSE=$(curl -s -X POST "$MEMORY_API_URL/api/v1/patterns/query" \\
|
|
437
|
-
-H "Authorization: Bearer $EKKOS_API_KEY" \\
|
|
438
|
-
-H "Content-Type: application/json" \\
|
|
439
|
-
-d "$JSON_PAYLOAD" \\
|
|
440
|
-
--connect-timeout 3 \\
|
|
441
|
-
--max-time 5 2>/dev/null || echo '{"patterns":[]}')
|
|
442
|
-
|
|
443
|
-
PATTERNS=$(echo "$API_RESPONSE" | jq '.patterns // []' 2>/dev/null || echo "[]")
|
|
444
|
-
PATTERN_COUNT=$(echo "$PATTERNS" | jq 'length' 2>/dev/null || echo "0")
|
|
445
|
-
|
|
446
|
-
if [ "$PATTERN_COUNT" -gt 0 ]; then
|
|
447
|
-
echo "[ekkOS_RETRIEVE] Found $PATTERN_COUNT patterns"
|
|
448
|
-
else
|
|
449
|
-
echo "[ekkOS_RETRIEVE] No patterns found (building memory)"
|
|
450
|
-
fi
|
|
451
|
-
|
|
452
|
-
# Store state
|
|
453
|
-
PATTERN_DATA=$(cat << EOF
|
|
454
|
-
{
|
|
455
|
-
"patterns": $PATTERNS,
|
|
456
|
-
"model_used": "$MODEL_INFO",
|
|
457
|
-
"task_id": "$TASK_ID",
|
|
458
|
-
"retrieved_at": "$TIMESTAMP",
|
|
459
|
-
"query": $(echo "$USER_QUERY" | jq -R -s .)
|
|
460
|
-
}
|
|
461
|
-
EOF
|
|
462
|
-
)
|
|
463
|
-
echo "$PATTERN_DATA" > "$STATE_DIR/patterns-\${SESSION_ID}.json"
|
|
464
|
-
|
|
465
|
-
# Display patterns
|
|
466
|
-
if [ "$PATTERN_COUNT" -gt 0 ]; then
|
|
467
|
-
AVG_SUCCESS=$(echo "$PATTERNS" | jq '[.[].success_rate // 0.9] | add / length * 100 | round' 2>/dev/null || echo "90")
|
|
468
|
-
echo "[ekkOS_INJECT] Loading $PATTERN_COUNT patterns (\${AVG_SUCCESS}% avg success)"
|
|
469
|
-
echo ""
|
|
470
|
-
|
|
471
|
-
echo "$PATTERNS" | jq -c '.[:5] | .[]' 2>/dev/null | while read -r pattern; do
|
|
472
|
-
TITLE=$(echo "$pattern" | jq -r '.title // "Untitled"')
|
|
473
|
-
SUCCESS_RATE=$(echo "$pattern" | jq -r 'if .success_rate then (.success_rate * 100 | round | tostring) + "%" else "~80%" end')
|
|
474
|
-
APPLIED_COUNT=$(echo "$pattern" | jq -r '.applied_count // 0')
|
|
475
|
-
GUIDANCE=$(echo "$pattern" | jq -r '.guidance // .content // "No guidance"')
|
|
476
|
-
|
|
477
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
478
|
-
echo "🧠 PATTERN: \\"$TITLE\\""
|
|
479
|
-
echo " Applied \${APPLIED_COUNT}x | \${SUCCESS_RATE} success"
|
|
480
|
-
echo ""
|
|
481
|
-
echo " $GUIDANCE" | head -c 500
|
|
482
|
-
echo ""
|
|
483
|
-
done
|
|
484
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
485
|
-
fi
|
|
486
|
-
|
|
487
|
-
echo ""
|
|
488
|
-
echo "[ekkOS_LEARN] 🔥 FORGE new patterns when you solve something!"
|
|
489
|
-
echo ""
|
|
490
|
-
|
|
491
|
-
exit 0
|
|
492
|
-
`;
|
|
493
|
-
}
|
|
494
|
-
function generateStopHook(apiKey) {
|
|
495
|
-
return `#!/bin/bash
|
|
496
|
-
# ekkOS Hook: Stop
|
|
497
|
-
# Golden Loop: CAPTURE → MEASURE
|
|
498
|
-
|
|
499
|
-
set -e
|
|
500
|
-
|
|
501
|
-
EKKOS_API_KEY="${apiKey}"
|
|
502
|
-
MEMORY_API_URL="https://mcp.ekkos.dev"
|
|
503
|
-
STATE_DIR="$HOME/.claude/state"
|
|
504
|
-
|
|
505
|
-
INPUT=$(cat)
|
|
506
|
-
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
|
|
507
|
-
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
508
|
-
|
|
509
|
-
PATTERNS_FILE="$STATE_DIR/patterns-\${SESSION_ID}.json"
|
|
510
|
-
PATTERN_COUNT=0
|
|
511
|
-
|
|
512
|
-
if [ -f "$PATTERNS_FILE" ]; then
|
|
513
|
-
STORED_DATA=$(cat "$PATTERNS_FILE" 2>/dev/null || echo "{}")
|
|
514
|
-
PATTERNS=$(echo "$STORED_DATA" | jq '.patterns // []' 2>/dev/null || echo "[]")
|
|
515
|
-
PATTERN_COUNT=$(echo "$PATTERNS" | jq 'length' 2>/dev/null || echo "0")
|
|
516
|
-
fi
|
|
517
|
-
|
|
518
|
-
echo ""
|
|
519
|
-
echo "[ekkOS_CAPTURE] ✓ Session saved"
|
|
520
|
-
|
|
521
|
-
if [ "$PATTERN_COUNT" -gt 0 ]; then
|
|
522
|
-
echo "[ekkOS_MEASURE] ✓ Tracked $PATTERN_COUNT patterns"
|
|
523
|
-
fi
|
|
524
|
-
|
|
525
|
-
rm -f "$PATTERNS_FILE" 2>/dev/null
|
|
526
|
-
|
|
527
|
-
echo ""
|
|
528
|
-
echo "┌─────────────────────────────────────────────┐"
|
|
529
|
-
echo "│ ekkOS_ GOLDEN LOOP COMPLETE │"
|
|
530
|
-
echo "│ The system learns from every interaction. │"
|
|
531
|
-
echo "└─────────────────────────────────────────────┘"
|
|
532
|
-
echo ""
|
|
533
|
-
|
|
534
|
-
exit 0
|
|
535
|
-
`;
|
|
536
|
-
}
|
|
341
|
+
// DEPRECATED: Hooks removed in hookless architecture migration
|
|
342
|
+
// generatePromptSubmitHook() and generateStopHook() removed.
|
|
537
343
|
function generateCursorRules() {
|
|
538
344
|
return `# ekkOS Golden Loop Integration
|
|
539
345
|
|
|
@@ -583,153 +389,5 @@ When you solve a problem that others might encounter:
|
|
|
583
389
|
The system learns from every interaction.
|
|
584
390
|
`;
|
|
585
391
|
}
|
|
586
|
-
//
|
|
587
|
-
//
|
|
588
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
589
|
-
function generatePromptSubmitHookPS(apiKey) {
|
|
590
|
-
return `# ekkOS Hook: UserPromptSubmit (Windows PowerShell)
|
|
591
|
-
# Golden Loop: RETRIEVE → INJECT
|
|
592
|
-
|
|
593
|
-
$ErrorActionPreference = "Stop"
|
|
594
|
-
|
|
595
|
-
$EKKOS_API_KEY = "${apiKey}"
|
|
596
|
-
$MEMORY_API_URL = "https://mcp.ekkos.dev"
|
|
597
|
-
$STATE_DIR = "$env:USERPROFILE\\.claude\\state"
|
|
598
|
-
|
|
599
|
-
if (!(Test-Path $STATE_DIR)) {
|
|
600
|
-
New-Item -ItemType Directory -Path $STATE_DIR -Force | Out-Null
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
$INPUT = $input | Out-String
|
|
604
|
-
$jsonInput = $INPUT | ConvertFrom-Json -ErrorAction SilentlyContinue
|
|
605
|
-
|
|
606
|
-
$USER_QUERY = if ($jsonInput.query) { $jsonInput.query } elseif ($jsonInput.message) { $jsonInput.message } elseif ($jsonInput.prompt) { $jsonInput.prompt } else { "" }
|
|
607
|
-
$SESSION_ID = if ($jsonInput.session_id) { $jsonInput.session_id } else { "unknown" }
|
|
608
|
-
$MODEL_INFO = if ($jsonInput.model) { $jsonInput.model } else { "claude-sonnet-4-5" }
|
|
609
|
-
|
|
610
|
-
if ([string]::IsNullOrEmpty($USER_QUERY)) {
|
|
611
|
-
exit 0
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
$TASK_ID = "claude-code-task-$SESSION_ID-$([DateTimeOffset]::Now.ToUnixTimeSeconds())"
|
|
615
|
-
$TIMESTAMP = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
|
|
616
|
-
|
|
617
|
-
Write-Host ""
|
|
618
|
-
Write-Host "[ekkOS_RETRIEVE] Searching memory..."
|
|
619
|
-
|
|
620
|
-
$body = @{
|
|
621
|
-
query = $USER_QUERY
|
|
622
|
-
k = 5
|
|
623
|
-
model_used = $MODEL_INFO
|
|
624
|
-
} | ConvertTo-Json
|
|
625
|
-
|
|
626
|
-
try {
|
|
627
|
-
$response = Invoke-RestMethod -Uri "$MEMORY_API_URL/api/v1/patterns/query" \`
|
|
628
|
-
-Method Post \`
|
|
629
|
-
-Headers @{ "Authorization" = "Bearer $EKKOS_API_KEY"; "Content-Type" = "application/json" } \`
|
|
630
|
-
-Body $body \`
|
|
631
|
-
-TimeoutSec 5
|
|
632
|
-
|
|
633
|
-
$PATTERNS = $response.patterns
|
|
634
|
-
$PATTERN_COUNT = if ($PATTERNS) { $PATTERNS.Count } else { 0 }
|
|
635
|
-
} catch {
|
|
636
|
-
$PATTERNS = @()
|
|
637
|
-
$PATTERN_COUNT = 0
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
if ($PATTERN_COUNT -gt 0) {
|
|
641
|
-
Write-Host "[ekkOS_RETRIEVE] Found $PATTERN_COUNT patterns"
|
|
642
|
-
} else {
|
|
643
|
-
Write-Host "[ekkOS_RETRIEVE] No patterns found (new territory)"
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
# Store for stop hook
|
|
647
|
-
$PATTERN_DATA = @{
|
|
648
|
-
patterns = $PATTERNS
|
|
649
|
-
model_used = $MODEL_INFO
|
|
650
|
-
task_id = $TASK_ID
|
|
651
|
-
retrieved_at = $TIMESTAMP
|
|
652
|
-
query = $USER_QUERY.Substring(0, [Math]::Min($USER_QUERY.Length, 1000))
|
|
653
|
-
} | ConvertTo-Json -Depth 10
|
|
654
|
-
|
|
655
|
-
$PATTERN_DATA | Out-File -FilePath "$STATE_DIR\\patterns-$SESSION_ID.json" -Encoding UTF8
|
|
656
|
-
|
|
657
|
-
# Display patterns
|
|
658
|
-
if ($PATTERN_COUNT -gt 0) {
|
|
659
|
-
$rates = $PATTERNS | ForEach-Object { if ($_.success_rate) { $_.success_rate } else { 0.9 } }
|
|
660
|
-
$AVG_SUCCESS = [math]::Round(($rates | Measure-Object -Average).Average * 100)
|
|
661
|
-
|
|
662
|
-
Write-Host "[ekkOS_INJECT] Loading $PATTERN_COUNT patterns ($AVG_SUCCESS% avg success)"
|
|
663
|
-
Write-Host ""
|
|
664
|
-
|
|
665
|
-
$idx = 0
|
|
666
|
-
foreach ($pattern in $PATTERNS | Select-Object -First 5) {
|
|
667
|
-
$idx++
|
|
668
|
-
$TITLE = if ($pattern.title) { $pattern.title } else { "Untitled" }
|
|
669
|
-
$SUCCESS_RATE = if ($pattern.success_rate) { "$([math]::Round($pattern.success_rate * 100))%" } else { "~80%" }
|
|
670
|
-
$APPLIED_COUNT = if ($pattern.applied_count) { $pattern.applied_count } else { 0 }
|
|
671
|
-
|
|
672
|
-
Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
673
|
-
Write-Host "🧠 LEARNED PATTERN #$idx: \`"$TITLE\`""
|
|
674
|
-
Write-Host " Applied $APPLIED_COUNT time(s) with $SUCCESS_RATE success rate"
|
|
675
|
-
Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
676
|
-
Write-Host ""
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
Write-Host "[ekkOS_LEARN] 🔥 FORGE new patterns when you solve something!"
|
|
681
|
-
Write-Host ""
|
|
682
|
-
|
|
683
|
-
exit 0
|
|
684
|
-
`;
|
|
685
|
-
}
|
|
686
|
-
function generateStopHookPS(apiKey) {
|
|
687
|
-
return `# ekkOS Hook: Stop (Windows PowerShell)
|
|
688
|
-
# Golden Loop: CAPTURE → MEASURE
|
|
689
|
-
|
|
690
|
-
$ErrorActionPreference = "SilentlyContinue"
|
|
691
|
-
|
|
692
|
-
$EKKOS_API_KEY = "${apiKey}"
|
|
693
|
-
$MEMORY_API_URL = "https://mcp.ekkos.dev"
|
|
694
|
-
$STATE_DIR = "$env:USERPROFILE\\.claude\\state"
|
|
695
|
-
|
|
696
|
-
$INPUT = $input | Out-String
|
|
697
|
-
$jsonInput = $INPUT | ConvertFrom-Json -ErrorAction SilentlyContinue
|
|
698
|
-
|
|
699
|
-
$SESSION_ID = if ($jsonInput.session_id) { $jsonInput.session_id } else { "unknown" }
|
|
700
|
-
$TIMESTAMP = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
|
|
701
|
-
|
|
702
|
-
# Load patterns from RETRIEVE step
|
|
703
|
-
$PATTERNS_FILE = "$STATE_DIR\\patterns-$SESSION_ID.json"
|
|
704
|
-
$PATTERN_COUNT = 0
|
|
705
|
-
$PATTERNS = @()
|
|
706
|
-
|
|
707
|
-
if (Test-Path $PATTERNS_FILE) {
|
|
708
|
-
try {
|
|
709
|
-
$STORED_DATA = Get-Content $PATTERNS_FILE -Raw | ConvertFrom-Json
|
|
710
|
-
$PATTERNS = $STORED_DATA.patterns
|
|
711
|
-
$PATTERN_COUNT = if ($PATTERNS) { $PATTERNS.Count } else { 0 }
|
|
712
|
-
} catch {}
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
Write-Host ""
|
|
716
|
-
Write-Host "[ekkOS_CAPTURE] ✓ Session saved"
|
|
717
|
-
|
|
718
|
-
if ($PATTERN_COUNT -gt 0) {
|
|
719
|
-
Write-Host "[ekkOS_MEASURE] ✓ Tracked $PATTERN_COUNT patterns"
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
# Cleanup
|
|
723
|
-
Remove-Item $PATTERNS_FILE -Force -ErrorAction SilentlyContinue
|
|
724
|
-
|
|
725
|
-
Write-Host ""
|
|
726
|
-
Write-Host "┌─────────────────────────────────────────────────────────────────┐"
|
|
727
|
-
Write-Host "│ ekkOS_ GOLDEN LOOP COMPLETE │"
|
|
728
|
-
Write-Host "├─────────────────────────────────────────────────────────────────┤"
|
|
729
|
-
Write-Host "│ The system learns from every interaction. │"
|
|
730
|
-
Write-Host "└─────────────────────────────────────────────────────────────────┘"
|
|
731
|
-
Write-Host ""
|
|
732
|
-
|
|
733
|
-
exit 0
|
|
734
|
-
`;
|
|
735
|
-
}
|
|
392
|
+
// DEPRECATED: Hooks removed in hookless architecture migration
|
|
393
|
+
// generatePromptSubmitHookPS() and generateStopHookPS() removed.
|
package/dist/deploy/hooks.d.ts
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* DEPRECATED: Hook deployment removed in hookless architecture migration.
|
|
3
|
+
* The CLI + proxy handle everything — hooks are no longer needed.
|
|
4
4
|
*/
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* @deprecated Hooks are no longer deployed. This is a no-op.
|
|
7
|
+
*/
|
|
8
|
+
export declare function deployHooks(_apiKey: string): {
|
|
6
9
|
count: number;
|
|
7
10
|
files: string[];
|
|
8
11
|
};
|
|
9
12
|
/**
|
|
10
|
-
*
|
|
13
|
+
* @deprecated Hooks are no longer deployed. Always returns false.
|
|
11
14
|
*/
|
|
12
15
|
export declare function areHooksDeployed(): boolean;
|
|
13
16
|
/**
|
|
14
|
-
*
|
|
17
|
+
* @deprecated Hooks are no longer deployed. Always returns 0.
|
|
15
18
|
*/
|
|
16
19
|
export declare function countDeployedHooks(): number;
|