@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.
- package/dist/cache/capture.js +0 -0
- package/dist/commands/dashboard.js +121 -66
- package/dist/commands/hooks.d.ts +25 -36
- package/dist/commands/hooks.js +43 -615
- package/dist/commands/init.js +7 -23
- package/dist/commands/run.js +90 -3
- package/dist/commands/setup.js +10 -352
- package/dist/deploy/hooks.d.ts +8 -5
- package/dist/deploy/hooks.js +12 -105
- package/dist/deploy/settings.d.ts +8 -2
- package/dist/deploy/settings.js +22 -51
- package/dist/index.js +17 -39
- package/dist/utils/state.js +7 -2
- package/package.json +1 -1
- package/templates/CLAUDE.md +82 -292
- package/templates/cursor-rules/ekkos-memory.md +48 -108
- package/templates/windsurf-rules/ekkos-memory.md +62 -64
- package/templates/cursor-hooks/after-agent-response.sh +0 -117
- package/templates/cursor-hooks/before-submit-prompt.sh +0 -419
- package/templates/cursor-hooks/hooks.json +0 -20
- package/templates/cursor-hooks/lib/contract.sh +0 -320
- package/templates/cursor-hooks/stop.sh +0 -75
- package/templates/hooks/assistant-response.ps1 +0 -256
- package/templates/hooks/assistant-response.sh +0 -160
- package/templates/hooks/hooks.json +0 -40
- package/templates/hooks/lib/contract.sh +0 -332
- package/templates/hooks/lib/count-tokens.cjs +0 -86
- package/templates/hooks/lib/ekkos-reminders.sh +0 -98
- package/templates/hooks/lib/state.sh +0 -210
- package/templates/hooks/session-start.ps1 +0 -146
- package/templates/hooks/session-start.sh +0 -353
- package/templates/hooks/stop.ps1 +0 -349
- package/templates/hooks/stop.sh +0 -382
- package/templates/hooks/user-prompt-submit.ps1 +0 -419
- package/templates/hooks/user-prompt-submit.sh +0 -516
- package/templates/project-stubs/session-start.ps1 +0 -63
- package/templates/project-stubs/session-start.sh +0 -55
- package/templates/project-stubs/stop.ps1 +0 -63
- package/templates/project-stubs/stop.sh +0 -55
- package/templates/project-stubs/user-prompt-submit.ps1 +0 -63
- package/templates/project-stubs/user-prompt-submit.sh +0 -55
- package/templates/windsurf-hooks/README.md +0 -212
- package/templates/windsurf-hooks/hooks.json +0 -17
- package/templates/windsurf-hooks/install.sh +0 -148
- package/templates/windsurf-hooks/lib/contract.sh +0 -322
- package/templates/windsurf-hooks/post-cascade-response.sh +0 -251
- package/templates/windsurf-hooks/pre-user-prompt.sh +0 -435
package/dist/cache/capture.js
CHANGED
|
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 =
|
|
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
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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
|
|
462
|
-
|
|
463
|
-
if (jsonlPath) {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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
|
|
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 —
|
|
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.
|
|
521
|
+
console.log(chalk_1.default.yellow(`\n Timeout. Launching with ${candidateName}...`));
|
|
532
522
|
const jsonlPath = resolveJsonlPath(candidateName, launchTs);
|
|
533
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
1114
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
1226
|
-
const rH = Math.floor(
|
|
1227
|
-
const rM =
|
|
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
|
|
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
|
-
` {${
|
|
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
|
|
1239
|
-
const
|
|
1240
|
-
const
|
|
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
|
-
`
|
|
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(
|
|
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
|
|
1501
|
+
// --wait-for-new: poll until session name appears (JSONL may not exist yet)
|
|
1447
1502
|
if (options.waitForNew) {
|
|
1448
|
-
const
|
|
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;
|
package/dist/commands/hooks.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
-
|
|
93
|
-
|
|
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
|
|
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
|
|
105
|
-
interface StatusOptions {
|
|
94
|
+
}): Promise<VerifyResult>;
|
|
95
|
+
export declare function hooksStatus(_options: {
|
|
106
96
|
verbose?: boolean;
|
|
107
|
-
}
|
|
108
|
-
export
|
|
109
|
-
export { findProjectRoot, expandPath };
|
|
97
|
+
}): Promise<void>;
|
|
98
|
+
export {};
|