@ekkos/cli 1.2.15 → 1.2.16
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 +69 -85
- package/dist/commands/run.js +9 -3
- package/package.json +1 -1
|
@@ -565,21 +565,24 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
|
|
|
565
565
|
// 3. Claude Code pane remains fully independent
|
|
566
566
|
// 4. Text selection works across panes without interference
|
|
567
567
|
// ══════════════════════════════════════════════════════════════════════════
|
|
568
|
-
// Open /dev/tty for blessed to use (gives it direct terminal access)
|
|
568
|
+
// Open /dev/tty for blessed to use (gives it direct terminal access).
|
|
569
|
+
// On macOS/Linux in tmux, this isolates the dashboard from stdout piping.
|
|
570
|
+
// On Windows (no /dev/tty), stdin/stdout is correct — blessed handles the
|
|
571
|
+
// alternate screen buffer natively for clean rendering.
|
|
569
572
|
const fs = require('fs');
|
|
570
573
|
let ttyInput = process.stdin;
|
|
571
574
|
let ttyOutput = process.stdout;
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
575
|
+
if (inTmux) {
|
|
576
|
+
try {
|
|
577
|
+
const ttyFd = fs.openSync('/dev/tty', 'r+');
|
|
578
|
+
ttyInput = new (require('tty').ReadStream)(ttyFd);
|
|
579
|
+
ttyOutput = new (require('tty').WriteStream)(ttyFd);
|
|
580
|
+
}
|
|
581
|
+
catch (e) {
|
|
582
|
+
// Fallback: if /dev/tty unavailable, use stdin/stdout
|
|
583
|
+
ttyInput = process.stdin;
|
|
584
|
+
ttyOutput = process.stdout;
|
|
585
|
+
}
|
|
583
586
|
}
|
|
584
587
|
const screen = blessed.screen({
|
|
585
588
|
smartCSR: true,
|
|
@@ -596,16 +599,17 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
|
|
|
596
599
|
resizeTimeout: 300, // Debounce resize events
|
|
597
600
|
});
|
|
598
601
|
// ══════════════════════════════════════════════════════════════════════════
|
|
599
|
-
// DISABLE TERMINAL CONTROL SEQUENCE HIJACKING
|
|
600
|
-
//
|
|
601
|
-
//
|
|
602
|
-
//
|
|
602
|
+
// DISABLE TERMINAL CONTROL SEQUENCE HIJACKING (TMUX ONLY)
|
|
603
|
+
// When running in a tmux split pane alongside Claude Code, prevent blessed
|
|
604
|
+
// from sending control sequences that would interfere with the other pane.
|
|
605
|
+
// In standalone mode (including Windows Terminal split), blessed needs the
|
|
606
|
+
// alternate buffer and raw mode for clean rendering without artifacts.
|
|
603
607
|
// ══════════════════════════════════════════════════════════════════════════
|
|
604
|
-
if (screen.program) {
|
|
605
|
-
// Override alternateBuffer to do nothing
|
|
608
|
+
if (inTmux && screen.program) {
|
|
609
|
+
// Override alternateBuffer to do nothing (tmux pane isolation)
|
|
606
610
|
screen.program.alternateBuffer = () => { };
|
|
607
611
|
screen.program.normalBuffer = () => { };
|
|
608
|
-
// Don't enter raw mode — let the OS handle it
|
|
612
|
+
// Don't enter raw mode — let the OS handle it (tmux pane isolation)
|
|
609
613
|
if (screen.program.setRawMode) {
|
|
610
614
|
screen.program.setRawMode = (enabled) => {
|
|
611
615
|
// Silently ignore raw mode requests
|
|
@@ -1077,72 +1081,50 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
|
|
|
1077
1081
|
let header = '';
|
|
1078
1082
|
let separator = '';
|
|
1079
1083
|
let rows = [];
|
|
1080
|
-
//
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
else if (w >= 30) {
|
|
1110
|
-
// Compact mode: drop cache split columns to preserve readable headers.
|
|
1111
|
-
const colNum = 4;
|
|
1112
|
-
const colM = 6;
|
|
1113
|
-
const colCtx = 6;
|
|
1114
|
-
const colCost = 6;
|
|
1115
|
-
const nDividers = 4;
|
|
1116
|
-
const outW = Math.max(4, w - (colNum + colM + colCtx + colCost + nDividers));
|
|
1117
|
-
header = `${pad('Turn', colNum)}${div}${rpad('Model', colM)}${div}${rpad('Context', colCtx)}${div}${rpad('Output', outW)}${div}${rpad('Cost', colCost)}`;
|
|
1118
|
-
separator = `${'─'.repeat(colNum)}┼${'─'.repeat(colM)}┼${'─'.repeat(colCtx)}┼${'─'.repeat(outW)}┼${'─'.repeat(colCost)}`;
|
|
1119
|
-
rows = turns.map(t => {
|
|
1120
|
-
const mTag = modelTag(t.routedModel);
|
|
1121
|
-
const mColor = t.routedModel.includes('haiku') ? 'green' : t.routedModel.includes('sonnet') ? 'blue' : 'magenta';
|
|
1122
|
-
const costFlag = t.cost > 1.0 ? '{red-fg}' : t.cost > 0.5 ? '{yellow-fg}' : '{white-fg}';
|
|
1123
|
-
const costEnd = t.cost > 1.0 ? '{/red-fg}' : t.cost > 0.5 ? '{/yellow-fg}' : '{/white-fg}';
|
|
1124
|
-
return (pad(String(t.turn), colNum) + div +
|
|
1125
|
-
`{${mColor}-fg}${cpad(mTag, colM)}{/${mColor}-fg}` + div +
|
|
1126
|
-
rpad(`${t.contextPct.toFixed(0)}%`, colCtx) + div +
|
|
1127
|
-
`{cyan-fg}${rpad(fmtK(t.output), outW)}{/cyan-fg}` + div +
|
|
1128
|
-
costFlag + rpad(`$${t.cost.toFixed(2)}`, colCost) + costEnd);
|
|
1129
|
-
});
|
|
1130
|
-
}
|
|
1131
|
-
else {
|
|
1132
|
-
// Minimal mode: guaranteed no-wrap fallback.
|
|
1133
|
-
const colNum = 4;
|
|
1134
|
-
const colCtx = 6;
|
|
1135
|
-
const colCost = 6;
|
|
1136
|
-
header = `${pad('Turn', colNum)}${div}${rpad('Context', colCtx)}${div}${rpad('Cost', colCost)}`;
|
|
1137
|
-
separator = `${'─'.repeat(colNum)}┼${'─'.repeat(colCtx)}┼${'─'.repeat(colCost)}`;
|
|
1138
|
-
rows = turns.map(t => {
|
|
1139
|
-
const costFlag = t.cost > 1.0 ? '{red-fg}' : t.cost > 0.5 ? '{yellow-fg}' : '{white-fg}';
|
|
1140
|
-
const costEnd = t.cost > 1.0 ? '{/red-fg}' : t.cost > 0.5 ? '{/yellow-fg}' : '{/white-fg}';
|
|
1141
|
-
return (pad(String(t.turn), colNum) + div +
|
|
1142
|
-
rpad(`${t.contextPct.toFixed(0)}%`, colCtx) + div +
|
|
1143
|
-
costFlag + rpad(`$${t.cost.toFixed(2)}`, colCost) + costEnd);
|
|
1144
|
-
});
|
|
1084
|
+
// Always show all 7 columns: Turn, Model, Context, Cache Rd, Cache Wr, Output, Cost
|
|
1085
|
+
// Shrink flex columns to fit narrow panes instead of dropping them.
|
|
1086
|
+
const colNum = 4;
|
|
1087
|
+
const colM = 7;
|
|
1088
|
+
const colCtx = 7;
|
|
1089
|
+
const colCost = 8;
|
|
1090
|
+
const nDividers = 6;
|
|
1091
|
+
const fixedW = colNum + colM + colCtx + colCost;
|
|
1092
|
+
const flexTotal = Math.max(0, w - fixedW - nDividers);
|
|
1093
|
+
let rdW = Math.max(5, Math.floor(flexTotal * 0.35));
|
|
1094
|
+
let wrW = Math.max(5, Math.floor(flexTotal * 0.30));
|
|
1095
|
+
let outW = Math.max(6, flexTotal - rdW - wrW);
|
|
1096
|
+
// Trim flex columns if they overflow available width
|
|
1097
|
+
let totalW = fixedW + nDividers + rdW + wrW + outW;
|
|
1098
|
+
if (totalW > w) {
|
|
1099
|
+
let overflow = totalW - w;
|
|
1100
|
+
const trimOut = Math.min(overflow, Math.max(0, outW - 4));
|
|
1101
|
+
outW -= trimOut;
|
|
1102
|
+
overflow -= trimOut;
|
|
1103
|
+
if (overflow > 0) {
|
|
1104
|
+
const trimWr = Math.min(overflow, Math.max(0, wrW - 4));
|
|
1105
|
+
wrW -= trimWr;
|
|
1106
|
+
overflow -= trimWr;
|
|
1107
|
+
}
|
|
1108
|
+
if (overflow > 0) {
|
|
1109
|
+
const trimRd = Math.min(overflow, Math.max(0, rdW - 4));
|
|
1110
|
+
rdW -= trimRd;
|
|
1111
|
+
}
|
|
1145
1112
|
}
|
|
1113
|
+
header = `{bold}${pad('Turn', colNum)}${div}${pad('Model', colM)}${div}${pad('Context', colCtx)}${div}${rpad('Cache Rd', rdW)}${div}${rpad('Cache Wr', wrW)}${div}${rpad('Output', outW)}${div}${rpad('Cost', colCost)}{/bold}`;
|
|
1114
|
+
separator = `{gray-fg}${'─'.repeat(colNum)}┼${'─'.repeat(colM)}┼${'─'.repeat(colCtx)}┼${'─'.repeat(rdW)}┼${'─'.repeat(wrW)}┼${'─'.repeat(outW)}┼${'─'.repeat(colCost)}{/gray-fg}`;
|
|
1115
|
+
rows = turns.map(t => {
|
|
1116
|
+
const mTag = modelTag(t.routedModel);
|
|
1117
|
+
const mColor = t.routedModel.includes('haiku') ? 'green' : t.routedModel.includes('sonnet') ? 'blue' : 'magenta';
|
|
1118
|
+
const costFlag = t.cost > 1.0 ? '{red-fg}' : t.cost > 0.5 ? '{yellow-fg}' : '{white-fg}';
|
|
1119
|
+
const costEnd = t.cost > 1.0 ? '{/red-fg}' : t.cost > 0.5 ? '{/yellow-fg}' : '{/white-fg}';
|
|
1120
|
+
return (pad(String(t.turn), colNum) + div +
|
|
1121
|
+
`{${mColor}-fg}${pad(mTag, colM)}{/${mColor}-fg}` + div +
|
|
1122
|
+
pad(`${t.contextPct.toFixed(0)}%`, colCtx) + div +
|
|
1123
|
+
`{green-fg}${rpad(fmtK(t.cacheRead), rdW)}{/green-fg}` + div +
|
|
1124
|
+
`{yellow-fg}${rpad(fmtK(t.cacheCreate), wrW)}{/yellow-fg}` + div +
|
|
1125
|
+
`{cyan-fg}${rpad(fmtK(t.output), outW)}{/cyan-fg}` + div +
|
|
1126
|
+
costFlag + rpad(`$${t.cost.toFixed(2)}`, colCost) + costEnd);
|
|
1127
|
+
});
|
|
1146
1128
|
const lines = [header, separator, ...rows];
|
|
1147
1129
|
turnBox.setContent(lines.join('\n'));
|
|
1148
1130
|
// Restore scroll position AFTER content update
|
|
@@ -1404,11 +1386,13 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
|
|
|
1404
1386
|
// Delay first ccusage call — let blessed render first, then load heavy data
|
|
1405
1387
|
setTimeout(() => updateWindowBox(), 2000);
|
|
1406
1388
|
const pollInterval = setInterval(updateDashboard, refreshMs);
|
|
1389
|
+
// Animation interval — slower on Windows to reduce blessed redraw flicker
|
|
1390
|
+
const animMs = process.platform === 'win32' ? 1000 : 500;
|
|
1407
1391
|
const headerAnimInterval = setInterval(() => {
|
|
1408
1392
|
// Keep advancing across the full session label; wrap at a large value.
|
|
1409
1393
|
waveOffset = (waveOffset + 1) % 1000000;
|
|
1410
1394
|
renderHeader();
|
|
1411
|
-
},
|
|
1395
|
+
}, animMs);
|
|
1412
1396
|
const fortuneInterval = setInterval(() => {
|
|
1413
1397
|
if (activeFortunes.length === 0)
|
|
1414
1398
|
return;
|
package/dist/commands/run.js
CHANGED
|
@@ -912,10 +912,11 @@ function launchWithWindowsTerminal(options) {
|
|
|
912
912
|
if (options.noProxy)
|
|
913
913
|
runArgs.push('--skip-proxy');
|
|
914
914
|
// Write a temp batch file to avoid all quoting issues
|
|
915
|
+
// wt.exe doesn't resolve PATH for npm global bins — must use `cmd /c`
|
|
915
916
|
const batPath = path.join(os.tmpdir(), `ekkos-wt-${launchTime}.cmd`);
|
|
916
917
|
const batContent = [
|
|
917
918
|
'@echo off',
|
|
918
|
-
`wt --title "Claude Code" -d "${cwd}" ekkos ${runArgs.join(' ')} ; split-pane -V -s 0.4 --title "ekkOS Dashboard" -d "${cwd}" ekkos dashboard --wait-for-new --refresh 2000`,
|
|
919
|
+
`wt --title "Claude Code" -d "${cwd}" cmd /c ekkos ${runArgs.join(' ')} ; split-pane -V -s 0.4 --title "ekkOS Dashboard" -d "${cwd}" cmd /c ekkos dashboard --wait-for-new --refresh 2000`,
|
|
919
920
|
].join('\r\n');
|
|
920
921
|
try {
|
|
921
922
|
fs.writeFileSync(batPath, batContent);
|
|
@@ -1083,7 +1084,10 @@ async function run(options) {
|
|
|
1083
1084
|
}
|
|
1084
1085
|
}
|
|
1085
1086
|
// Check PTY availability early (deterministic, no async race)
|
|
1086
|
-
|
|
1087
|
+
// Windows: SKIP node-pty entirely. ConPTY corrupts Ink's cursor management sequences
|
|
1088
|
+
// (hide/show/move), causing ghost block cursor artifacts. Use stdio:'inherit' spawn
|
|
1089
|
+
// instead so Ink gets direct terminal access. Hooks still handle session tracking.
|
|
1090
|
+
const loadedPty = isWindows ? null : await loadPty();
|
|
1087
1091
|
const usePty = loadedPty !== null;
|
|
1088
1092
|
// ══════════════════════════════════════════════════════════════════════════
|
|
1089
1093
|
// CONCURRENT STARTUP: Spawn Claude while animation runs
|
|
@@ -1319,8 +1323,10 @@ async function run(options) {
|
|
|
1319
1323
|
console.log('');
|
|
1320
1324
|
}
|
|
1321
1325
|
else {
|
|
1326
|
+
// Static banner for Windows / remote / no-splash — no cursor manipulation
|
|
1327
|
+
console.log('');
|
|
1328
|
+
console.log(chalk_1.default.hex('#FF6B35').bold(' ekkOS_Pulse') + chalk_1.default.gray(' — Context is finite. Intelligence isn\'t.'));
|
|
1322
1329
|
console.log('');
|
|
1323
|
-
console.log(chalk_1.default.cyan(' ekkOS remote session ready'));
|
|
1324
1330
|
if (bypass) {
|
|
1325
1331
|
console.log(chalk_1.default.yellow(' ⚡ Bypass permissions mode enabled'));
|
|
1326
1332
|
}
|