@ekkos/cli 1.2.18 → 1.3.1

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 +121 -66
  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,38 +1065,39 @@ 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();
1081
1089
  let header = '';
1082
1090
  let separator = '';
1083
1091
  let rows = [];
1084
- // Always show all 7 columns: Turn, Model, Context, Cache Rd, Cache Wr, Output, Cost
1092
+ // Always show all 8 columns: Turn, Time, Model, Context, Cache Rd, Cache Wr, Output, Cost
1085
1093
  // Shrink flex columns to fit narrow panes instead of dropping them.
1086
1094
  const colNum = 4;
1095
+ const colTime = 8; // "HH:MM:SS" or "H:MM AM"
1087
1096
  const colM = 7;
1088
1097
  const colCtx = 7;
1089
1098
  const colCost = 8;
1090
- const nDividers = 6;
1091
- const fixedW = colNum + colM + colCtx + colCost;
1099
+ const nDividers = 7;
1100
+ const fixedW = colNum + colTime + colM + colCtx + colCost;
1092
1101
  const flexTotal = Math.max(0, w - fixedW - nDividers);
1093
1102
  let rdW = Math.max(5, Math.floor(flexTotal * 0.35));
1094
1103
  let wrW = Math.max(5, Math.floor(flexTotal * 0.30));
@@ -1110,14 +1119,30 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
1110
1119
  rdW -= trimRd;
1111
1120
  }
1112
1121
  }
1113
- header = `{bold}${pad('Turn', colNum)}${div}${pad('Model', colM)}${div}${pad('Context', colCtx)}${div}${rpad('Cache Rd', rdW)}${div}${rpad('Cache Wr', wrW)}${div}${rpad('Output', outW)}${div}${rpad('Cost', colCost)}{/bold}`;
1114
- separator = `{gray-fg}${'─'.repeat(colNum)}┼${'─'.repeat(colM)}┼${'─'.repeat(colCtx)}┼${'─'.repeat(rdW)}┼${'─'.repeat(wrW)}┼${'─'.repeat(outW)}┼${'─'.repeat(colCost)}{/gray-fg}`;
1122
+ // Format turn timestamp to short time string
1123
+ const fmtTime = (iso) => {
1124
+ if (!iso)
1125
+ return '--:--';
1126
+ try {
1127
+ const d = new Date(iso);
1128
+ const h = d.getHours();
1129
+ const m = d.getMinutes().toString().padStart(2, '0');
1130
+ const s = d.getSeconds().toString().padStart(2, '0');
1131
+ return `${h}:${m}:${s}`;
1132
+ }
1133
+ catch {
1134
+ return '--:--';
1135
+ }
1136
+ };
1137
+ header = `{bold}${pad('Turn', colNum)}${div}${pad('Time', colTime)}${div}${pad('Model', colM)}${div}${pad('Context', colCtx)}${div}${rpad('Cache Rd', rdW)}${div}${rpad('Cache Wr', wrW)}${div}${rpad('Output', outW)}${div}${rpad('Cost', colCost)}{/bold}`;
1138
+ separator = `{gray-fg}${'─'.repeat(colNum)}┼${'─'.repeat(colTime)}┼${'─'.repeat(colM)}┼${'─'.repeat(colCtx)}┼${'─'.repeat(rdW)}┼${'─'.repeat(wrW)}┼${'─'.repeat(outW)}┼${'─'.repeat(colCost)}{/gray-fg}`;
1115
1139
  rows = turns.map(t => {
1116
1140
  const mTag = modelTag(t.routedModel);
1117
1141
  const mColor = t.routedModel.includes('haiku') ? 'green' : t.routedModel.includes('sonnet') ? 'blue' : 'magenta';
1118
1142
  const costFlag = t.cost > 1.0 ? '{red-fg}' : t.cost > 0.5 ? '{yellow-fg}' : '{white-fg}';
1119
1143
  const costEnd = t.cost > 1.0 ? '{/red-fg}' : t.cost > 0.5 ? '{/yellow-fg}' : '{/white-fg}';
1120
1144
  return (pad(String(t.turn), colNum) + div +
1145
+ `{gray-fg}${pad(fmtTime(t.timestamp), colTime)}{/gray-fg}` + div +
1121
1146
  `{${mColor}-fg}${pad(mTag, colM)}{/${mColor}-fg}` + div +
1122
1147
  pad(`${t.contextPct.toFixed(0)}%`, colCtx) + div +
1123
1148
  `{green-fg}${rpad(fmtK(t.cacheRead), rdW)}{/green-fg}` + div +
@@ -1212,9 +1237,22 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
1212
1237
  return null;
1213
1238
  }
1214
1239
  }
1215
- async function updateWindowBox() {
1240
+ // Cache last fetched usage data so the countdown can tick every second
1241
+ let cachedUsage = null;
1242
+ // Fetch fresh usage data from API (called every 15s)
1243
+ async function fetchAndCacheUsage() {
1244
+ try {
1245
+ cachedUsage = await fetchAnthropicUsage();
1246
+ }
1247
+ catch (err) {
1248
+ dlog(`Window fetch: ${err.message}`);
1249
+ }
1250
+ renderWindowBox();
1251
+ }
1252
+ // Render countdown from cached data (called every 1s)
1253
+ function renderWindowBox() {
1216
1254
  try {
1217
- const usage = await fetchAnthropicUsage();
1255
+ const usage = cachedUsage;
1218
1256
  let line1 = ' {gray-fg}No usage data{/gray-fg}';
1219
1257
  let line2 = '';
1220
1258
  // ── 5h Window (from Anthropic OAuth API) ──
@@ -1222,26 +1260,38 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
1222
1260
  const pct = usage.five_hour.utilization;
1223
1261
  const resetAt = new Date(usage.five_hour.resets_at).getTime();
1224
1262
  const remainMs = Math.max(0, resetAt - Date.now());
1225
- const remainMin = Math.round(remainMs / 60000);
1226
- const rH = Math.floor(remainMin / 60);
1227
- const rM = remainMin % 60;
1263
+ const remainSec = Math.round(remainMs / 1000);
1264
+ const rH = Math.floor(remainSec / 3600);
1265
+ const rM = Math.floor((remainSec % 3600) / 60);
1266
+ const rS = remainSec % 60;
1228
1267
  const pctColor = pct < 50 ? 'green' : pct < 80 ? 'yellow' : 'red';
1229
- const timeColor = remainMin > 120 ? 'green' : remainMin > 60 ? 'yellow' : 'red';
1268
+ const countdown = `${rH}h${rM.toString().padStart(2, '0')}m${rS.toString().padStart(2, '0')}s`;
1269
+ const resetDate = new Date(resetAt);
1270
+ const resetTime = resetDate.toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit' });
1230
1271
  line1 = ` {bold}5h:{/bold}` +
1231
1272
  ` {${pctColor}-fg}${pct.toFixed(0)}% used{/${pctColor}-fg}` +
1232
- ` {${timeColor}-fg}${rH}h${rM}m left{/${timeColor}-fg}`;
1273
+ ` {${pctColor}-fg}${countdown}{/${pctColor}-fg}` +
1274
+ ` resets ${resetTime}`;
1233
1275
  }
1234
1276
  // ── Weekly (from Anthropic OAuth API) ──
1235
1277
  if (usage?.seven_day) {
1236
1278
  const pct = usage.seven_day.utilization;
1237
1279
  const resetAt = new Date(usage.seven_day.resets_at);
1238
- const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
1239
- const resetDay = dayNames[resetAt.getDay()];
1240
- const resetHour = resetAt.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
1280
+ const remainMs = Math.max(0, resetAt.getTime() - Date.now());
1281
+ const remainSec = Math.round(remainMs / 1000);
1282
+ const rD = Math.floor(remainSec / 86400);
1283
+ const rH = Math.floor((remainSec % 86400) / 3600);
1284
+ const rM = Math.floor((remainSec % 3600) / 60);
1285
+ const rS = remainSec % 60;
1241
1286
  const pctColor = pct < 50 ? 'green' : pct < 80 ? 'yellow' : 'red';
1287
+ const countdown = rD > 0
1288
+ ? `${rD}d${rH}h${rM.toString().padStart(2, '0')}m`
1289
+ : `${rH}h${rM.toString().padStart(2, '0')}m${rS.toString().padStart(2, '0')}s`;
1290
+ const resetTime = resetAt.toLocaleString('en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit' });
1242
1291
  line2 = ` {bold}Week:{/bold}` +
1243
1292
  ` {${pctColor}-fg}${pct.toFixed(0)}% used{/${pctColor}-fg}` +
1244
- ` resets ${resetDay} ${resetHour}`;
1293
+ ` {${pctColor}-fg}⏱ ${countdown}{/${pctColor}-fg}` +
1294
+ ` resets ${resetTime}`;
1245
1295
  }
1246
1296
  windowBox.setContent(line1 + (line2 ? '\n' + line2 : ''));
1247
1297
  }
@@ -1254,6 +1304,8 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
1254
1304
  }
1255
1305
  catch { }
1256
1306
  }
1307
+ // Legacy wrapper for backward compat
1308
+ async function updateWindowBox() { await fetchAndCacheUsage(); }
1257
1309
  // ── Handle terminal resize ──
1258
1310
  // Recalculate all widget positions from new screen.height
1259
1311
  screen.on('resize', () => {
@@ -1284,6 +1336,7 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
1284
1336
  screen.key(['C-c'], () => {
1285
1337
  clearInterval(pollInterval);
1286
1338
  clearInterval(windowPollInterval);
1339
+ clearInterval(countdownInterval);
1287
1340
  clearInterval(headerAnimInterval);
1288
1341
  clearInterval(fortuneInterval);
1289
1342
  screen.destroy();
@@ -1293,6 +1346,7 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
1293
1346
  screen.key(['q'], () => {
1294
1347
  clearInterval(pollInterval);
1295
1348
  clearInterval(windowPollInterval);
1349
+ clearInterval(countdownInterval);
1296
1350
  clearInterval(headerAnimInterval);
1297
1351
  clearInterval(fortuneInterval);
1298
1352
  screen.destroy();
@@ -1400,7 +1454,8 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
1400
1454
  fortuneText = activeFortunes[fortuneIdx];
1401
1455
  renderHeader();
1402
1456
  }, 30000);
1403
- const windowPollInterval = setInterval(updateWindowBox, 15000); // every 15s
1457
+ const windowPollInterval = setInterval(fetchAndCacheUsage, 15000); // fetch fresh data every 15s
1458
+ const countdownInterval = setInterval(renderWindowBox, 1000); // tick countdown every 1s
1404
1459
  }
1405
1460
  // ── Helpers ──
1406
1461
  function fmtK(n) {
@@ -1443,10 +1498,10 @@ exports.dashboardCommand = new commander_1.Command('dashboard')
1443
1498
  .option('--compact', 'Minimal layout for small terminals')
1444
1499
  .action(async (sessionNameArg, options) => {
1445
1500
  const refreshMs = parseInt(options.refresh) || 2000;
1446
- // --wait-for-new: poll until a brand new session appears
1501
+ // --wait-for-new: poll until session name appears (JSONL may not exist yet)
1447
1502
  if (options.waitForNew) {
1448
- const { sessionName, jsonlPath } = await waitForNewSession();
1449
- await launchDashboard(sessionName, jsonlPath, refreshMs);
1503
+ const result = await waitForNewSession();
1504
+ await launchDashboard(result.sessionName, result.jsonlPath, refreshMs, result.launchCwd, result.launchTs);
1450
1505
  return;
1451
1506
  }
1452
1507
  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 {};