@ekkos/cli 1.3.0 → 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 +203 -66
- 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}`);
|
|
@@ -1089,14 +1139,15 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1089
1139
|
let header = '';
|
|
1090
1140
|
let separator = '';
|
|
1091
1141
|
let rows = [];
|
|
1092
|
-
// Always show all
|
|
1142
|
+
// Always show all 8 columns: Turn, Time, Model, Context, Cache Rd, Cache Wr, Output, Cost
|
|
1093
1143
|
// Shrink flex columns to fit narrow panes instead of dropping them.
|
|
1094
1144
|
const colNum = 4;
|
|
1145
|
+
const colTime = 8; // "HH:MM:SS" or "H:MM AM"
|
|
1095
1146
|
const colM = 7;
|
|
1096
1147
|
const colCtx = 7;
|
|
1097
1148
|
const colCost = 8;
|
|
1098
|
-
const nDividers =
|
|
1099
|
-
const fixedW = colNum + colM + colCtx + colCost;
|
|
1149
|
+
const nDividers = 7;
|
|
1150
|
+
const fixedW = colNum + colTime + colM + colCtx + colCost;
|
|
1100
1151
|
const flexTotal = Math.max(0, w - fixedW - nDividers);
|
|
1101
1152
|
let rdW = Math.max(5, Math.floor(flexTotal * 0.35));
|
|
1102
1153
|
let wrW = Math.max(5, Math.floor(flexTotal * 0.30));
|
|
@@ -1118,14 +1169,30 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1118
1169
|
rdW -= trimRd;
|
|
1119
1170
|
}
|
|
1120
1171
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1172
|
+
// Format turn timestamp to short time string
|
|
1173
|
+
const fmtTime = (iso) => {
|
|
1174
|
+
if (!iso)
|
|
1175
|
+
return '--:--';
|
|
1176
|
+
try {
|
|
1177
|
+
const d = new Date(iso);
|
|
1178
|
+
const h = d.getHours();
|
|
1179
|
+
const m = d.getMinutes().toString().padStart(2, '0');
|
|
1180
|
+
const s = d.getSeconds().toString().padStart(2, '0');
|
|
1181
|
+
return `${h}:${m}:${s}`;
|
|
1182
|
+
}
|
|
1183
|
+
catch {
|
|
1184
|
+
return '--:--';
|
|
1185
|
+
}
|
|
1186
|
+
};
|
|
1187
|
+
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}`;
|
|
1188
|
+
separator = `{gray-fg}${'─'.repeat(colNum)}┼${'─'.repeat(colTime)}┼${'─'.repeat(colM)}┼${'─'.repeat(colCtx)}┼${'─'.repeat(rdW)}┼${'─'.repeat(wrW)}┼${'─'.repeat(outW)}┼${'─'.repeat(colCost)}{/gray-fg}`;
|
|
1123
1189
|
rows = turns.map(t => {
|
|
1124
1190
|
const mTag = modelTag(t.routedModel);
|
|
1125
1191
|
const mColor = t.routedModel.includes('haiku') ? 'green' : t.routedModel.includes('sonnet') ? 'blue' : 'magenta';
|
|
1126
1192
|
const costFlag = t.cost > 1.0 ? '{red-fg}' : t.cost > 0.5 ? '{yellow-fg}' : '{white-fg}';
|
|
1127
1193
|
const costEnd = t.cost > 1.0 ? '{/red-fg}' : t.cost > 0.5 ? '{/yellow-fg}' : '{/white-fg}';
|
|
1128
1194
|
return (pad(String(t.turn), colNum) + div +
|
|
1195
|
+
`{gray-fg}${pad(fmtTime(t.timestamp), colTime)}{/gray-fg}` + div +
|
|
1129
1196
|
`{${mColor}-fg}${pad(mTag, colM)}{/${mColor}-fg}` + div +
|
|
1130
1197
|
pad(`${t.contextPct.toFixed(0)}%`, colCtx) + div +
|
|
1131
1198
|
`{green-fg}${rpad(fmtK(t.cacheRead), rdW)}{/green-fg}` + div +
|
|
@@ -1145,13 +1212,20 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1145
1212
|
dlog(`Table: ${err.message}`);
|
|
1146
1213
|
}
|
|
1147
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;
|
|
1148
1222
|
try {
|
|
1149
|
-
const totalTokensM = ((
|
|
1150
|
-
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);
|
|
1151
1225
|
// Model routing breakdown (uses routedModel for actual model counts)
|
|
1152
|
-
const opusCount =
|
|
1153
|
-
const sonnetCount =
|
|
1154
|
-
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;
|
|
1155
1229
|
const routingParts = [`{magenta-fg}O{/magenta-fg}:${opusCount}`];
|
|
1156
1230
|
if (sonnetCount > 0)
|
|
1157
1231
|
routingParts.push(`{blue-fg}S{/blue-fg}:${sonnetCount}`);
|
|
@@ -1160,25 +1234,30 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1160
1234
|
const savingsStr = totalSavings > 0
|
|
1161
1235
|
? ` {green-fg}saved $${totalSavings.toFixed(2)}{/green-fg}`
|
|
1162
1236
|
: '';
|
|
1237
|
+
const timerStr = sessionStartMs
|
|
1238
|
+
? `{cyan-fg}${formatElapsed(sessionStartMs)}{/cyan-fg} `
|
|
1239
|
+
: '';
|
|
1163
1240
|
footerBox.setLabel(` ${sessionName} `);
|
|
1164
|
-
footerBox.setContent(` {
|
|
1241
|
+
footerBox.setContent(` ${timerStr}` +
|
|
1242
|
+
`{green-fg}$${d.totalCost.toFixed(2)}{/green-fg}` +
|
|
1165
1243
|
` ${totalTokensM}M` +
|
|
1166
1244
|
` ${routingStr}` +
|
|
1167
|
-
` R[A:${
|
|
1245
|
+
` R[A:${d.replayAppliedCount} SZ:${d.replaySkippedSizeCount} ST:${d.replaySkipStoreCount}]` +
|
|
1168
1246
|
savingsStr +
|
|
1169
1247
|
(inTmux
|
|
1170
|
-
? ` {gray-fg}Ctrl+
|
|
1248
|
+
? ` {gray-fg}Ctrl+Q quit{/gray-fg}`
|
|
1171
1249
|
: ` {gray-fg}? help q quit r refresh{/gray-fg}`));
|
|
1172
1250
|
}
|
|
1173
1251
|
catch (err) {
|
|
1174
1252
|
dlog(`Footer: ${err.message}`);
|
|
1175
1253
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1254
|
+
if (!skipRender)
|
|
1255
|
+
try {
|
|
1256
|
+
screen.render();
|
|
1257
|
+
}
|
|
1258
|
+
catch (err) {
|
|
1259
|
+
dlog(`Render: ${err.message}`);
|
|
1260
|
+
}
|
|
1182
1261
|
}
|
|
1183
1262
|
// ── Usage window update — calls Anthropic's OAuth usage API ──
|
|
1184
1263
|
/**
|
|
@@ -1220,36 +1299,72 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1220
1299
|
return null;
|
|
1221
1300
|
}
|
|
1222
1301
|
}
|
|
1223
|
-
|
|
1302
|
+
// Cache last fetched usage data so the countdown can tick every second
|
|
1303
|
+
let cachedUsage = null;
|
|
1304
|
+
// Fetch fresh usage data from API (called every 15s)
|
|
1305
|
+
async function fetchAndCacheUsage() {
|
|
1306
|
+
try {
|
|
1307
|
+
cachedUsage = await fetchAnthropicUsage();
|
|
1308
|
+
}
|
|
1309
|
+
catch (err) {
|
|
1310
|
+
dlog(`Window fetch: ${err.message}`);
|
|
1311
|
+
}
|
|
1312
|
+
renderWindowBox();
|
|
1313
|
+
}
|
|
1314
|
+
// Render countdown from cached data (called every 1s)
|
|
1315
|
+
function renderWindowBox(skipRender = false) {
|
|
1224
1316
|
try {
|
|
1225
|
-
const usage =
|
|
1317
|
+
const usage = cachedUsage;
|
|
1226
1318
|
let line1 = ' {gray-fg}No usage data{/gray-fg}';
|
|
1227
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);
|
|
1228
1329
|
// ── 5h Window (from Anthropic OAuth API) ──
|
|
1229
1330
|
if (usage?.five_hour) {
|
|
1230
1331
|
const pct = usage.five_hour.utilization;
|
|
1231
1332
|
const resetAt = new Date(usage.five_hour.resets_at).getTime();
|
|
1232
1333
|
const remainMs = Math.max(0, resetAt - Date.now());
|
|
1233
|
-
const
|
|
1234
|
-
const rH = Math.floor(
|
|
1235
|
-
const rM =
|
|
1334
|
+
const remainSec = Math.round(remainMs / 1000);
|
|
1335
|
+
const rH = Math.floor(remainSec / 3600);
|
|
1336
|
+
const rM = Math.floor((remainSec % 3600) / 60);
|
|
1337
|
+
const rS = remainSec % 60;
|
|
1236
1338
|
const pctColor = pct < 50 ? 'green' : pct < 80 ? 'yellow' : 'red';
|
|
1237
|
-
const
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1339
|
+
const countdown = `${rH}h${rM.toString().padStart(2, '0')}m${rS.toString().padStart(2, '0')}s`;
|
|
1340
|
+
const pctStr = `${pct.toFixed(0)}% used`;
|
|
1341
|
+
const resetDate = new Date(resetAt);
|
|
1342
|
+
const resetTime = resetDate.toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit' });
|
|
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}`;
|
|
1241
1347
|
}
|
|
1242
1348
|
// ── Weekly (from Anthropic OAuth API) ──
|
|
1243
1349
|
if (usage?.seven_day) {
|
|
1244
1350
|
const pct = usage.seven_day.utilization;
|
|
1245
1351
|
const resetAt = new Date(usage.seven_day.resets_at);
|
|
1246
|
-
const
|
|
1247
|
-
const
|
|
1248
|
-
const
|
|
1352
|
+
const remainMs = Math.max(0, resetAt.getTime() - Date.now());
|
|
1353
|
+
const remainSec = Math.round(remainMs / 1000);
|
|
1354
|
+
const rD = Math.floor(remainSec / 86400);
|
|
1355
|
+
const rH = Math.floor((remainSec % 86400) / 3600);
|
|
1356
|
+
const rM = Math.floor((remainSec % 3600) / 60);
|
|
1357
|
+
const rS = remainSec % 60;
|
|
1249
1358
|
const pctColor = pct < 50 ? 'green' : pct < 80 ? 'yellow' : 'red';
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1359
|
+
const countdown = rD > 0
|
|
1360
|
+
? `${rD}d${rH}h${rM.toString().padStart(2, '0')}m`
|
|
1361
|
+
: `${rH}h${rM.toString().padStart(2, '0')}m${rS.toString().padStart(2, '0')}s`;
|
|
1362
|
+
const pctStr = `${pct.toFixed(0)}% used`;
|
|
1363
|
+
const resetTime = resetAt.toLocaleString('en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit' });
|
|
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}`;
|
|
1253
1368
|
}
|
|
1254
1369
|
windowBox.setContent(line1 + (line2 ? '\n' + line2 : ''));
|
|
1255
1370
|
}
|
|
@@ -1257,11 +1372,14 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1257
1372
|
dlog(`Window: ${err.message}`);
|
|
1258
1373
|
windowBox.setContent(` {gray-fg}Usage data unavailable{/gray-fg}`);
|
|
1259
1374
|
}
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1375
|
+
if (!skipRender)
|
|
1376
|
+
try {
|
|
1377
|
+
screen.render();
|
|
1378
|
+
}
|
|
1379
|
+
catch { }
|
|
1264
1380
|
}
|
|
1381
|
+
// Legacy wrapper for backward compat
|
|
1382
|
+
async function updateWindowBox() { await fetchAndCacheUsage(); }
|
|
1265
1383
|
// ── Handle terminal resize ──
|
|
1266
1384
|
// Recalculate all widget positions from new screen.height
|
|
1267
1385
|
screen.on('resize', () => {
|
|
@@ -1289,9 +1407,10 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1289
1407
|
// KEYBOARD SHORTCUTS - Only capture when dashboard pane has focus
|
|
1290
1408
|
// In tmux split mode, this prevents capturing keys from Claude Code pane
|
|
1291
1409
|
// ══════════════════════════════════════════════════════════════════════════
|
|
1292
|
-
screen.key(['C-c'], () => {
|
|
1410
|
+
screen.key(['C-c', 'C-q'], () => {
|
|
1293
1411
|
clearInterval(pollInterval);
|
|
1294
1412
|
clearInterval(windowPollInterval);
|
|
1413
|
+
clearInterval(tickInterval);
|
|
1295
1414
|
clearInterval(headerAnimInterval);
|
|
1296
1415
|
clearInterval(fortuneInterval);
|
|
1297
1416
|
screen.destroy();
|
|
@@ -1301,6 +1420,7 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1301
1420
|
screen.key(['q'], () => {
|
|
1302
1421
|
clearInterval(pollInterval);
|
|
1303
1422
|
clearInterval(windowPollInterval);
|
|
1423
|
+
clearInterval(tickInterval);
|
|
1304
1424
|
clearInterval(headerAnimInterval);
|
|
1305
1425
|
clearInterval(fortuneInterval);
|
|
1306
1426
|
screen.destroy();
|
|
@@ -1363,7 +1483,7 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1363
1483
|
'\n' +
|
|
1364
1484
|
'{bold}Controls{/bold}\n' +
|
|
1365
1485
|
' r Refresh now\n' +
|
|
1366
|
-
' q/Ctrl+
|
|
1486
|
+
' q/Ctrl+Q Quit\n' +
|
|
1367
1487
|
'\n' +
|
|
1368
1488
|
'{gray-fg}Press any key to close{/gray-fg}'),
|
|
1369
1489
|
tags: true,
|
|
@@ -1408,7 +1528,17 @@ async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs,
|
|
|
1408
1528
|
fortuneText = activeFortunes[fortuneIdx];
|
|
1409
1529
|
renderHeader();
|
|
1410
1530
|
}, 30000);
|
|
1411
|
-
const windowPollInterval = setInterval(
|
|
1531
|
+
const windowPollInterval = setInterval(fetchAndCacheUsage, 15000); // fetch fresh data every 15s
|
|
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);
|
|
1412
1542
|
}
|
|
1413
1543
|
// ── Helpers ──
|
|
1414
1544
|
function fmtK(n) {
|
|
@@ -1419,6 +1549,14 @@ function fmtK(n) {
|
|
|
1419
1549
|
return String(n);
|
|
1420
1550
|
}
|
|
1421
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
|
+
}
|
|
1422
1560
|
// ── Session picker ──
|
|
1423
1561
|
async function pickSession() {
|
|
1424
1562
|
const sessions = await (0, usage_parser_js_1.listEkkosSessions)(20);
|
|
@@ -1476,9 +1614,8 @@ exports.dashboardCommand = new commander_1.Command('dashboard')
|
|
|
1476
1614
|
}
|
|
1477
1615
|
const jsonlPath = resolveJsonlPath(sessionName);
|
|
1478
1616
|
if (!jsonlPath) {
|
|
1479
|
-
|
|
1480
|
-
console.log(chalk_1.default.gray(
|
|
1481
|
-
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}"...`));
|
|
1482
1619
|
}
|
|
1483
|
-
await launchDashboard(sessionName, jsonlPath, refreshMs);
|
|
1620
|
+
await launchDashboard(sessionName, jsonlPath || null, refreshMs, null, Date.now());
|
|
1484
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:'));
|