@ekkos/cli 1.0.33 → 1.0.35
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 +221 -259
- 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 +4 -2
- package/templates/CLAUDE.md +23 -135
- 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/ekkos-manifest.json +8 -8
- package/templates/hooks/assistant-response.ps1 +160 -256
- package/templates/hooks/assistant-response.sh +66 -130
- package/templates/hooks/hooks.json +0 -6
- package/templates/hooks/lib/contract.sh +31 -43
- package/templates/hooks/lib/count-tokens.cjs +0 -0
- package/templates/hooks/lib/ekkos-reminders.sh +0 -0
- package/templates/hooks/lib/state.sh +1 -53
- package/templates/hooks/session-start.ps1 +391 -91
- package/templates/hooks/session-start.sh +166 -201
- package/templates/hooks/stop.ps1 +341 -202
- package/templates/hooks/stop.sh +948 -275
- package/templates/hooks/user-prompt-submit.ps1 +548 -224
- package/templates/hooks/user-prompt-submit.sh +456 -382
- 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/hooks.json +2 -9
- package/templates/windsurf-hooks/install.sh +0 -0
- package/templates/windsurf-hooks/lib/contract.sh +0 -2
- 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");
|
|
@@ -54,10 +187,6 @@ const transcript_repair_1 = require("../capture/transcript-repair");
|
|
|
54
187
|
let pty = null;
|
|
55
188
|
let ptyLoadPromise = null;
|
|
56
189
|
async function loadPty() {
|
|
57
|
-
// node-pty uses native addons that don't load cleanly on Windows.
|
|
58
|
-
// All PTY code paths already guard with `!isWindows`, so skip the import entirely.
|
|
59
|
-
if (isWindows)
|
|
60
|
-
return null;
|
|
61
190
|
if (pty)
|
|
62
191
|
return pty;
|
|
63
192
|
if (!ptyLoadPromise) {
|
|
@@ -119,17 +248,11 @@ const INTERRUPTED_REGEX = /interrupted.*what should claude do instead/i;
|
|
|
119
248
|
const PALETTE_INDICATOR_REGEX = /\/(clear|continue|compact|help|bug|config)/i;
|
|
120
249
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
121
250
|
// SESSION NAME DETECTION (3-word slug: word-word-word)
|
|
122
|
-
// Claude prints session name in footer:
|
|
123
|
-
// · 🧠 ekkOS_™ · Turn N · groovy-koala-saves · 📅
|
|
124
|
-
// · 🧠 ekkOS_™ · groovy-koala-saves · 📅
|
|
251
|
+
// Claude prints session name in footer: · Turn N · groovy-koala-saves · 📅
|
|
125
252
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
126
|
-
// Strong signal:
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
// "· ekkOS_™ · <session> · 📅"
|
|
130
|
-
const SESSION_NAME_IN_EKKOS_FOOTER_REGEX = /ekkos[^\n·]*·\s*(?:turn\s+\d+\s*·\s*)?([a-z]+-[a-z]+-[a-z]+)\s*·/i;
|
|
131
|
-
// Legacy fallback: plain Claude status line with explicit turn marker.
|
|
132
|
-
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;
|
|
133
256
|
// Weaker signal: any 3-word slug (word-word-word pattern)
|
|
134
257
|
const SESSION_NAME_REGEX = /\b([a-z]+-[a-z]+-[a-z]+)\b/i;
|
|
135
258
|
// Orphan tool_result marker emitted by ccDNA validate mode
|
|
@@ -310,8 +433,7 @@ const isWindows = os.platform() === 'win32';
|
|
|
310
433
|
// 'latest' = use latest version, or specify like '2.1.33' for specific version
|
|
311
434
|
// Core ekkOS patches (eviction, context management) work with all recent versions
|
|
312
435
|
// Cosmetic patches may fail on newer versions but don't affect functionality
|
|
313
|
-
const PINNED_CLAUDE_VERSION = '
|
|
314
|
-
const MIN_CLAUDE_VERSION_FOR_LATEST = process.env.EKKOS_MIN_CLAUDE_VERSION || '2.1.49';
|
|
436
|
+
const PINNED_CLAUDE_VERSION = '2.1.37';
|
|
315
437
|
// Max output tokens for Claude responses
|
|
316
438
|
// Default: 16384 (safe for Sonnet 4.5)
|
|
317
439
|
// Opus 4.5 supports up to 64k - set EKKOS_MAX_OUTPUT_TOKENS=32768 or =65536 to use higher limits
|
|
@@ -351,8 +473,9 @@ function getEkkosEnv() {
|
|
|
351
473
|
// Let Claude Code use its own default max_tokens (don't override)
|
|
352
474
|
};
|
|
353
475
|
/* eslint-enable no-restricted-syntax */
|
|
354
|
-
// Check if proxy is disabled
|
|
355
|
-
|
|
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;
|
|
356
479
|
if (!proxyDisabled) {
|
|
357
480
|
env.EKKOS_PROXY_MODE = '1';
|
|
358
481
|
// Enable ultra-minimal mode by default (30%→20% eviction for constant-cost infinite context)
|
|
@@ -361,14 +484,10 @@ function getEkkosEnv() {
|
|
|
361
484
|
// This fixes the mismatch where CLI generated one name but Claude Code used another
|
|
362
485
|
// The hook calls POST /proxy/session/bind with Claude's actual session name
|
|
363
486
|
if (!cliSessionName) {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
env.EKKOS_PENDING_SESSION = cliSessionName;
|
|
370
|
-
if (cliSessionId)
|
|
371
|
-
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
|
+
}
|
|
372
491
|
// Get full userId from config (NOT the truncated version from auth token)
|
|
373
492
|
// Config has full UUID like "d4532ba0-0a86-42ce-bab4-22aa62b55ce6"
|
|
374
493
|
// This matches the turns/ R2 structure: turns/{fullUserId}/{sessionName}/
|
|
@@ -389,8 +508,8 @@ function getEkkosEnv() {
|
|
|
389
508
|
// Format: https://mcp.ekkos.dev/proxy/{userId}/{sessionName}?project={base64(cwd)}
|
|
390
509
|
// Gateway extracts from URL: /proxy/{userId}/{sessionName}/v1/messages
|
|
391
510
|
// Project path is base64-encoded to handle special chars safely
|
|
392
|
-
const projectPath = process.cwd()
|
|
393
|
-
const projectPathEncoded =
|
|
511
|
+
const projectPath = process.cwd();
|
|
512
|
+
const projectPathEncoded = Buffer.from(projectPath).toString('base64url');
|
|
394
513
|
const proxyUrl = `${EKKOS_PROXY_URL}/proxy/${encodeURIComponent(userId)}/${encodeURIComponent(cliSessionName)}?project=${projectPathEncoded}`;
|
|
395
514
|
env.ANTHROPIC_BASE_URL = proxyUrl;
|
|
396
515
|
// Proxy URL contains userId + project path — don't leak to terminal
|
|
@@ -400,59 +519,16 @@ function getEkkosEnv() {
|
|
|
400
519
|
}
|
|
401
520
|
return env;
|
|
402
521
|
}
|
|
403
|
-
// ekkOS-managed Claude installation path
|
|
522
|
+
// ekkOS-managed Claude installation path
|
|
404
523
|
const EKKOS_CLAUDE_DIR = path.join(os.homedir(), '.ekkos', 'claude-code');
|
|
405
|
-
const EKKOS_CLAUDE_BIN = path.join(EKKOS_CLAUDE_DIR, 'node_modules', '.bin',
|
|
406
|
-
// Native installer versions directory (Anthropic's official distribution — no npm warning)
|
|
407
|
-
// Claude Code uses XDG_DATA_HOME/.local/share on all platforms (no Windows-specific AppData branch).
|
|
408
|
-
// On Windows this resolves to %USERPROFILE%\.local\share\claude\versions\<version>
|
|
409
|
-
const NATIVE_CLAUDE_VERSIONS_DIR = path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share'), 'claude', 'versions');
|
|
410
|
-
/**
|
|
411
|
-
* Find the latest native-installed Claude binary.
|
|
412
|
-
* Anthropic's native installer puts versioned binaries under ~/.local/share/claude/versions/<version>
|
|
413
|
-
* These do NOT print the npm-to-native migration warning.
|
|
414
|
-
* Returns the path to the latest binary, or null if none found.
|
|
415
|
-
*/
|
|
416
|
-
function resolveNativeClaudeBin() {
|
|
417
|
-
try {
|
|
418
|
-
if (!fs.existsSync(NATIVE_CLAUDE_VERSIONS_DIR))
|
|
419
|
-
return null;
|
|
420
|
-
const entries = fs.readdirSync(NATIVE_CLAUDE_VERSIONS_DIR)
|
|
421
|
-
.filter(v => !v.endsWith('.tmp') && !v.endsWith('.lock'));
|
|
422
|
-
// Strip .exe suffix for sorting, then pick latest semver
|
|
423
|
-
const versions = entries
|
|
424
|
-
.map(v => v.replace(/\.exe$/i, ''))
|
|
425
|
-
.filter(v => /^\d+\.\d+\.\d+$/.test(v))
|
|
426
|
-
.sort((a, b) => {
|
|
427
|
-
const pa = a.split('.').map(Number);
|
|
428
|
-
const pb = b.split('.').map(Number);
|
|
429
|
-
for (let i = 0; i < 3; i++) {
|
|
430
|
-
if ((pa[i] || 0) !== (pb[i] || 0))
|
|
431
|
-
return (pb[i] || 0) - (pa[i] || 0);
|
|
432
|
-
}
|
|
433
|
-
return 0;
|
|
434
|
-
});
|
|
435
|
-
if (versions.length === 0)
|
|
436
|
-
return null;
|
|
437
|
-
// On Windows the binary is named "<version>.exe", on Unix just "<version>"
|
|
438
|
-
const binName = isWindows ? `${versions[0]}.exe` : versions[0];
|
|
439
|
-
const bin = path.join(NATIVE_CLAUDE_VERSIONS_DIR, binName);
|
|
440
|
-
if (fs.existsSync(bin) && fs.statSync(bin).isFile())
|
|
441
|
-
return bin;
|
|
442
|
-
return null;
|
|
443
|
-
}
|
|
444
|
-
catch {
|
|
445
|
-
return null;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
524
|
+
const EKKOS_CLAUDE_BIN = path.join(EKKOS_CLAUDE_DIR, 'node_modules', '.bin', 'claude');
|
|
448
525
|
/**
|
|
449
526
|
* Check if a Claude installation exists and get its version
|
|
450
527
|
* Returns version string if found, null otherwise
|
|
451
528
|
*/
|
|
452
529
|
function getClaudeVersion(claudePath) {
|
|
453
530
|
try {
|
|
454
|
-
const
|
|
455
|
-
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();
|
|
456
532
|
// Look for pattern like "2.1.6 (Claude Code)" or just "2.1.6" anywhere in output
|
|
457
533
|
const match = version.match(/(\d+\.\d+\.\d+)\s*\(Claude Code\)/);
|
|
458
534
|
if (match)
|
|
@@ -466,24 +542,6 @@ function getClaudeVersion(claudePath) {
|
|
|
466
542
|
return null;
|
|
467
543
|
}
|
|
468
544
|
}
|
|
469
|
-
function compareSemver(a, b) {
|
|
470
|
-
const parse = (v) => {
|
|
471
|
-
const m = v.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
472
|
-
if (!m)
|
|
473
|
-
return [0, 0, 0];
|
|
474
|
-
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
475
|
-
};
|
|
476
|
-
const pa = parse(a);
|
|
477
|
-
const pb = parse(b);
|
|
478
|
-
for (let i = 0; i < 3; i++) {
|
|
479
|
-
if (pa[i] !== pb[i])
|
|
480
|
-
return pa[i] - pb[i];
|
|
481
|
-
}
|
|
482
|
-
return 0;
|
|
483
|
-
}
|
|
484
|
-
function isVersionAtLeast(version, minVersion) {
|
|
485
|
-
return compareSemver(version, minVersion) >= 0;
|
|
486
|
-
}
|
|
487
545
|
/**
|
|
488
546
|
* Check if a Claude installation matches our required version
|
|
489
547
|
* When PINNED_CLAUDE_VERSION is 'latest', any version is acceptable
|
|
@@ -493,9 +551,8 @@ function checkClaudeVersion(claudePath) {
|
|
|
493
551
|
if (!version)
|
|
494
552
|
return false;
|
|
495
553
|
// 'latest' means any version is acceptable
|
|
496
|
-
if (PINNED_CLAUDE_VERSION === 'latest')
|
|
497
|
-
return
|
|
498
|
-
}
|
|
554
|
+
if (PINNED_CLAUDE_VERSION === 'latest')
|
|
555
|
+
return true;
|
|
499
556
|
return version === PINNED_CLAUDE_VERSION;
|
|
500
557
|
}
|
|
501
558
|
/**
|
|
@@ -566,37 +623,34 @@ function installEkkosClaudeVersion() {
|
|
|
566
623
|
}
|
|
567
624
|
}
|
|
568
625
|
/**
|
|
569
|
-
* 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.
|
|
570
635
|
*
|
|
571
636
|
* Priority:
|
|
572
|
-
* 1.
|
|
573
|
-
* 2.
|
|
574
|
-
* 3.
|
|
575
|
-
* 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)
|
|
576
640
|
*/
|
|
577
641
|
function resolveClaudePath() {
|
|
578
|
-
// PRIORITY 1:
|
|
579
|
-
// These binaries do not print the npm-to-native migration warning.
|
|
580
|
-
const nativeBin = resolveNativeClaudeBin();
|
|
581
|
-
if (nativeBin) {
|
|
582
|
-
const nativeVersion = getClaudeVersion(nativeBin);
|
|
583
|
-
if (PINNED_CLAUDE_VERSION !== 'latest' ||
|
|
584
|
-
(nativeVersion && isVersionAtLeast(nativeVersion, MIN_CLAUDE_VERSION_FOR_LATEST))) {
|
|
585
|
-
return nativeBin;
|
|
586
|
-
}
|
|
587
|
-
console.log(chalk_1.default.yellow(` Native Claude ${nativeVersion || 'unknown'} is below minimum ${MIN_CLAUDE_VERSION_FOR_LATEST}; using managed latest.`));
|
|
588
|
-
}
|
|
589
|
-
// PRIORITY 2: ekkOS-managed npm installation (existing users before native installer)
|
|
642
|
+
// PRIORITY 1: ekkOS-managed installation
|
|
590
643
|
if (fs.existsSync(EKKOS_CLAUDE_BIN) && checkClaudeVersion(EKKOS_CLAUDE_BIN)) {
|
|
591
644
|
return EKKOS_CLAUDE_BIN;
|
|
592
645
|
}
|
|
593
|
-
// PRIORITY
|
|
646
|
+
// PRIORITY 2: Auto-install to ekkOS-managed directory (user's Claude stays untouched)
|
|
594
647
|
if (installEkkosClaudeVersion()) {
|
|
595
648
|
if (fs.existsSync(EKKOS_CLAUDE_BIN)) {
|
|
596
649
|
return EKKOS_CLAUDE_BIN;
|
|
597
650
|
}
|
|
598
651
|
}
|
|
599
|
-
// PRIORITY
|
|
652
|
+
// PRIORITY 3: Fall back to npx with pinned version (shows update message)
|
|
653
|
+
// This is rare - only happens if install failed
|
|
600
654
|
return `npx:${PINNED_CLAUDE_VERSION}`;
|
|
601
655
|
}
|
|
602
656
|
/**
|
|
@@ -643,8 +697,7 @@ function resolveGlobalClaudePath() {
|
|
|
643
697
|
}
|
|
644
698
|
}
|
|
645
699
|
try {
|
|
646
|
-
const
|
|
647
|
-
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();
|
|
648
701
|
if (result && fs.existsSync(result)) {
|
|
649
702
|
return result;
|
|
650
703
|
}
|
|
@@ -652,7 +705,7 @@ function resolveGlobalClaudePath() {
|
|
|
652
705
|
catch {
|
|
653
706
|
// Ignore errors
|
|
654
707
|
}
|
|
655
|
-
return
|
|
708
|
+
return 'claude';
|
|
656
709
|
}
|
|
657
710
|
/**
|
|
658
711
|
* Sleep helper
|
|
@@ -750,72 +803,6 @@ function cleanupInstanceFile(instanceId) {
|
|
|
750
803
|
// Ignore cleanup errors
|
|
751
804
|
}
|
|
752
805
|
}
|
|
753
|
-
/**
|
|
754
|
-
* Launch ekkos run + dashboard using Windows Terminal split panes (Windows equivalent of tmux)
|
|
755
|
-
*/
|
|
756
|
-
function launchWithDashboardWindows(options) {
|
|
757
|
-
const launchTime = Date.now();
|
|
758
|
-
// Build the ekkos run command WITHOUT --dashboard (prevent recursion)
|
|
759
|
-
const runArgs = ['run'];
|
|
760
|
-
if (options.session)
|
|
761
|
-
runArgs.push('-s', options.session);
|
|
762
|
-
if (options.bypass)
|
|
763
|
-
runArgs.push('-b');
|
|
764
|
-
if (options.verbose)
|
|
765
|
-
runArgs.push('-v');
|
|
766
|
-
if (options.doctor)
|
|
767
|
-
runArgs.push('-d');
|
|
768
|
-
if (options.research)
|
|
769
|
-
runArgs.push('-r');
|
|
770
|
-
if (options.noInject)
|
|
771
|
-
runArgs.push('--skip-inject');
|
|
772
|
-
if (options.noDna)
|
|
773
|
-
runArgs.push('--skip-dna');
|
|
774
|
-
if (options.noProxy)
|
|
775
|
-
runArgs.push('--skip-proxy');
|
|
776
|
-
const ekkosCmd = process.argv[1];
|
|
777
|
-
const cwd = process.cwd();
|
|
778
|
-
// Write dashboard launch marker
|
|
779
|
-
const markerPath = path.join(state_1.EKKOS_DIR, '.dashboard-launch-ts');
|
|
780
|
-
try {
|
|
781
|
-
fs.writeFileSync(markerPath, `${launchTime}\n${cwd}`);
|
|
782
|
-
}
|
|
783
|
-
catch { }
|
|
784
|
-
// Use -EncodedCommand (UTF-16LE Base64) to pass PowerShell scripts to wt panes.
|
|
785
|
-
// This completely avoids nested quote hell: cmd.exe sees no " inside the wt command string,
|
|
786
|
-
// and PowerShell decodes the base64 itself, preserving backslashes and special chars exactly.
|
|
787
|
-
const ekkosCmdEscaped = ekkosCmd.replace(/'/g, "''");
|
|
788
|
-
const cwdEscaped = cwd.replace(/'/g, "''");
|
|
789
|
-
function toPsEncoded(script) {
|
|
790
|
-
// PowerShell -EncodedCommand expects UTF-16LE Base64
|
|
791
|
-
return Buffer.from(script, 'utf16le').toString('base64');
|
|
792
|
-
}
|
|
793
|
-
// cd to original CWD first so ekkos run registers the correct projectPath
|
|
794
|
-
const runScript = `Set-Location '${cwdEscaped}'; & node '${ekkosCmdEscaped}' ${runArgs.join(' ')}`;
|
|
795
|
-
const dashScript = `& node '${ekkosCmdEscaped}' dashboard --wait-for-new --refresh 2000`;
|
|
796
|
-
const runEncoded = toPsEncoded(runScript);
|
|
797
|
-
const dashEncoded = toPsEncoded(dashScript);
|
|
798
|
-
// Windows Terminal split pane command.
|
|
799
|
-
// No nested double-quotes in the PowerShell portion — only the WT --title/--startingDirectory
|
|
800
|
-
// values need quoting, which cmd.exe handles cleanly.
|
|
801
|
-
const wtCmd = [
|
|
802
|
-
'wt',
|
|
803
|
-
`new-tab --startingDirectory "${cwd}" --title ekkOS powershell -NoExit -EncodedCommand ${runEncoded}`,
|
|
804
|
-
`; split-pane -V --size 0.4 --title Dashboard powershell -NoExit -EncodedCommand ${dashEncoded}`
|
|
805
|
-
].join(' ');
|
|
806
|
-
try {
|
|
807
|
-
(0, child_process_1.execSync)(wtCmd, { stdio: 'inherit', shell: true });
|
|
808
|
-
console.log(chalk_1.default.cyan('\n Dashboard launched in right pane (40%)'));
|
|
809
|
-
console.log(chalk_1.default.gray(' Switch panes: Alt+Left / Alt+Right in Windows Terminal'));
|
|
810
|
-
// Exit current process — the new WT window takes over
|
|
811
|
-
process.exit(0);
|
|
812
|
-
}
|
|
813
|
-
catch (err) {
|
|
814
|
-
console.log(chalk_1.default.red(`Windows Terminal error: ${err.message}`));
|
|
815
|
-
console.log(chalk_1.default.gray('Tip: Install Windows Terminal from the Microsoft Store for split-pane support.'));
|
|
816
|
-
console.log(chalk_1.default.gray('Falling back to normal mode. Run "ekkos dashboard --latest" in another terminal.'));
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
806
|
/**
|
|
820
807
|
* Launch ekkos run + dashboard in isolated tmux panes (60/40 split)
|
|
821
808
|
*/
|
|
@@ -840,6 +827,7 @@ function launchWithDashboard(options) {
|
|
|
840
827
|
runArgs.push('--skip-dna');
|
|
841
828
|
if (options.noProxy)
|
|
842
829
|
runArgs.push('--skip-proxy');
|
|
830
|
+
runArgs.push('--kickstart'); // Auto-send "test" to create session immediately for dashboard
|
|
843
831
|
const ekkosCmd = process.argv[1]; // Path to ekkos CLI
|
|
844
832
|
const cwd = process.cwd();
|
|
845
833
|
const termCols = process.stdout.columns ?? 160;
|
|
@@ -895,65 +883,32 @@ function launchWithDashboard(options) {
|
|
|
895
883
|
}
|
|
896
884
|
}
|
|
897
885
|
async function run(options) {
|
|
898
|
-
// ══════════════════════════════════════════════════════════════════════════
|
|
899
|
-
// AUTO-SETUP: Run setup inline if this is a fresh install
|
|
900
|
-
// New users can just type `ekkos run` and get the full onboarding flow
|
|
901
|
-
// ══════════════════════════════════════════════════════════════════════════
|
|
902
|
-
const configFile = path.join(os.homedir(), '.ekkos', 'config.json');
|
|
903
|
-
if (!fs.existsSync(configFile)) {
|
|
904
|
-
console.log(chalk_1.default.cyan('\n👋 Welcome to ekkOS! Setting up your environment...\n'));
|
|
905
|
-
const { setup } = await Promise.resolve().then(() => __importStar(require('./setup.js')));
|
|
906
|
-
await setup({ ide: 'all' });
|
|
907
|
-
console.log('');
|
|
908
|
-
}
|
|
909
886
|
const verbose = options.verbose || false;
|
|
910
887
|
const bypass = options.bypass || false;
|
|
911
888
|
const noInject = options.noInject || false;
|
|
912
|
-
// Set proxy mode based on options
|
|
913
|
-
|
|
914
|
-
proxyModeEnabled = !(options.noProxy || false || proxyDisabledByEnv);
|
|
889
|
+
// Set proxy mode based on options (used by getEkkosEnv)
|
|
890
|
+
proxyModeEnabled = !(options.noProxy || false);
|
|
915
891
|
if (proxyModeEnabled) {
|
|
916
892
|
console.log(chalk_1.default.cyan(' 🧠 ekkOS_Continuum Loaded!'));
|
|
917
893
|
}
|
|
918
|
-
else if (proxyDisabledByEnv) {
|
|
919
|
-
console.log(chalk_1.default.yellow(' ⏭️ API proxy disabled by EKKOS_DISABLE_PROXY=1'));
|
|
920
|
-
console.log(chalk_1.default.gray(' Unset EKKOS_DISABLE_PROXY to re-enable proxy routing.'));
|
|
921
|
-
}
|
|
922
894
|
else if (verbose) {
|
|
923
|
-
console.log(chalk_1.default.yellow(' ⏭️ API proxy disabled (--
|
|
895
|
+
console.log(chalk_1.default.yellow(' ⏭️ API proxy disabled (--no-proxy)'));
|
|
924
896
|
}
|
|
925
897
|
// ══════════════════════════════════════════════════════════════════════════
|
|
926
|
-
// DASHBOARD MODE: tmux
|
|
898
|
+
// DASHBOARD MODE: Launch via tmux with isolated dashboard pane (60/40)
|
|
927
899
|
// ══════════════════════════════════════════════════════════════════════════
|
|
928
900
|
if (options.dashboard) {
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
(
|
|
933
|
-
launchWithDashboardWindows(options);
|
|
901
|
+
try {
|
|
902
|
+
const tmuxPath = (0, child_process_1.execSync)('which tmux', { encoding: 'utf-8' }).trim();
|
|
903
|
+
if (tmuxPath) {
|
|
904
|
+
launchWithDashboard(options);
|
|
934
905
|
return;
|
|
935
906
|
}
|
|
936
|
-
catch {
|
|
937
|
-
console.log(chalk_1.default.yellow(' Windows Terminal not found.'));
|
|
938
|
-
console.log(chalk_1.default.gray(' Install from Microsoft Store: https://aka.ms/terminal'));
|
|
939
|
-
console.log(chalk_1.default.gray(' Alternative: run "ekkos dashboard --latest" in a separate terminal'));
|
|
940
|
-
console.log(chalk_1.default.gray(' Continuing without dashboard...\n'));
|
|
941
|
-
}
|
|
942
907
|
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
if (tmuxPath) {
|
|
948
|
-
launchWithDashboard(options);
|
|
949
|
-
return;
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
catch {
|
|
953
|
-
console.log(chalk_1.default.yellow(' tmux not found. Install: brew install tmux'));
|
|
954
|
-
console.log(chalk_1.default.gray(' Alternative: run "ekkos dashboard --latest" in a separate terminal'));
|
|
955
|
-
console.log(chalk_1.default.gray(' Continuing without dashboard...\n'));
|
|
956
|
-
}
|
|
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'));
|
|
957
912
|
}
|
|
958
913
|
}
|
|
959
914
|
// Generate instance ID for this run
|
|
@@ -1009,15 +964,22 @@ async function run(options) {
|
|
|
1009
964
|
}
|
|
1010
965
|
}
|
|
1011
966
|
// ══════════════════════════════════════════════════════════════════════════
|
|
1012
|
-
//
|
|
1013
|
-
//
|
|
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
|
|
1014
970
|
// ══════════════════════════════════════════════════════════════════════════
|
|
1015
971
|
const noDna = options.noDna || false;
|
|
1016
|
-
|
|
1017
|
-
|
|
972
|
+
let ccdnaVersion = null;
|
|
973
|
+
if (noDna) {
|
|
974
|
+
if (verbose) {
|
|
975
|
+
console.log(chalk_1.default.yellow(' ⏭️ Skipping ccDNA injection (--no-dna)'));
|
|
976
|
+
}
|
|
1018
977
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
978
|
+
else {
|
|
979
|
+
if (verbose && claudeCliPath) {
|
|
980
|
+
console.log(chalk_1.default.gray(` 🔧 Patching: ${claudeCliPath}`));
|
|
981
|
+
}
|
|
982
|
+
ccdnaVersion = applyCcdnaPatches(verbose, claudeCliPath);
|
|
1021
983
|
}
|
|
1022
984
|
const pinnedVersion = isNpxMode ? rawClaudePath.split(':')[1] : null;
|
|
1023
985
|
const claudePath = isNpxMode ? 'npx' : rawClaudePath;
|
|
@@ -1178,10 +1140,10 @@ async function run(options) {
|
|
|
1178
1140
|
logoLines.forEach(line => console.log(chalk_1.default.magenta(line)));
|
|
1179
1141
|
console.log('');
|
|
1180
1142
|
// ══════════════════════════════════════════════════════════════════════════
|
|
1181
|
-
// ANIMATED TITLE: "
|
|
1143
|
+
// ANIMATED TITLE: "Cognitive Continuity Engine" with orange/white shine
|
|
1182
1144
|
// ══════════════════════════════════════════════════════════════════════════
|
|
1183
|
-
const titleText = '
|
|
1184
|
-
const taglineText = '
|
|
1145
|
+
const titleText = 'Cognitive Continuity Engine';
|
|
1146
|
+
const taglineText = 'Context is finite. Intelligence isn\'t.';
|
|
1185
1147
|
// Color palette for shine effect
|
|
1186
1148
|
const whiteShine = chalk_1.default.whiteBright;
|
|
1187
1149
|
// Phase 1: Typewriter effect for title
|
|
@@ -1261,6 +1223,9 @@ async function run(options) {
|
|
|
1261
1223
|
if (bypass) {
|
|
1262
1224
|
console.log(chalk_1.default.yellow(' ⚡ Bypass permissions mode enabled'));
|
|
1263
1225
|
}
|
|
1226
|
+
if (noDna) {
|
|
1227
|
+
console.log(chalk_1.default.yellow(' ⏭️ ccDNA injection skipped (--no-dna)'));
|
|
1228
|
+
}
|
|
1264
1229
|
if (verbose) {
|
|
1265
1230
|
console.log(chalk_1.default.gray(` 📁 Debug log: ${config.debugLogPath}`));
|
|
1266
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)`));
|
|
@@ -1273,6 +1238,9 @@ async function run(options) {
|
|
|
1273
1238
|
if (bypass) {
|
|
1274
1239
|
console.log(chalk_1.default.yellow(' ⚡ Bypass permissions mode enabled'));
|
|
1275
1240
|
}
|
|
1241
|
+
if (noDna) {
|
|
1242
|
+
console.log(chalk_1.default.yellow(' ⏭️ ccDNA injection skipped (--no-dna)'));
|
|
1243
|
+
}
|
|
1276
1244
|
if (verbose) {
|
|
1277
1245
|
console.log(chalk_1.default.gray(` 📁 Debug log: ${config.debugLogPath}`));
|
|
1278
1246
|
}
|
|
@@ -1359,7 +1327,7 @@ async function run(options) {
|
|
|
1359
1327
|
// Claude creates the transcript file BEFORE outputting the session name
|
|
1360
1328
|
// So we watch for new files rather than parsing TUI output (which is slower)
|
|
1361
1329
|
// ════════════════════════════════════════════════════════════════════════════
|
|
1362
|
-
const encodedCwd = process.cwd().replace(
|
|
1330
|
+
const encodedCwd = process.cwd().replace(/\//g, '-');
|
|
1363
1331
|
const projectDir = path.join(os.homedir(), '.claude', 'projects', encodedCwd);
|
|
1364
1332
|
const launchTime = Date.now();
|
|
1365
1333
|
// Track existing jsonl files at startup
|
|
@@ -1530,10 +1498,8 @@ async function run(options) {
|
|
|
1530
1498
|
});
|
|
1531
1499
|
return false;
|
|
1532
1500
|
}
|
|
1533
|
-
// Check it
|
|
1534
|
-
|
|
1535
|
-
/^[A-Za-z]:[\\/]/.test(pathToCheck) || pathToCheck.startsWith('\\\\');
|
|
1536
|
-
if (!isAbsolutePath) {
|
|
1501
|
+
// Check it starts with / or ~ (absolute path)
|
|
1502
|
+
if (!pathToCheck.startsWith('/') && !pathToCheck.startsWith('~')) {
|
|
1537
1503
|
evictionDebugLog('PATH_INVALID', 'Transcript path is not absolute - clearing', {
|
|
1538
1504
|
path: pathToCheck.slice(0, 100),
|
|
1539
1505
|
});
|
|
@@ -1565,7 +1531,7 @@ async function run(options) {
|
|
|
1565
1531
|
function bindRealSessionToProxy(sessionName, source) {
|
|
1566
1532
|
if (!proxyModeEnabled)
|
|
1567
1533
|
return;
|
|
1568
|
-
if (!sessionName || sessionName === '_pending'
|
|
1534
|
+
if (!sessionName || sessionName === '_pending')
|
|
1569
1535
|
return;
|
|
1570
1536
|
if (boundProxySession === sessionName || bindingSessionInFlight === sessionName)
|
|
1571
1537
|
return;
|
|
@@ -1573,8 +1539,7 @@ async function run(options) {
|
|
|
1573
1539
|
void (async () => {
|
|
1574
1540
|
const maxAttempts = 3;
|
|
1575
1541
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1576
|
-
const
|
|
1577
|
-
const success = await (0, session_binding_1.bindSession)(sessionName, process.cwd(), pendingSession);
|
|
1542
|
+
const success = await (0, session_binding_1.bindSession)(sessionName, process.cwd());
|
|
1578
1543
|
if (success) {
|
|
1579
1544
|
boundProxySession = sessionName;
|
|
1580
1545
|
bindingSessionInFlight = null;
|
|
@@ -1715,8 +1680,12 @@ async function run(options) {
|
|
|
1715
1680
|
console.log(chalk_1.default.yellow(' Monitor-only mode (--no-inject)'));
|
|
1716
1681
|
}
|
|
1717
1682
|
if (verbose) {
|
|
1683
|
+
// Show Claude version with ccDNA version if patched
|
|
1718
1684
|
const ccVersion = pinnedVersion || PINNED_CLAUDE_VERSION;
|
|
1719
|
-
|
|
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}`));
|
|
1720
1689
|
if (currentSession) {
|
|
1721
1690
|
console.log(chalk_1.default.green(` 📍 Session: ${currentSession}`));
|
|
1722
1691
|
}
|
|
@@ -1778,8 +1747,7 @@ async function run(options) {
|
|
|
1778
1747
|
const spawnedProcess = (0, child_process_1.spawn)(claudePath, args, {
|
|
1779
1748
|
stdio: 'inherit',
|
|
1780
1749
|
cwd: process.cwd(),
|
|
1781
|
-
env: getEkkosEnv()
|
|
1782
|
-
shell: isWindows // Required on Windows to execute .cmd files
|
|
1750
|
+
env: getEkkosEnv()
|
|
1783
1751
|
});
|
|
1784
1752
|
spawnedProcess.on('exit', (code) => process.exit(code ?? 0));
|
|
1785
1753
|
spawnedProcess.on('error', (e) => {
|
|
@@ -1803,8 +1771,7 @@ async function run(options) {
|
|
|
1803
1771
|
const spawnedProcess = (0, child_process_1.spawn)(claudePath, args, {
|
|
1804
1772
|
stdio: 'inherit',
|
|
1805
1773
|
cwd: process.cwd(),
|
|
1806
|
-
env: getEkkosEnv()
|
|
1807
|
-
shell: isWindows // Required on Windows to execute .cmd files
|
|
1774
|
+
env: getEkkosEnv()
|
|
1808
1775
|
});
|
|
1809
1776
|
spawnedProcess.on('exit', (code) => {
|
|
1810
1777
|
process.exit(code ?? 0);
|
|
@@ -2308,9 +2275,7 @@ async function run(options) {
|
|
|
2308
2275
|
if (transcriptMatch) {
|
|
2309
2276
|
const candidatePath = transcriptMatch[1];
|
|
2310
2277
|
// Validate it's an actual path (not garbage from terminal output)
|
|
2311
|
-
|
|
2312
|
-
/^[A-Za-z]:[\\/]/.test(candidatePath);
|
|
2313
|
-
if (isAbsCandidate) {
|
|
2278
|
+
if (candidatePath.startsWith('/') || candidatePath.startsWith('~')) {
|
|
2314
2279
|
const resolvedPath = candidatePath.startsWith('~')
|
|
2315
2280
|
? path.join(os.homedir(), candidatePath.slice(1))
|
|
2316
2281
|
: candidatePath;
|
|
@@ -2357,15 +2322,12 @@ async function run(options) {
|
|
|
2357
2322
|
}
|
|
2358
2323
|
// ════════════════════════════════════════════════════════════════════════
|
|
2359
2324
|
// SESSION NAME DETECTION (PRIMARY METHOD)
|
|
2360
|
-
// Claude footer
|
|
2361
|
-
// "· 🧠 ekkOS_™ · Turn N · groovy-koala-saves · 📅 2026-01-17"
|
|
2362
|
-
// "· 🧠 ekkOS_™ · groovy-koala-saves · 📅 2026-01-17"
|
|
2325
|
+
// Claude footer: "· Turn N · groovy-koala-saves · 📅 2026-01-17"
|
|
2363
2326
|
// This is MORE reliable than UUID extraction
|
|
2364
2327
|
// ════════════════════════════════════════════════════════════════════════
|
|
2365
2328
|
const plain = stripAnsi(data);
|
|
2366
|
-
// Strong signal: session name
|
|
2367
|
-
const statusMatch = plain.match(
|
|
2368
|
-
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);
|
|
2369
2331
|
if (statusMatch) {
|
|
2370
2332
|
const detectedSession = statusMatch[1].toLowerCase();
|
|
2371
2333
|
// Validate against word lists (SOURCE OF TRUTH)
|