@ekkos/cli 1.2.16 → 1.2.18

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.
@@ -469,13 +469,12 @@ function getEkkosEnv() {
469
469
  env.EKKOS_PROXY_MODE = '1';
470
470
  // Enable ultra-minimal mode by default (30%→20% eviction for constant-cost infinite context)
471
471
  env.EKKOS_ULTRA_MINIMAL = '1';
472
- // Use a unique pending scope until Claude exposes its real session UUID.
473
- // run.ts binds this scope to the real session immediately on transcript detect.
472
+ // Generate a deterministic word-triple session name from a fresh UUID.
473
+ // This eliminates _pending-* placeholders proxy sees a real name from request #1.
474
474
  if (!cliSessionName) {
475
- const pendingToken = crypto.randomBytes(8).toString('hex');
476
- cliSessionName = `_pending-${pendingToken}`;
477
- cliSessionId = `pending-${pendingToken}`;
478
- console.log(chalk_1.default.gray(` 📂 Session: pending (will bind to Claude session)`));
475
+ cliSessionId = crypto.randomUUID();
476
+ cliSessionName = (0, state_1.uuidToWords)(cliSessionId);
477
+ console.log(chalk_1.default.gray(` 📂 Session: ${cliSessionName}`));
479
478
  }
480
479
  // Get full userId from config (NOT the truncated version from auth token)
481
480
  // Config has full UUID like "d4532ba0-0a86-42ce-bab4-22aa62b55ce6"
@@ -499,7 +498,7 @@ function getEkkosEnv() {
499
498
  // Project path is base64-encoded to handle special chars safely
500
499
  const projectPath = process.cwd();
501
500
  const projectPathEncoded = Buffer.from(projectPath).toString('base64url');
502
- const proxyUrl = `${EKKOS_PROXY_URL}/proxy/${encodeURIComponent(userId)}/${encodeURIComponent(cliSessionName)}?project=${projectPathEncoded}`;
501
+ const proxyUrl = `${EKKOS_PROXY_URL}/proxy/${encodeURIComponent(userId)}/${encodeURIComponent(cliSessionName)}?project=${projectPathEncoded}&sid=${encodeURIComponent(cliSessionId)}`;
503
502
  env.ANTHROPIC_BASE_URL = proxyUrl;
504
503
  // Proxy URL contains userId + project path — don't leak to terminal
505
504
  }
@@ -1352,7 +1351,7 @@ async function run(options) {
1352
1351
  }
1353
1352
  // Track state — only use explicit -s option; never inherit stale session from state.json
1354
1353
  // The real session name will be detected from Claude Code's output (line ~2552)
1355
- let currentSession = options.session || null;
1354
+ let currentSession = options.session || cliSessionName || null;
1356
1355
  // Write initial instance file
1357
1356
  const startedAt = new Date().toISOString();
1358
1357
  writeInstanceFile(instanceId, {
@@ -1419,18 +1418,50 @@ async function run(options) {
1419
1418
  // Claude creates the transcript file BEFORE outputting the session name
1420
1419
  // So we watch for new files rather than parsing TUI output (which is slower)
1421
1420
  // ════════════════════════════════════════════════════════════════════════════
1422
- const encodedCwd = process.cwd().replace(/\//g, '-');
1423
- const projectDir = path.join(os.homedir(), '.claude', 'projects', encodedCwd);
1421
+ const projectPath = process.cwd();
1422
+ const projectsRoot = path.join(os.homedir(), '.claude', 'projects');
1423
+ const projectDirCandidates = (() => {
1424
+ // Claude's project-dir encoding is platform/version dependent.
1425
+ // Probe a small set of known-safe variants to avoid missing the session file.
1426
+ const encodings = new Set([
1427
+ projectPath.replace(/\//g, '-'),
1428
+ projectPath.replace(/[\\/]/g, '-'),
1429
+ projectPath.replace(/[:\\/]/g, '-'),
1430
+ `-${projectPath.replace(/[:\\/]/g, '-').replace(/^-+/, '')}`,
1431
+ projectPath.replace(/[^a-zA-Z0-9]/g, '-'),
1432
+ `-${projectPath.replace(/^[\\/]+/, '').replace(/[^a-zA-Z0-9]/g, '-')}`,
1433
+ ]);
1434
+ return [...encodings]
1435
+ .filter(Boolean)
1436
+ .map(encoded => path.join(projectsRoot, encoded));
1437
+ })();
1424
1438
  const launchTime = Date.now();
1439
+ function listCandidateJsonlFiles() {
1440
+ const jsonlFiles = [];
1441
+ for (const candidateDir of projectDirCandidates) {
1442
+ if (!fs.existsSync(candidateDir))
1443
+ continue;
1444
+ try {
1445
+ for (const file of fs.readdirSync(candidateDir)) {
1446
+ if (file.endsWith('.jsonl')) {
1447
+ jsonlFiles.push(path.join(candidateDir, file));
1448
+ }
1449
+ }
1450
+ }
1451
+ catch {
1452
+ // Ignore candidate-dir read errors and keep scanning others.
1453
+ }
1454
+ }
1455
+ return jsonlFiles;
1456
+ }
1425
1457
  // Track existing jsonl files at startup
1426
1458
  let existingJsonlFiles = new Set();
1427
1459
  try {
1428
- const files = fs.readdirSync(projectDir);
1429
- existingJsonlFiles = new Set(files.filter(f => f.endsWith('.jsonl')));
1460
+ existingJsonlFiles = new Set(listCandidateJsonlFiles());
1430
1461
  dlog(`[TRANSCRIPT] Found ${existingJsonlFiles.size} existing jsonl files at startup`);
1431
1462
  }
1432
1463
  catch {
1433
- dlog('[TRANSCRIPT] Project dir does not exist yet');
1464
+ dlog('[TRANSCRIPT] No candidate project dir exists yet');
1434
1465
  }
1435
1466
  // Poll for new transcript file every 500ms for up to 30 seconds.
1436
1467
  // Safety rule: do NOT guess using "most recent" files; that can cross-bind sessions.
@@ -1450,13 +1481,11 @@ async function run(options) {
1450
1481
  // In proxy mode this is intentionally disabled to avoid cross-session mixing.
1451
1482
  if (!proxyModeEnabled && !transcriptPath) {
1452
1483
  try {
1453
- const files = fs.readdirSync(projectDir);
1454
- const jsonlFiles = files
1455
- .filter(f => f.endsWith('.jsonl'))
1456
- .map(f => ({
1457
- name: f,
1458
- path: path.join(projectDir, f),
1459
- mtime: fs.statSync(path.join(projectDir, f)).mtimeMs
1484
+ const jsonlFiles = listCandidateJsonlFiles()
1485
+ .map(fullPath => ({
1486
+ name: path.basename(fullPath),
1487
+ path: fullPath,
1488
+ mtime: fs.statSync(fullPath).mtimeMs
1460
1489
  }))
1461
1490
  .sort((a, b) => b.mtime - a.mtime);
1462
1491
  if (jsonlFiles.length > 0) {
@@ -1486,14 +1515,13 @@ async function run(options) {
1486
1515
  return;
1487
1516
  }
1488
1517
  try {
1489
- const currentFiles = fs.readdirSync(projectDir);
1490
- const jsonlFiles = currentFiles.filter(f => f.endsWith('.jsonl'));
1518
+ const jsonlFiles = listCandidateJsonlFiles();
1491
1519
  // Find NEW files (created after we started)
1492
1520
  for (const file of jsonlFiles) {
1493
1521
  if (!existingJsonlFiles.has(file)) {
1494
1522
  // New file! This is our transcript
1495
- const fullPath = path.join(projectDir, file);
1496
- const sessionId = file.replace('.jsonl', '');
1523
+ const fullPath = file;
1524
+ const sessionId = path.basename(file).replace('.jsonl', '');
1497
1525
  transcriptPath = fullPath;
1498
1526
  currentSessionId = sessionId;
1499
1527
  currentSession = (0, state_1.uuidToWords)(sessionId);
@@ -1608,8 +1636,10 @@ async function run(options) {
1608
1636
  function resolveTranscriptFromSessionId(source) {
1609
1637
  if (!currentSessionId || transcriptPath)
1610
1638
  return;
1611
- const candidate = path.join(projectDir, `${currentSessionId}.jsonl`);
1612
- if (!fs.existsSync(candidate))
1639
+ const candidate = projectDirCandidates
1640
+ .map(projectDir => path.join(projectDir, `${currentSessionId}.jsonl`))
1641
+ .find(fullPath => fs.existsSync(fullPath));
1642
+ if (!candidate)
1613
1643
  return;
1614
1644
  transcriptPath = candidate;
1615
1645
  evictionDebugLog('TRANSCRIPT_SET', `Set from session ID (${source})`, {
@@ -1626,7 +1656,7 @@ async function run(options) {
1626
1656
  return;
1627
1657
  if (boundProxySession === sessionName || bindingSessionInFlight === sessionName)
1628
1658
  return;
1629
- const pendingSession = cliSessionName?.startsWith('_pending') ? cliSessionName : undefined;
1659
+ const pendingSession = undefined; // CLI session names are real word-triples from the start
1630
1660
  const bindSessionId = sessionIdHint || currentSessionId || undefined;
1631
1661
  bindingSessionInFlight = sessionName;
1632
1662
  void (async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekkos/cli",
3
- "version": "1.2.16",
3
+ "version": "1.2.18",
4
4
  "description": "Setup ekkOS memory for AI coding assistants (Claude Code, Cursor, Windsurf)",
5
5
  "main": "dist/index.js",
6
6
  "bin": {