@ekkos/cli 1.0.34 → 1.0.36
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/capture/jsonl-rewriter.js +72 -7
- package/dist/commands/dashboard.js +186 -557
- package/dist/commands/init.js +3 -15
- package/dist/commands/run.js +222 -256
- package/dist/commands/setup.js +0 -47
- package/dist/commands/swarm-dashboard.js +4 -13
- package/dist/deploy/instructions.d.ts +2 -5
- package/dist/deploy/instructions.js +8 -11
- package/dist/deploy/settings.js +21 -15
- package/dist/deploy/skills.d.ts +0 -8
- package/dist/deploy/skills.js +0 -26
- package/dist/index.js +2 -2
- package/dist/lib/usage-parser.js +1 -2
- package/dist/utils/platform.d.ts +0 -3
- package/dist/utils/platform.js +1 -4
- package/dist/utils/session-binding.d.ts +1 -1
- package/dist/utils/session-binding.js +2 -3
- package/package.json +1 -1
- package/templates/agents/README.md +182 -0
- package/templates/agents/code-reviewer.md +166 -0
- package/templates/agents/debug-detective.md +169 -0
- package/templates/agents/ekkOS_Vercel.md +99 -0
- package/templates/agents/extension-manager.md +229 -0
- package/templates/agents/git-companion.md +185 -0
- package/templates/agents/github-test-agent.md +321 -0
- package/templates/agents/railway-manager.md +179 -0
- package/templates/hooks/assistant-response.ps1 +26 -94
- package/templates/hooks/lib/count-tokens.cjs +0 -0
- package/templates/hooks/lib/ekkos-reminders.sh +0 -0
- package/templates/hooks/session-start.ps1 +224 -61
- package/templates/hooks/session-start.sh +1 -1
- package/templates/hooks/stop.ps1 +249 -103
- package/templates/hooks/stop.sh +1 -1
- package/templates/hooks/user-prompt-submit.ps1 +519 -129
- package/templates/hooks/user-prompt-submit.sh +2 -2
- package/templates/plan-template.md +0 -0
- package/templates/spec-template.md +0 -0
- package/templates/windsurf-hooks/before-submit-prompt.sh +238 -0
- package/templates/windsurf-hooks/install.sh +0 -0
- package/templates/windsurf-hooks/lib/contract.sh +0 -0
- package/templates/windsurf-hooks/post-cascade-response.sh +0 -0
- package/templates/windsurf-hooks/pre-user-prompt.sh +0 -0
- package/templates/windsurf-skills/ekkos-memory/SKILL.md +219 -0
- package/README.md +0 -57
package/dist/commands/run.js
CHANGED
|
@@ -43,6 +43,139 @@ const fs = __importStar(require("fs"));
|
|
|
43
43
|
const path = __importStar(require("path"));
|
|
44
44
|
const os = __importStar(require("os"));
|
|
45
45
|
const child_process_1 = require("child_process");
|
|
46
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
47
|
+
// ccDNA AUTO-LOAD: Apply Claude Code patches before spawning
|
|
48
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
49
|
+
const CCDNA_PATHS = [
|
|
50
|
+
// Development path (DEV sibling directory)
|
|
51
|
+
// From: EKKOS/packages/ekkos-cli/dist/commands/ → DEV/ekkos-ccdna/
|
|
52
|
+
path.join(__dirname, '..', '..', '..', '..', '..', 'ekkos-ccdna', 'dist', 'index.mjs'),
|
|
53
|
+
// User install path
|
|
54
|
+
path.join(os.homedir(), '.ekkos', 'ccdna', 'dist', 'index.mjs'),
|
|
55
|
+
// npm global (homebrew)
|
|
56
|
+
'/opt/homebrew/lib/node_modules/ekkos-ccdna/dist/index.mjs',
|
|
57
|
+
// npm global (standard)
|
|
58
|
+
path.join(os.homedir(), '.npm-global', 'lib', 'node_modules', 'ekkos-ccdna', 'dist', 'index.mjs'),
|
|
59
|
+
];
|
|
60
|
+
/**
|
|
61
|
+
* Find ccDNA installation path
|
|
62
|
+
*/
|
|
63
|
+
function findCcdnaPath() {
|
|
64
|
+
for (const p of CCDNA_PATHS) {
|
|
65
|
+
if (fs.existsSync(p)) {
|
|
66
|
+
return p;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Apply ccDNA patches silently before Claude spawns
|
|
73
|
+
* Returns version string if patches were applied, null otherwise
|
|
74
|
+
*
|
|
75
|
+
* @param verbose - Show detailed output
|
|
76
|
+
* @param claudePath - Path to Claude Code to patch (if different from default)
|
|
77
|
+
*/
|
|
78
|
+
function applyCcdnaPatches(verbose, claudePath) {
|
|
79
|
+
// DISABLED: ccDNA patching is currently corrupting cli.js (JSON parse error at position 7945)
|
|
80
|
+
// See: https://github.com/anthropics/ekkos/issues/2856
|
|
81
|
+
// The patching process is injecting code that breaks the minified cli.js
|
|
82
|
+
// Temporarily disabled until ccDNA is fixed upstream
|
|
83
|
+
if (verbose) {
|
|
84
|
+
console.log(chalk_1.default.gray(' ccDNA patching disabled (see issue #2856)'));
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
// Original implementation (disabled):
|
|
88
|
+
/*
|
|
89
|
+
const ccdnaPath = findCcdnaPath();
|
|
90
|
+
if (!ccdnaPath) {
|
|
91
|
+
if (verbose) {
|
|
92
|
+
console.log(chalk.gray(' ccDNA not found - skipping patches'));
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Read ccDNA version from package.json FIRST
|
|
98
|
+
let ccdnaVersion = 'unknown';
|
|
99
|
+
try {
|
|
100
|
+
const pkgPath = path.join(path.dirname(ccdnaPath), '..', 'package.json');
|
|
101
|
+
if (fs.existsSync(pkgPath)) {
|
|
102
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
103
|
+
ccdnaVersion = pkg.version || 'unknown';
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
// Ignore version detection errors
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
// Set env var to tell ccDNA which Claude to patch
|
|
111
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
112
|
+
const env = { ...process.env };
|
|
113
|
+
if (claudePath) {
|
|
114
|
+
// ccDNA checks CCDNA_CC_INSTALLATION_PATH to override default detection
|
|
115
|
+
env.CCDNA_CC_INSTALLATION_PATH = claudePath;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Run ccDNA in apply mode (non-interactive)
|
|
119
|
+
execSync(`node "${ccdnaPath}" -a`, {
|
|
120
|
+
stdio: verbose ? 'inherit' : 'pipe',
|
|
121
|
+
timeout: 30000, // 30 second timeout
|
|
122
|
+
env,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (verbose) {
|
|
126
|
+
console.log(chalk.green(` ✓ ccDNA v${ccdnaVersion} patches applied`));
|
|
127
|
+
}
|
|
128
|
+
return ccdnaVersion;
|
|
129
|
+
} catch (err) {
|
|
130
|
+
if (verbose) {
|
|
131
|
+
console.log(chalk.yellow(` ⚠ ccDNA patch failed: ${(err as Error).message}`));
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
*/
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Restore original Claude Code (remove ccDNA patches) on exit
|
|
139
|
+
* This restores the ekkOS-managed installation (~/.ekkos/claude-code/) to its base state
|
|
140
|
+
*
|
|
141
|
+
* NOTE: We intentionally DON'T restore on exit anymore because:
|
|
142
|
+
* 1. ekkOS uses a SEPARATE installation (~/.ekkos/claude-code/) from homebrew
|
|
143
|
+
* 2. The homebrew `claude` command should always be vanilla (untouched)
|
|
144
|
+
* 3. The ekkOS installation can stay patched - it's only used by `ekkos run`
|
|
145
|
+
*
|
|
146
|
+
* This function is kept for manual/explicit restore scenarios.
|
|
147
|
+
*/
|
|
148
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
149
|
+
function restoreCcdnaPatches(verbose, claudePath) {
|
|
150
|
+
const ccdnaPath = findCcdnaPath();
|
|
151
|
+
if (!ccdnaPath) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
// Set env var to tell ccDNA which Claude to restore
|
|
156
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
157
|
+
const env = { ...process.env };
|
|
158
|
+
if (claudePath) {
|
|
159
|
+
env.CCDNA_CC_INSTALLATION_PATH = claudePath;
|
|
160
|
+
}
|
|
161
|
+
// Run ccDNA in restore mode (non-interactive)
|
|
162
|
+
(0, child_process_1.execSync)(`node "${ccdnaPath}" -r`, {
|
|
163
|
+
stdio: verbose ? 'inherit' : 'pipe',
|
|
164
|
+
timeout: 30000, // 30 second timeout
|
|
165
|
+
env,
|
|
166
|
+
});
|
|
167
|
+
if (verbose) {
|
|
168
|
+
console.log(chalk_1.default.green(' ✓ ccDNA patches removed (vanilla restored)'));
|
|
169
|
+
}
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
if (verbose) {
|
|
174
|
+
console.log(chalk_1.default.yellow(` ⚠ ccDNA restore failed: ${err.message}`));
|
|
175
|
+
}
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
46
179
|
const state_1 = require("../utils/state");
|
|
47
180
|
const session_binding_1 = require("../utils/session-binding");
|
|
48
181
|
const doctor_1 = require("./doctor");
|
|
@@ -115,17 +248,11 @@ const INTERRUPTED_REGEX = /interrupted.*what should claude do instead/i;
|
|
|
115
248
|
const PALETTE_INDICATOR_REGEX = /\/(clear|continue|compact|help|bug|config)/i;
|
|
116
249
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
117
250
|
// SESSION NAME DETECTION (3-word slug: word-word-word)
|
|
118
|
-
// Claude prints session name in footer:
|
|
119
|
-
// · 🧠 ekkOS_™ · Turn N · groovy-koala-saves · 📅
|
|
120
|
-
// · 🧠 ekkOS_™ · groovy-koala-saves · 📅
|
|
251
|
+
// Claude prints session name in footer: · Turn N · groovy-koala-saves · 📅
|
|
121
252
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
122
|
-
// Strong signal:
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
// "· ekkOS_™ · <session> · 📅"
|
|
126
|
-
const SESSION_NAME_IN_EKKOS_FOOTER_REGEX = /ekkos[^\n·]*·\s*(?:turn\s+\d+\s*·\s*)?([a-z]+-[a-z]+-[a-z]+)\s*·/i;
|
|
127
|
-
// Legacy fallback: plain Claude status line with explicit turn marker.
|
|
128
|
-
const SESSION_NAME_IN_TURN_FOOTER_REGEX = /turn\s+\d+\s*·\s*([a-z]+-[a-z]+-[a-z]+)\s*·/i;
|
|
253
|
+
// Strong signal: explicit turn footer emitted by Claude/ekkOS status line.
|
|
254
|
+
// Requires "Turn <n> · <session> ·" to avoid matching arbitrary slug text.
|
|
255
|
+
const SESSION_NAME_IN_STATUS_REGEX = /turn\s+\d+\s*·\s*([a-z]+-[a-z]+-[a-z]+)\s*·/i;
|
|
129
256
|
// Weaker signal: any 3-word slug (word-word-word pattern)
|
|
130
257
|
const SESSION_NAME_REGEX = /\b([a-z]+-[a-z]+-[a-z]+)\b/i;
|
|
131
258
|
// Orphan tool_result marker emitted by ccDNA validate mode
|
|
@@ -306,8 +433,7 @@ const isWindows = os.platform() === 'win32';
|
|
|
306
433
|
// 'latest' = use latest version, or specify like '2.1.33' for specific version
|
|
307
434
|
// Core ekkOS patches (eviction, context management) work with all recent versions
|
|
308
435
|
// Cosmetic patches may fail on newer versions but don't affect functionality
|
|
309
|
-
const PINNED_CLAUDE_VERSION = '
|
|
310
|
-
const MIN_CLAUDE_VERSION_FOR_LATEST = process.env.EKKOS_MIN_CLAUDE_VERSION || '2.1.49';
|
|
436
|
+
const PINNED_CLAUDE_VERSION = '2.1.37';
|
|
311
437
|
// Max output tokens for Claude responses
|
|
312
438
|
// Default: 16384 (safe for Sonnet 4.5)
|
|
313
439
|
// Opus 4.5 supports up to 64k - set EKKOS_MAX_OUTPUT_TOKENS=32768 or =65536 to use higher limits
|
|
@@ -347,8 +473,9 @@ function getEkkosEnv() {
|
|
|
347
473
|
// Let Claude Code use its own default max_tokens (don't override)
|
|
348
474
|
};
|
|
349
475
|
/* eslint-enable no-restricted-syntax */
|
|
350
|
-
// Check if proxy is disabled
|
|
351
|
-
|
|
476
|
+
// Check if proxy is disabled via env var or options
|
|
477
|
+
// eslint-disable-next-line no-restricted-syntax -- Feature flag, not API key
|
|
478
|
+
const proxyDisabled = process.env.EKKOS_DISABLE_PROXY === '1' || !proxyModeEnabled;
|
|
352
479
|
if (!proxyDisabled) {
|
|
353
480
|
env.EKKOS_PROXY_MODE = '1';
|
|
354
481
|
// Enable ultra-minimal mode by default (30%→20% eviction for constant-cost infinite context)
|
|
@@ -357,14 +484,10 @@ function getEkkosEnv() {
|
|
|
357
484
|
// This fixes the mismatch where CLI generated one name but Claude Code used another
|
|
358
485
|
// The hook calls POST /proxy/session/bind with Claude's actual session name
|
|
359
486
|
if (!cliSessionName) {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
env.EKKOS_PENDING_SESSION = cliSessionName;
|
|
366
|
-
if (cliSessionId)
|
|
367
|
-
env.EKKOS_PENDING_SESSION_ID = cliSessionId;
|
|
487
|
+
cliSessionName = '_pending'; // Placeholder - hook will bind real name
|
|
488
|
+
cliSessionId = `pending-${Date.now()}`;
|
|
489
|
+
console.log(chalk_1.default.gray(` 📂 Session: pending (will bind to Claude session)`));
|
|
490
|
+
}
|
|
368
491
|
// Get full userId from config (NOT the truncated version from auth token)
|
|
369
492
|
// Config has full UUID like "d4532ba0-0a86-42ce-bab4-22aa62b55ce6"
|
|
370
493
|
// This matches the turns/ R2 structure: turns/{fullUserId}/{sessionName}/
|
|
@@ -385,8 +508,8 @@ function getEkkosEnv() {
|
|
|
385
508
|
// Format: https://mcp.ekkos.dev/proxy/{userId}/{sessionName}?project={base64(cwd)}
|
|
386
509
|
// Gateway extracts from URL: /proxy/{userId}/{sessionName}/v1/messages
|
|
387
510
|
// Project path is base64-encoded to handle special chars safely
|
|
388
|
-
const projectPath = process.cwd()
|
|
389
|
-
const projectPathEncoded =
|
|
511
|
+
const projectPath = process.cwd();
|
|
512
|
+
const projectPathEncoded = Buffer.from(projectPath).toString('base64url');
|
|
390
513
|
const proxyUrl = `${EKKOS_PROXY_URL}/proxy/${encodeURIComponent(userId)}/${encodeURIComponent(cliSessionName)}?project=${projectPathEncoded}`;
|
|
391
514
|
env.ANTHROPIC_BASE_URL = proxyUrl;
|
|
392
515
|
// Proxy URL contains userId + project path — don't leak to terminal
|
|
@@ -396,59 +519,16 @@ function getEkkosEnv() {
|
|
|
396
519
|
}
|
|
397
520
|
return env;
|
|
398
521
|
}
|
|
399
|
-
// ekkOS-managed Claude installation path
|
|
522
|
+
// ekkOS-managed Claude installation path
|
|
400
523
|
const EKKOS_CLAUDE_DIR = path.join(os.homedir(), '.ekkos', 'claude-code');
|
|
401
|
-
const EKKOS_CLAUDE_BIN = path.join(EKKOS_CLAUDE_DIR, 'node_modules', '.bin',
|
|
402
|
-
// Native installer versions directory (Anthropic's official distribution — no npm warning)
|
|
403
|
-
// Claude Code uses XDG_DATA_HOME/.local/share on all platforms (no Windows-specific AppData branch).
|
|
404
|
-
// On Windows this resolves to %USERPROFILE%\.local\share\claude\versions\<version>
|
|
405
|
-
const NATIVE_CLAUDE_VERSIONS_DIR = path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share'), 'claude', 'versions');
|
|
406
|
-
/**
|
|
407
|
-
* Find the latest native-installed Claude binary.
|
|
408
|
-
* Anthropic's native installer puts versioned binaries under ~/.local/share/claude/versions/<version>
|
|
409
|
-
* These do NOT print the npm-to-native migration warning.
|
|
410
|
-
* Returns the path to the latest binary, or null if none found.
|
|
411
|
-
*/
|
|
412
|
-
function resolveNativeClaudeBin() {
|
|
413
|
-
try {
|
|
414
|
-
if (!fs.existsSync(NATIVE_CLAUDE_VERSIONS_DIR))
|
|
415
|
-
return null;
|
|
416
|
-
const entries = fs.readdirSync(NATIVE_CLAUDE_VERSIONS_DIR)
|
|
417
|
-
.filter(v => !v.endsWith('.tmp') && !v.endsWith('.lock'));
|
|
418
|
-
// Strip .exe suffix for sorting, then pick latest semver
|
|
419
|
-
const versions = entries
|
|
420
|
-
.map(v => v.replace(/\.exe$/i, ''))
|
|
421
|
-
.filter(v => /^\d+\.\d+\.\d+$/.test(v))
|
|
422
|
-
.sort((a, b) => {
|
|
423
|
-
const pa = a.split('.').map(Number);
|
|
424
|
-
const pb = b.split('.').map(Number);
|
|
425
|
-
for (let i = 0; i < 3; i++) {
|
|
426
|
-
if ((pa[i] || 0) !== (pb[i] || 0))
|
|
427
|
-
return (pb[i] || 0) - (pa[i] || 0);
|
|
428
|
-
}
|
|
429
|
-
return 0;
|
|
430
|
-
});
|
|
431
|
-
if (versions.length === 0)
|
|
432
|
-
return null;
|
|
433
|
-
// On Windows the binary is named "<version>.exe", on Unix just "<version>"
|
|
434
|
-
const binName = isWindows ? `${versions[0]}.exe` : versions[0];
|
|
435
|
-
const bin = path.join(NATIVE_CLAUDE_VERSIONS_DIR, binName);
|
|
436
|
-
if (fs.existsSync(bin) && fs.statSync(bin).isFile())
|
|
437
|
-
return bin;
|
|
438
|
-
return null;
|
|
439
|
-
}
|
|
440
|
-
catch {
|
|
441
|
-
return null;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
524
|
+
const EKKOS_CLAUDE_BIN = path.join(EKKOS_CLAUDE_DIR, 'node_modules', '.bin', 'claude');
|
|
444
525
|
/**
|
|
445
526
|
* Check if a Claude installation exists and get its version
|
|
446
527
|
* Returns version string if found, null otherwise
|
|
447
528
|
*/
|
|
448
529
|
function getClaudeVersion(claudePath) {
|
|
449
530
|
try {
|
|
450
|
-
const
|
|
451
|
-
const version = (0, child_process_1.execSync)(`"${claudePath}" --version ${suppressStderr}`, { encoding: 'utf-8' }).trim();
|
|
531
|
+
const version = (0, child_process_1.execSync)(`"${claudePath}" --version 2>/dev/null`, { encoding: 'utf-8' }).trim();
|
|
452
532
|
// Look for pattern like "2.1.6 (Claude Code)" or just "2.1.6" anywhere in output
|
|
453
533
|
const match = version.match(/(\d+\.\d+\.\d+)\s*\(Claude Code\)/);
|
|
454
534
|
if (match)
|
|
@@ -462,24 +542,6 @@ function getClaudeVersion(claudePath) {
|
|
|
462
542
|
return null;
|
|
463
543
|
}
|
|
464
544
|
}
|
|
465
|
-
function compareSemver(a, b) {
|
|
466
|
-
const parse = (v) => {
|
|
467
|
-
const m = v.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
468
|
-
if (!m)
|
|
469
|
-
return [0, 0, 0];
|
|
470
|
-
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
471
|
-
};
|
|
472
|
-
const pa = parse(a);
|
|
473
|
-
const pb = parse(b);
|
|
474
|
-
for (let i = 0; i < 3; i++) {
|
|
475
|
-
if (pa[i] !== pb[i])
|
|
476
|
-
return pa[i] - pb[i];
|
|
477
|
-
}
|
|
478
|
-
return 0;
|
|
479
|
-
}
|
|
480
|
-
function isVersionAtLeast(version, minVersion) {
|
|
481
|
-
return compareSemver(version, minVersion) >= 0;
|
|
482
|
-
}
|
|
483
545
|
/**
|
|
484
546
|
* Check if a Claude installation matches our required version
|
|
485
547
|
* When PINNED_CLAUDE_VERSION is 'latest', any version is acceptable
|
|
@@ -489,9 +551,8 @@ function checkClaudeVersion(claudePath) {
|
|
|
489
551
|
if (!version)
|
|
490
552
|
return false;
|
|
491
553
|
// 'latest' means any version is acceptable
|
|
492
|
-
if (PINNED_CLAUDE_VERSION === 'latest')
|
|
493
|
-
return
|
|
494
|
-
}
|
|
554
|
+
if (PINNED_CLAUDE_VERSION === 'latest')
|
|
555
|
+
return true;
|
|
495
556
|
return version === PINNED_CLAUDE_VERSION;
|
|
496
557
|
}
|
|
497
558
|
/**
|
|
@@ -562,37 +623,34 @@ function installEkkosClaudeVersion() {
|
|
|
562
623
|
}
|
|
563
624
|
}
|
|
564
625
|
/**
|
|
565
|
-
* Resolve full path to claude executable
|
|
626
|
+
* Resolve full path to claude executable
|
|
627
|
+
* Returns direct path if found with correct version, otherwise 'npx:VERSION'
|
|
628
|
+
*
|
|
629
|
+
* IMPORTANT: We pin to a specific Claude Code version (currently 2.1.33) to ensure
|
|
630
|
+
* consistent behavior with ekkOS context management and eviction patches.
|
|
631
|
+
*
|
|
632
|
+
* CRITICAL: ekkos run ONLY uses the ekkOS-managed installation at ~/.ekkos/claude-code/
|
|
633
|
+
* This ensures complete separation from the user's existing Claude installation (Homebrew/npm).
|
|
634
|
+
* The user's `claude` command remains untouched and can be any version.
|
|
566
635
|
*
|
|
567
636
|
* Priority:
|
|
568
|
-
* 1.
|
|
569
|
-
* 2.
|
|
570
|
-
* 3.
|
|
571
|
-
* 4. npx fallback (rare, shows npm deprecation warning)
|
|
637
|
+
* 1. ekkOS-managed installation (~/.ekkos/claude-code) - ONLY option for ekkos run
|
|
638
|
+
* 2. Auto-install if doesn't exist
|
|
639
|
+
* 3. npx with pinned version (fallback if install fails)
|
|
572
640
|
*/
|
|
573
641
|
function resolveClaudePath() {
|
|
574
|
-
// PRIORITY 1:
|
|
575
|
-
// These binaries do not print the npm-to-native migration warning.
|
|
576
|
-
const nativeBin = resolveNativeClaudeBin();
|
|
577
|
-
if (nativeBin) {
|
|
578
|
-
const nativeVersion = getClaudeVersion(nativeBin);
|
|
579
|
-
if (PINNED_CLAUDE_VERSION !== 'latest' ||
|
|
580
|
-
(nativeVersion && isVersionAtLeast(nativeVersion, MIN_CLAUDE_VERSION_FOR_LATEST))) {
|
|
581
|
-
return nativeBin;
|
|
582
|
-
}
|
|
583
|
-
console.log(chalk_1.default.yellow(` Native Claude ${nativeVersion || 'unknown'} is below minimum ${MIN_CLAUDE_VERSION_FOR_LATEST}; using managed latest.`));
|
|
584
|
-
}
|
|
585
|
-
// PRIORITY 2: ekkOS-managed npm installation (existing users before native installer)
|
|
642
|
+
// PRIORITY 1: ekkOS-managed installation
|
|
586
643
|
if (fs.existsSync(EKKOS_CLAUDE_BIN) && checkClaudeVersion(EKKOS_CLAUDE_BIN)) {
|
|
587
644
|
return EKKOS_CLAUDE_BIN;
|
|
588
645
|
}
|
|
589
|
-
// PRIORITY
|
|
646
|
+
// PRIORITY 2: Auto-install to ekkOS-managed directory (user's Claude stays untouched)
|
|
590
647
|
if (installEkkosClaudeVersion()) {
|
|
591
648
|
if (fs.existsSync(EKKOS_CLAUDE_BIN)) {
|
|
592
649
|
return EKKOS_CLAUDE_BIN;
|
|
593
650
|
}
|
|
594
651
|
}
|
|
595
|
-
// PRIORITY
|
|
652
|
+
// PRIORITY 3: Fall back to npx with pinned version (shows update message)
|
|
653
|
+
// This is rare - only happens if install failed
|
|
596
654
|
return `npx:${PINNED_CLAUDE_VERSION}`;
|
|
597
655
|
}
|
|
598
656
|
/**
|
|
@@ -639,8 +697,7 @@ function resolveGlobalClaudePath() {
|
|
|
639
697
|
}
|
|
640
698
|
}
|
|
641
699
|
try {
|
|
642
|
-
const
|
|
643
|
-
const result = (0, child_process_1.execSync)(`${whichCmd} claude`, { encoding: 'utf-8' }).trim().split('\n')[0];
|
|
700
|
+
const result = (0, child_process_1.execSync)('which claude', { encoding: 'utf-8' }).trim();
|
|
644
701
|
if (result && fs.existsSync(result)) {
|
|
645
702
|
return result;
|
|
646
703
|
}
|
|
@@ -648,7 +705,7 @@ function resolveGlobalClaudePath() {
|
|
|
648
705
|
catch {
|
|
649
706
|
// Ignore errors
|
|
650
707
|
}
|
|
651
|
-
return
|
|
708
|
+
return 'claude';
|
|
652
709
|
}
|
|
653
710
|
/**
|
|
654
711
|
* Sleep helper
|
|
@@ -746,72 +803,6 @@ function cleanupInstanceFile(instanceId) {
|
|
|
746
803
|
// Ignore cleanup errors
|
|
747
804
|
}
|
|
748
805
|
}
|
|
749
|
-
/**
|
|
750
|
-
* Launch ekkos run + dashboard using Windows Terminal split panes (Windows equivalent of tmux)
|
|
751
|
-
*/
|
|
752
|
-
function launchWithDashboardWindows(options) {
|
|
753
|
-
const launchTime = Date.now();
|
|
754
|
-
// Build the ekkos run command WITHOUT --dashboard (prevent recursion)
|
|
755
|
-
const runArgs = ['run'];
|
|
756
|
-
if (options.session)
|
|
757
|
-
runArgs.push('-s', options.session);
|
|
758
|
-
if (options.bypass)
|
|
759
|
-
runArgs.push('-b');
|
|
760
|
-
if (options.verbose)
|
|
761
|
-
runArgs.push('-v');
|
|
762
|
-
if (options.doctor)
|
|
763
|
-
runArgs.push('-d');
|
|
764
|
-
if (options.research)
|
|
765
|
-
runArgs.push('-r');
|
|
766
|
-
if (options.noInject)
|
|
767
|
-
runArgs.push('--skip-inject');
|
|
768
|
-
if (options.noDna)
|
|
769
|
-
runArgs.push('--skip-dna');
|
|
770
|
-
if (options.noProxy)
|
|
771
|
-
runArgs.push('--skip-proxy');
|
|
772
|
-
const ekkosCmd = process.argv[1];
|
|
773
|
-
const cwd = process.cwd();
|
|
774
|
-
// Write dashboard launch marker
|
|
775
|
-
const markerPath = path.join(state_1.EKKOS_DIR, '.dashboard-launch-ts');
|
|
776
|
-
try {
|
|
777
|
-
fs.writeFileSync(markerPath, `${launchTime}\n${cwd}`);
|
|
778
|
-
}
|
|
779
|
-
catch { }
|
|
780
|
-
// Use -EncodedCommand (UTF-16LE Base64) to pass PowerShell scripts to wt panes.
|
|
781
|
-
// This completely avoids nested quote hell: cmd.exe sees no " inside the wt command string,
|
|
782
|
-
// and PowerShell decodes the base64 itself, preserving backslashes and special chars exactly.
|
|
783
|
-
const ekkosCmdEscaped = ekkosCmd.replace(/'/g, "''");
|
|
784
|
-
const cwdEscaped = cwd.replace(/'/g, "''");
|
|
785
|
-
function toPsEncoded(script) {
|
|
786
|
-
// PowerShell -EncodedCommand expects UTF-16LE Base64
|
|
787
|
-
return Buffer.from(script, 'utf16le').toString('base64');
|
|
788
|
-
}
|
|
789
|
-
// cd to original CWD first so ekkos run registers the correct projectPath
|
|
790
|
-
const runScript = `Set-Location '${cwdEscaped}'; & node '${ekkosCmdEscaped}' ${runArgs.join(' ')}`;
|
|
791
|
-
const dashScript = `& node '${ekkosCmdEscaped}' dashboard --wait-for-new --refresh 2000`;
|
|
792
|
-
const runEncoded = toPsEncoded(runScript);
|
|
793
|
-
const dashEncoded = toPsEncoded(dashScript);
|
|
794
|
-
// Windows Terminal split pane command.
|
|
795
|
-
// No nested double-quotes in the PowerShell portion — only the WT --title/--startingDirectory
|
|
796
|
-
// values need quoting, which cmd.exe handles cleanly.
|
|
797
|
-
const wtCmd = [
|
|
798
|
-
'wt',
|
|
799
|
-
`new-tab --startingDirectory "${cwd}" --title ekkOS powershell -NoExit -EncodedCommand ${runEncoded}`,
|
|
800
|
-
`; split-pane -V --size 0.4 --title Dashboard powershell -NoExit -EncodedCommand ${dashEncoded}`
|
|
801
|
-
].join(' ');
|
|
802
|
-
try {
|
|
803
|
-
(0, child_process_1.execSync)(wtCmd, { stdio: 'inherit', shell: true });
|
|
804
|
-
console.log(chalk_1.default.cyan('\n Dashboard launched in right pane (40%)'));
|
|
805
|
-
console.log(chalk_1.default.gray(' Switch panes: Alt+Left / Alt+Right in Windows Terminal'));
|
|
806
|
-
// Exit current process — the new WT window takes over
|
|
807
|
-
process.exit(0);
|
|
808
|
-
}
|
|
809
|
-
catch (err) {
|
|
810
|
-
console.log(chalk_1.default.red(`Windows Terminal error: ${err.message}`));
|
|
811
|
-
console.log(chalk_1.default.gray('Tip: Install Windows Terminal from the Microsoft Store for split-pane support.'));
|
|
812
|
-
console.log(chalk_1.default.gray('Falling back to normal mode. Run "ekkos dashboard --latest" in another terminal.'));
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
806
|
/**
|
|
816
807
|
* Launch ekkos run + dashboard in isolated tmux panes (60/40 split)
|
|
817
808
|
*/
|
|
@@ -836,6 +827,7 @@ function launchWithDashboard(options) {
|
|
|
836
827
|
runArgs.push('--skip-dna');
|
|
837
828
|
if (options.noProxy)
|
|
838
829
|
runArgs.push('--skip-proxy');
|
|
830
|
+
runArgs.push('--kickstart'); // Auto-send "test" to create session immediately for dashboard
|
|
839
831
|
const ekkosCmd = process.argv[1]; // Path to ekkos CLI
|
|
840
832
|
const cwd = process.cwd();
|
|
841
833
|
const termCols = process.stdout.columns ?? 160;
|
|
@@ -891,65 +883,32 @@ function launchWithDashboard(options) {
|
|
|
891
883
|
}
|
|
892
884
|
}
|
|
893
885
|
async function run(options) {
|
|
894
|
-
// ══════════════════════════════════════════════════════════════════════════
|
|
895
|
-
// AUTO-SETUP: Run setup inline if this is a fresh install
|
|
896
|
-
// New users can just type `ekkos run` and get the full onboarding flow
|
|
897
|
-
// ══════════════════════════════════════════════════════════════════════════
|
|
898
|
-
const configFile = path.join(os.homedir(), '.ekkos', 'config.json');
|
|
899
|
-
if (!fs.existsSync(configFile)) {
|
|
900
|
-
console.log(chalk_1.default.cyan('\n👋 Welcome to ekkOS! Setting up your environment...\n'));
|
|
901
|
-
const { setup } = await Promise.resolve().then(() => __importStar(require('./setup.js')));
|
|
902
|
-
await setup({ ide: 'all' });
|
|
903
|
-
console.log('');
|
|
904
|
-
}
|
|
905
886
|
const verbose = options.verbose || false;
|
|
906
887
|
const bypass = options.bypass || false;
|
|
907
888
|
const noInject = options.noInject || false;
|
|
908
|
-
// Set proxy mode based on options
|
|
909
|
-
|
|
910
|
-
proxyModeEnabled = !(options.noProxy || false || proxyDisabledByEnv);
|
|
889
|
+
// Set proxy mode based on options (used by getEkkosEnv)
|
|
890
|
+
proxyModeEnabled = !(options.noProxy || false);
|
|
911
891
|
if (proxyModeEnabled) {
|
|
912
892
|
console.log(chalk_1.default.cyan(' 🧠 ekkOS_Continuum Loaded!'));
|
|
913
893
|
}
|
|
914
|
-
else if (proxyDisabledByEnv) {
|
|
915
|
-
console.log(chalk_1.default.yellow(' ⏭️ API proxy disabled by EKKOS_DISABLE_PROXY=1'));
|
|
916
|
-
console.log(chalk_1.default.gray(' Unset EKKOS_DISABLE_PROXY to re-enable proxy routing.'));
|
|
917
|
-
}
|
|
918
894
|
else if (verbose) {
|
|
919
|
-
console.log(chalk_1.default.yellow(' ⏭️ API proxy disabled (--
|
|
895
|
+
console.log(chalk_1.default.yellow(' ⏭️ API proxy disabled (--no-proxy)'));
|
|
920
896
|
}
|
|
921
897
|
// ══════════════════════════════════════════════════════════════════════════
|
|
922
|
-
// DASHBOARD MODE: tmux
|
|
898
|
+
// DASHBOARD MODE: Launch via tmux with isolated dashboard pane (60/40)
|
|
923
899
|
// ══════════════════════════════════════════════════════════════════════════
|
|
924
900
|
if (options.dashboard) {
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
(
|
|
929
|
-
launchWithDashboardWindows(options);
|
|
901
|
+
try {
|
|
902
|
+
const tmuxPath = (0, child_process_1.execSync)('which tmux', { encoding: 'utf-8' }).trim();
|
|
903
|
+
if (tmuxPath) {
|
|
904
|
+
launchWithDashboard(options);
|
|
930
905
|
return;
|
|
931
906
|
}
|
|
932
|
-
catch {
|
|
933
|
-
console.log(chalk_1.default.yellow(' Windows Terminal not found.'));
|
|
934
|
-
console.log(chalk_1.default.gray(' Install from Microsoft Store: https://aka.ms/terminal'));
|
|
935
|
-
console.log(chalk_1.default.gray(' Alternative: run "ekkos dashboard --latest" in a separate terminal'));
|
|
936
|
-
console.log(chalk_1.default.gray(' Continuing without dashboard...\n'));
|
|
937
|
-
}
|
|
938
907
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
if (tmuxPath) {
|
|
944
|
-
launchWithDashboard(options);
|
|
945
|
-
return;
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
catch {
|
|
949
|
-
console.log(chalk_1.default.yellow(' tmux not found. Install: brew install tmux'));
|
|
950
|
-
console.log(chalk_1.default.gray(' Alternative: run "ekkos dashboard --latest" in a separate terminal'));
|
|
951
|
-
console.log(chalk_1.default.gray(' Continuing without dashboard...\n'));
|
|
952
|
-
}
|
|
908
|
+
catch {
|
|
909
|
+
console.log(chalk_1.default.yellow(' tmux not found. Install: brew install tmux'));
|
|
910
|
+
console.log(chalk_1.default.gray(' Alternative: run "ekkos dashboard --latest" in a separate terminal'));
|
|
911
|
+
console.log(chalk_1.default.gray(' Continuing without dashboard...\n'));
|
|
953
912
|
}
|
|
954
913
|
}
|
|
955
914
|
// Generate instance ID for this run
|
|
@@ -1005,15 +964,22 @@ async function run(options) {
|
|
|
1005
964
|
}
|
|
1006
965
|
}
|
|
1007
966
|
// ══════════════════════════════════════════════════════════════════════════
|
|
1008
|
-
//
|
|
1009
|
-
//
|
|
967
|
+
// ccDNA AUTO-PATCH: Apply Claude Code customizations before spawn
|
|
968
|
+
// This patches the context warning, themes, and other ccDNA features
|
|
969
|
+
// Skip if --no-dna flag is set
|
|
1010
970
|
// ══════════════════════════════════════════════════════════════════════════
|
|
1011
971
|
const noDna = options.noDna || false;
|
|
1012
|
-
|
|
1013
|
-
|
|
972
|
+
let ccdnaVersion = null;
|
|
973
|
+
if (noDna) {
|
|
974
|
+
if (verbose) {
|
|
975
|
+
console.log(chalk_1.default.yellow(' ⏭️ Skipping ccDNA injection (--no-dna)'));
|
|
976
|
+
}
|
|
1014
977
|
}
|
|
1015
|
-
|
|
1016
|
-
|
|
978
|
+
else {
|
|
979
|
+
if (verbose && claudeCliPath) {
|
|
980
|
+
console.log(chalk_1.default.gray(` 🔧 Patching: ${claudeCliPath}`));
|
|
981
|
+
}
|
|
982
|
+
ccdnaVersion = applyCcdnaPatches(verbose, claudeCliPath);
|
|
1017
983
|
}
|
|
1018
984
|
const pinnedVersion = isNpxMode ? rawClaudePath.split(':')[1] : null;
|
|
1019
985
|
const claudePath = isNpxMode ? 'npx' : rawClaudePath;
|
|
@@ -1045,7 +1011,7 @@ async function run(options) {
|
|
|
1045
1011
|
// Handler reference for early spawn (allows replacing buffer handler with real handler)
|
|
1046
1012
|
let earlyDataHandler = null;
|
|
1047
1013
|
// Start spawning Claude NOW (before animation) if PTY available
|
|
1048
|
-
if (usePty && pty) {
|
|
1014
|
+
if (usePty && pty && !isWindows) {
|
|
1049
1015
|
try {
|
|
1050
1016
|
earlySpawnedShell = pty.spawn(claudePath, earlyArgs, {
|
|
1051
1017
|
name: 'xterm-256color',
|
|
@@ -1174,10 +1140,10 @@ async function run(options) {
|
|
|
1174
1140
|
logoLines.forEach(line => console.log(chalk_1.default.magenta(line)));
|
|
1175
1141
|
console.log('');
|
|
1176
1142
|
// ══════════════════════════════════════════════════════════════════════════
|
|
1177
|
-
// ANIMATED TITLE: "
|
|
1143
|
+
// ANIMATED TITLE: "Cognitive Continuity Engine" with orange/white shine
|
|
1178
1144
|
// ══════════════════════════════════════════════════════════════════════════
|
|
1179
|
-
const titleText = '
|
|
1180
|
-
const taglineText = '
|
|
1145
|
+
const titleText = 'Cognitive Continuity Engine';
|
|
1146
|
+
const taglineText = 'Context is finite. Intelligence isn\'t.';
|
|
1181
1147
|
// Color palette for shine effect
|
|
1182
1148
|
const whiteShine = chalk_1.default.whiteBright;
|
|
1183
1149
|
// Phase 1: Typewriter effect for title
|
|
@@ -1257,6 +1223,9 @@ async function run(options) {
|
|
|
1257
1223
|
if (bypass) {
|
|
1258
1224
|
console.log(chalk_1.default.yellow(' ⚡ Bypass permissions mode enabled'));
|
|
1259
1225
|
}
|
|
1226
|
+
if (noDna) {
|
|
1227
|
+
console.log(chalk_1.default.yellow(' ⏭️ ccDNA injection skipped (--no-dna)'));
|
|
1228
|
+
}
|
|
1260
1229
|
if (verbose) {
|
|
1261
1230
|
console.log(chalk_1.default.gray(` 📁 Debug log: ${config.debugLogPath}`));
|
|
1262
1231
|
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)`));
|
|
@@ -1269,6 +1238,9 @@ async function run(options) {
|
|
|
1269
1238
|
if (bypass) {
|
|
1270
1239
|
console.log(chalk_1.default.yellow(' ⚡ Bypass permissions mode enabled'));
|
|
1271
1240
|
}
|
|
1241
|
+
if (noDna) {
|
|
1242
|
+
console.log(chalk_1.default.yellow(' ⏭️ ccDNA injection skipped (--no-dna)'));
|
|
1243
|
+
}
|
|
1272
1244
|
if (verbose) {
|
|
1273
1245
|
console.log(chalk_1.default.gray(` 📁 Debug log: ${config.debugLogPath}`));
|
|
1274
1246
|
}
|
|
@@ -1355,7 +1327,7 @@ async function run(options) {
|
|
|
1355
1327
|
// Claude creates the transcript file BEFORE outputting the session name
|
|
1356
1328
|
// So we watch for new files rather than parsing TUI output (which is slower)
|
|
1357
1329
|
// ════════════════════════════════════════════════════════════════════════════
|
|
1358
|
-
const encodedCwd = process.cwd().replace(
|
|
1330
|
+
const encodedCwd = process.cwd().replace(/\//g, '-');
|
|
1359
1331
|
const projectDir = path.join(os.homedir(), '.claude', 'projects', encodedCwd);
|
|
1360
1332
|
const launchTime = Date.now();
|
|
1361
1333
|
// Track existing jsonl files at startup
|
|
@@ -1526,10 +1498,8 @@ async function run(options) {
|
|
|
1526
1498
|
});
|
|
1527
1499
|
return false;
|
|
1528
1500
|
}
|
|
1529
|
-
// Check it
|
|
1530
|
-
|
|
1531
|
-
/^[A-Za-z]:[\\/]/.test(pathToCheck) || pathToCheck.startsWith('\\\\');
|
|
1532
|
-
if (!isAbsolutePath) {
|
|
1501
|
+
// Check it starts with / or ~ (absolute path)
|
|
1502
|
+
if (!pathToCheck.startsWith('/') && !pathToCheck.startsWith('~')) {
|
|
1533
1503
|
evictionDebugLog('PATH_INVALID', 'Transcript path is not absolute - clearing', {
|
|
1534
1504
|
path: pathToCheck.slice(0, 100),
|
|
1535
1505
|
});
|
|
@@ -1561,7 +1531,7 @@ async function run(options) {
|
|
|
1561
1531
|
function bindRealSessionToProxy(sessionName, source) {
|
|
1562
1532
|
if (!proxyModeEnabled)
|
|
1563
1533
|
return;
|
|
1564
|
-
if (!sessionName || sessionName === '_pending'
|
|
1534
|
+
if (!sessionName || sessionName === '_pending')
|
|
1565
1535
|
return;
|
|
1566
1536
|
if (boundProxySession === sessionName || bindingSessionInFlight === sessionName)
|
|
1567
1537
|
return;
|
|
@@ -1569,8 +1539,7 @@ async function run(options) {
|
|
|
1569
1539
|
void (async () => {
|
|
1570
1540
|
const maxAttempts = 3;
|
|
1571
1541
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1572
|
-
const
|
|
1573
|
-
const success = await (0, session_binding_1.bindSession)(sessionName, process.cwd(), pendingSession);
|
|
1542
|
+
const success = await (0, session_binding_1.bindSession)(sessionName, process.cwd());
|
|
1574
1543
|
if (success) {
|
|
1575
1544
|
boundProxySession = sessionName;
|
|
1576
1545
|
bindingSessionInFlight = null;
|
|
@@ -1711,8 +1680,12 @@ async function run(options) {
|
|
|
1711
1680
|
console.log(chalk_1.default.yellow(' Monitor-only mode (--no-inject)'));
|
|
1712
1681
|
}
|
|
1713
1682
|
if (verbose) {
|
|
1683
|
+
// Show Claude version with ccDNA version if patched
|
|
1714
1684
|
const ccVersion = pinnedVersion || PINNED_CLAUDE_VERSION;
|
|
1715
|
-
|
|
1685
|
+
const versionStr = ccdnaVersion
|
|
1686
|
+
? `Claude Code v${ccVersion} + ekkOS_Continuum v${ccdnaVersion}`
|
|
1687
|
+
: `Claude Code v${ccVersion}`;
|
|
1688
|
+
console.log(chalk_1.default.gray(` 🤖 ${versionStr}`));
|
|
1716
1689
|
if (currentSession) {
|
|
1717
1690
|
console.log(chalk_1.default.green(` 📍 Session: ${currentSession}`));
|
|
1718
1691
|
}
|
|
@@ -1774,8 +1747,7 @@ async function run(options) {
|
|
|
1774
1747
|
const spawnedProcess = (0, child_process_1.spawn)(claudePath, args, {
|
|
1775
1748
|
stdio: 'inherit',
|
|
1776
1749
|
cwd: process.cwd(),
|
|
1777
|
-
env: getEkkosEnv()
|
|
1778
|
-
shell: isWindows // Required on Windows to execute .cmd files
|
|
1750
|
+
env: getEkkosEnv()
|
|
1779
1751
|
});
|
|
1780
1752
|
spawnedProcess.on('exit', (code) => process.exit(code ?? 0));
|
|
1781
1753
|
spawnedProcess.on('error', (e) => {
|
|
@@ -1799,8 +1771,7 @@ async function run(options) {
|
|
|
1799
1771
|
const spawnedProcess = (0, child_process_1.spawn)(claudePath, args, {
|
|
1800
1772
|
stdio: 'inherit',
|
|
1801
1773
|
cwd: process.cwd(),
|
|
1802
|
-
env: getEkkosEnv()
|
|
1803
|
-
shell: isWindows // Required on Windows to execute .cmd files
|
|
1774
|
+
env: getEkkosEnv()
|
|
1804
1775
|
});
|
|
1805
1776
|
spawnedProcess.on('exit', (code) => {
|
|
1806
1777
|
process.exit(code ?? 0);
|
|
@@ -2304,9 +2275,7 @@ async function run(options) {
|
|
|
2304
2275
|
if (transcriptMatch) {
|
|
2305
2276
|
const candidatePath = transcriptMatch[1];
|
|
2306
2277
|
// Validate it's an actual path (not garbage from terminal output)
|
|
2307
|
-
|
|
2308
|
-
/^[A-Za-z]:[\\/]/.test(candidatePath);
|
|
2309
|
-
if (isAbsCandidate) {
|
|
2278
|
+
if (candidatePath.startsWith('/') || candidatePath.startsWith('~')) {
|
|
2310
2279
|
const resolvedPath = candidatePath.startsWith('~')
|
|
2311
2280
|
? path.join(os.homedir(), candidatePath.slice(1))
|
|
2312
2281
|
: candidatePath;
|
|
@@ -2353,15 +2322,12 @@ async function run(options) {
|
|
|
2353
2322
|
}
|
|
2354
2323
|
// ════════════════════════════════════════════════════════════════════════
|
|
2355
2324
|
// SESSION NAME DETECTION (PRIMARY METHOD)
|
|
2356
|
-
// Claude footer
|
|
2357
|
-
// "· 🧠 ekkOS_™ · Turn N · groovy-koala-saves · 📅 2026-01-17"
|
|
2358
|
-
// "· 🧠 ekkOS_™ · groovy-koala-saves · 📅 2026-01-17"
|
|
2325
|
+
// Claude footer: "· Turn N · groovy-koala-saves · 📅 2026-01-17"
|
|
2359
2326
|
// This is MORE reliable than UUID extraction
|
|
2360
2327
|
// ════════════════════════════════════════════════════════════════════════
|
|
2361
2328
|
const plain = stripAnsi(data);
|
|
2362
|
-
// Strong signal: session name
|
|
2363
|
-
const statusMatch = plain.match(
|
|
2364
|
-
plain.match(SESSION_NAME_IN_TURN_FOOTER_REGEX);
|
|
2329
|
+
// Strong signal: session name between dot separators in status/footer line
|
|
2330
|
+
const statusMatch = plain.match(SESSION_NAME_IN_STATUS_REGEX);
|
|
2365
2331
|
if (statusMatch) {
|
|
2366
2332
|
const detectedSession = statusMatch[1].toLowerCase();
|
|
2367
2333
|
// Validate against word lists (SOURCE OF TRUTH)
|