@ekkos/cli 0.2.10 → 0.2.11

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.
@@ -445,6 +445,72 @@ async function run(options) {
445
445
  const config = getConfig(options);
446
446
  setDebugLogPath(config.debugLogPath);
447
447
  // ══════════════════════════════════════════════════════════════════════════
448
+ // EARLY SETUP: Resolve paths and prepare spawn BEFORE animation
449
+ // This lets us start Claude while animations run (perceived performance)
450
+ // ══════════════════════════════════════════════════════════════════════════
451
+ (0, state_1.ensureEkkosDir)();
452
+ (0, state_1.clearAutoClearFlag)();
453
+ const rawClaudePath = resolveClaudePath();
454
+ const isNpxMode = rawClaudePath.startsWith('npx:');
455
+ const pinnedVersion = isNpxMode ? rawClaudePath.split(':')[1] : null;
456
+ const claudePath = isNpxMode ? 'npx' : rawClaudePath;
457
+ // Build args early
458
+ const earlyArgs = [];
459
+ if (isNpxMode) {
460
+ earlyArgs.push(`@anthropic-ai/claude-code@${pinnedVersion}`);
461
+ }
462
+ if (bypass) {
463
+ earlyArgs.push('--dangerously-skip-permissions');
464
+ }
465
+ // Check PTY availability early
466
+ const usePty = pty !== null;
467
+ const monitorOnlyMode = noInject || (isWindows && !usePty);
468
+ // ══════════════════════════════════════════════════════════════════════════
469
+ // CONCURRENT STARTUP: Spawn Claude while animation runs
470
+ // Buffer output until animation completes, then flush
471
+ // ══════════════════════════════════════════════════════════════════════════
472
+ let earlySpawnedShell = null;
473
+ let bufferedOutput = [];
474
+ let animationComplete = false;
475
+ let shellReady = false;
476
+ let shellError = null;
477
+ // Handler reference for early spawn (allows replacing buffer handler with real handler)
478
+ let earlyDataHandler = null;
479
+ // Start spawning Claude NOW (before animation) if PTY available
480
+ if (usePty && pty && !isWindows) {
481
+ try {
482
+ earlySpawnedShell = pty.spawn(claudePath, earlyArgs, {
483
+ name: 'xterm-256color',
484
+ cols: process.stdout.columns || 80,
485
+ rows: process.stdout.rows || 24,
486
+ cwd: process.cwd(),
487
+ env: process.env
488
+ });
489
+ // Buffer output until animation completes using delegating handler
490
+ earlyDataHandler = (data) => {
491
+ bufferedOutput.push(data);
492
+ };
493
+ earlySpawnedShell.onData((data) => {
494
+ // Delegate to current handler (buffer during animation, real handler after)
495
+ if (earlyDataHandler) {
496
+ earlyDataHandler(data);
497
+ }
498
+ });
499
+ earlySpawnedShell.onExit(() => {
500
+ if (!animationComplete) {
501
+ // Claude exited during animation - will handle after
502
+ shellError = new Error('Claude exited during startup');
503
+ }
504
+ });
505
+ shellReady = true;
506
+ dlog('Claude spawned early (buffering output during animation)');
507
+ }
508
+ catch (err) {
509
+ dlog(`Early spawn failed: ${err.message}, will retry after animation`);
510
+ shellError = err;
511
+ }
512
+ }
513
+ // ══════════════════════════════════════════════════════════════════════════
448
514
  // STARTUP BANNER WITH COLOR PULSE ANIMATION
449
515
  // ══════════════════════════════════════════════════════════════════════════
450
516
  const logoLines = [
@@ -532,9 +598,87 @@ async function run(options) {
532
598
  process.stdout.write('\x1B[6A');
533
599
  logoLines.forEach(line => console.log(chalk_1.default.magenta(line)));
534
600
  console.log('');
535
- console.log(chalk_1.default.cyan.bold(' Auto-Continue Wrapper'));
536
- console.log(chalk_1.default.gray(' Monitors for context limit and auto-restores sessions'));
537
- console.log(chalk_1.default.gray(' Uses full 200K context window - triggers on actual limit'));
601
+ // ══════════════════════════════════════════════════════════════════════════
602
+ // ANIMATED TITLE: "Cognitive Continuity Engine" with orange/white shine
603
+ // ══════════════════════════════════════════════════════════════════════════
604
+ const titleText = 'Cognitive Continuity Engine';
605
+ const taglineText = 'Context is finite. Intelligence isn\'t.';
606
+ // Color palette for shine effect
607
+ const whiteShine = chalk_1.default.whiteBright;
608
+ // Phase 1: Typewriter effect for title
609
+ process.stdout.write(' ');
610
+ for (let i = 0; i < titleText.length; i++) {
611
+ const char = titleText[i];
612
+ // Flash white then settle to orange
613
+ process.stdout.write(whiteShine(char));
614
+ await sleep(25);
615
+ process.stdout.write('\b' + chalk_1.default.hex('#FF6B35').bold(char));
616
+ }
617
+ console.log('');
618
+ // Phase 2: Shine sweep across title (3 passes)
619
+ const SHINE_PASSES = 3;
620
+ const SHINE_WIDTH = 4;
621
+ for (let pass = 0; pass < SHINE_PASSES; pass++) {
622
+ for (let shinePos = -SHINE_WIDTH; shinePos <= titleText.length + SHINE_WIDTH; shinePos++) {
623
+ process.stdout.write('\x1B[1A'); // Move up one line
624
+ process.stdout.write('\r '); // Return to start
625
+ let output = '';
626
+ for (let i = 0; i < titleText.length; i++) {
627
+ const distFromShine = Math.abs(i - shinePos);
628
+ if (distFromShine === 0) {
629
+ output += whiteShine.bold(titleText[i]);
630
+ }
631
+ else if (distFromShine === 1) {
632
+ output += chalk_1.default.hex('#FFFFFF')(titleText[i]);
633
+ }
634
+ else if (distFromShine === 2) {
635
+ output += chalk_1.default.hex('#FFD700')(titleText[i]);
636
+ }
637
+ else if (distFromShine === 3) {
638
+ output += chalk_1.default.hex('#FFA500')(titleText[i]);
639
+ }
640
+ else {
641
+ output += chalk_1.default.hex('#FF6B35').bold(titleText[i]);
642
+ }
643
+ }
644
+ process.stdout.write(output + '\n'); // Write and move down for next frame
645
+ await sleep(15);
646
+ }
647
+ }
648
+ // Final title state
649
+ process.stdout.write('\x1B[1A\r');
650
+ console.log(' ' + chalk_1.default.hex('#FF6B35').bold(titleText));
651
+ // Phase 3: Tagline fade-in with shimmer
652
+ await sleep(100);
653
+ // Build up tagline with wave effect
654
+ const taglineColors = [
655
+ chalk_1.default.hex('#444444'),
656
+ chalk_1.default.hex('#666666'),
657
+ chalk_1.default.hex('#888888'),
658
+ chalk_1.default.hex('#AAAAAA'),
659
+ chalk_1.default.hex('#CCCCCC'),
660
+ chalk_1.default.hex('#EEEEEE'),
661
+ chalk_1.default.gray,
662
+ ];
663
+ for (let wave = 0; wave < taglineColors.length; wave++) {
664
+ process.stdout.write('\r ');
665
+ process.stdout.write(taglineColors[wave](taglineText));
666
+ await sleep(40);
667
+ }
668
+ console.log('');
669
+ // Phase 4: Quick orange accent pulse on tagline
670
+ for (let pulse = 0; pulse < 2; pulse++) {
671
+ await sleep(80);
672
+ process.stdout.write('\x1B[1A\r');
673
+ console.log(' ' + chalk_1.default.hex('#FF8C00')(taglineText));
674
+ await sleep(80);
675
+ process.stdout.write('\x1B[1A\r');
676
+ console.log(' ' + chalk_1.default.gray(taglineText));
677
+ }
678
+ // Final tagline state with subtle orange tint
679
+ process.stdout.write('\x1B[1A\r');
680
+ console.log(' ' + chalk_1.default.hex('#B8860B')(taglineText));
681
+ console.log('');
538
682
  if (bypass) {
539
683
  console.log(chalk_1.default.yellow(' ⚡ Bypass permissions mode enabled'));
540
684
  }
@@ -543,10 +687,18 @@ async function run(options) {
543
687
  console.log(chalk_1.default.gray(` ⏱ Timing: clear=${config.clearWaitMs}ms, idleMax=${config.maxIdleWaitMs}ms (~${Math.round((config.clearWaitMs + config.maxIdleWaitMs * 2 + 1700) / 1000)}s total)`));
544
688
  }
545
689
  console.log('');
546
- // Ensure .ekkos directory exists
547
- (0, state_1.ensureEkkosDir)();
548
- // Clear any stale flags
549
- (0, state_1.clearAutoClearFlag)();
690
+ // ══════════════════════════════════════════════════════════════════════════
691
+ // ANIMATION COMPLETE: Mark ready and flush buffered Claude output
692
+ // ══════════════════════════════════════════════════════════════════════════
693
+ animationComplete = true;
694
+ dlog(`Animation complete. shellReady=${shellReady}, buffered=${bufferedOutput.length} chunks`);
695
+ // Show loading indicator if Claude is still initializing
696
+ if (shellReady && bufferedOutput.length === 0) {
697
+ process.stdout.write(chalk_1.default.gray(' Connecting to Claude...'));
698
+ // Brief wait for any buffered output
699
+ await sleep(100);
700
+ process.stdout.write('\r' + ' '.repeat(30) + '\r'); // Clear the line
701
+ }
550
702
  // Track state
551
703
  let currentSession = options.session || (0, state_1.getCurrentSessionName)();
552
704
  // Write initial instance file
@@ -627,39 +779,13 @@ async function run(options) {
627
779
  // Debounce tracking to prevent double triggers
628
780
  let lastDetectionTime = 0;
629
781
  const DETECTION_COOLDOWN = 30000; // 30 seconds cooldown
630
- // Resolve claude path
631
- const rawClaudePath = resolveClaudePath();
632
- // Handle npx:VERSION format for pinned version
633
- const isNpxMode = rawClaudePath.startsWith('npx:');
634
- const pinnedVersion = isNpxMode ? rawClaudePath.split(':')[1] : null;
635
- const claudePath = isNpxMode ? 'npx' : rawClaudePath;
636
- if (verbose) {
637
- if (isNpxMode) {
638
- console.log(chalk_1.default.gray(` 🤖 Using claude-code@${pinnedVersion} via npx (pinned for better context)`));
639
- }
640
- else {
641
- console.log(chalk_1.default.gray(` 🤖 Using claude at: ${claudePath}`));
642
- }
643
- if (currentSession) {
644
- console.log(chalk_1.default.green(` 📍 Session: ${currentSession}`));
645
- }
646
- }
647
- // Build args - prepend package name if using npx
648
- const args = [];
649
- if (isNpxMode) {
650
- args.push(`@anthropic-ai/claude-code@${pinnedVersion}`);
651
- }
652
- if (bypass) {
653
- args.push('--dangerously-skip-permissions');
654
- }
655
- // Determine which mode to use
656
- const usePty = pty !== null;
782
+ // Use args from early setup
783
+ const args = earlyArgs;
657
784
  // ══════════════════════════════════════════════════════════════════════════
658
785
  // WINDOWS: MONITOR-ONLY MODE WITHOUT PTY (Per Spec v1.2 Addendum)
659
786
  // Without node-pty/ConPTY, auto-continue cannot work on Windows.
660
787
  // Instead of hard-failing, we enter monitor-only mode.
661
788
  // ══════════════════════════════════════════════════════════════════════════
662
- const monitorOnlyMode = noInject || (isWindows && !usePty);
663
789
  if (isWindows && !usePty) {
664
790
  console.log('');
665
791
  console.log(chalk_1.default.yellow.bold('⚠️ Monitor-only mode (PTY not available)'));
@@ -679,46 +805,84 @@ async function run(options) {
679
805
  console.log(chalk_1.default.yellow(' Monitor-only mode (--no-inject)'));
680
806
  }
681
807
  if (verbose) {
808
+ if (isNpxMode) {
809
+ console.log(chalk_1.default.gray(` 🤖 Using claude-code@${pinnedVersion} via npx (pinned for better context)`));
810
+ }
811
+ else {
812
+ console.log(chalk_1.default.gray(` 🤖 Using claude at: ${claudePath}`));
813
+ }
814
+ if (currentSession) {
815
+ console.log(chalk_1.default.green(` 📍 Session: ${currentSession}`));
816
+ }
682
817
  console.log(chalk_1.default.gray(` 💻 PTY mode: ${usePty ? 'node-pty' : 'spawn+script (fallback)'}`));
683
818
  console.log('');
684
819
  }
685
820
  let shell;
821
+ // ══════════════════════════════════════════════════════════════════════════
822
+ // USE EARLY-SPAWNED SHELL OR CREATE NEW ONE
823
+ // If we spawned Claude during animation, reuse it. Otherwise spawn now.
824
+ // ══════════════════════════════════════════════════════════════════════════
686
825
  if (usePty && pty) {
687
- // Use node-pty for proper PTY control
688
- try {
689
- const ptyShell = pty.spawn(claudePath, args, {
690
- name: 'xterm-256color',
691
- cols: process.stdout.columns || 80,
692
- rows: process.stdout.rows || 24,
693
- cwd: process.cwd(),
694
- env: process.env
695
- });
826
+ if (earlySpawnedShell && shellReady && !shellError) {
827
+ // REUSE early-spawned shell - Claude loaded during animation!
828
+ dlog('Reusing early-spawned shell (Claude loaded during animation)');
696
829
  shell = {
697
- write: (data) => ptyShell.write(data),
698
- kill: () => ptyShell.kill(),
699
- resize: (cols, rows) => ptyShell.resize(cols, rows),
700
- onData: (callback) => ptyShell.onData(callback),
701
- onExit: (callback) => ptyShell.onExit(callback)
830
+ write: (data) => earlySpawnedShell.write(data),
831
+ kill: () => earlySpawnedShell.kill(),
832
+ resize: (cols, rows) => earlySpawnedShell.resize(cols, rows),
833
+ onData: (callback) => {
834
+ // Switch the delegating handler from buffer mode to real handler
835
+ // This replaces buffering with actual output processing
836
+ earlyDataHandler = callback;
837
+ },
838
+ onExit: (callback) => earlySpawnedShell.onExit(callback)
702
839
  };
703
- // Handle terminal resize
704
- process.stdout.on('resize', () => {
705
- shell.resize?.(process.stdout.columns || 80, process.stdout.rows || 24);
706
- });
840
+ // Flush buffered output NOW (before switching handler)
841
+ if (bufferedOutput.length > 0) {
842
+ dlog(`Flushing ${bufferedOutput.length} buffered output chunks`);
843
+ for (const chunk of bufferedOutput) {
844
+ process.stdout.write(chunk);
845
+ // Also populate output buffer for pattern detection
846
+ outputBuffer += chunk;
847
+ }
848
+ bufferedOutput = [];
849
+ }
707
850
  }
708
- catch (err) {
709
- console.log(chalk_1.default.yellow(`node-pty failed, falling back to spawn+script mode`));
710
- if (verbose) {
711
- console.log(chalk_1.default.gray(`Error: ${err.message}`));
851
+ else {
852
+ // Spawn fresh (early spawn failed or wasn't attempted)
853
+ dlog('Spawning Claude fresh (early spawn not available)');
854
+ try {
855
+ const ptyShell = pty.spawn(claudePath, args, {
856
+ name: 'xterm-256color',
857
+ cols: process.stdout.columns || 80,
858
+ rows: process.stdout.rows || 24,
859
+ cwd: process.cwd(),
860
+ env: process.env
861
+ });
862
+ shell = {
863
+ write: (data) => ptyShell.write(data),
864
+ kill: () => ptyShell.kill(),
865
+ resize: (cols, rows) => ptyShell.resize(cols, rows),
866
+ onData: (callback) => ptyShell.onData(callback),
867
+ onExit: (callback) => ptyShell.onExit(callback)
868
+ };
869
+ }
870
+ catch (err) {
871
+ dlog(`node-pty spawn failed: ${err.message}`);
872
+ // Fall through to spawn mode
873
+ return runWithSpawn(claudePath, args, options, {
874
+ currentSession,
875
+ isAutoClearInProgress,
876
+ transcriptPath,
877
+ currentSessionId,
878
+ outputBuffer
879
+ });
712
880
  }
713
- // Fall through to spawn mode
714
- return runWithSpawn(claudePath, args, options, {
715
- currentSession,
716
- isAutoClearInProgress,
717
- transcriptPath,
718
- currentSessionId,
719
- outputBuffer
720
- });
721
881
  }
882
+ // Handle terminal resize
883
+ process.stdout.on('resize', () => {
884
+ shell.resize?.(process.stdout.columns || 80, process.stdout.rows || 24);
885
+ });
722
886
  }
723
887
  else {
724
888
  // Fallback: use spawn+script for PTY emulation
@@ -1023,6 +1187,66 @@ async function run(options) {
1023
1187
  }
1024
1188
  }
1025
1189
  });
1190
+ // ══════════════════════════════════════════════════════════════════════════
1191
+ // RESEARCH MODE: Auto-type research prompt after Claude is ready
1192
+ // Triggers: `ekkos run -r` or `ekkos run --research`
1193
+ // Works like /clear continue - waits for idle prompt, then injects text
1194
+ // ══════════════════════════════════════════════════════════════════════════
1195
+ if (options.research) {
1196
+ dlog('Research mode enabled - will auto-inject research prompt');
1197
+ // Research prompt - comprehensive autonomous research scanning
1198
+ const researchPrompt = `You are the ekkOS Research Scout agent. Your task:
1199
+
1200
+ 1. **Scan arXiv** (cs.AI, cs.LG, cs.CL) for papers from the last 7 days
1201
+ 2. **Filter for relevance** to ekkOS memory systems:
1202
+ - Memory-augmented neural networks
1203
+ - Retrieval-augmented generation (RAG)
1204
+ - Long-term memory for LLMs
1205
+ - Pattern learning and extraction
1206
+ - Cognitive architectures
1207
+ - Knowledge graphs
1208
+ - Episodic/semantic memory systems
1209
+ 3. **Analyze each relevant paper**:
1210
+ - Core technique and novelty
1211
+ - Applicability to ekkOS 11-layer architecture
1212
+ - Implementation complexity (low/medium/high)
1213
+ - Potential impact (1-5 scale)
1214
+ 4. **Update docs/EKKOS_RESEARCH_ROADMAP.md** with findings
1215
+ 5. **Forge high-value insights as patterns** using ekkOS_Forge
1216
+
1217
+ Use Perplexity for deep research. Be thorough but efficient. Start now.`;
1218
+ // Start research injection after a delay to let Claude initialize
1219
+ setTimeout(async () => {
1220
+ dlog('Starting research prompt injection...');
1221
+ // Wait for idle prompt (same gate as /clear continue uses)
1222
+ const readiness = await waitForIdlePrompt(getOutputBuffer, config);
1223
+ if (!readiness.ready || readiness.interrupted) {
1224
+ dlog('Claude not ready for research prompt - aborting');
1225
+ return;
1226
+ }
1227
+ // PAUSE STDIN during injection to prevent user interference
1228
+ process.stdin.off('data', onStdinData);
1229
+ dlog('Stdin paused during research injection');
1230
+ try {
1231
+ // Clear any existing input
1232
+ shell.write('\x15'); // Ctrl+U
1233
+ await sleep(60);
1234
+ // Type the research prompt character by character (faster for long prompts)
1235
+ dlog('Typing research prompt...');
1236
+ const fastCharDelay = Math.min(config.charDelayMs, 10); // Faster typing for long prompt
1237
+ await typeSlowly(shell, researchPrompt, fastCharDelay);
1238
+ // Send Enter to execute
1239
+ await sleep(100);
1240
+ shell.write('\r');
1241
+ dlog('Research prompt sent');
1242
+ }
1243
+ finally {
1244
+ // RESUME STDIN
1245
+ process.stdin.on('data', onStdinData);
1246
+ dlog('Stdin resumed after research injection');
1247
+ }
1248
+ }, 4000); // Wait 4 seconds for Claude to fully initialize
1249
+ }
1026
1250
  // Handle PTY exit
1027
1251
  shell.onExit(({ exitCode }) => {
1028
1252
  (0, state_1.clearAutoClearFlag)();
@@ -0,0 +1,20 @@
1
+ /**
2
+ * ekkos setup-remote - One-command setup for remote terminal access
3
+ *
4
+ * This command does everything automatically:
5
+ * 1. Checks if user is logged in (prompts login if not)
6
+ * 2. Generates unique device ID and fingerprint
7
+ * 3. Opens browser for device pairing approval
8
+ * 4. Installs background agent as system service
9
+ * 5. Starts agent immediately
10
+ * 6. Verifies connection to cloud relay
11
+ */
12
+ export interface SetupRemoteOptions {
13
+ force?: boolean;
14
+ skipService?: boolean;
15
+ verbose?: boolean;
16
+ }
17
+ /**
18
+ * Main setup command
19
+ */
20
+ export declare function setupRemote(options?: SetupRemoteOptions): Promise<void>;