@ekkos/cli 1.1.5 → 1.2.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.
@@ -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
  }
@@ -257,18 +251,28 @@ async function deployForClaude(apiKey, userId, options) {
257
251
  catch (error) {
258
252
  spinner.fail('MCP server configuration failed');
259
253
  }
260
- // Settings.json (hook registration)
261
- spinner = (0, ora_1.default)('Deploying hooks configuration...').start();
262
- try {
263
- (0, settings_1.deployClaudeSettings)();
254
+ // Settings.json (hook registration) — skipped on Windows (proxy-only mode)
255
+ if (platform_1.isWindows) {
256
+ spinner = (0, ora_1.default)('Hooks skipped (Windows proxy-only mode)').start();
257
+ spinner.info('Hooks skipped (Windows uses proxy-only mode)');
264
258
  result.settings = true;
265
- spinner.succeed('Hooks configuration');
266
259
  }
267
- catch (error) {
268
- spinner.fail('Hooks configuration failed');
260
+ else {
261
+ spinner = (0, ora_1.default)('Deploying hooks configuration...').start();
262
+ try {
263
+ (0, settings_1.deployClaudeSettings)();
264
+ result.settings = true;
265
+ spinner.succeed('Hooks configuration');
266
+ }
267
+ catch (error) {
268
+ spinner.fail('Hooks configuration failed');
269
+ }
269
270
  }
270
- // Hook scripts
271
- if (!options.skipHooks) {
271
+ // Hook scripts — skipped on Windows
272
+ if (platform_1.isWindows) {
273
+ // Skip hook scripts on Windows
274
+ }
275
+ else if (!options.skipHooks) {
272
276
  spinner = (0, ora_1.default)('Deploying hook scripts...').start();
273
277
  try {
274
278
  result.hooks = (0, hooks_1.deployHooks)(apiKey);
@@ -456,23 +460,36 @@ async function init(options) {
456
460
  };
457
461
  (0, fs_1.writeFileSync)(platform_1.EKKOS_CONFIG, JSON.stringify(config, null, 2));
458
462
  (0, fs_1.chmodSync)(platform_1.EKKOS_CONFIG, '600'); // Secure permissions
459
- // Summary
460
- console.log('');
461
- console.log(chalk_1.default.gray('═'.repeat(40)));
462
- console.log(chalk_1.default.green.bold('✓ Setup complete!'));
463
- console.log(chalk_1.default.gray('═'.repeat(40)));
463
+ // Verify connectivity
464
464
  console.log('');
465
- if (installedIDEs.includes('claude')) {
466
- console.log(chalk_1.default.yellow('→ Restart Claude Code to activate ekkOS'));
467
- }
468
- if (installedIDEs.includes('cursor')) {
469
- 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
+ }
470
477
  }
471
- if (installedIDEs.includes('windsurf')) {
472
- console.log(chalk_1.default.yellow(' Restart Windsurf to activate ekkOS'));
478
+ catch {
479
+ verifySpinner.warn('Could not reach ekkOS API — check your network');
473
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'));
474
492
  console.log('');
475
- console.log(chalk_1.default.gray(`Run ${chalk_1.default.white('ekkos status')} to verify installation`));
476
- 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`));
477
494
  console.log('');
478
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;
@@ -889,6 +877,23 @@ function launchWithDashboard(options) {
889
877
  }
890
878
  }
891
879
  async function run(options) {
880
+ // ══════════════════════════════════════════════════════════════════════════
881
+ // FIRST-RUN CHECK: If no config exists, run init first
882
+ // ══════════════════════════════════════════════════════════════════════════
883
+ const existingConfig = (0, state_1.getConfig)();
884
+ if (!existingConfig) {
885
+ console.log('');
886
+ console.log(chalk_1.default.yellow(' Welcome to ekkOS! Let\'s get you set up first.'));
887
+ console.log('');
888
+ const { init } = await Promise.resolve().then(() => __importStar(require('./init')));
889
+ await init({});
890
+ // Re-check after init — if user cancelled or it failed, exit gracefully
891
+ const postInitConfig = (0, state_1.getConfig)();
892
+ if (!postInitConfig) {
893
+ console.log(chalk_1.default.gray(' Setup incomplete. Run `ekkos init` to try again.'));
894
+ return;
895
+ }
896
+ }
892
897
  const verbose = options.verbose || false;
893
898
  const bypass = options.bypass || false;
894
899
  const noInject = options.noInject || false;
@@ -901,26 +906,44 @@ async function run(options) {
901
906
  console.log(chalk_1.default.yellow(' ⏭️ API proxy disabled (--no-proxy)'));
902
907
  }
903
908
  // ══════════════════════════════════════════════════════════════════════════
909
+ // WINDOWS: Disable hooks for reliability — proxy-only mode
910
+ // Hooks can cause PowerShell execution policy issues and hangs on Windows.
911
+ // The proxy handles context management; hooks aren't strictly required.
912
+ // ══════════════════════════════════════════════════════════════════════════
913
+ if (isWindows) {
914
+ try {
915
+ const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
916
+ if (fs.existsSync(settingsPath)) {
917
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
918
+ if (settings.hooks) {
919
+ delete settings.hooks;
920
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
921
+ console.log(chalk_1.default.gray(' ⏭️ Hooks disabled (Windows proxy-only mode)'));
922
+ }
923
+ }
924
+ }
925
+ catch {
926
+ // Non-fatal — continue without hook cleanup
927
+ }
928
+ }
929
+ // ══════════════════════════════════════════════════════════════════════════
904
930
  // DASHBOARD MODE: Launch via tmux with isolated dashboard pane (60/40)
905
931
  // ══════════════════════════════════════════════════════════════════════════
906
932
  if (options.dashboard) {
907
- if (isWindows) {
908
- console.log(chalk_1.default.yellow(' Dashboard split-pane requires tmux (not available on Windows)'));
933
+ try {
934
+ const tmuxCheck = isWindows ? 'where tmux' : 'command -v tmux';
935
+ (0, child_process_1.execSync)(tmuxCheck, { encoding: 'utf-8', stdio: 'pipe' }).trim();
936
+ launchWithDashboard(options);
937
+ return;
938
+ }
939
+ catch {
940
+ const installHint = isWindows
941
+ ? 'Install: scoop install tmux (or use WSL)'
942
+ : 'Install: brew install tmux';
943
+ console.log(chalk_1.default.yellow(` tmux not found. ${installHint}`));
909
944
  console.log(chalk_1.default.gray(' Alternative: run "ekkos dashboard --latest" in a separate terminal'));
910
945
  console.log(chalk_1.default.gray(' Continuing without dashboard...\n'));
911
946
  }
912
- else {
913
- try {
914
- (0, child_process_1.execSync)('command -v tmux', { encoding: 'utf-8', stdio: 'pipe' }).trim();
915
- launchWithDashboard(options);
916
- return;
917
- }
918
- catch {
919
- console.log(chalk_1.default.yellow(' tmux not found. Install: brew install tmux'));
920
- console.log(chalk_1.default.gray(' Alternative: run "ekkos dashboard --latest" in a separate terminal'));
921
- console.log(chalk_1.default.gray(' Continuing without dashboard...\n'));
922
- }
923
- }
924
947
  }
925
948
  // Generate instance ID for this run
926
949
  const instanceId = generateInstanceId();
@@ -1472,11 +1495,6 @@ async function run(options) {
1472
1495
  let orphanScanCursor = 0;
1473
1496
  const ORPHAN_SCAN_TAIL_SLACK = 256; // Keep some overlap for chunk boundary tolerance
1474
1497
  // ══════════════════════════════════════════════════════════════════════════
1475
- // SILENT FAILURE DETECTION - Catch API errors even without ccDNA markers
1476
- // ══════════════════════════════════════════════════════════════════════════
1477
- let lastSilentFailureTime = 0;
1478
- let silentFailureCount = 0;
1479
- const MAX_SILENT_FAILURES_BEFORE_ALERT = 2; // Alert user after 2 silent failures
1480
1498
  // ══════════════════════════════════════════════════════════════════════════
1481
1499
  // TURN-END EVICTION - Only clean up when Claude is idle (safe state)
1482
1500
  // ══════════════════════════════════════════════════════════════════════════
@@ -1971,6 +1989,11 @@ async function run(options) {
1971
1989
  // The in-memory state is fine; we just fix the disk and log the bug.
1972
1990
  // ══════════════════════════════════════════════════════════════════════════
1973
1991
  function handleOrphanToolResult(orphan) {
1992
+ // Proxy is sole eviction authority — never touch JSONL in proxy mode
1993
+ if (proxyModeEnabled) {
1994
+ dlog('Orphan recovery skipped - proxy mode (proxy is sole authority)');
1995
+ return;
1996
+ }
1974
1997
  const now = Date.now();
1975
1998
  if (isOrphanRecoveryInProgress) {
1976
1999
  dlog('Orphan recovery already in progress, ignoring');
@@ -2172,71 +2195,6 @@ async function run(options) {
2172
2195
  }, TEST_POLL_MS);
2173
2196
  }
2174
2197
  // ══════════════════════════════════════════════════════════════════════════
2175
- // SILENT FAILURE DETECTION HANDLER
2176
- // Catches API 400 errors and orphan-related messages even without ccDNA markers
2177
- // This is a backup for when ccDNA validate mode isn't working or is disabled
2178
- // ══════════════════════════════════════════════════════════════════════════
2179
- function handleSilentFailure(matchType, matchedText) {
2180
- const now = Date.now();
2181
- // Cooldown check
2182
- if (now - lastSilentFailureTime < SILENT_FAILURE_COOLDOWN_MS) {
2183
- dlog(`Silent failure suppressed by cooldown (${matchType})`);
2184
- return;
2185
- }
2186
- // Don't trigger during active recovery
2187
- if (isOrphanRecoveryInProgress || isAutoClearInProgress) {
2188
- dlog(`Silent failure ignored - recovery in progress (${matchType})`);
2189
- return;
2190
- }
2191
- lastSilentFailureTime = now;
2192
- silentFailureCount++;
2193
- evictionDebugLog('SILENT_FAILURE_DETECTED', '════════════════════════════════════════════════════════', {
2194
- alert: '⚠️ SILENT FAILURE - API error detected without ccDNA marker',
2195
- matchType,
2196
- matchedText: matchedText.slice(0, 200),
2197
- silentFailureCount,
2198
- transcriptPath,
2199
- diagnosis: 'Possible orphan tool_result or ccDNA not in validate mode',
2200
- });
2201
- console.log(`\n[ekkOS] ⚠️ Silent failure detected: ${matchType}`);
2202
- // After multiple failures, alert user and suggest action
2203
- if (silentFailureCount >= MAX_SILENT_FAILURES_BEFORE_ALERT) {
2204
- console.log(`[ekkOS] ⚠️ Multiple API errors detected (${silentFailureCount}x)`);
2205
- console.log(`[ekkOS] This may indicate orphan tool_results in the transcript`);
2206
- console.log(`[ekkOS] Try: /clear then /continue to rebuild message state`);
2207
- evictionDebugLog('SILENT_FAILURE_ALERT', 'Multiple silent failures - user alerted', {
2208
- count: silentFailureCount,
2209
- suggestion: '/clear + /continue',
2210
- });
2211
- // Reset counter after alerting
2212
- silentFailureCount = 0;
2213
- }
2214
- // Attempt proactive repair if we have a transcript
2215
- if (transcriptPath && validateTranscriptPath(transcriptPath) && !hasInFlightTools()) {
2216
- dlog('Attempting proactive repair due to silent failure');
2217
- try {
2218
- // eslint-disable-next-line @typescript-eslint/no-require-imports
2219
- const { countOrphansInJsonl } = require('../capture/transcript-repair');
2220
- const { orphans: orphanCount, orphanIds } = countOrphansInJsonl(transcriptPath);
2221
- if (orphanCount > 0) {
2222
- evictionDebugLog('SILENT_FAILURE_ORPHANS_FOUND', `Proactive scan found ${orphanCount} orphans`, {
2223
- transcriptPath,
2224
- orphanCount,
2225
- orphanIds: orphanIds.slice(0, 5), // Log first 5 IDs
2226
- });
2227
- console.log(`[ekkOS] 🔍 Found ${orphanCount} orphan(s) in transcript - triggering repair`);
2228
- // Trigger orphan recovery (reuse existing handler)
2229
- void handleOrphanToolResult({ idx: -1, tool_use_id: 'silent_failure_detected' });
2230
- }
2231
- else {
2232
- dlog('Proactive scan found no orphans - API error may be unrelated');
2233
- }
2234
- }
2235
- catch (err) {
2236
- dlog(`Proactive repair scan failed: ${err.message}`);
2237
- }
2238
- }
2239
- }
2240
2198
  // Monitor PTY output
2241
2199
  shell.onData((data) => {
2242
2200
  // Pass through to terminal
@@ -2248,38 +2206,20 @@ async function run(options) {
2248
2206
  outputBuffer = outputBuffer.slice(-2000);
2249
2207
  }
2250
2208
  // ══════════════════════════════════════════════════════════════════════════
2251
- // ORPHAN TOOL_RESULT DETECTION
2209
+ // ORPHAN TOOL_RESULT DETECTION (LOCAL MODE ONLY)
2252
2210
  // ccDNA validate mode emits [ekkOS] ORPHAN_TOOL_RESULT when it detects
2253
2211
  // tool_results without matching tool_uses. This triggers automatic repair.
2254
- // Uses separate larger buffer to avoid truncation issues.
2212
+ // DISABLED in proxy mode: proxy is sole eviction authority, CLI must not
2213
+ // touch the local JSONL. Also requires ccDNA which is currently disabled.
2255
2214
  // ══════════════════════════════════════════════════════════════════════════
2256
- if (!isAutoClearInProgress && !isOrphanRecoveryInProgress) {
2257
- // Append to orphan detection buffer (larger than main buffer to catch full markers)
2215
+ if (!proxyModeEnabled && !isAutoClearInProgress && !isOrphanRecoveryInProgress) {
2258
2216
  orphanDetectionBuffer += stripAnsi(data);
2259
2217
  if (orphanDetectionBuffer.length > 10000) {
2260
2218
  const trimAmount = orphanDetectionBuffer.length - 8000;
2261
2219
  orphanDetectionBuffer = orphanDetectionBuffer.slice(-8000);
2262
- // Adjust cursor to account for trimmed portion
2263
2220
  orphanScanCursor = Math.max(0, orphanScanCursor - trimAmount);
2264
2221
  }
2265
- // Run detection (extracted function for testability)
2266
2222
  runOrphanDetection();
2267
- // ════════════════════════════════════════════════════════════════════════
2268
- // SILENT FAILURE DETECTION - Catch API errors without ccDNA markers
2269
- // ════════════════════════════════════════════════════════════════════════
2270
- const normalizedForSilent = stripAnsi(data);
2271
- // Check for API 400 errors
2272
- if (API_400_REGEX.test(normalizedForSilent)) {
2273
- handleSilentFailure('API_400', normalizedForSilent.match(API_400_REGEX)?.[0] || '400');
2274
- }
2275
- // Check for explicit orphan-related API error messages
2276
- else if (ORPHAN_API_ERROR_REGEX.test(normalizedForSilent)) {
2277
- handleSilentFailure('ORPHAN_API_ERROR', normalizedForSilent.match(ORPHAN_API_ERROR_REGEX)?.[0] || 'orphan');
2278
- }
2279
- // Check for generic invalid message errors
2280
- else if (INVALID_MESSAGE_REGEX.test(normalizedForSilent)) {
2281
- handleSilentFailure('INVALID_MESSAGE', normalizedForSilent.match(INVALID_MESSAGE_REGEX)?.[0] || 'invalid');
2282
- }
2283
2223
  }
2284
2224
  // Try to extract transcript path from output (Claude shows it on startup)
2285
2225
  // CRITICAL: Strip ANSI codes FIRST to prevent capturing terminal garbage
@@ -2499,36 +2439,6 @@ async function run(options) {
2499
2439
  }
2500
2440
  });
2501
2441
  // ══════════════════════════════════════════════════════════════════════════
2502
- // KICKSTART MODE: Auto-send "test" to create session immediately
2503
- // Used by --dashboard to eliminate wait for first user message
2504
- // ══════════════════════════════════════════════════════════════════════════
2505
- if (options.kickstart) {
2506
- dlog('Kickstart mode enabled - will auto-send "test" to create session');
2507
- setTimeout(async () => {
2508
- dlog('Starting kickstart injection...');
2509
- const readiness = await waitForIdlePrompt(getOutputBuffer, config);
2510
- if (!readiness.ready || readiness.interrupted) {
2511
- dlog('Claude not ready for kickstart - aborting');
2512
- return;
2513
- }
2514
- // PAUSE STDIN during injection
2515
- process.stdin.off('data', onStdinData);
2516
- dlog('Stdin paused during kickstart');
2517
- try {
2518
- shell.write('\x15'); // Ctrl+U - clear any existing input
2519
- await sleep(60);
2520
- await typeSlowly(shell, 'test', config.charDelayMs);
2521
- await sleep(100);
2522
- shell.write('\r'); // Enter
2523
- dlog('Kickstart "test" sent - session should be created');
2524
- }
2525
- finally {
2526
- process.stdin.on('data', onStdinData);
2527
- dlog('Stdin resumed after kickstart');
2528
- }
2529
- }, 3000); // 3s for Claude to initialize
2530
- }
2531
- // ══════════════════════════════════════════════════════════════════════════
2532
2442
  // RESEARCH MODE: Auto-type research prompt after Claude is ready
2533
2443
  // Triggers: `ekkos run -r` or `ekkos run --research`
2534
2444
  // Works like /clear continue - waits for idle prompt, then injects text
package/dist/index.js CHANGED
@@ -157,7 +157,7 @@ commander_1.program
157
157
  title: 'Running',
158
158
  icon: '▸',
159
159
  commands: [
160
- { name: 'run', desc: 'Launch Claude Code with ekkOS memory + auto-continue', note: 'default' },
160
+ { name: 'run', desc: 'Start Claude Code with ekkOS memory', note: 'default' },
161
161
  { name: 'test-claude', desc: 'Launch Claude with proxy only (no ccDNA/PTY) for debugging' },
162
162
  { name: 'sessions', desc: 'List active Claude Code sessions' },
163
163
  ],
@@ -239,7 +239,7 @@ commander_1.program
239
239
  // Run command - launches Claude with auto-continue wrapper
240
240
  commander_1.program
241
241
  .command('run')
242
- .description('Launch Claude Code with auto-continue (auto /clear + /continue when context is high)')
242
+ .description('Start Claude Code with ekkOS memory (default command)')
243
243
  .option('-s, --session <name>', 'Session name to restore on clear')
244
244
  .option('-b, --bypass', 'Enable bypass permissions mode (dangerously skip all permission checks)')
245
245
  .option('-v, --verbose', 'Show debug output')
@@ -249,7 +249,6 @@ commander_1.program
249
249
  .option('--skip-dna', 'Skip ccDNA injection (bypass Claude Code patching)')
250
250
  .option('--skip-proxy', 'Skip API proxy (use direct Anthropic API, disables seamless context eviction)')
251
251
  .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
252
  .option('--add-dir <dirs...>', 'Additional directories Claude Code can access (outside working directory)')
254
253
  .action((options) => {
255
254
  (0, run_1.run)({
@@ -262,7 +261,6 @@ commander_1.program
262
261
  noDna: options.skipDna,
263
262
  noProxy: options.skipProxy,
264
263
  dashboard: options.dashboard,
265
- kickstart: options.kickstart,
266
264
  addDirs: options.addDir,
267
265
  });
268
266
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekkos/cli",
3
- "version": "1.1.5",
3
+ "version": "1.2.0",
4
4
  "description": "Setup ekkOS memory for AI coding assistants (Claude Code, Cursor, Windsurf)",
5
5
  "main": "dist/index.js",
6
6
  "bin": {