@ekkos/cli 1.3.1 → 1.3.2
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 +147 -57
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +54 -16
- package/dist/commands/run.js +163 -44
- package/dist/commands/status.d.ts +4 -1
- package/dist/commands/status.js +165 -27
- package/dist/commands/synk.d.ts +7 -0
- package/dist/commands/synk.js +339 -0
- package/dist/deploy/settings.d.ts +6 -5
- package/dist/deploy/settings.js +27 -17
- package/dist/index.js +12 -82
- package/dist/lib/usage-parser.d.ts +1 -1
- package/dist/lib/usage-parser.js +5 -3
- package/dist/local/index.d.ts +14 -0
- package/dist/local/index.js +28 -0
- package/dist/local/local-embeddings.d.ts +49 -0
- package/dist/local/local-embeddings.js +232 -0
- package/dist/local/offline-fallback.d.ts +44 -0
- package/dist/local/offline-fallback.js +159 -0
- package/dist/local/sqlite-store.d.ts +126 -0
- package/dist/local/sqlite-store.js +393 -0
- package/dist/local/sync-engine.d.ts +42 -0
- package/dist/local/sync-engine.js +223 -0
- package/dist/synk/api.d.ts +22 -0
- package/dist/synk/api.js +133 -0
- package/dist/synk/auth.d.ts +7 -0
- package/dist/synk/auth.js +30 -0
- package/dist/synk/config.d.ts +18 -0
- package/dist/synk/config.js +37 -0
- package/dist/synk/daemon/control-client.d.ts +11 -0
- package/dist/synk/daemon/control-client.js +101 -0
- package/dist/synk/daemon/control-server.d.ts +24 -0
- package/dist/synk/daemon/control-server.js +91 -0
- package/dist/synk/daemon/run.d.ts +14 -0
- package/dist/synk/daemon/run.js +338 -0
- package/dist/synk/encryption.d.ts +17 -0
- package/dist/synk/encryption.js +133 -0
- package/dist/synk/index.d.ts +13 -0
- package/dist/synk/index.js +36 -0
- package/dist/synk/machine-client.d.ts +42 -0
- package/dist/synk/machine-client.js +218 -0
- package/dist/synk/persistence.d.ts +51 -0
- package/dist/synk/persistence.js +211 -0
- package/dist/synk/qr.d.ts +5 -0
- package/dist/synk/qr.js +33 -0
- package/dist/synk/session-bridge.d.ts +58 -0
- package/dist/synk/session-bridge.js +171 -0
- package/dist/synk/session-client.d.ts +46 -0
- package/dist/synk/session-client.js +240 -0
- package/dist/synk/types.d.ts +574 -0
- package/dist/synk/types.js +74 -0
- package/dist/utils/platform.d.ts +5 -1
- package/dist/utils/platform.js +24 -4
- package/dist/utils/proxy-url.d.ts +10 -0
- package/dist/utils/proxy-url.js +19 -0
- package/dist/utils/state.d.ts +1 -1
- package/dist/utils/state.js +11 -3
- package/package.json +13 -4
- package/templates/claude-plugins-admin/AGENT_TEAM_PROPOSALS.md +0 -819
- package/templates/claude-plugins-admin/README.md +0 -446
- package/templates/claude-plugins-admin/autonomous-admin-agent/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/autonomous-admin-agent/commands/agent.md +0 -595
- package/templates/claude-plugins-admin/backend-agent/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/backend-agent/commands/backend.md +0 -798
- package/templates/claude-plugins-admin/deploy-guardian/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/deploy-guardian/commands/deploy.md +0 -554
- package/templates/claude-plugins-admin/frontend-agent/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/frontend-agent/commands/frontend.md +0 -881
- package/templates/claude-plugins-admin/mcp-server-manager/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/mcp-server-manager/commands/mcp.md +0 -85
- package/templates/claude-plugins-admin/memory-system-monitor/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/memory-system-monitor/commands/memory-health.md +0 -569
- package/templates/claude-plugins-admin/qa-agent/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/qa-agent/commands/qa.md +0 -863
- package/templates/claude-plugins-admin/tech-lead-agent/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins-admin/tech-lead-agent/commands/lead.md +0 -732
- package/templates/hooks-node/lib/state.js +0 -187
- package/templates/hooks-node/stop.js +0 -416
- package/templates/hooks-node/user-prompt-submit.js +0 -337
- package/templates/rules/00-hooks-contract.mdc +0 -89
- package/templates/rules/30-ekkos-core.mdc +0 -188
- package/templates/rules/31-ekkos-messages.mdc +0 -78
|
@@ -322,6 +322,17 @@ function displaySessionName(rawName) {
|
|
|
322
322
|
return rawName;
|
|
323
323
|
return resolveSessionAlias(rawName) || (0, state_js_1.uuidToWords)(rawName);
|
|
324
324
|
}
|
|
325
|
+
function isStableSessionId(sessionId) {
|
|
326
|
+
return typeof sessionId === 'string' && UUID_REGEX.test(sessionId);
|
|
327
|
+
}
|
|
328
|
+
function isPendingSessionId(sessionId) {
|
|
329
|
+
if (typeof sessionId !== 'string')
|
|
330
|
+
return true;
|
|
331
|
+
const normalized = sessionId.trim().toLowerCase();
|
|
332
|
+
if (!normalized)
|
|
333
|
+
return true;
|
|
334
|
+
return normalized === 'pending' || normalized === '_pending' || normalized.startsWith('_pending-');
|
|
335
|
+
}
|
|
325
336
|
// ── Resolve session to JSONL path ──
|
|
326
337
|
function resolveJsonlPath(sessionName, createdAfterMs) {
|
|
327
338
|
// 1) Try standard resolution (works when sessionId is a real UUID)
|
|
@@ -331,13 +342,21 @@ function resolveJsonlPath(sessionName, createdAfterMs) {
|
|
|
331
342
|
if (fs.existsSync(jsonlPath))
|
|
332
343
|
return jsonlPath;
|
|
333
344
|
}
|
|
334
|
-
// 2)
|
|
345
|
+
// 2) Active-session fallback.
|
|
346
|
+
// IMPORTANT: never resolve "pending" sessions to "latest file in project" because
|
|
347
|
+
// that can cross-bind concurrent sessions to the same dashboard transcript.
|
|
335
348
|
const activeSessionsPath = path.join(os.homedir(), '.ekkos', 'active-sessions.json');
|
|
336
349
|
if (fs.existsSync(activeSessionsPath)) {
|
|
337
350
|
try {
|
|
338
351
|
const sessions = JSON.parse(fs.readFileSync(activeSessionsPath, 'utf-8'));
|
|
339
352
|
const match = sessions.find((s) => s.sessionName === sessionName);
|
|
340
353
|
if (match?.projectPath) {
|
|
354
|
+
if (isStableSessionId(match.sessionId)) {
|
|
355
|
+
return findJsonlBySessionId(match.projectPath, match.sessionId);
|
|
356
|
+
}
|
|
357
|
+
if (isPendingSessionId(match.sessionId)) {
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
341
360
|
return findLatestJsonl(match.projectPath, createdAfterMs);
|
|
342
361
|
}
|
|
343
362
|
}
|
|
@@ -353,12 +372,15 @@ function resolveJsonlPath(sessionName, createdAfterMs) {
|
|
|
353
372
|
function findLatestJsonl(projectPath, createdAfterMs) {
|
|
354
373
|
// Claude encodes project paths by replacing separators with '-'.
|
|
355
374
|
// On Windows, ':' is also illegal in directory names so it gets replaced too.
|
|
375
|
+
// Claude also replaces underscores and other non-alphanumeric chars with '-'.
|
|
356
376
|
// Try all plausible encodings since Claude's exact scheme varies by platform.
|
|
357
377
|
const candidateEncodings = new Set([
|
|
358
378
|
projectPath.replace(/[\\/]/g, '-'), // C:-Users-name (backslash only)
|
|
359
379
|
projectPath.replace(/[:\\/]/g, '-'), // C--Users-name (colon + backslash)
|
|
360
380
|
'-' + projectPath.replace(/[:\\/]/g, '-'), // -C--Users-name (leading separator)
|
|
361
381
|
projectPath.replace(/\//g, '-'), // macOS: /Users/name → -Users-name
|
|
382
|
+
projectPath.replace(/[^a-zA-Z0-9]/g, '-'), // Replace ALL non-alphanumeric (handles _)
|
|
383
|
+
`-${projectPath.replace(/^[\\/]+/, '').replace(/[^a-zA-Z0-9]/g, '-')}`, // Leading - variant
|
|
362
384
|
]);
|
|
363
385
|
const projectsRoot = path.join(os.homedir(), '.claude', 'projects');
|
|
364
386
|
for (const encoded of Array.from(candidateEncodings)) {
|
|
@@ -390,6 +412,8 @@ function findJsonlBySessionId(projectPath, sessionId) {
|
|
|
390
412
|
projectPath.replace(/[:\\/]/g, '-'),
|
|
391
413
|
'-' + projectPath.replace(/[:\\/]/g, '-'),
|
|
392
414
|
projectPath.replace(/\//g, '-'),
|
|
415
|
+
projectPath.replace(/[^a-zA-Z0-9]/g, '-'),
|
|
416
|
+
`-${projectPath.replace(/^[\\/]+/, '').replace(/[^a-zA-Z0-9]/g, '-')}`,
|
|
393
417
|
]);
|
|
394
418
|
const projectsRoot = path.join(os.homedir(), '.claude', 'projects');
|
|
395
419
|
for (const encoded of Array.from(candidateEncodings)) {
|
|
@@ -436,8 +460,8 @@ async function waitForNewSession() {
|
|
|
436
460
|
const hint = JSON.parse(fs.readFileSync(hintPath, 'utf-8'));
|
|
437
461
|
if (hint.ts >= launchTs - 5000 && hint.sessionName && hint.projectPath) {
|
|
438
462
|
candidateName = hint.sessionName;
|
|
439
|
-
const
|
|
440
|
-
|
|
463
|
+
const hintSessionId = typeof hint.sessionId === 'string' ? hint.sessionId : '';
|
|
464
|
+
const jsonlPath = (isStableSessionId(hintSessionId) ? findJsonlBySessionId(hint.projectPath, hintSessionId) : null)
|
|
441
465
|
|| resolveJsonlPath(hint.sessionName, launchTs);
|
|
442
466
|
// Return immediately — JSONL may be null; dashboard will lazy-resolve
|
|
443
467
|
console.log(chalk_1.default.green(` Found session (hook hint): ${hint.sessionName}`));
|
|
@@ -451,26 +475,24 @@ async function waitForNewSession() {
|
|
|
451
475
|
for (const s of sessions) {
|
|
452
476
|
const startedMs = new Date(s.startedAt).getTime();
|
|
453
477
|
if (startedMs >= launchTs - 2000) {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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;
|
|
464
|
-
}
|
|
478
|
+
if (!isStableSessionId(s.sessionId)) {
|
|
479
|
+
// Wait for stable session identity; pending IDs can collide across concurrent runs.
|
|
480
|
+
continue;
|
|
465
481
|
}
|
|
482
|
+
candidateName = s.sessionName;
|
|
483
|
+
// Try exact by sessionId first, then constrained name resolution.
|
|
484
|
+
let jsonlPath = s.projectPath ? findJsonlBySessionId(s.projectPath, s.sessionId) : null;
|
|
485
|
+
if (!jsonlPath)
|
|
486
|
+
jsonlPath = resolveJsonlPath(s.sessionName, launchTs);
|
|
466
487
|
// Return immediately with session name — JSONL may still be null
|
|
467
488
|
// (Claude Code hasn't created it yet). Dashboard will lazy-resolve.
|
|
468
|
-
console.log(chalk_1.default.green(` Found session: ${s.sessionName}`));
|
|
489
|
+
console.log(chalk_1.default.green(` Found session: ${s.sessionName}${jsonlPath ? '' : ' (awaiting transcript)'}`));
|
|
469
490
|
return { sessionName: s.sessionName, jsonlPath, launchCwd: s.projectPath || launchCwd, launchTs };
|
|
470
491
|
}
|
|
471
492
|
}
|
|
472
|
-
// Fallback: use launch CWD to find any new JSONL
|
|
473
|
-
|
|
493
|
+
// Fallback: use launch CWD to find any new JSONL only if we don't even
|
|
494
|
+
// have a candidate session name yet.
|
|
495
|
+
if (launchCwd && !candidateName) {
|
|
474
496
|
const latestJsonl = findLatestJsonl(launchCwd, launchTs);
|
|
475
497
|
if (latestJsonl) {
|
|
476
498
|
const name = candidateName || path.basename(latestJsonl, '.jsonl');
|
|
@@ -478,8 +500,9 @@ async function waitForNewSession() {
|
|
|
478
500
|
return { sessionName: name, jsonlPath: latestJsonl, launchCwd, launchTs };
|
|
479
501
|
}
|
|
480
502
|
}
|
|
481
|
-
// Broad fallback: scan ALL project directories for any new JSONL
|
|
482
|
-
|
|
503
|
+
// Broad fallback: scan ALL project directories for any new JSONL only
|
|
504
|
+
// when no candidate name is known.
|
|
505
|
+
if (!candidateName) {
|
|
483
506
|
const projectsRoot = path.join(os.homedir(), '.claude', 'projects');
|
|
484
507
|
try {
|
|
485
508
|
if (fs.existsSync(projectsRoot)) {
|
|
@@ -580,7 +603,7 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
580
603
|
mouse: false, // Disable ALL mouse capture (allows terminal text selection)
|
|
581
604
|
grabKeys: false, // Don't grab keyboard input from other panes
|
|
582
605
|
sendFocus: false, // Don't send focus events (breaks paste)
|
|
583
|
-
ignoreLocked: ['C-c'], //
|
|
606
|
+
ignoreLocked: ['C-c', 'C-q'], // Capture Ctrl+C and Ctrl+Q for quit
|
|
584
607
|
input: ttyInput, // Use /dev/tty for input (isolated from stdout pipe)
|
|
585
608
|
output: ttyOutput, // Use /dev/tty for output (isolated from stdout pipe)
|
|
586
609
|
forceUnicode: true, // Better text rendering
|
|
@@ -678,6 +701,7 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
678
701
|
let layout = calcLayout();
|
|
679
702
|
let lastFileSize = 0;
|
|
680
703
|
let lastData = null;
|
|
704
|
+
let sessionStartMs = launchTs || null; // epoch ms of session start, for live timer
|
|
681
705
|
let lastChartSeries = null;
|
|
682
706
|
let lastScrollPerc = 0; // Preserve scroll position across updates
|
|
683
707
|
let fortuneIdx = Math.floor(Math.random() * activeFortunes.length);
|
|
@@ -945,14 +969,36 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
945
969
|
}
|
|
946
970
|
catch { }
|
|
947
971
|
}
|
|
972
|
+
function renderPreTurnState() {
|
|
973
|
+
try {
|
|
974
|
+
contextBox.setContent(` {green-fg}Session active{/green-fg} {gray-fg}${sessionName}{/gray-fg}\n` +
|
|
975
|
+
` Token and cost metrics appear after the first assistant response.`);
|
|
976
|
+
turnBox.setContent(`{bold}Turns{/bold}\n` +
|
|
977
|
+
`{gray-fg}—{/gray-fg}`);
|
|
978
|
+
const timerStr = sessionStartMs
|
|
979
|
+
? `{cyan-fg}${formatElapsed(sessionStartMs)}{/cyan-fg} `
|
|
980
|
+
: '';
|
|
981
|
+
footerBox.setLabel(` ${sessionName} `);
|
|
982
|
+
footerBox.setContent(` ${timerStr}{green-fg}Ready{/green-fg}` +
|
|
983
|
+
(inTmux
|
|
984
|
+
? ` {gray-fg}Ctrl+Q quit{/gray-fg}`
|
|
985
|
+
: ` {gray-fg}? help q quit r refresh{/gray-fg}`));
|
|
986
|
+
}
|
|
987
|
+
catch (err) {
|
|
988
|
+
dlog(`Pre-turn render: ${err.message}`);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
948
991
|
function updateDashboard() {
|
|
949
992
|
ensureLayoutSynced();
|
|
950
993
|
// ── Lazy JSONL resolution ──────────────────────────────────────────────
|
|
951
994
|
// Dashboard may launch before Claude Code creates the JSONL file.
|
|
952
995
|
// Keep trying to find it on each poll tick.
|
|
953
996
|
if (!jsonlPath || !fs.existsSync(jsonlPath)) {
|
|
997
|
+
const shouldUseCwdFallback = initialSessionName === 'initializing'
|
|
998
|
+
|| initialSessionName === 'session'
|
|
999
|
+
|| UUID_REGEX.test(initialSessionName);
|
|
954
1000
|
const resolved = resolveJsonlPath(initialSessionName, launchTs)
|
|
955
|
-
|| (launchCwd ? findLatestJsonl(launchCwd, launchTs) : null);
|
|
1001
|
+
|| (shouldUseCwdFallback && launchCwd ? findLatestJsonl(launchCwd, launchTs) : null);
|
|
956
1002
|
if (resolved) {
|
|
957
1003
|
jsonlPath = resolved;
|
|
958
1004
|
dlog(`Lazy-resolved JSONL: ${jsonlPath}`);
|
|
@@ -960,6 +1006,7 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
960
1006
|
else {
|
|
961
1007
|
// Still no JSONL — render the header/footer so the dashboard isn't blank
|
|
962
1008
|
renderHeader();
|
|
1009
|
+
renderPreTurnState();
|
|
963
1010
|
try {
|
|
964
1011
|
screen.render();
|
|
965
1012
|
}
|
|
@@ -994,6 +1041,9 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
994
1041
|
lastFileSize = stat.size;
|
|
995
1042
|
data = parseJsonlFile(jsonlPath, sessionName);
|
|
996
1043
|
lastData = data;
|
|
1044
|
+
if (!sessionStartMs && data.startedAt) {
|
|
1045
|
+
sessionStartMs = new Date(data.startedAt).getTime();
|
|
1046
|
+
}
|
|
997
1047
|
}
|
|
998
1048
|
catch (err) {
|
|
999
1049
|
dlog(`Parse error: ${err.message}`);
|
|
@@ -1162,13 +1212,20 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1162
1212
|
dlog(`Table: ${err.message}`);
|
|
1163
1213
|
}
|
|
1164
1214
|
// ── Session Totals (footer) ──
|
|
1215
|
+
renderFooter(data);
|
|
1216
|
+
}
|
|
1217
|
+
/** Render footer bar — called from updateDashboard and the 1s timer tick */
|
|
1218
|
+
function renderFooter(data, skipRender = false) {
|
|
1219
|
+
const d = data || lastData;
|
|
1220
|
+
if (!d)
|
|
1221
|
+
return;
|
|
1165
1222
|
try {
|
|
1166
|
-
const totalTokensM = ((
|
|
1167
|
-
const totalSavings =
|
|
1223
|
+
const totalTokensM = ((d.totalCacheRead + d.totalCacheCreate + d.totalOutput) / 1000000).toFixed(2);
|
|
1224
|
+
const totalSavings = d.turns.reduce((s, t) => s + t.savings, 0);
|
|
1168
1225
|
// Model routing breakdown (uses routedModel for actual model counts)
|
|
1169
|
-
const opusCount =
|
|
1170
|
-
const sonnetCount =
|
|
1171
|
-
const haikuCount =
|
|
1226
|
+
const opusCount = d.turns.filter(t => t.routedModel.includes('opus')).length;
|
|
1227
|
+
const sonnetCount = d.turns.filter(t => t.routedModel.includes('sonnet')).length;
|
|
1228
|
+
const haikuCount = d.turns.filter(t => t.routedModel.includes('haiku')).length;
|
|
1172
1229
|
const routingParts = [`{magenta-fg}O{/magenta-fg}:${opusCount}`];
|
|
1173
1230
|
if (sonnetCount > 0)
|
|
1174
1231
|
routingParts.push(`{blue-fg}S{/blue-fg}:${sonnetCount}`);
|
|
@@ -1177,25 +1234,30 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1177
1234
|
const savingsStr = totalSavings > 0
|
|
1178
1235
|
? ` {green-fg}saved $${totalSavings.toFixed(2)}{/green-fg}`
|
|
1179
1236
|
: '';
|
|
1237
|
+
const timerStr = sessionStartMs
|
|
1238
|
+
? `{cyan-fg}${formatElapsed(sessionStartMs)}{/cyan-fg} `
|
|
1239
|
+
: '';
|
|
1180
1240
|
footerBox.setLabel(` ${sessionName} `);
|
|
1181
|
-
footerBox.setContent(` {
|
|
1241
|
+
footerBox.setContent(` ${timerStr}` +
|
|
1242
|
+
`{green-fg}$${d.totalCost.toFixed(2)}{/green-fg}` +
|
|
1182
1243
|
` ${totalTokensM}M` +
|
|
1183
1244
|
` ${routingStr}` +
|
|
1184
|
-
` R[A:${
|
|
1245
|
+
` R[A:${d.replayAppliedCount} SZ:${d.replaySkippedSizeCount} ST:${d.replaySkipStoreCount}]` +
|
|
1185
1246
|
savingsStr +
|
|
1186
1247
|
(inTmux
|
|
1187
|
-
? ` {gray-fg}Ctrl+
|
|
1248
|
+
? ` {gray-fg}Ctrl+Q quit{/gray-fg}`
|
|
1188
1249
|
: ` {gray-fg}? help q quit r refresh{/gray-fg}`));
|
|
1189
1250
|
}
|
|
1190
1251
|
catch (err) {
|
|
1191
1252
|
dlog(`Footer: ${err.message}`);
|
|
1192
1253
|
}
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1254
|
+
if (!skipRender)
|
|
1255
|
+
try {
|
|
1256
|
+
screen.render();
|
|
1257
|
+
}
|
|
1258
|
+
catch (err) {
|
|
1259
|
+
dlog(`Render: ${err.message}`);
|
|
1260
|
+
}
|
|
1199
1261
|
}
|
|
1200
1262
|
// ── Usage window update — calls Anthropic's OAuth usage API ──
|
|
1201
1263
|
/**
|
|
@@ -1250,11 +1312,20 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1250
1312
|
renderWindowBox();
|
|
1251
1313
|
}
|
|
1252
1314
|
// Render countdown from cached data (called every 1s)
|
|
1253
|
-
function renderWindowBox() {
|
|
1315
|
+
function renderWindowBox(skipRender = false) {
|
|
1254
1316
|
try {
|
|
1255
1317
|
const usage = cachedUsage;
|
|
1256
1318
|
let line1 = ' {gray-fg}No usage data{/gray-fg}';
|
|
1257
1319
|
let line2 = '';
|
|
1320
|
+
// Fixed-width column helpers for aligned rendering:
|
|
1321
|
+
// " 5h: 26% used ⏱ 1h16m12s resets Feb 27, 2026, 12:00 AM"
|
|
1322
|
+
// " Week: 91% used ⏱ 0h16m12s resets Thu, Feb 26, 2026, 11:00 PM"
|
|
1323
|
+
// ^^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
1324
|
+
// label pct col countdown reset time
|
|
1325
|
+
const COL_LABEL = 6; // "5h: " or "Week: " — padded to same width
|
|
1326
|
+
const COL_PCT = 9; // "100% used" max
|
|
1327
|
+
const COL_CD = 11; // "10h00m00s " max
|
|
1328
|
+
const rpad = (s, w) => s.length >= w ? s : s + ' '.repeat(w - s.length);
|
|
1258
1329
|
// ── 5h Window (from Anthropic OAuth API) ──
|
|
1259
1330
|
if (usage?.five_hour) {
|
|
1260
1331
|
const pct = usage.five_hour.utilization;
|
|
@@ -1266,12 +1337,13 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1266
1337
|
const rS = remainSec % 60;
|
|
1267
1338
|
const pctColor = pct < 50 ? 'green' : pct < 80 ? 'yellow' : 'red';
|
|
1268
1339
|
const countdown = `${rH}h${rM.toString().padStart(2, '0')}m${rS.toString().padStart(2, '0')}s`;
|
|
1340
|
+
const pctStr = `${pct.toFixed(0)}% used`;
|
|
1269
1341
|
const resetDate = new Date(resetAt);
|
|
1270
1342
|
const resetTime = resetDate.toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit' });
|
|
1271
|
-
line1 = ` {bold}5h:{/bold}` +
|
|
1272
|
-
`
|
|
1273
|
-
` {${pctColor}-fg}⏱ ${countdown}{/${pctColor}-fg}` +
|
|
1274
|
-
`
|
|
1343
|
+
line1 = ` {bold}${rpad('5h:', COL_LABEL)}{/bold}` +
|
|
1344
|
+
`{${pctColor}-fg}${rpad(pctStr, COL_PCT)}{/${pctColor}-fg}` +
|
|
1345
|
+
` {${pctColor}-fg}⏱ ${rpad(countdown, COL_CD)}{/${pctColor}-fg}` +
|
|
1346
|
+
`resets ${resetTime}`;
|
|
1275
1347
|
}
|
|
1276
1348
|
// ── Weekly (from Anthropic OAuth API) ──
|
|
1277
1349
|
if (usage?.seven_day) {
|
|
@@ -1287,11 +1359,12 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1287
1359
|
const countdown = rD > 0
|
|
1288
1360
|
? `${rD}d${rH}h${rM.toString().padStart(2, '0')}m`
|
|
1289
1361
|
: `${rH}h${rM.toString().padStart(2, '0')}m${rS.toString().padStart(2, '0')}s`;
|
|
1362
|
+
const pctStr = `${pct.toFixed(0)}% used`;
|
|
1290
1363
|
const resetTime = resetAt.toLocaleString('en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit' });
|
|
1291
|
-
line2 = ` {bold}Week:{/bold}` +
|
|
1292
|
-
`
|
|
1293
|
-
` {${pctColor}-fg}⏱ ${countdown}{/${pctColor}-fg}` +
|
|
1294
|
-
`
|
|
1364
|
+
line2 = ` {bold}${rpad('Week:', COL_LABEL)}{/bold}` +
|
|
1365
|
+
`{${pctColor}-fg}${rpad(pctStr, COL_PCT)}{/${pctColor}-fg}` +
|
|
1366
|
+
` {${pctColor}-fg}⏱ ${rpad(countdown, COL_CD)}{/${pctColor}-fg}` +
|
|
1367
|
+
`resets ${resetTime}`;
|
|
1295
1368
|
}
|
|
1296
1369
|
windowBox.setContent(line1 + (line2 ? '\n' + line2 : ''));
|
|
1297
1370
|
}
|
|
@@ -1299,10 +1372,11 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1299
1372
|
dlog(`Window: ${err.message}`);
|
|
1300
1373
|
windowBox.setContent(` {gray-fg}Usage data unavailable{/gray-fg}`);
|
|
1301
1374
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1375
|
+
if (!skipRender)
|
|
1376
|
+
try {
|
|
1377
|
+
screen.render();
|
|
1378
|
+
}
|
|
1379
|
+
catch { }
|
|
1306
1380
|
}
|
|
1307
1381
|
// Legacy wrapper for backward compat
|
|
1308
1382
|
async function updateWindowBox() { await fetchAndCacheUsage(); }
|
|
@@ -1333,10 +1407,10 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1333
1407
|
// KEYBOARD SHORTCUTS - Only capture when dashboard pane has focus
|
|
1334
1408
|
// In tmux split mode, this prevents capturing keys from Claude Code pane
|
|
1335
1409
|
// ══════════════════════════════════════════════════════════════════════════
|
|
1336
|
-
screen.key(['C-c'], () => {
|
|
1410
|
+
screen.key(['C-c', 'C-q'], () => {
|
|
1337
1411
|
clearInterval(pollInterval);
|
|
1338
1412
|
clearInterval(windowPollInterval);
|
|
1339
|
-
clearInterval(
|
|
1413
|
+
clearInterval(tickInterval);
|
|
1340
1414
|
clearInterval(headerAnimInterval);
|
|
1341
1415
|
clearInterval(fortuneInterval);
|
|
1342
1416
|
screen.destroy();
|
|
@@ -1346,7 +1420,7 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1346
1420
|
screen.key(['q'], () => {
|
|
1347
1421
|
clearInterval(pollInterval);
|
|
1348
1422
|
clearInterval(windowPollInterval);
|
|
1349
|
-
clearInterval(
|
|
1423
|
+
clearInterval(tickInterval);
|
|
1350
1424
|
clearInterval(headerAnimInterval);
|
|
1351
1425
|
clearInterval(fortuneInterval);
|
|
1352
1426
|
screen.destroy();
|
|
@@ -1409,7 +1483,7 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1409
1483
|
'\n' +
|
|
1410
1484
|
'{bold}Controls{/bold}\n' +
|
|
1411
1485
|
' r Refresh now\n' +
|
|
1412
|
-
' q/Ctrl+
|
|
1486
|
+
' q/Ctrl+Q Quit\n' +
|
|
1413
1487
|
'\n' +
|
|
1414
1488
|
'{gray-fg}Press any key to close{/gray-fg}'),
|
|
1415
1489
|
tags: true,
|
|
@@ -1455,7 +1529,16 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1455
1529
|
renderHeader();
|
|
1456
1530
|
}, 30000);
|
|
1457
1531
|
const windowPollInterval = setInterval(fetchAndCacheUsage, 15000); // fetch fresh data every 15s
|
|
1458
|
-
|
|
1532
|
+
// Single 1s tick for both countdown + session timer (one screen.render instead of three)
|
|
1533
|
+
const tickInterval = setInterval(() => {
|
|
1534
|
+
renderWindowBox(true);
|
|
1535
|
+
if (sessionStartMs)
|
|
1536
|
+
renderFooter(null, true);
|
|
1537
|
+
try {
|
|
1538
|
+
screen.render();
|
|
1539
|
+
}
|
|
1540
|
+
catch { }
|
|
1541
|
+
}, 1000);
|
|
1459
1542
|
}
|
|
1460
1543
|
// ── Helpers ──
|
|
1461
1544
|
function fmtK(n) {
|
|
@@ -1466,6 +1549,14 @@ function fmtK(n) {
|
|
|
1466
1549
|
return String(n);
|
|
1467
1550
|
}
|
|
1468
1551
|
function formatK(n) { return fmtK(n); }
|
|
1552
|
+
function formatElapsed(startMs) {
|
|
1553
|
+
const diff = Math.max(0, Math.floor((Date.now() - startMs) / 1000));
|
|
1554
|
+
const h = Math.floor(diff / 3600);
|
|
1555
|
+
const m = Math.floor((diff % 3600) / 60);
|
|
1556
|
+
const s = diff % 60;
|
|
1557
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
1558
|
+
return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${m}:${pad(s)}`;
|
|
1559
|
+
}
|
|
1469
1560
|
// ── Session picker ──
|
|
1470
1561
|
async function pickSession() {
|
|
1471
1562
|
const sessions = await (0, usage_parser_js_1.listEkkosSessions)(20);
|
|
@@ -1523,9 +1614,8 @@ exports.dashboardCommand = new commander_1.Command('dashboard')
|
|
|
1523
1614
|
}
|
|
1524
1615
|
const jsonlPath = resolveJsonlPath(sessionName);
|
|
1525
1616
|
if (!jsonlPath) {
|
|
1526
|
-
|
|
1527
|
-
console.log(chalk_1.default.gray(
|
|
1528
|
-
process.exit(1);
|
|
1617
|
+
// JSONL may not exist yet (session just started) — launch with lazy resolution
|
|
1618
|
+
console.log(chalk_1.default.gray(`Waiting for JSONL for "${sessionName}"...`));
|
|
1529
1619
|
}
|
|
1530
|
-
await launchDashboard(sessionName, jsonlPath, refreshMs);
|
|
1620
|
+
await launchDashboard(sessionName, jsonlPath || null, refreshMs, null, Date.now());
|
|
1531
1621
|
});
|
package/dist/commands/init.d.ts
CHANGED
package/dist/commands/init.js
CHANGED
|
@@ -11,8 +11,8 @@ const ora_1 = __importDefault(require("ora"));
|
|
|
11
11
|
const open_1 = __importDefault(require("open"));
|
|
12
12
|
const platform_1 = require("../utils/platform");
|
|
13
13
|
const mcp_1 = require("../deploy/mcp");
|
|
14
|
+
const settings_1 = require("../deploy/settings");
|
|
14
15
|
// DEPRECATED: Hooks removed in hookless architecture migration
|
|
15
|
-
// import { deployClaudeSettings } from '../deploy/settings';
|
|
16
16
|
// import { deployHooks } from '../deploy/hooks';
|
|
17
17
|
const skills_1 = require("../deploy/skills");
|
|
18
18
|
const agents_1 = require("../deploy/agents");
|
|
@@ -200,23 +200,37 @@ async function manualKeyAuth(providedKey) {
|
|
|
200
200
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
201
201
|
// IDE SETUP
|
|
202
202
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
203
|
-
async function selectIDEs() {
|
|
203
|
+
async function selectIDEs(autoSelect = false) {
|
|
204
204
|
console.log(chalk_1.default.cyan('Step 2/3: IDE Setup'));
|
|
205
205
|
console.log(chalk_1.default.gray('─'.repeat(40)));
|
|
206
206
|
console.log('');
|
|
207
207
|
const detected = (0, platform_1.detectInstalledIDEs)();
|
|
208
208
|
const current = (0, platform_1.detectCurrentIDE)();
|
|
209
|
-
|
|
210
|
-
|
|
209
|
+
// Build the unique ordered list: current IDE first, then any others detected
|
|
210
|
+
const detectedSet = new Set(detected);
|
|
211
|
+
if (current && (current === 'claude' || current === 'cursor' || current === 'windsurf')) {
|
|
212
|
+
detectedSet.add(current);
|
|
213
|
+
}
|
|
214
|
+
const detectedList = Array.from(detectedSet);
|
|
215
|
+
if (detectedList.length > 0) {
|
|
216
|
+
console.log(chalk_1.default.gray(`Detected: ${detectedList.join(', ')}`));
|
|
211
217
|
if (current) {
|
|
212
218
|
console.log(chalk_1.default.gray(`Current: ${current}`));
|
|
213
219
|
}
|
|
214
220
|
console.log('');
|
|
215
221
|
}
|
|
222
|
+
// Auto-select when exactly one IDE is detected or autoSelect flag is set
|
|
223
|
+
if (autoSelect || detectedList.length === 1) {
|
|
224
|
+
const autoIDE = detectedList.length === 1 ? detectedList[0] : (current ?? detectedList[0] ?? 'claude');
|
|
225
|
+
const ideName = autoIDE === 'claude' ? 'Claude Code' : autoIDE === 'cursor' ? 'Cursor' : 'Windsurf';
|
|
226
|
+
console.log(chalk_1.default.green(`✓ Auto-detected: ${chalk_1.default.bold(ideName)}`));
|
|
227
|
+
console.log('');
|
|
228
|
+
return [autoIDE];
|
|
229
|
+
}
|
|
216
230
|
const ideChoices = [
|
|
217
|
-
{ name: 'Claude Code', value: 'claude', checked:
|
|
218
|
-
{ name: 'Cursor', value: 'cursor', checked:
|
|
219
|
-
{ name: 'Windsurf (Cascade)', value: 'windsurf', checked:
|
|
231
|
+
{ name: 'Claude Code', value: 'claude', checked: detectedList.includes('claude') || current === 'claude' },
|
|
232
|
+
{ name: 'Cursor', value: 'cursor', checked: detectedList.includes('cursor') || current === 'cursor' },
|
|
233
|
+
{ name: 'Windsurf (Cascade)', value: 'windsurf', checked: detectedList.includes('windsurf') || current === 'windsurf' }
|
|
220
234
|
];
|
|
221
235
|
// If nothing was auto-detected, default to Claude Code
|
|
222
236
|
if (!ideChoices.some(c => c.checked)) {
|
|
@@ -252,10 +266,17 @@ async function deployForClaude(apiKey, userId, options) {
|
|
|
252
266
|
catch (error) {
|
|
253
267
|
spinner.fail('MCP server configuration failed');
|
|
254
268
|
}
|
|
255
|
-
//
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
269
|
+
// Claude Code settings (disable auto-memory, clean up legacy hooks)
|
|
270
|
+
spinner = (0, ora_1.default)('Configuring Claude Code settings...').start();
|
|
271
|
+
try {
|
|
272
|
+
(0, settings_1.deployClaudeSettings)();
|
|
273
|
+
result.settings = true;
|
|
274
|
+
spinner.succeed('Claude Code settings (auto-memory disabled)');
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
spinner.fail('Claude Code settings failed');
|
|
278
|
+
result.settings = true; // Non-critical — don't block init
|
|
279
|
+
}
|
|
259
280
|
// Skills
|
|
260
281
|
if (!options.skipSkills) {
|
|
261
282
|
spinner = (0, ora_1.default)('Deploying skills...').start();
|
|
@@ -325,9 +346,15 @@ async function deployForWindsurf(apiKey, userId) {
|
|
|
325
346
|
// MAIN INIT COMMAND
|
|
326
347
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
327
348
|
async function init(options) {
|
|
349
|
+
const isQuick = options.quick ?? false;
|
|
328
350
|
console.log('');
|
|
329
351
|
console.log(chalk_1.default.cyan.bold('╔═══════════════════════════════════════╗'));
|
|
330
|
-
|
|
352
|
+
if (isQuick) {
|
|
353
|
+
console.log(chalk_1.default.cyan.bold('║ ⚡ ekkOS Quick Setup ║'));
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
console.log(chalk_1.default.cyan.bold('║ 🧠 ekkOS Memory System Setup ║'));
|
|
357
|
+
}
|
|
331
358
|
console.log(chalk_1.default.cyan.bold('╚═══════════════════════════════════════╝'));
|
|
332
359
|
console.log('');
|
|
333
360
|
// Check templates exist
|
|
@@ -347,11 +374,11 @@ async function init(options) {
|
|
|
347
374
|
// STEP 1: Authentication
|
|
348
375
|
let auth;
|
|
349
376
|
if (options.key) {
|
|
350
|
-
// Manual API key provided
|
|
377
|
+
// Manual API key provided — always use it (respects --quick)
|
|
351
378
|
auth = await manualKeyAuth(options.key);
|
|
352
379
|
}
|
|
353
380
|
else if (existingConfig?.apiKey && !options.force) {
|
|
354
|
-
// Already authenticated
|
|
381
|
+
// Already authenticated — skip auth step entirely (works great with --quick)
|
|
355
382
|
console.log(chalk_1.default.cyan('Step 1/3: Authentication'));
|
|
356
383
|
console.log(chalk_1.default.gray('─'.repeat(40)));
|
|
357
384
|
console.log('');
|
|
@@ -365,6 +392,10 @@ async function init(options) {
|
|
|
365
392
|
tier: existingConfig.tier || 'unknown'
|
|
366
393
|
};
|
|
367
394
|
}
|
|
395
|
+
else if (isQuick) {
|
|
396
|
+
// --quick with no stored key: fall back to manual key prompt (one question only)
|
|
397
|
+
auth = await manualKeyAuth();
|
|
398
|
+
}
|
|
368
399
|
else {
|
|
369
400
|
// Device auth flow
|
|
370
401
|
try {
|
|
@@ -395,7 +426,8 @@ async function init(options) {
|
|
|
395
426
|
console.log('');
|
|
396
427
|
}
|
|
397
428
|
else {
|
|
398
|
-
|
|
429
|
+
// Pass autoSelect=true when --quick is set so we skip the checkbox prompt
|
|
430
|
+
selectedIDEs = await selectIDEs(isQuick);
|
|
399
431
|
}
|
|
400
432
|
// STEP 3: Deployment
|
|
401
433
|
console.log(chalk_1.default.cyan('Step 3/3: Deploying'));
|
|
@@ -461,7 +493,13 @@ async function init(options) {
|
|
|
461
493
|
};
|
|
462
494
|
console.log('');
|
|
463
495
|
console.log(chalk_1.default.green.bold('╔═══════════════════════════════════════╗'));
|
|
464
|
-
|
|
496
|
+
if (isQuick) {
|
|
497
|
+
const quickIdeName = ideNames.join(' / ') || 'Claude Code';
|
|
498
|
+
console.log(chalk_1.default.green.bold(`║ ⚡ Quick setup complete for ${quickIdeName.padEnd(11)}║`));
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
console.log(chalk_1.default.green.bold('║ ✓ Setup complete! ║'));
|
|
502
|
+
}
|
|
465
503
|
console.log(chalk_1.default.green.bold('╚═══════════════════════════════════════╝'));
|
|
466
504
|
console.log('');
|
|
467
505
|
console.log(chalk_1.default.white.bold(' MCP configured:'));
|