@ekkos/cli 1.0.25 → 1.0.27
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/commands/dashboard.js +60 -2
- package/dist/commands/run.js +57 -17
- package/package.json +1 -1
|
@@ -67,6 +67,7 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
67
67
|
const commander_1 = require("commander");
|
|
68
68
|
const usage_parser_js_1 = require("../lib/usage-parser.js");
|
|
69
69
|
const state_js_1 = require("../utils/state.js");
|
|
70
|
+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
70
71
|
// ── Pricing ──
|
|
71
72
|
// Pricing per MTok from https://platform.claude.com/docs/en/about-claude/pricing
|
|
72
73
|
const MODEL_PRICING = {
|
|
@@ -272,6 +273,55 @@ function parseJsonlFile(jsonlPath, sessionName) {
|
|
|
272
273
|
turns,
|
|
273
274
|
};
|
|
274
275
|
}
|
|
276
|
+
function readJsonFile(filePath) {
|
|
277
|
+
try {
|
|
278
|
+
if (!fs.existsSync(filePath))
|
|
279
|
+
return null;
|
|
280
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function resolveSessionAlias(sessionId) {
|
|
287
|
+
const normalized = sessionId.toLowerCase();
|
|
288
|
+
// Project-local hook state (most reliable for the active session)
|
|
289
|
+
const projectState = readJsonFile(path.join(process.cwd(), '.claude', 'state', 'current-session.json'));
|
|
290
|
+
if (projectState?.session_id?.toLowerCase() === normalized &&
|
|
291
|
+
projectState.session_name &&
|
|
292
|
+
!UUID_REGEX.test(projectState.session_name)) {
|
|
293
|
+
return projectState.session_name;
|
|
294
|
+
}
|
|
295
|
+
// Global Claude hook state
|
|
296
|
+
const claudeState = readJsonFile(path.join(os.homedir(), '.claude', 'state', 'current-session.json'));
|
|
297
|
+
if (claudeState?.session_id?.toLowerCase() === normalized &&
|
|
298
|
+
claudeState.session_name &&
|
|
299
|
+
!UUID_REGEX.test(claudeState.session_name)) {
|
|
300
|
+
return claudeState.session_name;
|
|
301
|
+
}
|
|
302
|
+
// ekkOS global state
|
|
303
|
+
const ekkosState = readJsonFile(path.join(os.homedir(), '.ekkos', 'current-session.json'));
|
|
304
|
+
if (ekkosState?.session_id?.toLowerCase() === normalized &&
|
|
305
|
+
ekkosState.session_name &&
|
|
306
|
+
!UUID_REGEX.test(ekkosState.session_name)) {
|
|
307
|
+
return ekkosState.session_name;
|
|
308
|
+
}
|
|
309
|
+
// Multi-session index fallback
|
|
310
|
+
const activeSessions = readJsonFile(path.join(os.homedir(), '.ekkos', 'active-sessions.json')) || [];
|
|
311
|
+
const bySessionId = activeSessions
|
|
312
|
+
.filter(s => s.sessionId?.toLowerCase() === normalized && s.sessionName && !UUID_REGEX.test(s.sessionName))
|
|
313
|
+
.sort((a, b) => (b.lastHeartbeat || '').localeCompare(a.lastHeartbeat || ''));
|
|
314
|
+
if (bySessionId.length > 0)
|
|
315
|
+
return bySessionId[0].sessionName || null;
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
function displaySessionName(rawName) {
|
|
319
|
+
if (!rawName)
|
|
320
|
+
return 'session';
|
|
321
|
+
if (!UUID_REGEX.test(rawName))
|
|
322
|
+
return rawName;
|
|
323
|
+
return resolveSessionAlias(rawName) || (0, state_js_1.uuidToWords)(rawName);
|
|
324
|
+
}
|
|
275
325
|
// ── Resolve session to JSONL path ──
|
|
276
326
|
function resolveJsonlPath(sessionName, createdAfterMs) {
|
|
277
327
|
// 1) Try standard resolution (works when sessionId is a real UUID)
|
|
@@ -499,7 +549,7 @@ function sleep(ms) {
|
|
|
499
549
|
}
|
|
500
550
|
// ── TUI Dashboard ──
|
|
501
551
|
async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
|
|
502
|
-
let sessionName = initialSessionName;
|
|
552
|
+
let sessionName = displaySessionName(initialSessionName);
|
|
503
553
|
const blessed = require('blessed');
|
|
504
554
|
const contrib = require('blessed-contrib');
|
|
505
555
|
const inTmux = process.env.TMUX !== undefined;
|
|
@@ -836,12 +886,20 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
|
|
|
836
886
|
const basename = path.basename(jsonlPath, '.jsonl');
|
|
837
887
|
// JSONL filename is the session UUID (e.g., 607bd8e4-0a04-4db2-acf5-3f794be0f956.jsonl)
|
|
838
888
|
if (/^[0-9a-f]{8}-/.test(basename)) {
|
|
839
|
-
sessionName = (
|
|
889
|
+
sessionName = displaySessionName(basename);
|
|
840
890
|
screen.title = `ekkOS - ${sessionName}`;
|
|
841
891
|
}
|
|
842
892
|
}
|
|
843
893
|
catch { }
|
|
844
894
|
}
|
|
895
|
+
// If we started with a UUID fallback, keep trying to resolve to the bound word session.
|
|
896
|
+
if (UUID_REGEX.test(sessionName)) {
|
|
897
|
+
const resolvedName = displaySessionName(sessionName);
|
|
898
|
+
if (resolvedName !== sessionName) {
|
|
899
|
+
sessionName = resolvedName;
|
|
900
|
+
screen.title = `ekkOS - ${sessionName}`;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
845
903
|
let data;
|
|
846
904
|
try {
|
|
847
905
|
const stat = fs.statSync(jsonlPath);
|
package/dist/commands/run.js
CHANGED
|
@@ -400,9 +400,51 @@ function getEkkosEnv() {
|
|
|
400
400
|
}
|
|
401
401
|
return env;
|
|
402
402
|
}
|
|
403
|
-
// ekkOS-managed Claude installation path
|
|
403
|
+
// ekkOS-managed Claude installation path (npm fallback)
|
|
404
404
|
const EKKOS_CLAUDE_DIR = path.join(os.homedir(), '.ekkos', 'claude-code');
|
|
405
405
|
const EKKOS_CLAUDE_BIN = path.join(EKKOS_CLAUDE_DIR, 'node_modules', '.bin', isWindows ? 'claude.cmd' : 'claude');
|
|
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
|
+
}
|
|
406
448
|
/**
|
|
407
449
|
* Check if a Claude installation exists and get its version
|
|
408
450
|
* Returns version string if found, null otherwise
|
|
@@ -504,34 +546,32 @@ function installEkkosClaudeVersion() {
|
|
|
504
546
|
}
|
|
505
547
|
}
|
|
506
548
|
/**
|
|
507
|
-
* Resolve full path to claude executable
|
|
508
|
-
* Returns direct path if found with correct version, otherwise 'npx:VERSION'
|
|
509
|
-
*
|
|
510
|
-
* IMPORTANT: We pin to a specific Claude Code version (currently 2.1.33) to ensure
|
|
511
|
-
* consistent behavior with ekkOS context management and eviction patches.
|
|
512
|
-
*
|
|
513
|
-
* CRITICAL: ekkos run ONLY uses the ekkOS-managed installation at ~/.ekkos/claude-code/
|
|
514
|
-
* This ensures complete separation from the user's existing Claude installation (Homebrew/npm).
|
|
515
|
-
* The user's `claude` command remains untouched and can be any version.
|
|
549
|
+
* Resolve full path to claude executable.
|
|
516
550
|
*
|
|
517
551
|
* Priority:
|
|
518
|
-
* 1.
|
|
519
|
-
* 2.
|
|
520
|
-
* 3.
|
|
552
|
+
* 1. Native installer binary (~/.local/share/claude/versions/<latest>) — no npm warning
|
|
553
|
+
* 2. ekkOS-managed npm installation (~/.ekkos/claude-code) — existing installs
|
|
554
|
+
* 3. Auto-install via npm to ekkOS-managed directory
|
|
555
|
+
* 4. npx fallback (rare, shows npm deprecation warning)
|
|
521
556
|
*/
|
|
522
557
|
function resolveClaudePath() {
|
|
523
|
-
// PRIORITY 1:
|
|
558
|
+
// PRIORITY 1: Native installer binary (Anthropic's official distribution)
|
|
559
|
+
// These binaries do not print the npm-to-native migration warning.
|
|
560
|
+
const nativeBin = resolveNativeClaudeBin();
|
|
561
|
+
if (nativeBin) {
|
|
562
|
+
return nativeBin;
|
|
563
|
+
}
|
|
564
|
+
// PRIORITY 2: ekkOS-managed npm installation (existing users before native installer)
|
|
524
565
|
if (fs.existsSync(EKKOS_CLAUDE_BIN) && checkClaudeVersion(EKKOS_CLAUDE_BIN)) {
|
|
525
566
|
return EKKOS_CLAUDE_BIN;
|
|
526
567
|
}
|
|
527
|
-
// PRIORITY
|
|
568
|
+
// PRIORITY 3: Auto-install to ekkOS-managed directory
|
|
528
569
|
if (installEkkosClaudeVersion()) {
|
|
529
570
|
if (fs.existsSync(EKKOS_CLAUDE_BIN)) {
|
|
530
571
|
return EKKOS_CLAUDE_BIN;
|
|
531
572
|
}
|
|
532
573
|
}
|
|
533
|
-
// PRIORITY
|
|
534
|
-
// This is rare - only happens if install failed
|
|
574
|
+
// PRIORITY 4: Fall back to npx (rare — only if install failed, will show deprecation warning)
|
|
535
575
|
return `npx:${PINNED_CLAUDE_VERSION}`;
|
|
536
576
|
}
|
|
537
577
|
/**
|