pi-mega-compact 0.7.8 → 0.7.9

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.
Files changed (112) hide show
  1. package/README.md +11 -12
  2. package/dist/extensions/dashboard-server/helpers.js +37 -0
  3. package/dist/extensions/dashboard-server/html/all-repos-tab.js +26 -0
  4. package/dist/extensions/dashboard-server/html/body-open.js +23 -0
  5. package/dist/extensions/dashboard-server/html/current-repo-tab.js +130 -0
  6. package/dist/extensions/dashboard-server/html/head-open.js +16 -0
  7. package/dist/extensions/dashboard-server/html/high-score-tab.js +25 -0
  8. package/dist/extensions/dashboard-server/html/repo-detail-modal.js +26 -0
  9. package/dist/extensions/dashboard-server/html/script.js +259 -0
  10. package/dist/extensions/dashboard-server/html/styles.js +103 -0
  11. package/dist/extensions/dashboard-server/html/summary-tab.js +19 -0
  12. package/dist/extensions/dashboard-server/html-template.js +41 -0
  13. package/dist/extensions/dashboard-server/html.js +756 -0
  14. package/dist/extensions/dashboard-server/index-reader.js +133 -0
  15. package/dist/extensions/dashboard-server/server.js +370 -0
  16. package/dist/extensions/dashboard-server/snapshot.js +43 -0
  17. package/dist/extensions/dashboard-server/state.js +30 -0
  18. package/dist/extensions/dashboard-server/types.js +5 -0
  19. package/dist/extensions/dashboard-server.js +7 -1315
  20. package/dist/extensions/mega-commands.js +162 -134
  21. package/dist/extensions/mega-compact.test.js +90 -21
  22. package/dist/extensions/mega-conflict-cmds.js +5 -1
  23. package/dist/extensions/mega-dashboard-cmds.js +29 -22
  24. package/dist/extensions/mega-db-cmds.js +11 -2
  25. package/dist/extensions/mega-events/agent-handlers.js +173 -0
  26. package/dist/extensions/mega-events/compact-handlers.js +133 -0
  27. package/dist/extensions/mega-events/context-handler.js +249 -0
  28. package/dist/extensions/mega-events/register.js +21 -0
  29. package/dist/extensions/mega-events/session-handlers.js +142 -0
  30. package/dist/extensions/mega-events.js +15 -699
  31. package/dist/extensions/mega-pipeline/compact.js +324 -0
  32. package/dist/extensions/mega-pipeline/memory-review.js +38 -0
  33. package/dist/extensions/mega-pipeline/recall.js +147 -0
  34. package/dist/extensions/mega-pipeline.js +9 -480
  35. package/dist/extensions/mega-runtime/helpers.js +40 -0
  36. package/dist/extensions/mega-runtime/query.js +29 -0
  37. package/dist/extensions/mega-runtime/state.js +711 -0
  38. package/dist/extensions/mega-runtime/widget.js +197 -0
  39. package/dist/extensions/mega-runtime.js +15 -947
  40. package/dist/src/store/sqlite/checkpoints.js +145 -0
  41. package/dist/src/store/sqlite/connection.js +35 -0
  42. package/dist/src/store/sqlite/dedup-mirror.js +64 -0
  43. package/dist/src/store/sqlite/foundation.js +38 -0
  44. package/dist/src/store/sqlite/global-index.js +224 -0
  45. package/dist/src/store/sqlite/index-store.js +167 -0
  46. package/dist/src/store/sqlite/maintenance.js +235 -0
  47. package/dist/src/store/sqlite/memories.js +164 -0
  48. package/dist/src/store/sqlite/memory.js +54 -0
  49. package/dist/src/store/sqlite/meta.js +82 -0
  50. package/dist/src/store/sqlite/minhash-lsh.js +47 -0
  51. package/dist/src/store/sqlite/model-snapshots.js +47 -0
  52. package/dist/src/store/sqlite/raptor.js +57 -0
  53. package/dist/src/store/sqlite/raw-transcript.js +134 -0
  54. package/dist/src/store/sqlite/schema.js +250 -0
  55. package/dist/src/store/sqlite/session-state.js +28 -0
  56. package/dist/src/store/sqlite/sessions.js +39 -0
  57. package/dist/src/store/sqlite/stats.js +66 -0
  58. package/dist/src/store/sqlite/transaction.js +19 -0
  59. package/dist/src/store/sqlite/utils.js +120 -0
  60. package/dist/src/store/sqlite.js +20 -1607
  61. package/dist/src/vectorStore/add.js +260 -0
  62. package/dist/src/vectorStore/dedup.js +52 -0
  63. package/dist/src/vectorStore/index.js +10 -0
  64. package/dist/src/vectorStore/queries.js +83 -0
  65. package/dist/src/vectorStore/search.js +95 -0
  66. package/dist/src/vectorStore/session.js +19 -0
  67. package/dist/src/vectorStore/store.js +105 -0
  68. package/dist/src/vectorStore/types.js +6 -0
  69. package/dist/src/vectorStore/utils.js +23 -0
  70. package/extensions/dashboard-server/html.ts +758 -0
  71. package/extensions/dashboard-server/index-reader.ts +130 -0
  72. package/extensions/dashboard-server/server.ts +358 -0
  73. package/extensions/dashboard-server/snapshot.ts +44 -0
  74. package/extensions/dashboard-server/state.ts +33 -0
  75. package/extensions/dashboard-server/types.ts +134 -0
  76. package/extensions/dashboard-server.ts +7 -1431
  77. package/extensions/mega-commands.ts +33 -10
  78. package/extensions/mega-compact.test.ts +198 -43
  79. package/extensions/mega-conflict-cmds.ts +6 -2
  80. package/extensions/mega-dashboard-cmds.ts +30 -23
  81. package/extensions/mega-db-cmds.ts +11 -3
  82. package/extensions/mega-events/agent-handlers.ts +214 -0
  83. package/extensions/mega-events/compact-handlers.ts +164 -0
  84. package/extensions/mega-events/context-handler.ts +290 -0
  85. package/extensions/mega-events/register.ts +37 -0
  86. package/extensions/mega-events/session-handlers.ts +165 -0
  87. package/extensions/mega-events.ts +15 -780
  88. package/extensions/mega-pipeline/compact.ts +366 -0
  89. package/extensions/mega-pipeline/memory-review.ts +46 -0
  90. package/extensions/mega-pipeline/recall.ts +165 -0
  91. package/extensions/mega-pipeline.ts +9 -537
  92. package/extensions/mega-runtime/helpers.ts +68 -0
  93. package/extensions/mega-runtime/query.ts +29 -0
  94. package/extensions/mega-runtime/state.ts +797 -0
  95. package/extensions/mega-runtime/widget.ts +258 -0
  96. package/extensions/mega-runtime.ts +15 -1093
  97. package/package.json +4 -3
  98. package/src/store/sqlite/checkpoints.ts +204 -0
  99. package/src/store/sqlite/dedup-mirror.ts +114 -0
  100. package/src/store/sqlite/foundation.ts +63 -0
  101. package/src/store/sqlite/global-index.ts +305 -0
  102. package/src/store/sqlite/maintenance.ts +294 -0
  103. package/src/store/sqlite/memories.ts +217 -0
  104. package/src/store/sqlite/meta.ts +108 -0
  105. package/src/store/sqlite/model-snapshots.ts +83 -0
  106. package/src/store/sqlite/raptor.ts +107 -0
  107. package/src/store/sqlite/raw-transcript.ts +221 -0
  108. package/src/store/sqlite/schema.ts +258 -0
  109. package/src/store/sqlite/session-state.ts +38 -0
  110. package/src/store/sqlite/stats.ts +127 -0
  111. package/src/store/sqlite/utils.ts +125 -0
  112. package/src/store/sqlite.ts +20 -2204
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Client-side script: snapshot polling, SSE event stream, multi-repo index
3
+ * polling, per-repo detail modal, and tab switching.
4
+ *
5
+ * One IIFE — the logic is cross-tab (the index poller writes to both the
6
+ * Current-repo table and the All-repos tab) so it stays cohesive rather than
7
+ * being split per-tab. `</body></html>` closes the document.
8
+ *
9
+ * PREVENT-PI-004: the two `fetch(...)` calls below are the dashboard's
10
+ * browser-side localhost probes and carry inline `// guardrails-allow
11
+ * PREVENT-PI-004: <reason>` annotations (scanner-enforced).
12
+ */
13
+ export function script() {
14
+ return `<script>
15
+ (function() {
16
+ var evBox = document.getElementById('events');
17
+ var evBuffer = [];
18
+ var MAX_EV = 50;
19
+ var offlineBanner = document.getElementById('offline-banner');
20
+
21
+ function bullet(el, on, na) {
22
+ el.className = 'bullet ' + (na ? 'bullet-na' : on ? 'bullet-on' : 'bullet-off');
23
+ }
24
+
25
+ function renderSnapshot(d) {
26
+ if (!d || !d.updatedAt) { offlineBanner.style.display = 'block'; return; }
27
+ offlineBanner.style.display = 'none';
28
+
29
+ var pct = d.context.percent || 0;
30
+ document.getElementById('ctx-pct').textContent = pct + '%';
31
+ var bar = document.getElementById('ctx-bar');
32
+ bar.style.width = Math.max(pct, 1) + '%';
33
+ bar.className = 'meter-fill ' + (pct >= 90 ? 'meter-red' : pct >= 70 ? 'meter-yellow' : 'meter-green');
34
+ var tok = d.context.tokens != null ? d.context.tokens.toLocaleString() : '?';
35
+ var win = d.context.contextWindow ? d.context.contextWindow.toLocaleString() : '?';
36
+ document.getElementById('ctx-sub').textContent = tok + ' / ' + win + ' tokens';
37
+
38
+ bullet(document.getElementById('tr-armed'), d.trigger.armed, false);
39
+ bullet(document.getElementById('tr-ready'), d.trigger.ready, !d.trigger.armed);
40
+ var state = d.trigger.ready ? 'THRESHOLD EXCEEDED — compacting next event' :
41
+ d.trigger.armed ? 'past fast gate — monitoring token count' : 'idle — below fast gate';
42
+ document.getElementById('tr-state').textContent = state;
43
+
44
+ document.getElementById('st-count').textContent = d.store.checkpointCount;
45
+ document.getElementById('st-tokens').textContent = d.store.totalTokenEstimate.toLocaleString();
46
+ document.getElementById('st-orig').textContent = (d.store.originalTokens || 0).toLocaleString();
47
+ document.getElementById('st-saved').textContent = (d.store.tokensSaved || 0).toLocaleString();
48
+ document.getElementById('st-injected').textContent = d.store.injectedCount;
49
+ document.getElementById('st-dedup').textContent = Math.round(d.store.dedupHitRate * 100) + '%';
50
+ var sdr = d.store.storageDedupRate || 0;
51
+ document.getElementById('st-sdedup').textContent = (sdr * 100 >= 10 ? Math.round(sdr * 100) : (sdr * 100).toFixed(1)) + '%';
52
+ document.getElementById('st-collapsed').textContent = d.store.dedupCollapsed || 0;
53
+ document.getElementById('st-lastid').textContent = d.session.lastCheckpointId || '—';
54
+
55
+ // Repo-wide (all sessions in this repo's SQLite store).
56
+ var repo = d.repo || { checkpointCount: 0, totalTokenEstimate: 0, originalTokens: 0, tokensSaved: 0, sessionCount: 0, dedupCollapsed: 0, storageDedupRate: 0 };
57
+ document.getElementById('rp-count').textContent = repo.checkpointCount;
58
+ document.getElementById('rp-tokens').textContent = repo.totalTokenEstimate.toLocaleString();
59
+ document.getElementById('rp-orig').textContent = (repo.originalTokens || 0).toLocaleString();
60
+ document.getElementById('rp-saved').textContent = (repo.tokensSaved || 0).toLocaleString();
61
+ document.getElementById('rp-sessions').textContent = repo.sessionCount || 0;
62
+ document.getElementById('rp-collapsed').textContent = repo.dedupCollapsed || 0;
63
+ var rsdr = repo.storageDedupRate || 0;
64
+ document.getElementById('rp-sdedup').textContent = (rsdr * 100 >= 10 ? Math.round(rsdr * 100) : (rsdr * 100).toFixed(1)) + '%';
65
+
66
+ // Data-safety invariant (Phase 0 — trust foundation).
67
+ var ig = d.integrity || { regionsRetained: 0, compressedOriginalBytes: 0, duplicatesCollapsed: 0, bytesPermanentlyDeleted: 0 };
68
+ function fmtBytes(b) {
69
+ b = b || 0;
70
+ if (b >= 1048576) return (b / 1048576).toFixed(1) + ' MiB';
71
+ if (b >= 1024) return (b / 1024).toFixed(1) + ' KiB';
72
+ return b + ' B';
73
+ }
74
+ document.getElementById('ig-retained').textContent = (ig.regionsRetained || 0).toLocaleString();
75
+ document.getElementById('ig-bytes').textContent = fmtBytes(ig.compressedOriginalBytes);
76
+ document.getElementById('ig-dupes').textContent = (ig.duplicatesCollapsed || 0).toLocaleString();
77
+ document.getElementById('ig-deleted').textContent = fmtBytes(ig.bytesPermanentlyDeleted);
78
+
79
+ // Crew / agents (live sub-agent activity + turn).
80
+ var crew = d.crew || { activeAgents: 0, currentTurn: 0 };
81
+ document.getElementById('cr-agents').textContent = crew.activeAgents || 0;
82
+ document.getElementById('cr-turn').textContent = crew.currentTurn || 0;
83
+ document.getElementById('cr-status').textContent = (crew.activeAgents > 0)
84
+ ? ('▶ ' + crew.activeAgents + ' running') : 'idle';
85
+
86
+ document.getElementById('cf-tier').textContent = d.tier;
87
+ document.getElementById('cf-threshold').textContent = d.config.thresholdTokens.toLocaleString();
88
+ document.getElementById('cf-gate').textContent = d.config.fastGatePct + '%';
89
+ document.getElementById('cf-auto').textContent = d.config.auto ? 'enabled' : 'disabled';
90
+ document.getElementById('cf-anchor').textContent = d.config.anchorUserMessages;
91
+
92
+ // --- Active model + cost savings (same calc as /mega-status) ---------------
93
+ var model = d.model;
94
+ document.getElementById('hdr-model').textContent = model && model.name ? model.name : '—';
95
+ document.getElementById('md-name').textContent = model && model.name ? model.name : '—';
96
+ document.getElementById('md-provider').textContent = model && model.providerName ? model.providerName : (model && model.provider ? model.provider : '—');
97
+ document.getElementById('md-input').textContent = model && model.inputRate ? '$' + (model.inputRate).toFixed(6) : '—';
98
+ document.getElementById('md-output').textContent = model && model.outputRate ? '$' + (model.outputRate).toFixed(6) : '—';
99
+ if (model && model.inputRate && repo.tokensSaved > 0) {
100
+ var usd = (repo.tokensSaved * model.inputRate);
101
+ var win = d.context.contextWindow || 0;
102
+ var windows = win > 0 ? (repo.tokensSaved / win).toFixed(1) : '0';
103
+ document.getElementById('cost-usd').textContent = '≈ $' + usd.toFixed(4) + ' saved';
104
+ document.getElementById('cost-windows').textContent = windows + ' context-windows extended';
105
+ } else {
106
+ document.getElementById('cost-usd').textContent = '≈ $0.00 saved';
107
+ document.getElementById('cost-windows').textContent = '0 context-windows extended';
108
+ }
109
+
110
+ document.getElementById('updated').textContent = 'Updated ' + new Date(d.updatedAt).toLocaleTimeString();
111
+ }
112
+
113
+ function sanitize(s) {
114
+ return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
115
+ }
116
+
117
+ function renderEvent(ev) {
118
+ evBuffer.unshift(ev);
119
+ if (evBuffer.length > MAX_EV) evBuffer.length = MAX_EV;
120
+ evBox.innerHTML = evBuffer.map(function(e) {
121
+ var t = e.ts ? new Date(e.ts).toLocaleTimeString() : '';
122
+ var detail = '';
123
+ if (e.data) {
124
+ if (e.data.checkpointId) detail = sanitize(e.data.checkpointId);
125
+ else if (e.data.query) detail = sanitize(e.data.query.slice(0, 80));
126
+ if (e.data.tokenEstimate != null) detail += ' ' + e.data.tokenEstimate + ' tok';
127
+ if (e.data.deduped) detail += ' (deduped)';
128
+ if (e.data.injected != null) detail = 'injected: ' + e.data.injected + (e.data.empty ? ' (empty)' : '');
129
+ }
130
+ return '<div class="ev">' +
131
+ '<span class="ev-time">' + t + '</span>' +
132
+ '<span class="ev-type ev-type-' + sanitize(e.type) + '">' + sanitize(e.type) + '</span>' +
133
+ '<span class="ev-detail">' + detail + '</span></div>';
134
+ }).join('');
135
+ }
136
+
137
+ // Poll snapshot every 2s
138
+ function pollSnapshot() {
139
+ fetch('/api/snapshot').then(function(r) { return r.json(); }).then(renderSnapshot).catch(function() {}); // guardrails-allow PREVENT-PI-004: dashboard browser-side fetch (localhost)
140
+ }
141
+ pollSnapshot();
142
+ setInterval(pollSnapshot, 2000);
143
+
144
+ // SSE for events
145
+ function connectSSE() {
146
+ var es = new EventSource('/api/events');
147
+ es.onmessage = function(msg) {
148
+ try { renderEvent(JSON.parse(msg.data)); } catch(e) {}
149
+ };
150
+ es.onerror = function() {
151
+ es.close();
152
+ setTimeout(connectSSE, 3000);
153
+ };
154
+ }
155
+ connectSSE();
156
+
157
+ // --- Multi-repo (index.sqlite via /api/index) ---------------------------
158
+ function fmtBytesTop(b) {
159
+ b = b || 0;
160
+ if (b >= 1048576) return (b / 1048576).toFixed(1) + ' MiB';
161
+ if (b >= 1024) return (b / 1024).toFixed(1) + ' KiB';
162
+ return b + ' B';
163
+ }
164
+ function renderIndex(d) {
165
+ d = d || { updatedAt: null, summary: null, repos: [] };
166
+ var repos = d.repos || [];
167
+ var s = d.summary || { totalRepos: 0, totalCheckpoints: 0, totalTokensSaved: 0, totalCompressedOriginalBytes: 0 };
168
+ document.getElementById('sm-repos').textContent = (s.totalRepos || 0).toLocaleString();
169
+ document.getElementById('sm-checkpoints').textContent = (s.totalCheckpoints || 0).toLocaleString();
170
+ document.getElementById('sm-saved').textContent = (s.totalTokensSaved || 0).toLocaleString();
171
+ document.getElementById('sm-bytes').textContent = fmtBytesTop(s.totalCompressedOriginalBytes);
172
+
173
+ // Shared clickable-row renderer for both the in-current table and the
174
+ // All-repos tab — each row opens the per-repo detail modal.
175
+ function rowsHtml() {
176
+ if (!repos.length) return '<tr><td colspan="6" class="repo-none">No repositories registered yet.</td></tr>';
177
+ return repos.map(function(r) {
178
+ var model = r.modelName
179
+ ? '<span class="repo-model">' + sanitize(r.modelName) + '</span>'
180
+ : '<span class="repo-none">—</span>';
181
+ var when = r.lastCompactedAt ? new Date(r.lastCompactedAt).toLocaleString() : '—';
182
+ return '<tr class="repo-link" data-repo="' + sanitize(r.repoRoot) + '">' +
183
+ '<td title="' + sanitize(r.repoRoot) + '">' + sanitize(r.displayName || r.repoRoot) + '</td>' +
184
+ '<td>' + model + '</td>' +
185
+ '<td class="num">' + (r.checkpointCount || 0).toLocaleString() + '</td>' +
186
+ '<td class="num">' + (r.tokensSaved || 0).toLocaleString() + '</td>' +
187
+ '<td class="num">' + fmtBytesTop(r.compressedOriginalBytes) + '</td>' +
188
+ '<td class="num">' + sanitize(when) + '</td>' +
189
+ '</tr>';
190
+ }).join('');
191
+ }
192
+ document.getElementById('cur-rows').innerHTML = rowsHtml();
193
+ document.getElementById('all-rows').innerHTML = rowsHtml();
194
+ bindRepoRows();
195
+
196
+ var stamp = d.updatedAt ? 'Updated ' + new Date(d.updatedAt).toLocaleTimeString() : '';
197
+ document.getElementById('cur-updated').textContent = stamp;
198
+ document.getElementById('all-updated').textContent = stamp;
199
+ document.getElementById('sm-updated').textContent = stamp;
200
+ }
201
+
202
+ // Per-repo detail modal ---------------------------------------------------
203
+ var detailEl = document.getElementById('repo-detail');
204
+ var indexCache = { repos: [] };
205
+ function openRepoDetail(root) {
206
+ var r = null;
207
+ for (var i = 0; i < indexCache.repos.length; i++) {
208
+ if (indexCache.repos[i].repoRoot === root) { r = indexCache.repos[i]; break; }
209
+ }
210
+ if (!r) return;
211
+ document.getElementById('rd-name').textContent = r.displayName || r.repoRoot;
212
+ document.getElementById('rd-path').textContent = r.repoRoot;
213
+ document.getElementById('rd-model').textContent = r.modelName || '—';
214
+ document.getElementById('rd-provider').textContent = r.providerName || (r.provider || '—');
215
+ document.getElementById('rd-cp').textContent = (r.checkpointCount || 0).toLocaleString();
216
+ document.getElementById('rd-saved').textContent = (r.tokensSaved || 0).toLocaleString();
217
+ document.getElementById('rd-bytes').textContent = fmtBytesTop(r.compressedOriginalBytes);
218
+ document.getElementById('rd-when').textContent = r.lastCompactedAt ? new Date(r.lastCompactedAt).toLocaleString() : '—';
219
+ detailEl.classList.add('open');
220
+ }
221
+ document.getElementById('rd-close').addEventListener('click', function() { detailEl.classList.remove('open'); });
222
+ detailEl.addEventListener('click', function(e) { if (e.target === detailEl) detailEl.classList.remove('open'); });
223
+ function bindRepoRows() {
224
+ var rows = document.querySelectorAll('.repo-link');
225
+ for (var i = 0; i < rows.length; i++) {
226
+ rows[i].addEventListener('click', function() { openRepoDetail(this.getAttribute('data-repo')); });
227
+ }
228
+ }
229
+ function pollIndex() {
230
+ fetch('/api/index').then(function(r) { return r.json(); }).then(function(d) { // guardrails-allow PREVENT-PI-004: dashboard browser-side fetch (localhost)
231
+ indexCache = d && d.repos ? d : indexCache;
232
+ renderIndex(d);
233
+ }).catch(function() {});
234
+ }
235
+ pollIndex();
236
+ setInterval(pollIndex, 5000);
237
+
238
+ // --- Tab switching ------------------------------------------------------
239
+ var tabs = document.querySelectorAll('.tab');
240
+ var panels = { current: 'panel-current', all: 'panel-all', summary: 'panel-summary', highscore: 'panel-highscore' };
241
+ for (var i = 0; i < tabs.length; i++) {
242
+ tabs[i].addEventListener('click', function() {
243
+ var name = this.getAttribute('data-tab');
244
+ for (var j = 0; j < tabs.length; j++) tabs[j].classList.remove('active');
245
+ this.classList.add('active');
246
+ for (var k in panels) {
247
+ if (Object.prototype.hasOwnProperty.call(panels, k)) {
248
+ var el = document.getElementById(panels[k]);
249
+ if (el) el.classList.toggle('active', k === name);
250
+ }
251
+ }
252
+ if (name === 'all' || name === 'summary') pollIndex();
253
+ });
254
+ }
255
+ })();
256
+ </script>
257
+ </body>
258
+ </html>`;
259
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Inline CSS for the dashboard.
3
+ *
4
+ * Extracted verbatim from the original monolithic `html-template.ts`.
5
+ * Kept as a single cohesive stylesheet — splitting CSS into per-tab files
6
+ * would fragment the design system (shared tokens, e.g. `.card`, `.stat-grid`,
7
+ * `.meter-*` are reused across all tabs).
8
+ */
9
+ export function styles() {
10
+ return `<style>
11
+ * { margin: 0; padding: 0; box-sizing: border-box; }
12
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background: #0d1117; color: #c9d1d9; padding: 24px; line-height: 1.5; }
13
+ h1 { font-size: 20px; font-weight: 600; margin-bottom: 20px; display: flex; align-items: center; gap: 10px; color: #f0f6fc; }
14
+ h1 .tier { background: #1f6feb; color: #fff; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 10px; text-transform: uppercase; letter-spacing: .5px; }
15
+ .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 20px; }
16
+ .card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; }
17
+ .card.safe { border-color: #238636; }
18
+ .card.safe h2 { color: #3fb950; }
19
+ .safe-note { font-size: 12px; color: #8b949e; margin: 12px 0 0; line-height: 1.5; }
20
+ .value.ok { color: #3fb950; }
21
+ .label {
22
+ cursor: help;
23
+ border-bottom: 1px dotted #484f58;
24
+ }
25
+ .card.legend { grid-column: 1 / -1; }
26
+ .legend-list { margin: 0; padding-left: 18px; color: #c9d1d9; }
27
+ .legend-list li { margin-bottom: 8px; line-height: 1.5; }
28
+ .legend-list b { color: #f0f6fc; }
29
+ .legend-note { font-size: 12px; color: #8b949e; margin: 12px 0 0; font-style: italic; }
30
+ .card h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .5px; color: #8b949e; margin-bottom: 12px; font-weight: 600; }
31
+ .meter-track { background: #21262d; border-radius: 4px; height: 20px; overflow: hidden; margin: 8px 0; }
32
+ .meter-fill { height: 100%; border-radius: 4px; transition: width .6s ease; min-width: 2px; }
33
+ .meter-green { background: #238636; }
34
+ .meter-yellow { background: #d29922; }
35
+ .meter-red { background: #f85149; }
36
+ .meter-label { font-size: 24px; font-weight: 700; color: #f0f6fc; }
37
+ .meter-sub { font-size: 12px; color: #8b949e; }
38
+ .status-row { display: flex; align-items: center; gap: 8px; margin: 6px 0; font-size: 14px; }
39
+ .status-row .bullet { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
40
+ .bullet-on { background: #3fb950; box-shadow: 0 0 6px #3fb95088; }
41
+ .bullet-off { background: #484f58; }
42
+ .bullet-na { background: #d29922; }
43
+ .state-text { font-size: 13px; color: #8b949e; margin-top: 8px; font-family: monospace; }
44
+ .stat-grid { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: 14px; }
45
+ .stat-grid .label { color: #8b949e; }
46
+ .stat-grid .value { color: #f0f6fc; font-weight: 600; font-family: monospace; }
47
+ .conf-grid { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: 14px; }
48
+ .conf-grid .label { color: #8b949e; }
49
+ .conf-grid .value { color: #f0f6fc; font-family: monospace; }
50
+ .events { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; }
51
+ .events h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .5px; color: #8b949e; margin-bottom: 12px; font-weight: 600; }
52
+ .events-wrap { max-height: 240px; overflow-y: auto; font-family: monospace; font-size: 12px; }
53
+ .ev { padding: 3px 0; border-bottom: 1px solid #21262d; display: flex; gap: 8px; align-items: baseline; }
54
+ .ev:last-child { border-bottom: none; }
55
+ .ev-type { font-weight: 700; min-width: 70px; text-align: right; }
56
+ .ev-type-compact { color: #3fb950; }
57
+ .ev-type-recall { color: #a371f7; }
58
+ .ev-time { color: #484f58; font-size: 10px; min-width: 80px; }
59
+ .ev-detail { color: #8b949e; flex: 1; }
60
+ .updated { font-size: 11px; color: #484f58; margin-top: 16px; text-align: right; }
61
+ .empty { color: #484f58; font-style: italic; font-size: 13px; padding: 8px 0; }
62
+ .offline-banner { background: #f8514922; border: 1px solid #f85149; border-radius: 6px; padding: 10px 16px; margin-bottom: 16px; font-size: 13px; color: #f85149; display: none; }
63
+ .tabs { display: flex; gap: 8px; margin-bottom: 20px; }
64
+ .tab { background: #161b22; color: #8b949e; border: 1px solid #30363d; border-radius: 6px; padding: 8px 16px; font-size: 13px; font-weight: 600; cursor: pointer; transition: all .15s ease; }
65
+ .tab:hover { color: #c9d1d9; border-color: #484f58; }
66
+ .tab.active { background: #1f6feb; color: #fff; border-color: #1f6feb; }
67
+ .tab.future { opacity: .7; }
68
+ .tab .soon { font-size: 9px; font-weight: 700; background: #6e40c9; color: #fff; padding: 1px 6px; border-radius: 8px; margin-left: 6px; text-transform: uppercase; letter-spacing: .5px; }
69
+ .tab-panel { display: none; }
70
+ .tab-panel.active { display: block; }
71
+ .summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; margin-bottom: 20px; }
72
+ .summary-card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; }
73
+ .summary-card .num { font-size: 24px; font-weight: 700; color: #f0f6fc; }
74
+ .summary-card .lbl { font-size: 12px; color: #8b949e; text-transform: uppercase; letter-spacing: .5px; margin-top: 4px; }
75
+ table.repos { width: 100%; border-collapse: collapse; background: #161b22; border: 1px solid #30363d; border-radius: 8px; overflow: hidden; }
76
+ table.repos th, table.repos td { text-align: left; padding: 10px 14px; font-size: 13px; border-bottom: 1px solid #21262d; }
77
+ table.repos th { color: #8b949e; text-transform: uppercase; letter-spacing: .5px; font-size: 11px; background: #0d1117; }
78
+ table.repos td.num { font-family: monospace; color: #f0f6fc; text-align: right; }
79
+ table.repos tr:last-child td { border-bottom: none; }
80
+ table.repos tr:hover td { background: #1c2128; }
81
+ .repo-model { color: #a371f7; }
82
+ .repo-none { color: #484f58; font-style: italic; }
83
+ .updated { font-size: 11px; color: #484f58; margin-top: 16px; text-align: right; }
84
+ .model-pill { background: #6e40c9; color: #fff; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 10px; text-transform: uppercase; letter-spacing: .5px; }
85
+ .card.cost h2 { color: #a371f7; }
86
+ .cost-usd { font-size: 22px; font-weight: 700; color: #3fb950; }
87
+ .cost-sub { font-size: 12px; color: #8b949e; margin-top: 4px; }
88
+ .repo-link { cursor: pointer; }
89
+ .repo-link:hover td { color: #58a6ff; }
90
+ .repo-detail { position: fixed; inset: 0; background: rgba(0,0,0,.6); display: none; align-items: center; justify-content: center; z-index: 50; }
91
+ .repo-detail.open { display: flex; }
92
+ .repo-detail-box { background: #161b22; border: 1px solid #30363d; border-radius: 10px; padding: 24px; width: 560px; max-width: 92vw; max-height: 86vh; overflow-y: auto; }
93
+ .repo-detail-box h2 { font-size: 14px; color: #f0f6fc; margin-bottom: 14px; display: flex; justify-content: space-between; align-items: center; }
94
+ .repo-close { cursor: pointer; color: #8b949e; font-size: 20px; line-height: 1; border: none; background: none; padding: 0 4px; }
95
+ .repo-close:hover { color: #f0f6fc; }
96
+ .repo-path { font-size: 11px; color: #484f58; word-break: break-all; margin: -8px 0 12px; }
97
+ .placeholder { text-align: center; padding: 48px 16px; color: #8b949e; }
98
+ .placeholder .em { font-size: 40px; margin-bottom: 12px; }
99
+ .placeholder h2 { font-size: 16px; color: #f0f6fc; margin-bottom: 8px; }
100
+ .placeholder p { font-size: 13px; line-height: 1.6; max-width: 420px; margin: 0 auto; }
101
+ </style>
102
+ `;
103
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * "Summary" tab panel — aggregate counts across all registered repos.
3
+ *
4
+ * Populated by the index poller in `script.ts`.
5
+ */
6
+ export function summaryTab() {
7
+ return `<!-- Summary (aggregate across all repos) -->
8
+ <div class="tab-panel" id="panel-summary">
9
+ <div class="summary-grid">
10
+ <div class="summary-card"><div class="num" id="sm-repos">0</div><div class="lbl">Repositories</div></div>
11
+ <div class="summary-card"><div class="num" id="sm-checkpoints">0</div><div class="lbl">Total Checkpoints</div></div>
12
+ <div class="summary-card"><div class="num" id="sm-saved">0</div><div class="lbl">Total Tokens Saved</div></div>
13
+ <div class="summary-card"><div class="num" id="sm-bytes">0 B</div><div class="lbl">Compressed-Original</div></div>
14
+ </div>
15
+ <div class="updated" id="sm-updated"></div>
16
+ </div>
17
+
18
+ `;
19
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * HTML template for the dashboard server — composer.
3
+ *
4
+ * Originally a single 518-line monolithic file returning one large HTML page
5
+ * template. Split into per-concern fragments under `./html/` (each tab is its
6
+ * own file; plus shared styles, header chrome, the repo-detail modal, and the
7
+ * client script). This file composes them back into the full document.
8
+ *
9
+ * `tierName` is threaded into the heading badge and the Configuration card.
10
+ *
11
+ * Fragment layout:
12
+ * headOpen() — <!DOCTYPE html>…<title>
13
+ * styles() — <style>…</style> (shared CSS, design-system level)
14
+ * bodyOpen(tier) — </head><body>…<h1>…</nav> (incl. High Score future tab)
15
+ * currentRepoTab(tier) — panel-current (cards + event stream + in-panel table)
16
+ * allReposTab() — panel-all (machine-wide repo table)
17
+ * summaryTab() — panel-summary (aggregate cards)
18
+ * highScoreTab() — panel-highscore (FUTURE placeholder, next milestone)
19
+ * repoDetailModal() — per-repo overlay (position-independent)
20
+ * script() — <script>…</script></body></html>
21
+ */
22
+ import { headOpen } from "./html/head-open.js";
23
+ import { styles } from "./html/styles.js";
24
+ import { bodyOpen } from "./html/body-open.js";
25
+ import { currentRepoTab } from "./html/current-repo-tab.js";
26
+ import { allReposTab } from "./html/all-repos-tab.js";
27
+ import { summaryTab } from "./html/summary-tab.js";
28
+ import { highScoreTab } from "./html/high-score-tab.js";
29
+ import { repoDetailModal } from "./html/repo-detail-modal.js";
30
+ import { script } from "./html/script.js";
31
+ export function dashboardHtml(tierName) {
32
+ return (headOpen() +
33
+ styles() +
34
+ bodyOpen(tierName) +
35
+ currentRepoTab(tierName) +
36
+ repoDetailModal() +
37
+ allReposTab() +
38
+ summaryTab() +
39
+ highScoreTab() +
40
+ script());
41
+ }