@ekkos/cli 0.2.9 → 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.
- package/dist/agent/daemon.d.ts +86 -0
- package/dist/agent/daemon.js +297 -0
- package/dist/agent/pty-runner.d.ts +51 -0
- package/dist/agent/pty-runner.js +184 -0
- package/dist/cache/LocalSessionStore.d.ts +34 -21
- package/dist/cache/LocalSessionStore.js +169 -53
- package/dist/cache/capture.d.ts +19 -11
- package/dist/cache/capture.js +243 -76
- package/dist/cache/types.d.ts +14 -1
- package/dist/commands/agent.d.ts +44 -0
- package/dist/commands/agent.js +300 -0
- package/dist/commands/doctor.d.ts +10 -0
- package/dist/commands/doctor.js +175 -87
- package/dist/commands/hooks.d.ts +109 -0
- package/dist/commands/hooks.js +668 -0
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.js +357 -85
- package/dist/commands/setup-remote.d.ts +20 -0
- package/dist/commands/setup-remote.js +467 -0
- package/dist/index.js +116 -1
- package/dist/restore/RestoreOrchestrator.d.ts +17 -3
- package/dist/restore/RestoreOrchestrator.js +64 -22
- package/dist/utils/paths.d.ts +125 -0
- package/dist/utils/paths.js +283 -0
- package/dist/utils/state.d.ts +2 -0
- package/package.json +1 -1
- package/templates/ekkos-manifest.json +223 -0
- package/templates/helpers/json-parse.cjs +101 -0
- package/templates/hooks/assistant-response.ps1 +256 -0
- package/templates/hooks/assistant-response.sh +124 -64
- package/templates/hooks/session-start.ps1 +107 -2
- package/templates/hooks/session-start.sh +201 -166
- package/templates/hooks/stop.ps1 +124 -3
- package/templates/hooks/stop.sh +470 -843
- package/templates/hooks/user-prompt-submit.ps1 +107 -22
- package/templates/hooks/user-prompt-submit.sh +403 -393
- package/templates/project-stubs/session-start.ps1 +63 -0
- package/templates/project-stubs/session-start.sh +55 -0
- package/templates/project-stubs/stop.ps1 +63 -0
- package/templates/project-stubs/stop.sh +55 -0
- package/templates/project-stubs/user-prompt-submit.ps1 +63 -0
- package/templates/project-stubs/user-prompt-submit.sh +55 -0
- package/templates/shared/hooks-enabled.json +22 -0
- package/templates/shared/session-words.json +45 -0
package/dist/commands/run.js
CHANGED
|
@@ -62,9 +62,10 @@ function getConfig(options) {
|
|
|
62
62
|
postEnterDelayMs: options.postEnterDelayMs ??
|
|
63
63
|
parseInt(process.env.EKKOS_POST_ENTER_DELAY_MS || '300', 10), // was 500
|
|
64
64
|
clearWaitMs: options.clearWaitMs ??
|
|
65
|
-
parseInt(process.env.EKKOS_CLEAR_WAIT_MS || '
|
|
65
|
+
parseInt(process.env.EKKOS_CLEAR_WAIT_MS || '1111', 10), // 1111 = symbolic ✨
|
|
66
66
|
idlePromptMs: parseInt(process.env.EKKOS_IDLE_PROMPT_MS || '250', 10),
|
|
67
67
|
paletteRetryMs: parseInt(process.env.EKKOS_PALETTE_RETRY_MS || '400', 10), // was 500
|
|
68
|
+
maxIdleWaitMs: parseInt(process.env.EKKOS_MAX_IDLE_WAIT_MS || '2000', 10), // was 10000
|
|
68
69
|
debugLogPath: options.debugLogPath ??
|
|
69
70
|
process.env.EKKOS_DEBUG_LOG_PATH ??
|
|
70
71
|
path.join(os.homedir(), '.ekkos', 'auto-continue.debug.log')
|
|
@@ -167,7 +168,8 @@ async function typeSlowly(shell, text, charDelayMs) {
|
|
|
167
168
|
* that hasn't changed for idlePromptMs. This prevents injecting while Claude
|
|
168
169
|
* is busy generating ("Channelling...").
|
|
169
170
|
*/
|
|
170
|
-
async function waitForIdlePrompt(getOutputBuffer, config
|
|
171
|
+
async function waitForIdlePrompt(getOutputBuffer, config) {
|
|
172
|
+
const maxWaitMs = config.maxIdleWaitMs;
|
|
171
173
|
const startTime = Date.now();
|
|
172
174
|
let lastOutput = '';
|
|
173
175
|
let stableTime = 0;
|
|
@@ -380,9 +382,45 @@ async function emergencyCapture(transcriptPath, sessionId) {
|
|
|
380
382
|
dlog('Warning: Could not capture context');
|
|
381
383
|
}
|
|
382
384
|
}
|
|
385
|
+
/**
|
|
386
|
+
* Generate a unique instance ID for this run
|
|
387
|
+
* Used for namespacing storage paths
|
|
388
|
+
*/
|
|
389
|
+
function generateInstanceId() {
|
|
390
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Write/update instance file
|
|
394
|
+
*/
|
|
395
|
+
function writeInstanceFile(instanceId, data) {
|
|
396
|
+
const instancesDir = path.join(state_1.EKKOS_DIR, 'instances');
|
|
397
|
+
if (!fs.existsSync(instancesDir)) {
|
|
398
|
+
fs.mkdirSync(instancesDir, { recursive: true });
|
|
399
|
+
}
|
|
400
|
+
const instanceFile = path.join(instancesDir, `${instanceId}.json`);
|
|
401
|
+
fs.writeFileSync(instanceFile, JSON.stringify(data, null, 2));
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Clean up instance file on exit
|
|
405
|
+
*/
|
|
406
|
+
function cleanupInstanceFile(instanceId) {
|
|
407
|
+
try {
|
|
408
|
+
const instanceFile = path.join(state_1.EKKOS_DIR, 'instances', `${instanceId}.json`);
|
|
409
|
+
if (fs.existsSync(instanceFile)) {
|
|
410
|
+
fs.unlinkSync(instanceFile);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
// Ignore cleanup errors
|
|
415
|
+
}
|
|
416
|
+
}
|
|
383
417
|
async function run(options) {
|
|
384
418
|
const verbose = options.verbose || false;
|
|
385
419
|
const bypass = options.bypass || false;
|
|
420
|
+
const noInject = options.noInject || false;
|
|
421
|
+
// Generate instance ID for this run
|
|
422
|
+
const instanceId = generateInstanceId();
|
|
423
|
+
process.env.EKKOS_INSTANCE_ID = instanceId;
|
|
386
424
|
// ══════════════════════════════════════════════════════════════════════════
|
|
387
425
|
// PRE-FLIGHT DIAGNOSTICS (--doctor flag)
|
|
388
426
|
// ══════════════════════════════════════════════════════════════════════════
|
|
@@ -407,6 +445,72 @@ async function run(options) {
|
|
|
407
445
|
const config = getConfig(options);
|
|
408
446
|
setDebugLogPath(config.debugLogPath);
|
|
409
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
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
410
514
|
// STARTUP BANNER WITH COLOR PULSE ANIMATION
|
|
411
515
|
// ══════════════════════════════════════════════════════════════════════════
|
|
412
516
|
const logoLines = [
|
|
@@ -494,23 +598,121 @@ async function run(options) {
|
|
|
494
598
|
process.stdout.write('\x1B[6A');
|
|
495
599
|
logoLines.forEach(line => console.log(chalk_1.default.magenta(line)));
|
|
496
600
|
console.log('');
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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('');
|
|
500
682
|
if (bypass) {
|
|
501
683
|
console.log(chalk_1.default.yellow(' ⚡ Bypass permissions mode enabled'));
|
|
502
684
|
}
|
|
503
685
|
if (verbose) {
|
|
504
686
|
console.log(chalk_1.default.gray(` 📁 Debug log: ${config.debugLogPath}`));
|
|
505
|
-
console.log(chalk_1.default.gray(` ⏱ Timing:
|
|
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)`));
|
|
506
688
|
}
|
|
507
689
|
console.log('');
|
|
508
|
-
//
|
|
509
|
-
|
|
510
|
-
//
|
|
511
|
-
|
|
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
|
+
}
|
|
512
702
|
// Track state
|
|
513
703
|
let currentSession = options.session || (0, state_1.getCurrentSessionName)();
|
|
704
|
+
// Write initial instance file
|
|
705
|
+
const startedAt = new Date().toISOString();
|
|
706
|
+
writeInstanceFile(instanceId, {
|
|
707
|
+
pid: process.pid,
|
|
708
|
+
projectPath: process.cwd(),
|
|
709
|
+
startedAt,
|
|
710
|
+
lastHeartbeat: startedAt,
|
|
711
|
+
sessionName: currentSession || undefined
|
|
712
|
+
});
|
|
713
|
+
if (verbose) {
|
|
714
|
+
console.log(chalk_1.default.gray(` 🆔 Instance: ${instanceId}`));
|
|
715
|
+
}
|
|
514
716
|
// ════════════════════════════════════════════════════════════════════════════
|
|
515
717
|
// MULTI-SESSION SUPPORT: Register this process as an active session
|
|
516
718
|
// This prevents state collision when multiple Claude Code instances run
|
|
@@ -530,7 +732,8 @@ async function run(options) {
|
|
|
530
732
|
let currentSessionId = null;
|
|
531
733
|
// Stream tailer for mid-turn context capture
|
|
532
734
|
let streamTailer = null;
|
|
533
|
-
|
|
735
|
+
// Instance-namespaced cache directory per spec v1.2
|
|
736
|
+
const streamCacheDir = path.join(os.homedir(), '.ekkos', 'cache', 'sessions', instanceId);
|
|
534
737
|
// Helper to start stream tailer when we have transcript path
|
|
535
738
|
function startStreamTailer(tPath, sId, sName) {
|
|
536
739
|
if (streamTailer)
|
|
@@ -576,103 +779,110 @@ async function run(options) {
|
|
|
576
779
|
// Debounce tracking to prevent double triggers
|
|
577
780
|
let lastDetectionTime = 0;
|
|
578
781
|
const DETECTION_COOLDOWN = 30000; // 30 seconds cooldown
|
|
579
|
-
//
|
|
580
|
-
const
|
|
581
|
-
// Handle npx:VERSION format for pinned version
|
|
582
|
-
const isNpxMode = rawClaudePath.startsWith('npx:');
|
|
583
|
-
const pinnedVersion = isNpxMode ? rawClaudePath.split(':')[1] : null;
|
|
584
|
-
const claudePath = isNpxMode ? 'npx' : rawClaudePath;
|
|
585
|
-
if (verbose) {
|
|
586
|
-
if (isNpxMode) {
|
|
587
|
-
console.log(chalk_1.default.gray(` 🤖 Using claude-code@${pinnedVersion} via npx (pinned for better context)`));
|
|
588
|
-
}
|
|
589
|
-
else {
|
|
590
|
-
console.log(chalk_1.default.gray(` 🤖 Using claude at: ${claudePath}`));
|
|
591
|
-
}
|
|
592
|
-
if (currentSession) {
|
|
593
|
-
console.log(chalk_1.default.green(` 📍 Session: ${currentSession}`));
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
// Build args - prepend package name if using npx
|
|
597
|
-
const args = [];
|
|
598
|
-
if (isNpxMode) {
|
|
599
|
-
args.push(`@anthropic-ai/claude-code@${pinnedVersion}`);
|
|
600
|
-
}
|
|
601
|
-
if (bypass) {
|
|
602
|
-
args.push('--dangerously-skip-permissions');
|
|
603
|
-
}
|
|
604
|
-
// Determine which mode to use
|
|
605
|
-
const usePty = pty !== null;
|
|
782
|
+
// Use args from early setup
|
|
783
|
+
const args = earlyArgs;
|
|
606
784
|
// ══════════════════════════════════════════════════════════════════════════
|
|
607
|
-
// WINDOWS:
|
|
785
|
+
// WINDOWS: MONITOR-ONLY MODE WITHOUT PTY (Per Spec v1.2 Addendum)
|
|
608
786
|
// Without node-pty/ConPTY, auto-continue cannot work on Windows.
|
|
609
|
-
//
|
|
787
|
+
// Instead of hard-failing, we enter monitor-only mode.
|
|
610
788
|
// ══════════════════════════════════════════════════════════════════════════
|
|
611
789
|
if (isWindows && !usePty) {
|
|
612
790
|
console.log('');
|
|
613
|
-
console.log(chalk_1.default.
|
|
791
|
+
console.log(chalk_1.default.yellow.bold('⚠️ Monitor-only mode (PTY not available)'));
|
|
614
792
|
console.log('');
|
|
615
|
-
console.log(chalk_1.default.
|
|
616
|
-
console.log(chalk_1.default.
|
|
793
|
+
console.log(chalk_1.default.gray('Without node-pty (ConPTY), auto-continue cannot inject commands.'));
|
|
794
|
+
console.log(chalk_1.default.gray('ekkOS will monitor context usage and provide instructions when needed.'));
|
|
617
795
|
console.log('');
|
|
618
|
-
console.log(chalk_1.default.cyan('To
|
|
619
|
-
console.log('');
|
|
620
|
-
console.log(chalk_1.default.white(' Option 1: Use Node 20 or 22 LTS (recommended)'));
|
|
796
|
+
console.log(chalk_1.default.cyan('To enable auto-continue:'));
|
|
797
|
+
console.log(chalk_1.default.white(' Option 1: Use Node 20 or 22 LTS'));
|
|
621
798
|
console.log(chalk_1.default.gray(' winget install OpenJS.NodeJS.LTS'));
|
|
622
|
-
console.log(chalk_1.default.
|
|
623
|
-
console.log('');
|
|
624
|
-
console.log(chalk_1.default.white(' Option 2: Install prebuilt PTY'));
|
|
625
|
-
console.log(chalk_1.default.gray(' npm install node-pty-prebuilt-multiarch'));
|
|
626
|
-
console.log('');
|
|
627
|
-
console.log(chalk_1.default.white(' Option 3: Build node-pty from source'));
|
|
628
|
-
console.log(chalk_1.default.gray(' 1. Install VS Build Tools (Desktop C++ workload)'));
|
|
629
|
-
console.log(chalk_1.default.gray(' 2. npm rebuild node-pty --build-from-source'));
|
|
799
|
+
console.log(chalk_1.default.white(' Option 2: npm install node-pty-prebuilt-multiarch'));
|
|
630
800
|
console.log('');
|
|
631
801
|
console.log(chalk_1.default.gray('Run `ekkos doctor` for detailed diagnostics.'));
|
|
632
802
|
console.log('');
|
|
633
|
-
|
|
803
|
+
}
|
|
804
|
+
else if (noInject) {
|
|
805
|
+
console.log(chalk_1.default.yellow(' Monitor-only mode (--no-inject)'));
|
|
634
806
|
}
|
|
635
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
|
+
}
|
|
636
817
|
console.log(chalk_1.default.gray(` 💻 PTY mode: ${usePty ? 'node-pty' : 'spawn+script (fallback)'}`));
|
|
637
818
|
console.log('');
|
|
638
819
|
}
|
|
639
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
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
640
825
|
if (usePty && pty) {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
name: 'xterm-256color',
|
|
645
|
-
cols: process.stdout.columns || 80,
|
|
646
|
-
rows: process.stdout.rows || 24,
|
|
647
|
-
cwd: process.cwd(),
|
|
648
|
-
env: process.env
|
|
649
|
-
});
|
|
826
|
+
if (earlySpawnedShell && shellReady && !shellError) {
|
|
827
|
+
// REUSE early-spawned shell - Claude loaded during animation!
|
|
828
|
+
dlog('Reusing early-spawned shell (Claude loaded during animation)');
|
|
650
829
|
shell = {
|
|
651
|
-
write: (data) =>
|
|
652
|
-
kill: () =>
|
|
653
|
-
resize: (cols, rows) =>
|
|
654
|
-
onData: (callback) =>
|
|
655
|
-
|
|
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)
|
|
656
839
|
};
|
|
657
|
-
//
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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
|
+
}
|
|
661
850
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
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
|
+
});
|
|
666
880
|
}
|
|
667
|
-
// Fall through to spawn mode
|
|
668
|
-
return runWithSpawn(claudePath, args, options, {
|
|
669
|
-
currentSession,
|
|
670
|
-
isAutoClearInProgress,
|
|
671
|
-
transcriptPath,
|
|
672
|
-
currentSessionId,
|
|
673
|
-
outputBuffer
|
|
674
|
-
});
|
|
675
881
|
}
|
|
882
|
+
// Handle terminal resize
|
|
883
|
+
process.stdout.on('resize', () => {
|
|
884
|
+
shell.resize?.(process.stdout.columns || 80, process.stdout.rows || 24);
|
|
885
|
+
});
|
|
676
886
|
}
|
|
677
887
|
else {
|
|
678
888
|
// Fallback: use spawn+script for PTY emulation
|
|
@@ -977,11 +1187,72 @@ async function run(options) {
|
|
|
977
1187
|
}
|
|
978
1188
|
}
|
|
979
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
|
+
}
|
|
980
1250
|
// Handle PTY exit
|
|
981
1251
|
shell.onExit(({ exitCode }) => {
|
|
982
1252
|
(0, state_1.clearAutoClearFlag)();
|
|
983
1253
|
stopStreamTailer(); // Stop stream capture
|
|
984
1254
|
(0, state_1.unregisterActiveSession)(); // Remove from active sessions registry
|
|
1255
|
+
cleanupInstanceFile(instanceId); // Clean up instance file
|
|
985
1256
|
// Restore terminal
|
|
986
1257
|
if (process.stdin.isTTY) {
|
|
987
1258
|
process.stdin.setRawMode(false);
|
|
@@ -996,6 +1267,7 @@ async function run(options) {
|
|
|
996
1267
|
(0, state_1.clearAutoClearFlag)();
|
|
997
1268
|
stopStreamTailer(); // Stop stream capture
|
|
998
1269
|
(0, state_1.unregisterActiveSession)(); // Remove from active sessions registry
|
|
1270
|
+
cleanupInstanceFile(instanceId); // Clean up instance file
|
|
999
1271
|
if (process.stdin.isTTY) {
|
|
1000
1272
|
process.stdin.setRawMode(false);
|
|
1001
1273
|
}
|
|
@@ -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>;
|