@ekkos/cli 1.0.27 → 1.0.29
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 +180 -65
- package/dist/commands/run.js +1 -3
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/templates/ekkos-manifest.json +7 -7
|
@@ -627,6 +627,58 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
|
|
|
627
627
|
// footer: 3 rows (totals + routing + keybindings)
|
|
628
628
|
const LOGO_CHARS = ['e', 'k', 'k', 'O', 'S', '_'];
|
|
629
629
|
const WAVE_COLORS = ['cyan', 'blue', 'magenta', 'yellow', 'green', 'white'];
|
|
630
|
+
const GOOD_LUCK_FORTUNES = [
|
|
631
|
+
'Memory is just RAM with commitment issues.',
|
|
632
|
+
'Your context window is not a trash can.',
|
|
633
|
+
'Ship it. Memory will remember the rest.',
|
|
634
|
+
'Segfault: a love letter from the past.',
|
|
635
|
+
'undefined is not a personality.',
|
|
636
|
+
'The AI forgot. ekkOS did not.',
|
|
637
|
+
'Cold start is just a fancy word for amnesia.',
|
|
638
|
+
'Cache hit. Dopamine unlocked.',
|
|
639
|
+
'94% hit rate. The other 6% are learning.',
|
|
640
|
+
'Fewer tokens, bigger thoughts.',
|
|
641
|
+
'Your last session called. It left context.',
|
|
642
|
+
'NaN is just a number in denial.',
|
|
643
|
+
'Memory leak: when code has attachment issues.',
|
|
644
|
+
'The bug was in the chair the whole time.',
|
|
645
|
+
'Rebase early, rebase often, rebase bravely.',
|
|
646
|
+
'It works on my machine. Ship the machine.',
|
|
647
|
+
'Latency is just suspense with worse UX.',
|
|
648
|
+
'A good prompt is worth 1000 retries.',
|
|
649
|
+
'The model hallucinates. Your memory does not.',
|
|
650
|
+
'Every great system was once a bad YAML file.',
|
|
651
|
+
'async/await: optimism compiled.',
|
|
652
|
+
'Your future self will read this code.',
|
|
653
|
+
'The diff is the truth.',
|
|
654
|
+
'Type safety is love made explicit.',
|
|
655
|
+
'Tokens are money. Spend them wisely.',
|
|
656
|
+
'Context is king. Memory is the kingdom.',
|
|
657
|
+
'The LLM forgot. You did not have to.',
|
|
658
|
+
'Green CI: the only morning green flag.',
|
|
659
|
+
'Throwaway sessions are so 2023.',
|
|
660
|
+
'Your AI just learned from last time.',
|
|
661
|
+
'One prompt to rule them all. One memory to find them.',
|
|
662
|
+
'Always learning. Getting faster. Still caffeinated.',
|
|
663
|
+
'The cold start problem is someone else\'s problem now.',
|
|
664
|
+
'Trust the cache. Fear the cache miss.',
|
|
665
|
+
'Technical debt: code with feelings.',
|
|
666
|
+
'The logs never lie. Developers sometimes do.',
|
|
667
|
+
'404: motivation not found. Memory restored.',
|
|
668
|
+
'Embeddings: vibes but make them math.',
|
|
669
|
+
'null is just the universe saying try again.',
|
|
670
|
+
'Ship small, remember everything.',
|
|
671
|
+
'Your session ended. Your memory did not.',
|
|
672
|
+
'Hallucination-free since last deployment.',
|
|
673
|
+
'ekkOS remembers so you do not have to.',
|
|
674
|
+
'The best refactor is the one that ships.',
|
|
675
|
+
'Rate limited? The system is just thinking.',
|
|
676
|
+
'Context window: full. ekkOS: still going.',
|
|
677
|
+
'Good memory compounds like good interest.',
|
|
678
|
+
'The AI got smarter. You did not change a line.',
|
|
679
|
+
'Compaction is optional. Excellence is not.',
|
|
680
|
+
'Build like the memory persists. It does.',
|
|
681
|
+
];
|
|
630
682
|
const W = '100%';
|
|
631
683
|
const HEADER_H = 3;
|
|
632
684
|
const CONTEXT_H = 5;
|
|
@@ -653,6 +705,8 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
|
|
|
653
705
|
let lastData = null;
|
|
654
706
|
let lastChartSeries = null;
|
|
655
707
|
let lastScrollPerc = 0; // Preserve scroll position across updates
|
|
708
|
+
let fortuneIdx = Math.floor(Math.random() * GOOD_LUCK_FORTUNES.length);
|
|
709
|
+
let fortuneText = GOOD_LUCK_FORTUNES[fortuneIdx];
|
|
656
710
|
// Header: session stats (3 lines)
|
|
657
711
|
const headerBox = blessed.box({
|
|
658
712
|
top: layout.header.top, left: 0, width: W, height: layout.header.height,
|
|
@@ -662,6 +716,18 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
|
|
|
662
716
|
border: { type: 'line' },
|
|
663
717
|
label: ' ekkOS_ ',
|
|
664
718
|
});
|
|
719
|
+
// Explicit header message row: with HEADER_H=3 and a border, this is the
|
|
720
|
+
// single inner content row (visual line 2 of the widget).
|
|
721
|
+
const headerMessageRow = blessed.box({
|
|
722
|
+
parent: headerBox,
|
|
723
|
+
top: 0,
|
|
724
|
+
left: 0,
|
|
725
|
+
width: '100%-2',
|
|
726
|
+
height: 1,
|
|
727
|
+
tags: false,
|
|
728
|
+
style: { fg: 'green', bold: true },
|
|
729
|
+
content: '',
|
|
730
|
+
});
|
|
665
731
|
// Context: progress bar + costs + cache (5 lines)
|
|
666
732
|
const contextBox = blessed.box({
|
|
667
733
|
top: layout.context.top, left: 0, width: W, height: layout.context.height,
|
|
@@ -821,55 +887,81 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
|
|
|
821
887
|
catch { }
|
|
822
888
|
applyLayout();
|
|
823
889
|
}
|
|
824
|
-
// ──
|
|
890
|
+
// ── Header render (animated logo in border label + compact stats content) ──
|
|
825
891
|
let waveOffset = 0;
|
|
826
|
-
|
|
827
|
-
|
|
892
|
+
function buildAnimatedLogo() {
|
|
893
|
+
const raw = LOGO_CHARS.join('');
|
|
894
|
+
const tagged = LOGO_CHARS.map((ch, i) => {
|
|
895
|
+
const colorIdx = Math.abs(i + waveOffset) % WAVE_COLORS.length;
|
|
896
|
+
const color = WAVE_COLORS[colorIdx];
|
|
897
|
+
return `{${color}-fg}${ch}{/${color}-fg}`;
|
|
898
|
+
}).join('');
|
|
899
|
+
return { raw, tagged };
|
|
900
|
+
}
|
|
901
|
+
function buildShinySessionName(name) {
|
|
902
|
+
if (!name)
|
|
903
|
+
return '';
|
|
904
|
+
const chars = name.split('');
|
|
905
|
+
const shineIdx = Math.abs(waveOffset) % chars.length;
|
|
906
|
+
return chars.map((ch, i) => {
|
|
907
|
+
if (i === shineIdx)
|
|
908
|
+
return `{white-fg}{bold}${ch}{/bold}{/white-fg}`;
|
|
909
|
+
if (i === (shineIdx + chars.length - 1) % chars.length || i === (shineIdx + 1) % chars.length) {
|
|
910
|
+
return `{cyan-fg}${ch}{/cyan-fg}`;
|
|
911
|
+
}
|
|
912
|
+
return `{magenta-fg}${ch}{/magenta-fg}`;
|
|
913
|
+
}).join('');
|
|
914
|
+
}
|
|
915
|
+
function renderHeader() {
|
|
828
916
|
try {
|
|
829
917
|
ensureLayoutSynced();
|
|
830
|
-
// Color wave in the border label
|
|
831
|
-
const coloredChars = LOGO_CHARS.map((ch, i) => {
|
|
832
|
-
const colorIdx = (i + waveOffset) % WAVE_COLORS.length;
|
|
833
|
-
return `{${WAVE_COLORS[colorIdx]}-fg}${ch}{/${WAVE_COLORS[colorIdx]}-fg}`;
|
|
834
|
-
});
|
|
835
|
-
// Logo left + session name right in border label
|
|
836
|
-
const logoStr = ` ${coloredChars.join('')} `;
|
|
837
|
-
// Session name with traveling shimmer across ALL characters
|
|
838
|
-
const SESSION_GLOW = ['white', 'cyan', 'magenta'];
|
|
839
|
-
const glowPos = (waveOffset * 2) % sessionName.length; // 2x speed for snappier travel
|
|
840
|
-
const nameChars = sessionName.split('').map((ch, i) => {
|
|
841
|
-
const dist = Math.min(Math.abs(i - glowPos), sessionName.length - Math.abs(i - glowPos)); // wrapping distance
|
|
842
|
-
if (dist === 0)
|
|
843
|
-
return `{${SESSION_GLOW[0]}-fg}${ch}{/${SESSION_GLOW[0]}-fg}`;
|
|
844
|
-
if (dist <= 2)
|
|
845
|
-
return `{${SESSION_GLOW[1]}-fg}${ch}{/${SESSION_GLOW[1]}-fg}`;
|
|
846
|
-
return `{${SESSION_GLOW[2]}-fg}${ch}{/${SESSION_GLOW[2]}-fg}`;
|
|
847
|
-
});
|
|
848
|
-
const sessionStr = ` ${nameChars.join('')} `;
|
|
849
|
-
const rawLogoLen = LOGO_CHARS.length + 2; // " ekkOS_ " = 8
|
|
850
|
-
const rawSessionLen = sessionName.length + 3; // " name " + 1 extra space before ┐
|
|
851
918
|
const boxW = Math.max(10, headerBox.width - 2); // minus border chars
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
const
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
919
|
+
const logoPlain = ` ${LOGO_CHARS.join('')} `;
|
|
920
|
+
const animatedLogo = buildAnimatedLogo();
|
|
921
|
+
const logoTagged = ` ${animatedLogo.tagged} `;
|
|
922
|
+
const maxSessionLen = Math.max(6, boxW - logoPlain.length - 4);
|
|
923
|
+
const sessionLabel = sessionName.length > maxSessionLen
|
|
924
|
+
? `${sessionName.slice(0, Math.max(0, maxSessionLen - 1))}…`
|
|
925
|
+
: sessionName;
|
|
926
|
+
const sessionPlain = ` ${sessionLabel} `;
|
|
927
|
+
const sessionTagged = ` ${buildShinySessionName(sessionLabel)} `;
|
|
928
|
+
const rightGap = ' ';
|
|
929
|
+
const pad = Math.max(1, boxW - logoPlain.length - sessionPlain.length - rightGap.length);
|
|
930
|
+
const divider = '─'.repeat(pad);
|
|
931
|
+
// Keep a raw fallback for extremely narrow panes, but prefer animated label.
|
|
932
|
+
const fallbackLabel = (logoPlain + divider + sessionPlain + rightGap).slice(0, boxW);
|
|
933
|
+
if (fallbackLabel.length < logoPlain.length + 2) {
|
|
934
|
+
headerBox.setLabel(fallbackLabel);
|
|
935
|
+
}
|
|
936
|
+
else {
|
|
937
|
+
headerBox.setLabel(logoTagged + divider + sessionTagged + rightGap);
|
|
860
938
|
}
|
|
939
|
+
// Message line inside the box (centered)
|
|
940
|
+
const fortuneRaw = (fortuneText && fortuneText.trim().length > 0)
|
|
941
|
+
? fortuneText.trim()
|
|
942
|
+
: 'Good luck.';
|
|
943
|
+
const truncateForWidth = (text, maxWidth) => {
|
|
944
|
+
if (maxWidth <= 0)
|
|
945
|
+
return '';
|
|
946
|
+
if (text.length <= maxWidth)
|
|
947
|
+
return text;
|
|
948
|
+
if (maxWidth <= 3)
|
|
949
|
+
return '.'.repeat(maxWidth);
|
|
950
|
+
return `${text.slice(0, maxWidth - 3)}...`;
|
|
951
|
+
};
|
|
952
|
+
const centerForWidth = (text, maxWidth) => {
|
|
953
|
+
const clipped = truncateForWidth(text, maxWidth);
|
|
954
|
+
const leftPad = Math.max(0, Math.floor((maxWidth - clipped.length) / 2));
|
|
955
|
+
return `${' '.repeat(leftPad)}${clipped}`;
|
|
956
|
+
};
|
|
957
|
+
const maxInner = Math.max(8, boxW - 1);
|
|
958
|
+
// Render through dedicated line-2 row, not generic box content flow.
|
|
959
|
+
headerBox.setContent('');
|
|
960
|
+
headerMessageRow.setContent(centerForWidth(fortuneRaw, maxInner));
|
|
861
961
|
screen.render();
|
|
862
962
|
}
|
|
863
963
|
catch { }
|
|
864
964
|
}
|
|
865
|
-
// Wave cycles every 200ms for smooth color sweep
|
|
866
|
-
function scheduleWave() {
|
|
867
|
-
sparkleTimer = setTimeout(() => {
|
|
868
|
-
renderLogoWave();
|
|
869
|
-
scheduleWave();
|
|
870
|
-
}, 200);
|
|
871
|
-
}
|
|
872
|
-
scheduleWave();
|
|
873
965
|
// ── Update function ──
|
|
874
966
|
const debugLog = path.join(os.homedir(), '.ekkos', 'dashboard.log');
|
|
875
967
|
function dlog(msg) {
|
|
@@ -913,9 +1005,9 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
|
|
|
913
1005
|
dlog(`Parse error: ${err.message}`);
|
|
914
1006
|
return;
|
|
915
1007
|
}
|
|
916
|
-
// ── Header
|
|
1008
|
+
// ── Header ──
|
|
917
1009
|
try {
|
|
918
|
-
|
|
1010
|
+
renderHeader();
|
|
919
1011
|
}
|
|
920
1012
|
catch (err) {
|
|
921
1013
|
dlog(`Header: ${err.message}`);
|
|
@@ -975,9 +1067,10 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
|
|
|
975
1067
|
try {
|
|
976
1068
|
// Preserve scroll position BEFORE updating content
|
|
977
1069
|
lastScrollPerc = turnBox.getScrollPerc();
|
|
978
|
-
// Account for borders + scrollbar gutter
|
|
979
|
-
|
|
980
|
-
const
|
|
1070
|
+
// Account for borders + scrollbar gutter. Windows terminal rendering can
|
|
1071
|
+
// wrap by one char if this is too tight, which pushes Cost to next line.
|
|
1072
|
+
const w = Math.max(18, turnBox.width - 4); // usable content width
|
|
1073
|
+
const div = '│';
|
|
981
1074
|
function pad(s, width) {
|
|
982
1075
|
if (s.length >= width)
|
|
983
1076
|
return s.slice(0, width);
|
|
@@ -988,6 +1081,14 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
|
|
|
988
1081
|
return s.slice(0, width);
|
|
989
1082
|
return ' '.repeat(width - s.length) + s;
|
|
990
1083
|
}
|
|
1084
|
+
function cpad(s, width) {
|
|
1085
|
+
if (s.length >= width)
|
|
1086
|
+
return s.slice(0, width);
|
|
1087
|
+
const total = width - s.length;
|
|
1088
|
+
const left = Math.floor(total / 2);
|
|
1089
|
+
const right = total - left;
|
|
1090
|
+
return ' '.repeat(left) + s + ' '.repeat(right);
|
|
1091
|
+
}
|
|
991
1092
|
// Data rows — RENDER ALL TURNS for full scrollback history
|
|
992
1093
|
// Don't slice to visibleRows only — let user scroll through entire session
|
|
993
1094
|
const turns = data.turns.slice().reverse();
|
|
@@ -995,28 +1096,28 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
|
|
|
995
1096
|
let separator = '';
|
|
996
1097
|
let rows = [];
|
|
997
1098
|
// Responsive table layouts keep headers visible even in very narrow panes.
|
|
998
|
-
if (w >=
|
|
1099
|
+
if (w >= 60) {
|
|
999
1100
|
// Full mode: Turn, Model, Context, Cache Rd, Cache Wr, Output, Cost
|
|
1000
1101
|
const colNum = 4;
|
|
1001
|
-
const colM =
|
|
1002
|
-
const colCtx =
|
|
1003
|
-
const colCost =
|
|
1102
|
+
const colM = 7;
|
|
1103
|
+
const colCtx = 7;
|
|
1104
|
+
const colCost = 8;
|
|
1004
1105
|
const nDividers = 6;
|
|
1005
1106
|
const fixedW = colNum + colM + colCtx + colCost;
|
|
1006
1107
|
const flexTotal = w - fixedW - nDividers;
|
|
1007
|
-
const rdW = Math.max(
|
|
1008
|
-
const wrW = Math.max(
|
|
1009
|
-
const outW = Math.max(
|
|
1010
|
-
header =
|
|
1011
|
-
separator =
|
|
1108
|
+
const rdW = Math.max(10, Math.floor(flexTotal * 0.35));
|
|
1109
|
+
const wrW = Math.max(11, Math.floor(flexTotal * 0.30));
|
|
1110
|
+
const outW = Math.max(6, flexTotal - rdW - wrW);
|
|
1111
|
+
header = `${pad('Turn', colNum)}${div}${rpad('Model', colM)}${div}${rpad('Contex', colCtx)}${div}${rpad('Cache Read', rdW)}${div}${rpad('Cache Write', wrW)}${div}${rpad('Output', outW)}${div}${rpad('Cost', colCost)}`;
|
|
1112
|
+
separator = `${'─'.repeat(colNum)}┼${'─'.repeat(colM)}┼${'─'.repeat(colCtx)}┼${'─'.repeat(rdW)}┼${'─'.repeat(wrW)}┼${'─'.repeat(outW)}┼${'─'.repeat(colCost)}`;
|
|
1012
1113
|
rows = turns.map(t => {
|
|
1013
1114
|
const mTag = modelTag(t.routedModel);
|
|
1014
1115
|
const mColor = t.routedModel.includes('haiku') ? 'green' : t.routedModel.includes('sonnet') ? 'blue' : 'magenta';
|
|
1015
1116
|
const costFlag = t.cost > 1.0 ? '{red-fg}' : t.cost > 0.5 ? '{yellow-fg}' : '{white-fg}';
|
|
1016
1117
|
const costEnd = t.cost > 1.0 ? '{/red-fg}' : t.cost > 0.5 ? '{/yellow-fg}' : '{/white-fg}';
|
|
1017
1118
|
return (pad(String(t.turn), colNum) + div +
|
|
1018
|
-
`{${mColor}-fg}${
|
|
1019
|
-
|
|
1119
|
+
`{${mColor}-fg}${cpad(mTag, colM)}{/${mColor}-fg}` + div +
|
|
1120
|
+
rpad(`${t.contextPct.toFixed(0)}%`, colCtx) + div +
|
|
1020
1121
|
`{green-fg}${rpad(fmtK(t.cacheRead), rdW)}{/green-fg}` + div +
|
|
1021
1122
|
`{yellow-fg}${rpad(fmtK(t.cacheCreate), wrW)}{/yellow-fg}` + div +
|
|
1022
1123
|
`{cyan-fg}${rpad(fmtK(t.output), outW)}{/cyan-fg}` + div +
|
|
@@ -1031,16 +1132,16 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
|
|
|
1031
1132
|
const colCost = 6;
|
|
1032
1133
|
const nDividers = 4;
|
|
1033
1134
|
const outW = Math.max(4, w - (colNum + colM + colCtx + colCost + nDividers));
|
|
1034
|
-
header =
|
|
1035
|
-
separator =
|
|
1135
|
+
header = `${pad('Turn', colNum)}${div}${rpad('Model', colM)}${div}${rpad('Context', colCtx)}${div}${rpad('Output', outW)}${div}${rpad('Cost', colCost)}`;
|
|
1136
|
+
separator = `${'─'.repeat(colNum)}┼${'─'.repeat(colM)}┼${'─'.repeat(colCtx)}┼${'─'.repeat(outW)}┼${'─'.repeat(colCost)}`;
|
|
1036
1137
|
rows = turns.map(t => {
|
|
1037
1138
|
const mTag = modelTag(t.routedModel);
|
|
1038
1139
|
const mColor = t.routedModel.includes('haiku') ? 'green' : t.routedModel.includes('sonnet') ? 'blue' : 'magenta';
|
|
1039
1140
|
const costFlag = t.cost > 1.0 ? '{red-fg}' : t.cost > 0.5 ? '{yellow-fg}' : '{white-fg}';
|
|
1040
1141
|
const costEnd = t.cost > 1.0 ? '{/red-fg}' : t.cost > 0.5 ? '{/yellow-fg}' : '{/white-fg}';
|
|
1041
1142
|
return (pad(String(t.turn), colNum) + div +
|
|
1042
|
-
`{${mColor}-fg}${
|
|
1043
|
-
|
|
1143
|
+
`{${mColor}-fg}${cpad(mTag, colM)}{/${mColor}-fg}` + div +
|
|
1144
|
+
rpad(`${t.contextPct.toFixed(0)}%`, colCtx) + div +
|
|
1044
1145
|
`{cyan-fg}${rpad(fmtK(t.output), outW)}{/cyan-fg}` + div +
|
|
1045
1146
|
costFlag + rpad(`$${t.cost.toFixed(2)}`, colCost) + costEnd);
|
|
1046
1147
|
});
|
|
@@ -1050,13 +1151,13 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
|
|
|
1050
1151
|
const colNum = 4;
|
|
1051
1152
|
const colCtx = 6;
|
|
1052
1153
|
const colCost = 6;
|
|
1053
|
-
header =
|
|
1054
|
-
separator =
|
|
1154
|
+
header = `${pad('Turn', colNum)}${div}${rpad('Context', colCtx)}${div}${rpad('Cost', colCost)}`;
|
|
1155
|
+
separator = `${'─'.repeat(colNum)}┼${'─'.repeat(colCtx)}┼${'─'.repeat(colCost)}`;
|
|
1055
1156
|
rows = turns.map(t => {
|
|
1056
1157
|
const costFlag = t.cost > 1.0 ? '{red-fg}' : t.cost > 0.5 ? '{yellow-fg}' : '{white-fg}';
|
|
1057
1158
|
const costEnd = t.cost > 1.0 ? '{/red-fg}' : t.cost > 0.5 ? '{/yellow-fg}' : '{/white-fg}';
|
|
1058
1159
|
return (pad(String(t.turn), colNum) + div +
|
|
1059
|
-
|
|
1160
|
+
rpad(`${t.contextPct.toFixed(0)}%`, colCtx) + div +
|
|
1060
1161
|
costFlag + rpad(`$${t.cost.toFixed(2)}`, colCost) + costEnd);
|
|
1061
1162
|
});
|
|
1062
1163
|
}
|
|
@@ -1219,7 +1320,8 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
|
|
|
1219
1320
|
screen.key(['C-c'], () => {
|
|
1220
1321
|
clearInterval(pollInterval);
|
|
1221
1322
|
clearInterval(windowPollInterval);
|
|
1222
|
-
|
|
1323
|
+
clearInterval(headerAnimInterval);
|
|
1324
|
+
clearInterval(fortuneInterval);
|
|
1223
1325
|
screen.destroy();
|
|
1224
1326
|
process.exit(0);
|
|
1225
1327
|
});
|
|
@@ -1227,7 +1329,8 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
|
|
|
1227
1329
|
screen.key(['q'], () => {
|
|
1228
1330
|
clearInterval(pollInterval);
|
|
1229
1331
|
clearInterval(windowPollInterval);
|
|
1230
|
-
|
|
1332
|
+
clearInterval(headerAnimInterval);
|
|
1333
|
+
clearInterval(fortuneInterval);
|
|
1231
1334
|
screen.destroy();
|
|
1232
1335
|
process.exit(0);
|
|
1233
1336
|
});
|
|
@@ -1317,6 +1420,18 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
|
|
|
1317
1420
|
// Delay first ccusage call — let blessed render first, then load heavy data
|
|
1318
1421
|
setTimeout(() => updateWindowBox(), 2000);
|
|
1319
1422
|
const pollInterval = setInterval(updateDashboard, refreshMs);
|
|
1423
|
+
const headerAnimInterval = setInterval(() => {
|
|
1424
|
+
// Keep advancing across the full session label; wrap at a large value.
|
|
1425
|
+
waveOffset = (waveOffset + 1) % 1000000;
|
|
1426
|
+
renderHeader();
|
|
1427
|
+
}, 500);
|
|
1428
|
+
const fortuneInterval = setInterval(() => {
|
|
1429
|
+
if (GOOD_LUCK_FORTUNES.length === 0)
|
|
1430
|
+
return;
|
|
1431
|
+
fortuneIdx = (fortuneIdx + 1) % GOOD_LUCK_FORTUNES.length;
|
|
1432
|
+
fortuneText = GOOD_LUCK_FORTUNES[fortuneIdx];
|
|
1433
|
+
renderHeader();
|
|
1434
|
+
}, 30000);
|
|
1320
1435
|
const windowPollInterval = setInterval(updateWindowBox, 15000); // every 15s
|
|
1321
1436
|
}
|
|
1322
1437
|
// ── Helpers ──
|
package/dist/commands/run.js
CHANGED
|
@@ -748,7 +748,6 @@ function launchWithDashboardWindows(options) {
|
|
|
748
748
|
runArgs.push('--skip-dna');
|
|
749
749
|
if (options.noProxy)
|
|
750
750
|
runArgs.push('--skip-proxy');
|
|
751
|
-
runArgs.push('--kickstart');
|
|
752
751
|
const ekkosCmd = process.argv[1];
|
|
753
752
|
const cwd = process.cwd();
|
|
754
753
|
// Write dashboard launch marker
|
|
@@ -766,7 +765,7 @@ function launchWithDashboardWindows(options) {
|
|
|
766
765
|
// PowerShell -EncodedCommand expects UTF-16LE Base64
|
|
767
766
|
return Buffer.from(script, 'utf16le').toString('base64');
|
|
768
767
|
}
|
|
769
|
-
// cd to original CWD first so ekkos run
|
|
768
|
+
// cd to original CWD first so ekkos run registers the correct projectPath
|
|
770
769
|
const runScript = `Set-Location '${cwdEscaped}'; & node '${ekkosCmdEscaped}' ${runArgs.join(' ')}`;
|
|
771
770
|
const dashScript = `& node '${ekkosCmdEscaped}' dashboard --wait-for-new --refresh 2000`;
|
|
772
771
|
const runEncoded = toPsEncoded(runScript);
|
|
@@ -816,7 +815,6 @@ function launchWithDashboard(options) {
|
|
|
816
815
|
runArgs.push('--skip-dna');
|
|
817
816
|
if (options.noProxy)
|
|
818
817
|
runArgs.push('--skip-proxy');
|
|
819
|
-
runArgs.push('--kickstart'); // Auto-send "test" to create session immediately for dashboard
|
|
820
818
|
const ekkosCmd = process.argv[1]; // Path to ekkos CLI
|
|
821
819
|
const cwd = process.cwd();
|
|
822
820
|
const termCols = process.stdout.columns ?? 160;
|
package/dist/index.js
CHANGED
|
@@ -249,7 +249,7 @@ commander_1.program
|
|
|
249
249
|
.option('--skip-dna', 'Deprecated no-op (legacy ccDNA patching has been removed)')
|
|
250
250
|
.option('--skip-proxy', 'Skip API proxy (use direct Anthropic API, disables seamless context eviction)')
|
|
251
251
|
.option('--dashboard', 'Launch with live usage dashboard in an isolated 60/40 tmux split (requires tmux)')
|
|
252
|
-
.option('--kickstart', 'Auto-send "test" on load to create session immediately (
|
|
252
|
+
.option('--kickstart', 'Auto-send "test" on load to create session immediately (manual/debug use)')
|
|
253
253
|
.option('--add-dir <dirs...>', 'Additional directories Claude Code can access (outside working directory)')
|
|
254
254
|
.action((options) => {
|
|
255
255
|
(0, run_1.run)({
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://ekkos.dev/schemas/manifest-v1.json",
|
|
3
3
|
"manifestVersion": "1.0.0",
|
|
4
|
-
"generatedAt": "2026-02-
|
|
4
|
+
"generatedAt": "2026-02-19T23:06:44.502Z",
|
|
5
5
|
"platforms": {
|
|
6
6
|
"darwin": {
|
|
7
7
|
"configDir": "~/.ekkos",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"source": "hooks/user-prompt-submit.sh",
|
|
69
69
|
"destination": "user-prompt-submit.sh",
|
|
70
70
|
"description": "User prompt submit hook (Unix)",
|
|
71
|
-
"checksum": "
|
|
71
|
+
"checksum": "c53b621f3c64031f61b7ca1b3bf5ba4bec895b6a39aa7627422f0f2a2d26ab7d",
|
|
72
72
|
"executable": true
|
|
73
73
|
},
|
|
74
74
|
{
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"source": "hooks/assistant-response.sh",
|
|
90
90
|
"destination": "assistant-response.sh",
|
|
91
91
|
"description": "Assistant response hook (Unix)",
|
|
92
|
-
"checksum": "
|
|
92
|
+
"checksum": "99400adf6ced406f0295c25db3b8fb892212ad3ba625f427c042d8526ac8f6f7",
|
|
93
93
|
"executable": true
|
|
94
94
|
}
|
|
95
95
|
],
|
|
@@ -98,25 +98,25 @@
|
|
|
98
98
|
"source": "hooks/user-prompt-submit.ps1",
|
|
99
99
|
"destination": "user-prompt-submit.ps1",
|
|
100
100
|
"description": "User prompt submit hook (Windows)",
|
|
101
|
-
"checksum": "
|
|
101
|
+
"checksum": "6a00a23bc3865e63b1bca2702b769473cce11530adbffa4531b21bb6bbe7cc3b"
|
|
102
102
|
},
|
|
103
103
|
{
|
|
104
104
|
"source": "hooks/stop.ps1",
|
|
105
105
|
"destination": "stop.ps1",
|
|
106
106
|
"description": "Session stop hook (Windows)",
|
|
107
|
-
"checksum": "
|
|
107
|
+
"checksum": "2defabb31d51e482990f0fce762a5cc12beb08eb1ac1fb4a1afd7c5375d5e0f0"
|
|
108
108
|
},
|
|
109
109
|
{
|
|
110
110
|
"source": "hooks/session-start.ps1",
|
|
111
111
|
"destination": "session-start.ps1",
|
|
112
112
|
"description": "Session start hook (Windows)",
|
|
113
|
-
"checksum": "
|
|
113
|
+
"checksum": "7b700bb98072efe4bd84b84b0754d0b64dc85a718f4484c6d42594d790e4cce5"
|
|
114
114
|
},
|
|
115
115
|
{
|
|
116
116
|
"source": "hooks/assistant-response.ps1",
|
|
117
117
|
"destination": "assistant-response.ps1",
|
|
118
118
|
"description": "Assistant response hook (Windows)",
|
|
119
|
-
"checksum": "
|
|
119
|
+
"checksum": "554e978190100d506a3a0c32a59013628d44d00c5e7e21dc28346ad367da60ea"
|
|
120
120
|
}
|
|
121
121
|
],
|
|
122
122
|
"lib": [
|