@ekkos/cli 1.0.28 → 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.
@@ -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
- // ── Logo color wave animation ──
890
+ // ── Header render (animated logo in border label + compact stats content) ──
825
891
  let waveOffset = 0;
826
- let sparkleTimer;
827
- function renderLogoWave() {
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 pad = Math.max(1, boxW - rawLogoLen - rawSessionLen);
853
- headerBox.setLabel(logoStr + '─'.repeat(pad) + sessionStr);
854
- waveOffset = (waveOffset + 1) % WAVE_COLORS.length;
855
- // Stats go inside the box
856
- const data = lastData;
857
- if (data) {
858
- const m = data.model.replace('claude-', '').replace(/-\d{8}$/, '');
859
- headerBox.setContent(` {green-fg}$${data.totalCost.toFixed(2)}{/green-fg} T${data.turnCount} ${data.duration} $${data.avgCostPerTurn.toFixed(2)}/t {cyan-fg}${m}{/cyan-fg}`);
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 — wave animation handles rendering, just trigger a frame ──
1008
+ // ── Header ──
917
1009
  try {
918
- renderLogoWave();
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 + padding so last column never wraps
979
- const w = Math.max(18, turnBox.width - 5); // usable content width
980
- const div = '{gray-fg}│{/gray-fg}';
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 >= 44) {
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 = 6;
1002
- const colCtx = 6;
1003
- const colCost = 6;
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(4, Math.floor(flexTotal * 0.35));
1008
- const wrW = Math.max(4, Math.floor(flexTotal * 0.30));
1009
- const outW = Math.max(4, flexTotal - rdW - wrW);
1010
- 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}`;
1011
- separator = `{gray-fg}${'─'.repeat(colNum)}┼${'─'.repeat(colM)}┼${'─'.repeat(colCtx)}┼${'─'.repeat(rdW)}┼${'─'.repeat(wrW)}┼${'─'.repeat(outW)}┼${'─'.repeat(colCost)}{/gray-fg}`;
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}${pad(mTag, colM)}{/${mColor}-fg}` + div +
1019
- pad(`${t.contextPct.toFixed(0)}%`, colCtx) + div +
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 = `{bold}${pad('Turn', colNum)}${div}${pad('Model', colM)}${div}${pad('Context', colCtx)}${div}${rpad('Output', outW)}${div}${rpad('Cost', colCost)}{/bold}`;
1035
- separator = `{gray-fg}${'─'.repeat(colNum)}┼${'─'.repeat(colM)}┼${'─'.repeat(colCtx)}┼${'─'.repeat(outW)}┼${'─'.repeat(colCost)}{/gray-fg}`;
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}${pad(mTag, colM)}{/${mColor}-fg}` + div +
1043
- pad(`${t.contextPct.toFixed(0)}%`, colCtx) + div +
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 = `{bold}${pad('Turn', colNum)}${div}${pad('Context', colCtx)}${div}${rpad('Cost', colCost)}{/bold}`;
1054
- separator = `{gray-fg}${'─'.repeat(colNum)}┼${'─'.repeat(colCtx)}┼${'─'.repeat(colCost)}{/gray-fg}`;
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
- pad(`${t.contextPct.toFixed(0)}%`, colCtx) + div +
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
- clearTimeout(sparkleTimer);
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
- clearTimeout(sparkleTimer);
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekkos/cli",
3
- "version": "1.0.28",
3
+ "version": "1.0.29",
4
4
  "description": "Setup ekkOS memory for AI coding assistants (Claude Code, Cursor, Windsurf)",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -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-18T04:29:25.535Z",
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": "ef294d0088a94c6588a91fc91e6cc06a14dd46bc83ca09d73c64abfb11984f40",
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": "11c3b84aff29552f8a28b59851a4fdc7de4414d28161cca7418dfc93e0c64eac",
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": "ba044ec066935a9413811e53fd70f42f0ffd959fc361b746c6b54768ac8c6fb9"
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": "455d9dfca8cea2f8289604ca2389b8e43c8af380fbcc5e2eef18382d30e4be6d"
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": "0897603df2b3261857be79108c0831b0fbc3744722b4923ce632844e7c6483c6"
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": "83fb65cec1cb9b1b9ef7a2036c9440e8b6ced16d82a2623b5353899918ae9653"
119
+ "checksum": "554e978190100d506a3a0c32a59013628d44d00c5e7e21dc28346ad367da60ea"
120
120
  }
121
121
  ],
122
122
  "lib": [