@ekkos/cli 1.2.18 → 1.3.0

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.
Files changed (47) hide show
  1. package/dist/cache/capture.js +0 -0
  2. package/dist/commands/dashboard.js +57 -49
  3. package/dist/commands/hooks.d.ts +25 -36
  4. package/dist/commands/hooks.js +43 -615
  5. package/dist/commands/init.js +7 -23
  6. package/dist/commands/run.js +90 -3
  7. package/dist/commands/setup.js +10 -352
  8. package/dist/deploy/hooks.d.ts +8 -5
  9. package/dist/deploy/hooks.js +12 -105
  10. package/dist/deploy/settings.d.ts +8 -2
  11. package/dist/deploy/settings.js +22 -51
  12. package/dist/index.js +17 -39
  13. package/dist/utils/state.js +7 -2
  14. package/package.json +1 -1
  15. package/templates/CLAUDE.md +82 -292
  16. package/templates/cursor-rules/ekkos-memory.md +48 -108
  17. package/templates/windsurf-rules/ekkos-memory.md +62 -64
  18. package/templates/cursor-hooks/after-agent-response.sh +0 -117
  19. package/templates/cursor-hooks/before-submit-prompt.sh +0 -419
  20. package/templates/cursor-hooks/hooks.json +0 -20
  21. package/templates/cursor-hooks/lib/contract.sh +0 -320
  22. package/templates/cursor-hooks/stop.sh +0 -75
  23. package/templates/hooks/assistant-response.ps1 +0 -256
  24. package/templates/hooks/assistant-response.sh +0 -160
  25. package/templates/hooks/hooks.json +0 -40
  26. package/templates/hooks/lib/contract.sh +0 -332
  27. package/templates/hooks/lib/count-tokens.cjs +0 -86
  28. package/templates/hooks/lib/ekkos-reminders.sh +0 -98
  29. package/templates/hooks/lib/state.sh +0 -210
  30. package/templates/hooks/session-start.ps1 +0 -146
  31. package/templates/hooks/session-start.sh +0 -353
  32. package/templates/hooks/stop.ps1 +0 -349
  33. package/templates/hooks/stop.sh +0 -382
  34. package/templates/hooks/user-prompt-submit.ps1 +0 -419
  35. package/templates/hooks/user-prompt-submit.sh +0 -516
  36. package/templates/project-stubs/session-start.ps1 +0 -63
  37. package/templates/project-stubs/session-start.sh +0 -55
  38. package/templates/project-stubs/stop.ps1 +0 -63
  39. package/templates/project-stubs/stop.sh +0 -55
  40. package/templates/project-stubs/user-prompt-submit.ps1 +0 -63
  41. package/templates/project-stubs/user-prompt-submit.sh +0 -55
  42. package/templates/windsurf-hooks/README.md +0 -212
  43. package/templates/windsurf-hooks/hooks.json +0 -17
  44. package/templates/windsurf-hooks/install.sh +0 -148
  45. package/templates/windsurf-hooks/lib/contract.sh +0 -322
  46. package/templates/windsurf-hooks/post-cascade-response.sh +0 -251
  47. package/templates/windsurf-hooks/pre-user-prompt.sh +0 -435
File without changes
@@ -361,7 +361,7 @@ function findLatestJsonl(projectPath, createdAfterMs) {
361
361
  projectPath.replace(/\//g, '-'), // macOS: /Users/name → -Users-name
362
362
  ]);
363
363
  const projectsRoot = path.join(os.homedir(), '.claude', 'projects');
364
- for (const encoded of candidateEncodings) {
364
+ for (const encoded of Array.from(candidateEncodings)) {
365
365
  const projectDir = path.join(projectsRoot, encoded);
366
366
  if (!fs.existsSync(projectDir))
367
367
  continue;
@@ -392,7 +392,7 @@ function findJsonlBySessionId(projectPath, sessionId) {
392
392
  projectPath.replace(/\//g, '-'),
393
393
  ]);
394
394
  const projectsRoot = path.join(os.homedir(), '.claude', 'projects');
395
- for (const encoded of candidateEncodings) {
395
+ for (const encoded of Array.from(candidateEncodings)) {
396
396
  const exactPath = path.join(projectsRoot, encoded, `${sessionId}.jsonl`);
397
397
  if (fs.existsSync(exactPath))
398
398
  return exactPath;
@@ -425,15 +425,11 @@ async function waitForNewSession() {
425
425
  catch { }
426
426
  console.log(chalk_1.default.gray(' Waiting for new session to start...'));
427
427
  const maxWaitMs = 120000; // 2 minutes max
428
- const pollMs = 3000;
428
+ const pollMs = 1000; // 1s poll — faster than before since we exit early
429
429
  const startWait = Date.now();
430
430
  let candidateName = null;
431
431
  while (Date.now() - startWait < maxWaitMs) {
432
432
  // ── Windows hook hint file ──────────────────────────────────────────────
433
- // On Windows, active-sessions.json is never populated because hook processes
434
- // are short-lived and their PIDs are dead by the time we poll. Instead, the
435
- // user-prompt-submit.ps1 hook writes ~/.ekkos/hook-session-hint.json with
436
- // { sessionName, sessionId, projectPath, ts } on every turn. Read it here.
437
433
  const hintPath = path.join(state_js_1.EKKOS_DIR, 'hook-session-hint.json');
438
434
  try {
439
435
  if (fs.existsSync(hintPath)) {
@@ -443,37 +439,34 @@ async function waitForNewSession() {
443
439
  const jsonlPath = findJsonlBySessionId(hint.projectPath, hint.sessionId || '')
444
440
  || findLatestJsonl(hint.projectPath, launchTs)
445
441
  || resolveJsonlPath(hint.sessionName, launchTs);
446
- if (jsonlPath) {
447
- console.log(chalk_1.default.green(` Found session (hook hint): ${hint.sessionName}`));
448
- return { sessionName: hint.sessionName, jsonlPath };
449
- }
442
+ // Return immediately — JSONL may be null; dashboard will lazy-resolve
443
+ console.log(chalk_1.default.green(` Found session (hook hint): ${hint.sessionName}`));
444
+ return { sessionName: hint.sessionName, jsonlPath, launchCwd: hint.projectPath || launchCwd, launchTs };
450
445
  }
451
446
  }
452
447
  }
453
448
  catch { /* ignore */ }
454
449
  // ── Standard: active-sessions.json (works on Mac/Linux) ────────────────
455
450
  const sessions = (0, state_js_1.getActiveSessions)();
456
- // Find sessions that started after our launch
457
451
  for (const s of sessions) {
458
452
  const startedMs = new Date(s.startedAt).getTime();
459
453
  if (startedMs >= launchTs - 2000) {
460
454
  candidateName = s.sessionName;
461
- // Try standard resolution (works when session bind has happened with real UUID)
462
- const jsonlPath = resolveJsonlPath(s.sessionName, launchTs);
463
- if (jsonlPath) {
464
- console.log(chalk_1.default.green(` Found session: ${s.sessionName}`));
465
- return { sessionName: s.sessionName, jsonlPath };
466
- }
467
- // Try all unique projectPaths for this session name (bind may create second entry)
468
- const allPaths = new Set(sessions.filter(x => x.sessionName === s.sessionName && x.projectPath)
469
- .map(x => x.projectPath));
470
- for (const pp of allPaths) {
471
- const latestJsonl = findLatestJsonl(pp, launchTs);
472
- if (latestJsonl) {
473
- console.log(chalk_1.default.green(` Found session: ${s.sessionName}`));
474
- return { sessionName: s.sessionName, jsonlPath: latestJsonl };
455
+ // Try to resolve JSONL immediately (may succeed if Claude already created it)
456
+ let jsonlPath = resolveJsonlPath(s.sessionName, launchTs);
457
+ if (!jsonlPath) {
458
+ const allPaths = new Set(sessions.filter(x => x.sessionName === s.sessionName && x.projectPath)
459
+ .map(x => x.projectPath));
460
+ for (const pp of Array.from(allPaths)) {
461
+ jsonlPath = findLatestJsonl(pp, launchTs);
462
+ if (jsonlPath)
463
+ break;
475
464
  }
476
465
  }
466
+ // Return immediately with session name — JSONL may still be null
467
+ // (Claude Code hasn't created it yet). Dashboard will lazy-resolve.
468
+ console.log(chalk_1.default.green(` Found session: ${s.sessionName}`));
469
+ return { sessionName: s.sessionName, jsonlPath, launchCwd: s.projectPath || launchCwd, launchTs };
477
470
  }
478
471
  }
479
472
  // Fallback: use launch CWD to find any new JSONL
@@ -482,12 +475,10 @@ async function waitForNewSession() {
482
475
  if (latestJsonl) {
483
476
  const name = candidateName || path.basename(latestJsonl, '.jsonl');
484
477
  console.log(chalk_1.default.green(` Found session via CWD: ${name}`));
485
- return { sessionName: name, jsonlPath: latestJsonl };
478
+ return { sessionName: name, jsonlPath: latestJsonl, launchCwd, launchTs };
486
479
  }
487
480
  }
488
- // Broad fallback: scan ALL project directories for any new JSONL (Windows safety net).
489
- // Claude creates the JSONL immediately when a session starts, before the first message.
490
- // This catches cases where path encoding doesn't match.
481
+ // Broad fallback: scan ALL project directories for any new JSONL
491
482
  {
492
483
  const projectsRoot = path.join(os.homedir(), '.claude', 'projects');
493
484
  try {
@@ -512,12 +503,11 @@ async function waitForNewSession() {
512
503
  .sort((a, b) => b.birthtime - a.birthtime);
513
504
  if (allNewJsonl.length > 0) {
514
505
  const jsonlPath = allNewJsonl[0].path;
515
- // Derive name from filename (e.g. abc123.jsonl → use candidateName if set, else basename)
516
506
  const baseName = path.basename(jsonlPath, '.jsonl');
517
507
  const derivedName = /^[0-9a-f]{8}-/.test(baseName) ? (0, state_js_1.uuidToWords)(baseName) : baseName;
518
508
  const name = candidateName || derivedName;
519
509
  console.log(chalk_1.default.green(` Found session via scan: ${name}`));
520
- return { sessionName: name, jsonlPath };
510
+ return { sessionName: name, jsonlPath, launchCwd, launchTs };
521
511
  }
522
512
  }
523
513
  }
@@ -526,20 +516,18 @@ async function waitForNewSession() {
526
516
  await sleep(pollMs);
527
517
  process.stdout.write(chalk_1.default.gray('.'));
528
518
  }
529
- // Timeout — try one more time with the candidate name (still use birthtime filter)
519
+ // Timeout — return candidate if we have one (dashboard will lazy-resolve JSONL)
530
520
  if (candidateName) {
531
- console.log(chalk_1.default.yellow(`\n Timeout. Trying ${candidateName} anyway...`));
521
+ console.log(chalk_1.default.yellow(`\n Timeout. Launching with ${candidateName}...`));
532
522
  const jsonlPath = resolveJsonlPath(candidateName, launchTs);
533
- if (jsonlPath)
534
- return { sessionName: candidateName, jsonlPath };
523
+ return { sessionName: candidateName, jsonlPath, launchCwd, launchTs };
535
524
  }
536
525
  // Last resort: latest session overall
537
526
  const latestName = getLatestSession();
538
527
  if (latestName) {
539
528
  console.log(chalk_1.default.yellow(`\n Falling back to latest: ${latestName}`));
540
529
  const jsonlPath = resolveJsonlPath(latestName);
541
- if (jsonlPath)
542
- return { sessionName: latestName, jsonlPath };
530
+ return { sessionName: latestName, jsonlPath, launchCwd, launchTs };
543
531
  }
544
532
  console.log(chalk_1.default.red(' No session found.'));
545
533
  process.exit(1);
@@ -548,7 +536,8 @@ function sleep(ms) {
548
536
  return new Promise(resolve => setTimeout(resolve, ms));
549
537
  }
550
538
  // ── TUI Dashboard ──
551
- async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
539
+ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs, launchCwd, launchTs) {
540
+ let jsonlPath = initialJsonlPath;
552
541
  let sessionName = displaySessionName(initialSessionName);
553
542
  const blessed = require('blessed');
554
543
  const contrib = require('blessed-contrib');
@@ -958,11 +947,30 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
958
947
  }
959
948
  function updateDashboard() {
960
949
  ensureLayoutSynced();
950
+ // ── Lazy JSONL resolution ──────────────────────────────────────────────
951
+ // Dashboard may launch before Claude Code creates the JSONL file.
952
+ // Keep trying to find it on each poll tick.
953
+ if (!jsonlPath || !fs.existsSync(jsonlPath)) {
954
+ const resolved = resolveJsonlPath(initialSessionName, launchTs)
955
+ || (launchCwd ? findLatestJsonl(launchCwd, launchTs) : null);
956
+ if (resolved) {
957
+ jsonlPath = resolved;
958
+ dlog(`Lazy-resolved JSONL: ${jsonlPath}`);
959
+ }
960
+ else {
961
+ // Still no JSONL — render the header/footer so the dashboard isn't blank
962
+ renderHeader();
963
+ try {
964
+ screen.render();
965
+ }
966
+ catch { }
967
+ return;
968
+ }
969
+ }
961
970
  // Resolve "initializing" → real session name from JSONL UUID filename
962
971
  if (sessionName === 'initializing' || sessionName === 'session') {
963
972
  try {
964
973
  const basename = path.basename(jsonlPath, '.jsonl');
965
- // JSONL filename is the session UUID (e.g., 607bd8e4-0a04-4db2-acf5-3f794be0f956.jsonl)
966
974
  if (/^[0-9a-f]{8}-/.test(basename)) {
967
975
  sessionName = displaySessionName(basename);
968
976
  screen.title = `ekkOS - ${sessionName}`;
@@ -1057,24 +1065,24 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
1057
1065
  // wrap by one char if this is too tight, which pushes Cost to next line.
1058
1066
  const w = Math.max(18, turnBox.width - 4); // usable content width
1059
1067
  const div = '│';
1060
- function pad(s, width) {
1068
+ const pad = (s, width) => {
1061
1069
  if (s.length >= width)
1062
1070
  return s.slice(0, width);
1063
1071
  return s + ' '.repeat(width - s.length);
1064
- }
1065
- function rpad(s, width) {
1072
+ };
1073
+ const rpad = (s, width) => {
1066
1074
  if (s.length >= width)
1067
1075
  return s.slice(0, width);
1068
1076
  return ' '.repeat(width - s.length) + s;
1069
- }
1070
- function cpad(s, width) {
1077
+ };
1078
+ const cpad = (s, width) => {
1071
1079
  if (s.length >= width)
1072
1080
  return s.slice(0, width);
1073
1081
  const total = width - s.length;
1074
1082
  const left = Math.floor(total / 2);
1075
1083
  const right = total - left;
1076
1084
  return ' '.repeat(left) + s + ' '.repeat(right);
1077
- }
1085
+ };
1078
1086
  // Data rows — RENDER ALL TURNS for full scrollback history
1079
1087
  // Don't slice to visibleRows only — let user scroll through entire session
1080
1088
  const turns = data.turns.slice().reverse();
@@ -1443,10 +1451,10 @@ exports.dashboardCommand = new commander_1.Command('dashboard')
1443
1451
  .option('--compact', 'Minimal layout for small terminals')
1444
1452
  .action(async (sessionNameArg, options) => {
1445
1453
  const refreshMs = parseInt(options.refresh) || 2000;
1446
- // --wait-for-new: poll until a brand new session appears
1454
+ // --wait-for-new: poll until session name appears (JSONL may not exist yet)
1447
1455
  if (options.waitForNew) {
1448
- const { sessionName, jsonlPath } = await waitForNewSession();
1449
- await launchDashboard(sessionName, jsonlPath, refreshMs);
1456
+ const result = await waitForNewSession();
1457
+ await launchDashboard(result.sessionName, result.jsonlPath, refreshMs, result.launchCwd, result.launchTs);
1450
1458
  return;
1451
1459
  }
1452
1460
  let sessionName = null;
@@ -1,12 +1,16 @@
1
1
  /**
2
- * ekkOS CLI: hooks subcommand
3
- * Implements: ekkos hooks install | verify | status
2
+ * ekkOS CLI: hooks subcommand — DEPRECATED
4
3
  *
5
- * Per ekkOS Onboarding Spec v1.2 + Addendum:
6
- * - Manifest-driven deployment
7
- * - SHA256 checksum verification
8
- * - Installed-state manifest tracking
4
+ * Hooks are no longer needed. ekkOS CLI and proxy handle everything.
5
+ * Use `ekkos run` to start.
6
+ *
7
+ * This command is kept for backwards compatibility. The install/verify/status
8
+ * subcommands print a deprecation notice and exit cleanly. Utility functions
9
+ * (findProjectRoot, expandPath, loadManifest) are preserved for internal use
10
+ * by other commands (e.g. doctor).
9
11
  */
12
+ export declare function expandPath(p: string): string;
13
+ export declare function findProjectRoot(startDir?: string): string;
10
14
  interface ManifestFile {
11
15
  source: string;
12
16
  destination: string;
@@ -61,49 +65,34 @@ interface Manifest {
61
65
  checksumAlgorithm: string;
62
66
  };
63
67
  }
64
- interface VerifyResult {
65
- status: 'PASS' | 'WARN' | 'FAIL';
66
- issues: Array<{
67
- severity: 'error' | 'warning';
68
- file?: string;
69
- message: string;
70
- }>;
71
- }
72
- declare function expandPath(p: string): string;
73
- /**
74
- * Find the ekkos-manifest.json file
75
- * Search order per spec:
76
- * 1. <packageRoot>/templates/ekkos-manifest.json
77
- * 2. <distRoot>/../templates/ekkos-manifest.json
78
- * 3. In monorepo dev: <repoRoot>/templates/ekkos-manifest.json
79
- */
80
68
  export declare function findManifest(): {
81
69
  path: string;
82
70
  templatesDir: string;
83
71
  } | null;
84
- /**
85
- * Load manifest from disk
86
- */
87
72
  export declare function loadManifest(): {
88
73
  manifest: Manifest;
89
74
  path: string;
90
75
  templatesDir: string;
91
76
  } | null;
92
- declare function findProjectRoot(startDir?: string): string;
93
- interface InstallOptions {
77
+ export interface VerifyResult {
78
+ status: 'PASS' | 'WARN' | 'FAIL';
79
+ issues: Array<{
80
+ severity: 'error' | 'warning';
81
+ file?: string;
82
+ message: string;
83
+ }>;
84
+ }
85
+ export declare function hooksInstall(_options: {
94
86
  global?: boolean;
95
87
  project?: boolean;
96
88
  verbose?: boolean;
97
- }
98
- export declare function hooksInstall(options: InstallOptions): Promise<void>;
99
- interface VerifyOptions {
89
+ }): Promise<void>;
90
+ export declare function hooksVerify(_options: {
100
91
  global?: boolean;
101
92
  project?: boolean;
102
93
  verbose?: boolean;
103
- }
104
- export declare function hooksVerify(options: VerifyOptions): Promise<VerifyResult>;
105
- interface StatusOptions {
94
+ }): Promise<VerifyResult>;
95
+ export declare function hooksStatus(_options: {
106
96
  verbose?: boolean;
107
- }
108
- export declare function hooksStatus(options: StatusOptions): Promise<void>;
109
- export { findProjectRoot, expandPath };
97
+ }): Promise<void>;
98
+ export {};