@ekkos/cli 1.2.18 → 1.3.1

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 +121 -66
  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 +90 -3
  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();
@@ -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 proxyUrl = `${EKKOS_PROXY_URL}/proxy/${encodeURIComponent(userId)}/${encodeURIComponent(cliSessionName)}?project=${projectPathEncoded}&sid=${encodeURIComponent(cliSessionId)}`;
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
- 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);
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
- 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);
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');
@@ -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;