@ekkos/cli 1.2.17 → 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.
Files changed (47) hide show
  1. package/dist/cache/capture.js +0 -0
  2. package/dist/commands/dashboard.js +57 -49
  3. package/dist/commands/hooks.d.ts +25 -36
  4. package/dist/commands/hooks.js +43 -615
  5. package/dist/commands/init.js +7 -23
  6. package/dist/commands/run.js +97 -11
  7. package/dist/commands/setup.js +10 -352
  8. package/dist/deploy/hooks.d.ts +8 -5
  9. package/dist/deploy/hooks.js +12 -105
  10. package/dist/deploy/settings.d.ts +8 -2
  11. package/dist/deploy/settings.js +22 -51
  12. package/dist/index.js +17 -39
  13. package/dist/utils/state.js +7 -2
  14. package/package.json +1 -1
  15. package/templates/CLAUDE.md +82 -292
  16. package/templates/cursor-rules/ekkos-memory.md +48 -108
  17. package/templates/windsurf-rules/ekkos-memory.md +62 -64
  18. package/templates/cursor-hooks/after-agent-response.sh +0 -117
  19. package/templates/cursor-hooks/before-submit-prompt.sh +0 -419
  20. package/templates/cursor-hooks/hooks.json +0 -20
  21. package/templates/cursor-hooks/lib/contract.sh +0 -320
  22. package/templates/cursor-hooks/stop.sh +0 -75
  23. package/templates/hooks/assistant-response.ps1 +0 -256
  24. package/templates/hooks/assistant-response.sh +0 -160
  25. package/templates/hooks/hooks.json +0 -40
  26. package/templates/hooks/lib/contract.sh +0 -332
  27. package/templates/hooks/lib/count-tokens.cjs +0 -86
  28. package/templates/hooks/lib/ekkos-reminders.sh +0 -98
  29. package/templates/hooks/lib/state.sh +0 -210
  30. package/templates/hooks/session-start.ps1 +0 -146
  31. package/templates/hooks/session-start.sh +0 -353
  32. package/templates/hooks/stop.ps1 +0 -349
  33. package/templates/hooks/stop.sh +0 -382
  34. package/templates/hooks/user-prompt-submit.ps1 +0 -419
  35. package/templates/hooks/user-prompt-submit.sh +0 -516
  36. package/templates/project-stubs/session-start.ps1 +0 -63
  37. package/templates/project-stubs/session-start.sh +0 -55
  38. package/templates/project-stubs/stop.ps1 +0 -63
  39. package/templates/project-stubs/stop.sh +0 -55
  40. package/templates/project-stubs/user-prompt-submit.ps1 +0 -63
  41. package/templates/project-stubs/user-prompt-submit.sh +0 -55
  42. package/templates/windsurf-hooks/README.md +0 -212
  43. package/templates/windsurf-hooks/hooks.json +0 -17
  44. package/templates/windsurf-hooks/install.sh +0 -148
  45. package/templates/windsurf-hooks/lib/contract.sh +0 -322
  46. package/templates/windsurf-hooks/post-cascade-response.sh +0 -251
  47. package/templates/windsurf-hooks/pre-user-prompt.sh +0 -435
@@ -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
- const settings_1 = require("../deploy/settings");
15
- const hooks_1 = require("../deploy/hooks");
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
- // Settings.json (hook registration)
255
- spinner = (0, ora_1.default)('Deploying hooks configuration...').start();
256
- try {
257
- (0, settings_1.deployClaudeSettings)();
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();
@@ -469,13 +469,12 @@ function getEkkosEnv() {
469
469
  env.EKKOS_PROXY_MODE = '1';
470
470
  // Enable ultra-minimal mode by default (30%→20% eviction for constant-cost infinite context)
471
471
  env.EKKOS_ULTRA_MINIMAL = '1';
472
- // Use a unique pending scope until Claude exposes its real session UUID.
473
- // run.ts binds this scope to the real session immediately on transcript detect.
472
+ // Generate a deterministic word-triple session name from a fresh UUID.
473
+ // This eliminates _pending-* placeholders proxy sees a real name from request #1.
474
474
  if (!cliSessionName) {
475
- const pendingToken = crypto.randomBytes(8).toString('hex');
476
- cliSessionName = `_pending-${pendingToken}`;
477
- cliSessionId = `pending-${pendingToken}`;
478
- console.log(chalk_1.default.gray(` 📂 Session: pending (will bind to Claude session)`));
475
+ cliSessionId = crypto.randomUUID();
476
+ cliSessionName = (0, state_1.uuidToWords)(cliSessionId);
477
+ console.log(chalk_1.default.gray(` 📂 Session: ${cliSessionName}`));
479
478
  }
480
479
  // Get full userId from config (NOT the truncated version from auth token)
481
480
  // Config has full UUID like "d4532ba0-0a86-42ce-bab4-22aa62b55ce6"
@@ -499,7 +498,8 @@ function getEkkosEnv() {
499
498
  // Project path is base64-encoded to handle special chars safely
500
499
  const projectPath = process.cwd();
501
500
  const projectPathEncoded = Buffer.from(projectPath).toString('base64url');
502
- const proxyUrl = `${EKKOS_PROXY_URL}/proxy/${encodeURIComponent(userId)}/${encodeURIComponent(cliSessionName)}?project=${projectPathEncoded}`;
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)}`;
503
503
  env.ANTHROPIC_BASE_URL = proxyUrl;
504
504
  // Proxy URL contains userId + project path — don't leak to terminal
505
505
  }
@@ -798,6 +798,38 @@ function cleanupInstanceFile(instanceId) {
798
798
  // Ignore cleanup errors
799
799
  }
800
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
+ }
801
833
  /**
802
834
  * Launch ekkos run + dashboard in isolated tmux panes (60/40 split)
803
835
  */
@@ -1352,7 +1384,7 @@ async function run(options) {
1352
1384
  }
1353
1385
  // Track state — only use explicit -s option; never inherit stale session from state.json
1354
1386
  // The real session name will be detected from Claude Code's output (line ~2552)
1355
- let currentSession = options.session || null;
1387
+ let currentSession = options.session || cliSessionName || null;
1356
1388
  // Write initial instance file
1357
1389
  const startedAt = new Date().toISOString();
1358
1390
  writeInstanceFile(instanceId, {
@@ -1371,6 +1403,7 @@ async function run(options) {
1371
1403
  // ════════════════════════════════════════════════════════════════════════════
1372
1404
  (0, state_1.registerActiveSession)('pending', // Session ID not yet known
1373
1405
  currentSession || 'initializing', process.cwd());
1406
+ writeSessionFiles(cliSessionId || 'pending', currentSession || 'initializing');
1374
1407
  dlog(`Registered active session (PID ${process.pid})`);
1375
1408
  // Show active sessions count if verbose
1376
1409
  if (verbose) {
@@ -1382,6 +1415,13 @@ async function run(options) {
1382
1415
  let isAutoClearInProgress = false;
1383
1416
  let transcriptPath = null;
1384
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)
1385
1425
  // Stream tailer for mid-turn context capture (must be declared before polling code)
1386
1426
  let streamTailer = null;
1387
1427
  const streamCacheDir = path.join(os.homedir(), '.ekkos', 'cache', 'sessions', instanceId);
@@ -1525,9 +1565,13 @@ async function run(options) {
1525
1565
  const sessionId = path.basename(file).replace('.jsonl', '');
1526
1566
  transcriptPath = fullPath;
1527
1567
  currentSessionId = sessionId;
1528
- currentSession = (0, state_1.uuidToWords)(sessionId);
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);
1529
1572
  (0, state_1.updateCurrentProcessSession)(currentSessionId, currentSession);
1530
1573
  (0, state_1.updateState)({ sessionId: currentSessionId, sessionName: currentSession });
1574
+ writeSessionFiles(currentSessionId, currentSession);
1531
1575
  bindRealSessionToProxy(currentSession, 'fast-transcript', currentSessionId);
1532
1576
  dlog(`[TRANSCRIPT] FAST DETECT: New transcript found! ${fullPath}`);
1533
1577
  evictionDebugLog('TRANSCRIPT_SET', 'Fast poll detected new file', {
@@ -1657,7 +1701,7 @@ async function run(options) {
1657
1701
  return;
1658
1702
  if (boundProxySession === sessionName || bindingSessionInFlight === sessionName)
1659
1703
  return;
1660
- const pendingSession = cliSessionName?.startsWith('_pending') ? cliSessionName : undefined;
1704
+ const pendingSession = undefined; // CLI session names are real word-triples from the start
1661
1705
  const bindSessionId = sessionIdHint || currentSessionId || undefined;
1662
1706
  bindingSessionInFlight = sessionName;
1663
1707
  void (async () => {
@@ -2307,6 +2351,45 @@ async function run(options) {
2307
2351
  outputBuffer = outputBuffer.slice(-2000);
2308
2352
  }
2309
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
+ // ══════════════════════════════════════════════════════════════════════════
2310
2393
  // ORPHAN TOOL_RESULT DETECTION (LOCAL MODE ONLY)
2311
2394
  // ccDNA validate mode emits [ekkOS] ORPHAN_TOOL_RESULT when it detects
2312
2395
  // tool_results without matching tool_uses. This triggers automatic repair.
@@ -2365,11 +2448,13 @@ async function run(options) {
2365
2448
  const sessionMatch = cleanData.match(/session[_\s]?(?:id)?[:\s]+([a-f0-9-]{36})/i);
2366
2449
  if (sessionMatch) {
2367
2450
  currentSessionId = sessionMatch[1];
2368
- currentSession = (0, state_1.uuidToWords)(currentSessionId);
2451
+ // Keep cliSessionName if set (proxy mode) — JSONL UUID differs from CLI UUID
2452
+ currentSession = cliSessionName || (0, state_1.uuidToWords)(currentSessionId);
2369
2453
  // Update THIS process's session entry (not global state.json)
2370
2454
  (0, state_1.updateCurrentProcessSession)(currentSessionId, currentSession);
2371
2455
  // Also update global state for backwards compatibility
2372
2456
  (0, state_1.updateState)({ sessionId: currentSessionId, sessionName: currentSession });
2457
+ writeSessionFiles(currentSessionId, currentSession);
2373
2458
  dlog(`Session detected from UUID: ${currentSession}`);
2374
2459
  resolveTranscriptFromSessionId('session-id-from-output');
2375
2460
  bindRealSessionToProxy(currentSession, 'session-id-from-output', currentSessionId || undefined);
@@ -2405,6 +2490,7 @@ async function run(options) {
2405
2490
  (0, state_1.updateCurrentProcessSession)(currentSessionId || 'unknown', currentSession);
2406
2491
  // Also update global state for backwards compatibility
2407
2492
  (0, state_1.updateState)({ sessionName: currentSession });
2493
+ writeSessionFiles(currentSessionId || 'unknown', currentSession);
2408
2494
  dlog(`Session detected from status line: ${currentSession} (observedSessionThisRun=true)`);
2409
2495
  bindRealSessionToProxy(currentSession, 'status-line', currentSessionId || undefined);
2410
2496
  resolveTranscriptFromSessionId('status-line');
@@ -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
- const hooks_js_1 = require("./hooks.js");
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
- const hooksDir = (0, path_1.join)(claudeDir, 'hooks');
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
- (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
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
- * Generate basic inline hooks as fallback when templates aren't available
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
- function generatePromptSubmitHook(apiKey) {
401
- return `#!/bin/bash
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
- // WINDOWS POWERSHELL HOOKS
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.
@@ -1,16 +1,19 @@
1
1
  /**
2
- * Deploy all hook scripts to ~/.claude/hooks/
3
- * Deploys .sh scripts on Unix, .ps1 scripts on Windows
2
+ * DEPRECATED: Hook deployment removed in hookless architecture migration.
3
+ * The CLI + proxy handle everything hooks are no longer needed.
4
4
  */
5
- export declare function deployHooks(apiKey: string): {
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
- * Check if hooks are deployed
13
+ * @deprecated Hooks are no longer deployed. Always returns false.
11
14
  */
12
15
  export declare function areHooksDeployed(): boolean;
13
16
  /**
14
- * Count deployed hook files
17
+ * @deprecated Hooks are no longer deployed. Always returns 0.
15
18
  */
16
19
  export declare function countDeployedHooks(): number;