@ekkos/cli 1.1.6 → 1.2.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.
@@ -627,62 +627,40 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
627
627
  // footer: 3 rows (totals + routing + keybindings)
628
628
  const LOGO_CHARS = ['e', 'k', 'k', 'O', 'S', '_'];
629
629
  const WAVE_COLORS = ['cyan', 'blue', 'magenta', 'yellow', 'green', 'white'];
630
- const GOOD_LUCK_FORTUNES = [
631
- // AI / LLM era humor
630
+ // Fallback fortunes when cloud endpoint is unreachable
631
+ const FALLBACK_FORTUNES = [
632
632
  'The AI was confident. It was also wrong.',
633
633
  'Vibe coded it. Ship it. Pray.',
634
634
  'Your model hallucinated. Your memory did not.',
635
- 'Claude said "I cannot assist with that." ekkOS remembered anyway.',
636
- 'The context window closed. The lesson did not.',
637
- 'Cursor wrote it. You own it. Good luck.',
638
635
  'It works. Nobody knows why. Memory saved the why.',
639
- 'LLM said "as of my knowledge cutoff." ekkOS said hold my cache.',
640
- 'Your agent forgot. Classic agent behavior.',
641
- 'GPT-5 dropped. Your memory still works.',
642
- 'Trained on the internet. Trusted by no one.',
643
- 'Fine-tuned on vibes. Running in production.',
644
- 'Prompt engineering is just yelling more politely.',
645
- 'The AI is confident 97% of the time. The other 3% is your bug.',
646
- // Friday deploys / prod pain
647
- 'Pushed to prod on a Friday. Memory captured the regret.',
648
- 'It was working this morning. The morning remembers.',
649
- 'The bug was in prod for 3 months. The fix took 4 minutes.',
650
- 'Hotfix on a hotfix. Classic.',
651
636
  'Rollback complete. Dignity: partial.',
652
- '"It works on my machine." Ship the machine.',
653
- 'The incident was resolved. The root cause was vibes.',
654
- 'Post-mortem written. Lessons immediately forgotten. Not anymore.',
655
- // Context / memory specific
656
- 'Cold start problem? Never met her.',
657
- '94% cache hit rate. The other 6% are trust issues.',
658
- '107 turns. Zero compaction. One very tired server.',
659
- 'Flat cost curve. Exponential confidence.',
660
- 'Your session ended. Your mistakes did not.',
661
- 'The context limit hit. The memory did not care.',
662
- 'Compaction is a skill issue.',
663
- // General dev pain
664
- 'The ticket said "small change." It was not small.',
665
637
  'Story points are astrology for engineers.',
666
- 'The meeting could have been a memory.',
667
- '"Just a quick question." 45 minutes ago.',
668
- 'Senior dev. 8 years experience. Still googles how to center a div.',
669
- 'Code review: where confidence goes to die.',
670
- 'The PR sat for 11 days. You merged it anyway.',
671
- 'Works fine until it\'s demoed. Classic.',
672
- 'Two spaces or four? Choose your enemies carefully.',
638
+ 'DNS. It\'s always DNS.',
639
+ 'The tests passed. The tests were wrong.',
673
640
  'Tech debt is just regular debt with better excuses.',
674
- 'The documentation was last updated in 2019. Press F.',
675
- 'Legacy code: someone\'s proudest moment, your worst nightmare.',
676
- 'Tabs vs spaces is still unresolved. The war continues.',
677
- 'LGTM. (I did not look at this.)',
678
- 'The standup is 15 minutes. It is never 15 minutes.',
679
- 'Agile: deadline anxiety, but make it a ceremony.',
680
- '"No breaking changes." — Famous last words.',
681
- 'Your regex is beautiful. Your regex is unmaintainable.',
682
- 'undefined is just the universe saying try again.',
683
641
  'It\'s not a bug. It\'s a negotiated feature.',
684
- 'Closed one ticket. Jira opened three.',
685
642
  ];
643
+ // Active fortune set — starts with fallback, replaced by cloud data
644
+ let activeFortunes = [...FALLBACK_FORTUNES];
645
+ // Fetch time-aware fortunes from the proxy
646
+ async function fetchCloudFortunes() {
647
+ try {
648
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
649
+ const resp = await fetch(`https://proxy.ekkos.dev/api/v1/fortunes?tz=${encodeURIComponent(tz)}`, { signal: AbortSignal.timeout(5000) });
650
+ if (!resp.ok)
651
+ return;
652
+ const data = await resp.json();
653
+ if (data.fortunes && data.fortunes.length > 0) {
654
+ activeFortunes = data.fortunes;
655
+ // Reset fortune index to start cycling the new set
656
+ fortuneIdx = Math.floor(Math.random() * activeFortunes.length);
657
+ fortuneText = activeFortunes[fortuneIdx];
658
+ }
659
+ }
660
+ catch {
661
+ // Cloud unavailable — keep using fallback
662
+ }
663
+ }
686
664
  const W = '100%';
687
665
  const HEADER_H = 3;
688
666
  const CONTEXT_H = 5;
@@ -709,8 +687,8 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
709
687
  let lastData = null;
710
688
  let lastChartSeries = null;
711
689
  let lastScrollPerc = 0; // Preserve scroll position across updates
712
- let fortuneIdx = Math.floor(Math.random() * GOOD_LUCK_FORTUNES.length);
713
- let fortuneText = GOOD_LUCK_FORTUNES[fortuneIdx];
690
+ let fortuneIdx = Math.floor(Math.random() * activeFortunes.length);
691
+ let fortuneText = activeFortunes[fortuneIdx];
714
692
  // Header: session stats (3 lines)
715
693
  const headerBox = blessed.box({
716
694
  top: layout.header.top, left: 0, width: W, height: layout.header.height,
@@ -1421,6 +1399,8 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
1421
1399
  // Dashboard is fully passive — no widget captures keyboard input
1422
1400
  updateDashboard();
1423
1401
  screen.render();
1402
+ // Fetch cloud fortunes in background (non-blocking, updates activeFortunes on success)
1403
+ fetchCloudFortunes().then(() => renderHeader()).catch(() => { });
1424
1404
  // Delay first ccusage call — let blessed render first, then load heavy data
1425
1405
  setTimeout(() => updateWindowBox(), 2000);
1426
1406
  const pollInterval = setInterval(updateDashboard, refreshMs);
@@ -1430,10 +1410,10 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
1430
1410
  renderHeader();
1431
1411
  }, 500);
1432
1412
  const fortuneInterval = setInterval(() => {
1433
- if (GOOD_LUCK_FORTUNES.length === 0)
1413
+ if (activeFortunes.length === 0)
1434
1414
  return;
1435
- fortuneIdx = (fortuneIdx + 1) % GOOD_LUCK_FORTUNES.length;
1436
- fortuneText = GOOD_LUCK_FORTUNES[fortuneIdx];
1415
+ fortuneIdx = (fortuneIdx + 1) % activeFortunes.length;
1416
+ fortuneText = activeFortunes[fortuneIdx];
1437
1417
  renderHeader();
1438
1418
  }, 30000);
1439
1419
  const windowPollInterval = setInterval(updateWindowBox, 15000); // every 15s
@@ -212,28 +212,22 @@ async function selectIDEs() {
212
212
  }
213
213
  console.log('');
214
214
  }
215
- const ideOptions = [
216
- { name: 'Claude Code', value: 'claude', default: detected.includes('claude') || current === 'claude' },
217
- { name: 'Cursor', value: 'cursor', default: detected.includes('cursor') || current === 'cursor' },
218
- { name: 'Windsurf (Cascade)', value: 'windsurf', default: detected.includes('windsurf') || current === 'windsurf' }
215
+ const ideChoices = [
216
+ { name: 'Claude Code', value: 'claude', checked: detected.includes('claude') || current === 'claude' },
217
+ { name: 'Cursor', value: 'cursor', checked: detected.includes('cursor') || current === 'cursor' },
218
+ { name: 'Windsurf (Cascade)', value: 'windsurf', checked: detected.includes('windsurf') || current === 'windsurf' }
219
219
  ];
220
- const selectedIDEs = [];
221
- // Ask for each IDE individually
222
- for (const ide of ideOptions) {
223
- const answer = await inquirer_1.default.prompt({
224
- type: 'confirm',
225
- name: 'selected',
226
- message: `Configure ${ide.name}?`,
227
- default: ide.default
228
- });
229
- if (answer.selected) {
230
- selectedIDEs.push(ide.value);
231
- }
232
- }
233
- if (selectedIDEs.length === 0) {
234
- console.log(chalk_1.default.yellow('No IDEs selected. Defaulting to Claude Code.'));
235
- selectedIDEs.push('claude');
236
- }
220
+ // If nothing was auto-detected, default to Claude Code
221
+ if (!ideChoices.some(c => c.checked)) {
222
+ ideChoices[0].checked = true;
223
+ }
224
+ const { selectedIDEs } = await inquirer_1.default.prompt({
225
+ type: 'checkbox',
226
+ name: 'selectedIDEs',
227
+ message: 'Which IDEs should ekkOS configure?',
228
+ choices: ideChoices,
229
+ validate: (input) => input.length > 0 || 'Select at least one IDE (use space to toggle)'
230
+ });
237
231
  console.log('');
238
232
  return selectedIDEs;
239
233
  }
@@ -466,23 +460,36 @@ async function init(options) {
466
460
  };
467
461
  (0, fs_1.writeFileSync)(platform_1.EKKOS_CONFIG, JSON.stringify(config, null, 2));
468
462
  (0, fs_1.chmodSync)(platform_1.EKKOS_CONFIG, '600'); // Secure permissions
469
- // Summary
470
- console.log('');
471
- console.log(chalk_1.default.gray('═'.repeat(40)));
472
- console.log(chalk_1.default.green.bold('✓ Setup complete!'));
473
- console.log(chalk_1.default.gray('═'.repeat(40)));
463
+ // Verify connectivity
474
464
  console.log('');
475
- if (installedIDEs.includes('claude')) {
476
- console.log(chalk_1.default.yellow('→ Restart Claude Code to activate ekkOS'));
477
- }
478
- if (installedIDEs.includes('cursor')) {
479
- console.log(chalk_1.default.yellow('→ Restart Cursor to activate ekkOS'));
465
+ const verifySpinner = (0, ora_1.default)('Verifying connection to ekkOS...').start();
466
+ try {
467
+ const healthRes = await fetch(`${platform_1.MCP_API_URL}/health`, {
468
+ method: 'GET',
469
+ headers: { 'Authorization': `Bearer ${auth.apiKey}` }
470
+ });
471
+ if (healthRes.ok) {
472
+ verifySpinner.succeed('Connected to ekkOS API');
473
+ }
474
+ else {
475
+ verifySpinner.warn(`API returned ${healthRes.status} — check your network`);
476
+ }
480
477
  }
481
- if (installedIDEs.includes('windsurf')) {
482
- console.log(chalk_1.default.yellow(' Restart Windsurf to activate ekkOS'));
478
+ catch {
479
+ verifySpinner.warn('Could not reach ekkOS API — check your network');
483
480
  }
481
+ // Summary with prominent next step
482
+ const ideNames = installedIDEs.map(id => id === 'claude' ? 'Claude Code' : id === 'cursor' ? 'Cursor' : 'Windsurf');
483
+ console.log('');
484
+ console.log(chalk_1.default.green.bold('╔═══════════════════════════════════════╗'));
485
+ console.log(chalk_1.default.green.bold('║ ✓ Setup complete! ║'));
486
+ console.log(chalk_1.default.green.bold('╚═══════════════════════════════════════╝'));
487
+ console.log('');
488
+ console.log(chalk_1.default.yellow.bold(' NEXT STEPS:'));
489
+ console.log('');
490
+ console.log(chalk_1.default.white(` 1. Restart ${ideNames.join(' / ')}`));
491
+ console.log(chalk_1.default.white(' 2. Run ') + chalk_1.default.cyan.bold('ekkos') + chalk_1.default.white(' to start coding with memory'));
484
492
  console.log('');
485
- console.log(chalk_1.default.gray(`Run ${chalk_1.default.white('ekkos status')} to verify installation`));
486
- console.log(chalk_1.default.gray(`View dashboard: ${chalk_1.default.white('https://platform.ekkos.dev/dashboard')}`));
493
+ console.log(chalk_1.default.gray(` Dashboard: https://platform.ekkos.dev/dashboard`));
487
494
  console.log('');
488
495
  }
@@ -8,7 +8,6 @@ interface RunOptions {
8
8
  noDna?: boolean;
9
9
  noProxy?: boolean;
10
10
  dashboard?: boolean;
11
- kickstart?: boolean;
12
11
  addDirs?: string[];
13
12
  slashOpenDelayMs?: number;
14
13
  charDelayMs?: number;
@@ -261,17 +261,6 @@ const ORPHAN_MARKER_REGEX = /\[ekkOS\]\s+ORPHAN_TOOL_RESULT\s+(\{.*?\})/gi;
261
261
  // Cooldown to prevent thrashing if output repeats the marker
262
262
  const ORPHAN_DETECTION_COOLDOWN_MS = 15000;
263
263
  // ═══════════════════════════════════════════════════════════════════════════
264
- // SILENT FAILURE DETECTION - Catch API errors even when ccDNA markers missing
265
- // ═══════════════════════════════════════════════════════════════════════════
266
- // Pattern 1: API returns 400 error (often due to orphan tool_results)
267
- const API_400_REGEX = /(?:status[:\s]*400|"status":\s*400|HTTP\/\d\.\d\s+400|error.*400)/i;
268
- // Pattern 2: Anthropic API specific error about tool_result without tool_use
269
- const ORPHAN_API_ERROR_REGEX = /tool_result.*(?:no matching|without|missing).*tool_use|tool_use.*not found/i;
270
- // Pattern 3: Generic "invalid" message structure error
271
- const INVALID_MESSAGE_REGEX = /invalid.*message|message.*invalid|malformed.*request/i;
272
- // Cooldown for silent failure detection (separate from orphan marker cooldown)
273
- const SILENT_FAILURE_COOLDOWN_MS = 30000;
274
- // ═══════════════════════════════════════════════════════════════════════════
275
264
  // SESSION NAME VALIDATION (MUST use words from session-words.json)
276
265
  // This is the SOURCE OF TRUTH for valid session names
277
266
  // ═══════════════════════════════════════════════════════════════════════════
@@ -833,7 +822,6 @@ function launchWithDashboard(options) {
833
822
  runArgs.push('--skip-dna');
834
823
  if (options.noProxy)
835
824
  runArgs.push('--skip-proxy');
836
- runArgs.push('--kickstart'); // Auto-send "test" to create session immediately for dashboard
837
825
  const ekkosCmd = process.argv[1]; // Path to ekkos CLI
838
826
  const cwd = process.cwd();
839
827
  const termCols = process.stdout.columns ?? 160;
@@ -888,7 +876,52 @@ function launchWithDashboard(options) {
888
876
  console.log(chalk_1.default.gray('Falling back to normal mode. Run "ekkos dashboard --latest" in another terminal.'));
889
877
  }
890
878
  }
879
+ /**
880
+ * Launch dashboard in a separate Windows Terminal pane.
881
+ * Unlike tmux, wt.exe opens a new split pane but doesn't manage the session —
882
+ * Claude runs in the original terminal, dashboard runs in the new pane.
883
+ */
884
+ function launchWithWindowsTerminal(options) {
885
+ const ekkosCmd = process.argv[1];
886
+ const cwd = process.cwd();
887
+ const launchTime = Date.now();
888
+ // Write marker file so dashboard knows to wait for a NEW session
889
+ const markerPath = path.join(state_1.EKKOS_DIR, '.dashboard-launch-ts');
890
+ try {
891
+ fs.writeFileSync(markerPath, `${launchTime}\n${cwd}`);
892
+ }
893
+ catch { }
894
+ const dashCommand = `node "${ekkosCmd}" dashboard --wait-for-new --refresh 2000`;
895
+ try {
896
+ // wt.exe split-pane launches a new pane in the current Windows Terminal window
897
+ // -V = vertical split, -s 0.4 = 40% width for dashboard, -d = working directory
898
+ (0, child_process_1.execSync)(`wt.exe -w 0 split-pane -V -s 0.4 -d "${cwd}" cmd /c "${dashCommand}"`, { stdio: 'pipe' });
899
+ console.log(chalk_1.default.cyan('\n Dashboard launched in right pane (40%)'));
900
+ console.log(chalk_1.default.gray(' Switch panes: Alt+Arrow keys'));
901
+ }
902
+ catch (err) {
903
+ console.log(chalk_1.default.yellow(` Windows Terminal split failed: ${err.message}`));
904
+ console.log(chalk_1.default.gray(' Run "ekkos dashboard --latest" in a separate terminal'));
905
+ }
906
+ }
891
907
  async function run(options) {
908
+ // ══════════════════════════════════════════════════════════════════════════
909
+ // FIRST-RUN CHECK: If no config exists, run init first
910
+ // ══════════════════════════════════════════════════════════════════════════
911
+ const existingConfig = (0, state_1.getConfig)();
912
+ if (!existingConfig) {
913
+ console.log('');
914
+ console.log(chalk_1.default.yellow(' Welcome to ekkOS! Let\'s get you set up first.'));
915
+ console.log('');
916
+ const { init } = await Promise.resolve().then(() => __importStar(require('./init')));
917
+ await init({});
918
+ // Re-check after init — if user cancelled or it failed, exit gracefully
919
+ const postInitConfig = (0, state_1.getConfig)();
920
+ if (!postInitConfig) {
921
+ console.log(chalk_1.default.gray(' Setup incomplete. Run `ekkos init` to try again.'));
922
+ return;
923
+ }
924
+ }
892
925
  const verbose = options.verbose || false;
893
926
  const bypass = options.bypass || false;
894
927
  const noInject = options.noInject || false;
@@ -901,38 +934,26 @@ async function run(options) {
901
934
  console.log(chalk_1.default.yellow(' ⏭️ API proxy disabled (--no-proxy)'));
902
935
  }
903
936
  // ══════════════════════════════════════════════════════════════════════════
904
- // WINDOWS: Disable hooks for reliability proxy-only mode
905
- // Hooks can cause PowerShell execution policy issues and hangs on Windows.
906
- // The proxy handles context management; hooks aren't strictly required.
907
- // ══════════════════════════════════════════════════════════════════════════
908
- if (isWindows) {
909
- try {
910
- const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
911
- if (fs.existsSync(settingsPath)) {
912
- const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
913
- if (settings.hooks) {
914
- delete settings.hooks;
915
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
916
- console.log(chalk_1.default.gray(' ⏭️ Hooks disabled (Windows proxy-only mode)'));
917
- }
918
- }
919
- }
920
- catch {
921
- // Non-fatal — continue without hook cleanup
922
- }
923
- }
924
- // ══════════════════════════════════════════════════════════════════════════
925
- // DASHBOARD MODE: Launch via tmux with isolated dashboard pane (60/40)
937
+ // DASHBOARD MODE: Launch via tmux (Unix) or Windows Terminal split pane
926
938
  // ══════════════════════════════════════════════════════════════════════════
927
939
  if (options.dashboard) {
928
940
  if (isWindows) {
929
- console.log(chalk_1.default.yellow(' Dashboard split-pane requires tmux (not available on Windows)'));
930
- console.log(chalk_1.default.gray(' Alternative: run "ekkos dashboard --latest" in a separate terminal'));
931
- console.log(chalk_1.default.gray(' Continuing without dashboard...\n'));
941
+ // Windows: launch dashboard in a separate Windows Terminal pane
942
+ try {
943
+ (0, child_process_1.execSync)('where wt.exe', { encoding: 'utf-8', stdio: 'pipe' });
944
+ launchWithWindowsTerminal(options);
945
+ // Don't return — continue to launch Claude in this terminal
946
+ }
947
+ catch {
948
+ console.log(chalk_1.default.yellow(' Windows Terminal (wt.exe) not found.'));
949
+ console.log(chalk_1.default.gray(' Alternative: run "ekkos dashboard --latest" in a separate terminal'));
950
+ console.log(chalk_1.default.gray(' Continuing without dashboard...\n'));
951
+ }
932
952
  }
933
953
  else {
954
+ // Unix: launch via tmux split pane
934
955
  try {
935
- (0, child_process_1.execSync)('command -v tmux', { encoding: 'utf-8', stdio: 'pipe' }).trim();
956
+ (0, child_process_1.execSync)('command -v tmux', { encoding: 'utf-8', stdio: 'pipe' });
936
957
  launchWithDashboard(options);
937
958
  return;
938
959
  }
@@ -1493,11 +1514,6 @@ async function run(options) {
1493
1514
  let orphanScanCursor = 0;
1494
1515
  const ORPHAN_SCAN_TAIL_SLACK = 256; // Keep some overlap for chunk boundary tolerance
1495
1516
  // ══════════════════════════════════════════════════════════════════════════
1496
- // SILENT FAILURE DETECTION - Catch API errors even without ccDNA markers
1497
- // ══════════════════════════════════════════════════════════════════════════
1498
- let lastSilentFailureTime = 0;
1499
- let silentFailureCount = 0;
1500
- const MAX_SILENT_FAILURES_BEFORE_ALERT = 2; // Alert user after 2 silent failures
1501
1517
  // ══════════════════════════════════════════════════════════════════════════
1502
1518
  // TURN-END EVICTION - Only clean up when Claude is idle (safe state)
1503
1519
  // ══════════════════════════════════════════════════════════════════════════
@@ -1992,6 +2008,11 @@ async function run(options) {
1992
2008
  // The in-memory state is fine; we just fix the disk and log the bug.
1993
2009
  // ══════════════════════════════════════════════════════════════════════════
1994
2010
  function handleOrphanToolResult(orphan) {
2011
+ // Proxy is sole eviction authority — never touch JSONL in proxy mode
2012
+ if (proxyModeEnabled) {
2013
+ dlog('Orphan recovery skipped - proxy mode (proxy is sole authority)');
2014
+ return;
2015
+ }
1995
2016
  const now = Date.now();
1996
2017
  if (isOrphanRecoveryInProgress) {
1997
2018
  dlog('Orphan recovery already in progress, ignoring');
@@ -2193,71 +2214,6 @@ async function run(options) {
2193
2214
  }, TEST_POLL_MS);
2194
2215
  }
2195
2216
  // ══════════════════════════════════════════════════════════════════════════
2196
- // SILENT FAILURE DETECTION HANDLER
2197
- // Catches API 400 errors and orphan-related messages even without ccDNA markers
2198
- // This is a backup for when ccDNA validate mode isn't working or is disabled
2199
- // ══════════════════════════════════════════════════════════════════════════
2200
- function handleSilentFailure(matchType, matchedText) {
2201
- const now = Date.now();
2202
- // Cooldown check
2203
- if (now - lastSilentFailureTime < SILENT_FAILURE_COOLDOWN_MS) {
2204
- dlog(`Silent failure suppressed by cooldown (${matchType})`);
2205
- return;
2206
- }
2207
- // Don't trigger during active recovery
2208
- if (isOrphanRecoveryInProgress || isAutoClearInProgress) {
2209
- dlog(`Silent failure ignored - recovery in progress (${matchType})`);
2210
- return;
2211
- }
2212
- lastSilentFailureTime = now;
2213
- silentFailureCount++;
2214
- evictionDebugLog('SILENT_FAILURE_DETECTED', '════════════════════════════════════════════════════════', {
2215
- alert: '⚠️ SILENT FAILURE - API error detected without ccDNA marker',
2216
- matchType,
2217
- matchedText: matchedText.slice(0, 200),
2218
- silentFailureCount,
2219
- transcriptPath,
2220
- diagnosis: 'Possible orphan tool_result or ccDNA not in validate mode',
2221
- });
2222
- console.log(`\n[ekkOS] ⚠️ Silent failure detected: ${matchType}`);
2223
- // After multiple failures, alert user and suggest action
2224
- if (silentFailureCount >= MAX_SILENT_FAILURES_BEFORE_ALERT) {
2225
- console.log(`[ekkOS] ⚠️ Multiple API errors detected (${silentFailureCount}x)`);
2226
- console.log(`[ekkOS] This may indicate orphan tool_results in the transcript`);
2227
- console.log(`[ekkOS] Try: /clear then /continue to rebuild message state`);
2228
- evictionDebugLog('SILENT_FAILURE_ALERT', 'Multiple silent failures - user alerted', {
2229
- count: silentFailureCount,
2230
- suggestion: '/clear + /continue',
2231
- });
2232
- // Reset counter after alerting
2233
- silentFailureCount = 0;
2234
- }
2235
- // Attempt proactive repair if we have a transcript
2236
- if (transcriptPath && validateTranscriptPath(transcriptPath) && !hasInFlightTools()) {
2237
- dlog('Attempting proactive repair due to silent failure');
2238
- try {
2239
- // eslint-disable-next-line @typescript-eslint/no-require-imports
2240
- const { countOrphansInJsonl } = require('../capture/transcript-repair');
2241
- const { orphans: orphanCount, orphanIds } = countOrphansInJsonl(transcriptPath);
2242
- if (orphanCount > 0) {
2243
- evictionDebugLog('SILENT_FAILURE_ORPHANS_FOUND', `Proactive scan found ${orphanCount} orphans`, {
2244
- transcriptPath,
2245
- orphanCount,
2246
- orphanIds: orphanIds.slice(0, 5), // Log first 5 IDs
2247
- });
2248
- console.log(`[ekkOS] 🔍 Found ${orphanCount} orphan(s) in transcript - triggering repair`);
2249
- // Trigger orphan recovery (reuse existing handler)
2250
- void handleOrphanToolResult({ idx: -1, tool_use_id: 'silent_failure_detected' });
2251
- }
2252
- else {
2253
- dlog('Proactive scan found no orphans - API error may be unrelated');
2254
- }
2255
- }
2256
- catch (err) {
2257
- dlog(`Proactive repair scan failed: ${err.message}`);
2258
- }
2259
- }
2260
- }
2261
2217
  // Monitor PTY output
2262
2218
  shell.onData((data) => {
2263
2219
  // Pass through to terminal
@@ -2269,38 +2225,20 @@ async function run(options) {
2269
2225
  outputBuffer = outputBuffer.slice(-2000);
2270
2226
  }
2271
2227
  // ══════════════════════════════════════════════════════════════════════════
2272
- // ORPHAN TOOL_RESULT DETECTION
2228
+ // ORPHAN TOOL_RESULT DETECTION (LOCAL MODE ONLY)
2273
2229
  // ccDNA validate mode emits [ekkOS] ORPHAN_TOOL_RESULT when it detects
2274
2230
  // tool_results without matching tool_uses. This triggers automatic repair.
2275
- // Uses separate larger buffer to avoid truncation issues.
2231
+ // DISABLED in proxy mode: proxy is sole eviction authority, CLI must not
2232
+ // touch the local JSONL. Also requires ccDNA which is currently disabled.
2276
2233
  // ══════════════════════════════════════════════════════════════════════════
2277
- if (!isAutoClearInProgress && !isOrphanRecoveryInProgress) {
2278
- // Append to orphan detection buffer (larger than main buffer to catch full markers)
2234
+ if (!proxyModeEnabled && !isAutoClearInProgress && !isOrphanRecoveryInProgress) {
2279
2235
  orphanDetectionBuffer += stripAnsi(data);
2280
2236
  if (orphanDetectionBuffer.length > 10000) {
2281
2237
  const trimAmount = orphanDetectionBuffer.length - 8000;
2282
2238
  orphanDetectionBuffer = orphanDetectionBuffer.slice(-8000);
2283
- // Adjust cursor to account for trimmed portion
2284
2239
  orphanScanCursor = Math.max(0, orphanScanCursor - trimAmount);
2285
2240
  }
2286
- // Run detection (extracted function for testability)
2287
2241
  runOrphanDetection();
2288
- // ════════════════════════════════════════════════════════════════════════
2289
- // SILENT FAILURE DETECTION - Catch API errors without ccDNA markers
2290
- // ════════════════════════════════════════════════════════════════════════
2291
- const normalizedForSilent = stripAnsi(data);
2292
- // Check for API 400 errors
2293
- if (API_400_REGEX.test(normalizedForSilent)) {
2294
- handleSilentFailure('API_400', normalizedForSilent.match(API_400_REGEX)?.[0] || '400');
2295
- }
2296
- // Check for explicit orphan-related API error messages
2297
- else if (ORPHAN_API_ERROR_REGEX.test(normalizedForSilent)) {
2298
- handleSilentFailure('ORPHAN_API_ERROR', normalizedForSilent.match(ORPHAN_API_ERROR_REGEX)?.[0] || 'orphan');
2299
- }
2300
- // Check for generic invalid message errors
2301
- else if (INVALID_MESSAGE_REGEX.test(normalizedForSilent)) {
2302
- handleSilentFailure('INVALID_MESSAGE', normalizedForSilent.match(INVALID_MESSAGE_REGEX)?.[0] || 'invalid');
2303
- }
2304
2242
  }
2305
2243
  // Try to extract transcript path from output (Claude shows it on startup)
2306
2244
  // CRITICAL: Strip ANSI codes FIRST to prevent capturing terminal garbage
@@ -2520,36 +2458,6 @@ async function run(options) {
2520
2458
  }
2521
2459
  });
2522
2460
  // ══════════════════════════════════════════════════════════════════════════
2523
- // KICKSTART MODE: Auto-send "test" to create session immediately
2524
- // Used by --dashboard to eliminate wait for first user message
2525
- // ══════════════════════════════════════════════════════════════════════════
2526
- if (options.kickstart) {
2527
- dlog('Kickstart mode enabled - will auto-send "test" to create session');
2528
- setTimeout(async () => {
2529
- dlog('Starting kickstart injection...');
2530
- const readiness = await waitForIdlePrompt(getOutputBuffer, config);
2531
- if (!readiness.ready || readiness.interrupted) {
2532
- dlog('Claude not ready for kickstart - aborting');
2533
- return;
2534
- }
2535
- // PAUSE STDIN during injection
2536
- process.stdin.off('data', onStdinData);
2537
- dlog('Stdin paused during kickstart');
2538
- try {
2539
- shell.write('\x15'); // Ctrl+U - clear any existing input
2540
- await sleep(60);
2541
- await typeSlowly(shell, 'test', config.charDelayMs);
2542
- await sleep(100);
2543
- shell.write('\r'); // Enter
2544
- dlog('Kickstart "test" sent - session should be created');
2545
- }
2546
- finally {
2547
- process.stdin.on('data', onStdinData);
2548
- dlog('Stdin resumed after kickstart');
2549
- }
2550
- }, 3000); // 3s for Claude to initialize
2551
- }
2552
- // ══════════════════════════════════════════════════════════════════════════
2553
2461
  // RESEARCH MODE: Auto-type research prompt after Claude is ready
2554
2462
  // Triggers: `ekkos run -r` or `ekkos run --research`
2555
2463
  // Works like /clear continue - waits for idle prompt, then injects text
package/dist/index.js CHANGED
@@ -51,9 +51,6 @@ const agent_1 = require("./commands/agent");
51
51
  const state_1 = require("./utils/state");
52
52
  const index_1 = require("./commands/usage/index");
53
53
  const dashboard_1 = require("./commands/dashboard");
54
- const swarm_1 = require("./commands/swarm");
55
- const swarm_dashboard_1 = require("./commands/swarm-dashboard");
56
- const swarm_setup_1 = require("./commands/swarm-setup");
57
54
  const chalk_1 = __importDefault(require("chalk"));
58
55
  const fs = __importStar(require("fs"));
59
56
  const path = __importStar(require("path"));
@@ -79,7 +76,7 @@ commander_1.program
79
76
  ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos run -b')} ${chalk_1.default.gray('Launch with bypass permissions mode')}`,
80
77
  ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos doctor --fix')} ${chalk_1.default.gray('Check and auto-fix system prerequisites')}`,
81
78
  ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos usage daily')} ${chalk_1.default.gray("View today's token usage and costs")}`,
82
- ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos swarm launch -t "build X"')} ${chalk_1.default.gray('Launch parallel workers on a task')}`,
79
+ ` ${chalk_1.default.gray('$')} ${chalk_1.default.white('ekkos sessions')} ${chalk_1.default.gray('List active Claude Code sessions')}`,
83
80
  '',
84
81
  chalk_1.default.gray(' Run ') + chalk_1.default.white('ekkos <command> --help') + chalk_1.default.gray(' for detailed options on any command.'),
85
82
  '',
@@ -157,7 +154,7 @@ commander_1.program
157
154
  title: 'Running',
158
155
  icon: '▸',
159
156
  commands: [
160
- { name: 'run', desc: 'Launch Claude Code with ekkOS memory + auto-continue', note: 'default' },
157
+ { name: 'run', desc: 'Start Claude Code with ekkOS memory', note: 'default' },
161
158
  { name: 'test-claude', desc: 'Launch Claude with proxy only (no ccDNA/PTY) for debugging' },
162
159
  { name: 'sessions', desc: 'List active Claude Code sessions' },
163
160
  ],
@@ -180,13 +177,6 @@ commander_1.program
180
177
  { name: 'agent', desc: 'Manage the remote terminal agent (start, stop, status, logs)' },
181
178
  ],
182
179
  },
183
- {
184
- title: 'Swarm (Multi-Agent)',
185
- icon: '▸',
186
- commands: [
187
- { name: 'swarm', desc: 'Parallel workers, Q-learning routing, swarm dashboard' },
188
- ],
189
- },
190
180
  ];
191
181
  const padCmd = 18;
192
182
  let output = '';
@@ -239,7 +229,7 @@ commander_1.program
239
229
  // Run command - launches Claude with auto-continue wrapper
240
230
  commander_1.program
241
231
  .command('run')
242
- .description('Launch Claude Code with auto-continue (auto /clear + /continue when context is high)')
232
+ .description('Start Claude Code with ekkOS memory (default command)')
243
233
  .option('-s, --session <name>', 'Session name to restore on clear')
244
234
  .option('-b, --bypass', 'Enable bypass permissions mode (dangerously skip all permission checks)')
245
235
  .option('-v, --verbose', 'Show debug output')
@@ -249,7 +239,6 @@ commander_1.program
249
239
  .option('--skip-dna', 'Skip ccDNA injection (bypass Claude Code patching)')
250
240
  .option('--skip-proxy', 'Skip API proxy (use direct Anthropic API, disables seamless context eviction)')
251
241
  .option('--dashboard', 'Launch with live usage dashboard in an isolated 60/40 tmux split (requires tmux)')
252
- .option('--kickstart', 'Auto-send "test" on load to create session immediately (used internally by --dashboard)')
253
242
  .option('--add-dir <dirs...>', 'Additional directories Claude Code can access (outside working directory)')
254
243
  .action((options) => {
255
244
  (0, run_1.run)({
@@ -262,7 +251,6 @@ commander_1.program
262
251
  noDna: options.skipDna,
263
252
  noProxy: options.skipProxy,
264
253
  dashboard: options.dashboard,
265
- kickstart: options.kickstart,
266
254
  addDirs: options.addDir,
267
255
  });
268
256
  });
@@ -354,10 +342,10 @@ hooksCmd
354
342
  (0, index_1.registerUsageCommand)(commander_1.program);
355
343
  // Dashboard command - live TUI for monitoring session usage
356
344
  commander_1.program.addCommand(dashboard_1.dashboardCommand);
357
- // Sessions command - list active Claude Code sessions (swarm support)
345
+ // Sessions command - list active Claude Code sessions
358
346
  commander_1.program
359
347
  .command('sessions')
360
- .description('List active Claude Code sessions (for swarm/multi-session support)')
348
+ .description('List active Claude Code sessions')
361
349
  .option('-j, --json', 'Output machine-readable JSON')
362
350
  .action((options) => {
363
351
  const sessions = (0, state_1.getActiveSessions)();
@@ -482,59 +470,6 @@ agentCmd
482
470
  .action((options) => {
483
471
  (0, agent_1.agentHealth)({ json: options.json });
484
472
  });
485
- // Swarm command - manage Q-learning routing
486
- const swarmCmd = commander_1.program
487
- .command('swarm')
488
- .description('Manage Swarm Q-learning model routing');
489
- swarmCmd
490
- .command('status')
491
- .description('Show Q-table stats (states, visits, epsilon, top actions)')
492
- .action(swarm_1.swarmStatus);
493
- swarmCmd
494
- .command('reset')
495
- .description('Clear Q-table from Redis (routing reverts to static rules)')
496
- .action(swarm_1.swarmReset);
497
- swarmCmd
498
- .command('export')
499
- .description('Export Q-table to .swarm/q-learning-model.json')
500
- .action(swarm_1.swarmExport);
501
- swarmCmd
502
- .command('import')
503
- .description('Import Q-table from .swarm/q-learning-model.json into Redis')
504
- .action(swarm_1.swarmImport);
505
- swarmCmd
506
- .command('launch')
507
- .description('Launch parallel workers on a decomposed task (opens wizard if --task is omitted)')
508
- .option('-w, --workers <count>', 'Number of parallel workers (2-8)', parseInt)
509
- .option('-t, --task <task>', 'Task description to decompose and execute')
510
- .option('--no-bypass', 'Disable bypass permissions mode')
511
- .option('--no-decompose', 'Skip AI decomposition (send same task to all workers)')
512
- .option('--no-queen', 'Skip launching the Python Queen coordinator')
513
- .option('--queen-strategy <strategy>', 'Queen strategy (adaptive-default, hierarchical-cascade, mesh-consensus)')
514
- .option('-v, --verbose', 'Show debug output')
515
- .action((options) => {
516
- // Auto-open wizard when --task is missing
517
- if (!options.task) {
518
- (0, swarm_setup_1.swarmSetup)();
519
- return;
520
- }
521
- (0, swarm_1.swarmLaunch)({
522
- workers: options.workers || 4,
523
- task: options.task,
524
- bypass: options.bypass !== false,
525
- noDecompose: options.decompose === false,
526
- noQueen: options.queen === false,
527
- queenStrategy: options.queenStrategy,
528
- verbose: options.verbose,
529
- });
530
- });
531
- swarmCmd
532
- .command('setup')
533
- .description('Interactive TUI wizard for configuring and launching a swarm')
534
- .action(() => {
535
- (0, swarm_setup_1.swarmSetup)();
536
- });
537
- swarmCmd.addCommand(swarm_dashboard_1.swarmDashboardCommand);
538
473
  // Handle `-help` (single dash) — rewrite to `--help` for Commander compatibility
539
474
  const helpIdx = process.argv.indexOf('-help');
540
475
  if (helpIdx !== -1) {
@@ -80,7 +80,7 @@ export declare function getMostRecentSession(): {
80
80
  */
81
81
  export declare function getActiveSessions(): ActiveSession[];
82
82
  /**
83
- * Register a new active session (for swarm tracking)
83
+ * Register a new active session
84
84
  */
85
85
  export declare function registerActiveSession(sessionId: string, sessionName: string, projectPath: string): ActiveSession;
86
86
  /**
@@ -244,7 +244,7 @@ function getMostRecentSession() {
244
244
  }
245
245
  }
246
246
  // ═══════════════════════════════════════════════════════════════════════════
247
- // MULTI-SESSION/SWARM SUPPORT
247
+ // MULTI-SESSION SUPPORT
248
248
  // Track multiple concurrent Claude Code sessions without state collision
249
249
  // ═══════════════════════════════════════════════════════════════════════════
250
250
  /**
@@ -271,7 +271,7 @@ function getActiveSessions() {
271
271
  }
272
272
  }
273
273
  /**
274
- * Register a new active session (for swarm tracking)
274
+ * Register a new active session
275
275
  */
276
276
  function registerActiveSession(sessionId, sessionName, projectPath) {
277
277
  ensureEkkosDir();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekkos/cli",
3
- "version": "1.1.6",
3
+ "version": "1.2.1",
4
4
  "description": "Setup ekkOS memory for AI coding assistants (Claude Code, Cursor, Windsurf)",
5
5
  "main": "dist/index.js",
6
6
  "bin": {