@echomem/mcp 1.4.2 → 1.4.4
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/README.md +12 -7
- package/dist/hud/capsule.js +68 -25
- package/dist/hud/cli.js +0 -0
- package/dist/hud/metric.js +16 -3
- package/dist/hud/monitor.js +152 -14
- package/dist/hud/web.js +595 -429
- package/dist/index.js +14 -1
- package/dist/package-metadata.js +4 -2
- package/dist/setup.js +306 -9
- package/dist/update-check.js +154 -0
- package/dist/v1-contract.js +18 -1
- package/package.json +2 -2
package/dist/hud/web.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MCP_PACKAGE_VERSION } from "../package-metadata.js";
|
|
1
2
|
export const HUD_HTML = String.raw `<!doctype html>
|
|
2
3
|
<html lang="en">
|
|
3
4
|
<head>
|
|
@@ -7,93 +8,353 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
7
8
|
<style>
|
|
8
9
|
:root {
|
|
9
10
|
color-scheme: light;
|
|
10
|
-
--
|
|
11
|
-
--
|
|
12
|
-
--
|
|
13
|
-
--
|
|
14
|
-
--
|
|
15
|
-
--
|
|
16
|
-
--
|
|
17
|
-
--
|
|
11
|
+
--paper: #f5f3ee;
|
|
12
|
+
--card: rgba(255, 253, 248, 0.98);
|
|
13
|
+
--ink: #141414;
|
|
14
|
+
--blue: #1a3a8f;
|
|
15
|
+
--muted: #6b675d;
|
|
16
|
+
--faint: #8a8678;
|
|
17
|
+
--brick: #c7372f;
|
|
18
|
+
--green: #43d17a;
|
|
19
|
+
--amber: #e9b949;
|
|
20
|
+
--line: rgba(26, 58, 143, 0.22);
|
|
21
|
+
--track: rgba(26, 58, 143, 0.1);
|
|
22
|
+
--shadow: drop-shadow(0 2px 7px rgba(26, 58, 143, 0.12));
|
|
18
23
|
font-family: "Avenir Next", Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
19
24
|
background: transparent;
|
|
20
25
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
26
|
+
* { box-sizing: border-box; }
|
|
27
|
+
body {
|
|
28
|
+
margin: 0;
|
|
29
|
+
min-height: 100vh;
|
|
30
|
+
display: grid;
|
|
31
|
+
place-items: start center;
|
|
32
|
+
overflow: hidden;
|
|
33
|
+
background: transparent;
|
|
34
|
+
color: var(--ink);
|
|
35
|
+
}
|
|
36
|
+
#hud {
|
|
37
|
+
width: min(360px, calc(100vw - 16px));
|
|
38
|
+
background: transparent;
|
|
39
|
+
-webkit-font-smoothing: antialiased;
|
|
40
|
+
}
|
|
41
|
+
.glance {
|
|
42
|
+
position: relative;
|
|
43
|
+
height: 112px;
|
|
44
|
+
user-select: none;
|
|
45
|
+
cursor: default;
|
|
46
|
+
}
|
|
47
|
+
.drag {
|
|
48
|
+
position: absolute;
|
|
49
|
+
left: 4px;
|
|
50
|
+
top: 40px;
|
|
51
|
+
width: 20px;
|
|
52
|
+
height: 32px;
|
|
53
|
+
border-radius: 8px;
|
|
54
|
+
-webkit-app-region: drag;
|
|
55
|
+
opacity: 0.78;
|
|
56
|
+
z-index: 5;
|
|
57
|
+
}
|
|
58
|
+
.drag::before {
|
|
59
|
+
content: "";
|
|
60
|
+
position: absolute;
|
|
61
|
+
left: 4px;
|
|
62
|
+
top: 7px;
|
|
63
|
+
width: 4px;
|
|
64
|
+
height: 4px;
|
|
65
|
+
border-radius: 50%;
|
|
66
|
+
background: rgba(26, 58, 143, 0.42);
|
|
67
|
+
box-shadow: 0 10px 0 rgba(26, 58, 143, 0.42), 10px 0 0 rgba(26, 58, 143, 0.42), 10px 10px 0 rgba(26, 58, 143, 0.42);
|
|
68
|
+
}
|
|
69
|
+
.mascot-stage {
|
|
70
|
+
position: absolute;
|
|
71
|
+
left: 24px;
|
|
72
|
+
top: 26px;
|
|
73
|
+
z-index: 4;
|
|
74
|
+
width: 70px;
|
|
75
|
+
height: 70px;
|
|
76
|
+
overflow: hidden;
|
|
77
|
+
border-radius: 18px;
|
|
78
|
+
transition: filter 0.45s ease;
|
|
79
|
+
}
|
|
80
|
+
@keyframes echo-bob {
|
|
81
|
+
0%, 100% { transform: translateY(0); }
|
|
82
|
+
50% { transform: translateY(-2.5px); }
|
|
83
|
+
}
|
|
84
|
+
.mascot {
|
|
85
|
+
position: absolute;
|
|
86
|
+
left: -4px;
|
|
87
|
+
top: -2px;
|
|
88
|
+
width: 80px;
|
|
89
|
+
height: 78px;
|
|
90
|
+
object-fit: cover;
|
|
91
|
+
object-position: center top;
|
|
92
|
+
pointer-events: none;
|
|
93
|
+
animation: echo-bob 4.5s ease-in-out infinite;
|
|
94
|
+
}
|
|
95
|
+
@media (prefers-reduced-motion: reduce) {
|
|
96
|
+
.mascot { animation: none; }
|
|
97
|
+
}
|
|
98
|
+
.status-bubble {
|
|
99
|
+
position: absolute;
|
|
100
|
+
left: 84px;
|
|
101
|
+
right: 6px;
|
|
102
|
+
top: 15px;
|
|
103
|
+
min-height: 72px;
|
|
104
|
+
z-index: 2;
|
|
105
|
+
border: 1.7px solid rgba(26, 58, 143, 0.78);
|
|
106
|
+
border-radius: 34px;
|
|
107
|
+
background: #fdfaf2;
|
|
108
|
+
filter: var(--shadow);
|
|
109
|
+
}
|
|
110
|
+
.status-bubble::after {
|
|
111
|
+
content: "";
|
|
112
|
+
position: absolute;
|
|
113
|
+
left: 58px;
|
|
114
|
+
bottom: -14px;
|
|
115
|
+
width: 18px;
|
|
116
|
+
height: 18px;
|
|
117
|
+
border-left: 1.7px solid rgba(26, 58, 143, 0.78);
|
|
118
|
+
border-bottom: 1.7px solid rgba(26, 58, 143, 0.78);
|
|
119
|
+
border-bottom-left-radius: 10px;
|
|
120
|
+
background: #fdfaf2;
|
|
121
|
+
transform: rotate(-18deg) skewY(-18deg);
|
|
122
|
+
}
|
|
123
|
+
.copy {
|
|
124
|
+
position: relative;
|
|
125
|
+
z-index: 2;
|
|
126
|
+
min-width: 0;
|
|
127
|
+
display: grid;
|
|
128
|
+
gap: 4px;
|
|
129
|
+
padding: 20px 22px 18px 30px;
|
|
130
|
+
}
|
|
131
|
+
.dot {
|
|
132
|
+
position: absolute;
|
|
133
|
+
left: 14px;
|
|
134
|
+
top: 23px;
|
|
135
|
+
z-index: 3;
|
|
136
|
+
width: 10px;
|
|
137
|
+
height: 10px;
|
|
138
|
+
border: 2px solid #fdfaf2;
|
|
139
|
+
border-radius: 999px;
|
|
140
|
+
background: var(--green);
|
|
141
|
+
box-shadow: 0 0 0 1px rgba(26, 58, 143, 0.14);
|
|
142
|
+
}
|
|
143
|
+
.dot.amber { background: var(--amber); }
|
|
40
144
|
.dot.red { background: #ff5f57; }
|
|
41
|
-
.details { display: none; position: relative; margin: 2px 8px 0 24px; border: 1px solid var(--echo-line-brand); border-radius: 18px; background: rgba(253, 250, 242, .98); padding: 14px 16px 15px; font-size: 12px; color: var(--echo-ink-text); filter: var(--echo-shadow-blue); }
|
|
42
|
-
#hud.open .details { display: block; }
|
|
43
|
-
.metric { display: flex; justify-content: space-between; gap: 16px; padding: 6px 0; border-bottom: 1px solid rgba(26, 58, 143, .10); }
|
|
44
|
-
.metric:last-child { border-bottom: 0; }
|
|
45
|
-
.metric span { color: var(--echo-ink-mute); }
|
|
46
|
-
.metric strong { color: var(--echo-ink-primary); font-weight: 700; }
|
|
47
|
-
button.renew-btn { display: none; width: 100%; margin-top: 12px; border: 1px solid rgba(199, 55, 47, .30); border-radius: 999px; padding: 9px 10px; background: rgba(199, 55, 47, .08); color: #c7372f; font-weight: 700; font-size: 13px; cursor: pointer; transition: background .2s ease; }
|
|
48
|
-
button.renew-btn:hover { background: rgba(199, 55, 47, .14); }
|
|
49
|
-
button.renew-btn:active { background: rgba(199, 55, 47, .22); }
|
|
50
|
-
button.renew-btn.loading { opacity: .6; cursor: wait; }
|
|
51
|
-
.renew-result { display: none; margin-top: 10px; border: 1px solid rgba(26, 58, 143, .18); border-radius: 12px; background: rgba(26, 58, 143, .04); padding: 10px 12px; }
|
|
52
|
-
.renew-result .renew-msg { font-size: 11.5px; font-weight: 700; color: var(--echo-ink-primary); margin-bottom: 6px; }
|
|
53
|
-
.renew-result pre { max-height: 120px; overflow: auto; font-family: "JetBrains Mono", ui-monospace, monospace; font-size: 9.5px; color: var(--echo-ink-text); white-space: pre-wrap; word-break: break-word; margin: 0; }
|
|
54
|
-
.renew-result .copy-btn { display: inline-block; margin-top: 6px; border: 1px solid rgba(26, 58, 143, .22); border-radius: 999px; padding: 5px 14px; background: rgba(26, 58, 143, .08); color: var(--echo-ink-primary); font-weight: 700; font-size: 11px; cursor: pointer; }
|
|
55
|
-
.renew-result .copy-btn:hover { background: rgba(26, 58, 143, .14); }
|
|
56
|
-
.path { margin-top: 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--echo-ink-mute); }
|
|
57
|
-
.missing { margin-top: 8px; color: var(--echo-ink-mute); }
|
|
58
|
-
.summary { margin: 0 0 8px; font-size: 12.5px; font-weight: 700; color: var(--echo-ink-primary); line-height: 1.25; }
|
|
59
|
-
.note { margin: 10px 0 0; font-size: 10.5px; color: var(--echo-ink-mute); line-height: 1.3; }
|
|
60
145
|
.dot.idle { background: #c9c5b8; }
|
|
61
|
-
.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
#hud.
|
|
96
|
-
|
|
146
|
+
.main {
|
|
147
|
+
min-width: 0;
|
|
148
|
+
overflow: hidden;
|
|
149
|
+
text-overflow: ellipsis;
|
|
150
|
+
white-space: nowrap;
|
|
151
|
+
font-size: 15px;
|
|
152
|
+
font-weight: 800;
|
|
153
|
+
letter-spacing: 0;
|
|
154
|
+
line-height: 1.1;
|
|
155
|
+
color: var(--ink);
|
|
156
|
+
}
|
|
157
|
+
.client {
|
|
158
|
+
min-width: 0;
|
|
159
|
+
overflow: hidden;
|
|
160
|
+
text-overflow: ellipsis;
|
|
161
|
+
white-space: nowrap;
|
|
162
|
+
font-family: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
|
|
163
|
+
font-size: 10px;
|
|
164
|
+
letter-spacing: 0.12em;
|
|
165
|
+
line-height: 1;
|
|
166
|
+
text-transform: uppercase;
|
|
167
|
+
color: var(--faint);
|
|
168
|
+
}
|
|
169
|
+
.details {
|
|
170
|
+
display: none;
|
|
171
|
+
position: relative;
|
|
172
|
+
margin: 2px 8px 0 24px;
|
|
173
|
+
padding: 16px 17px 15px;
|
|
174
|
+
border: 1px solid var(--line);
|
|
175
|
+
border-radius: 16px;
|
|
176
|
+
background: var(--card);
|
|
177
|
+
color: var(--ink);
|
|
178
|
+
filter: var(--shadow);
|
|
179
|
+
}
|
|
180
|
+
#hud.open .details { display: block; }
|
|
181
|
+
.head {
|
|
182
|
+
display: flex;
|
|
183
|
+
justify-content: flex-end;
|
|
184
|
+
min-height: 15px;
|
|
185
|
+
margin-bottom: 10px;
|
|
186
|
+
}
|
|
187
|
+
.turn {
|
|
188
|
+
overflow: hidden;
|
|
189
|
+
text-overflow: ellipsis;
|
|
190
|
+
white-space: nowrap;
|
|
191
|
+
font-size: 11px;
|
|
192
|
+
color: var(--faint);
|
|
193
|
+
}
|
|
194
|
+
.state-row {
|
|
195
|
+
display: flex;
|
|
196
|
+
align-items: center;
|
|
197
|
+
gap: 9px;
|
|
198
|
+
margin-bottom: 7px;
|
|
199
|
+
}
|
|
200
|
+
.state-dot {
|
|
201
|
+
width: 11px;
|
|
202
|
+
height: 11px;
|
|
203
|
+
flex: none;
|
|
204
|
+
border-radius: 999px;
|
|
205
|
+
background: var(--green);
|
|
206
|
+
box-shadow: 0 0 0 1px rgba(26, 58, 143, 0.12);
|
|
207
|
+
}
|
|
208
|
+
.state-dot.amber { background: var(--amber); }
|
|
209
|
+
.state-dot.red { background: #ff5f57; }
|
|
210
|
+
.state-word {
|
|
211
|
+
min-width: 0;
|
|
212
|
+
overflow: hidden;
|
|
213
|
+
text-overflow: ellipsis;
|
|
214
|
+
white-space: nowrap;
|
|
215
|
+
font-size: 16px;
|
|
216
|
+
font-weight: 800;
|
|
217
|
+
}
|
|
218
|
+
.full {
|
|
219
|
+
min-height: 17px;
|
|
220
|
+
margin-bottom: 14px;
|
|
221
|
+
font-size: 12px;
|
|
222
|
+
font-weight: 650;
|
|
223
|
+
color: #4a7a3e;
|
|
224
|
+
}
|
|
225
|
+
.lead {
|
|
226
|
+
margin-bottom: 5px;
|
|
227
|
+
font-size: 13.5px;
|
|
228
|
+
font-weight: 750;
|
|
229
|
+
color: #2f7d4f;
|
|
230
|
+
}
|
|
231
|
+
.evidence {
|
|
232
|
+
min-height: 38px;
|
|
233
|
+
margin-bottom: 12px;
|
|
234
|
+
color: var(--muted);
|
|
235
|
+
font-size: 12.5px;
|
|
236
|
+
line-height: 1.5;
|
|
237
|
+
}
|
|
238
|
+
.oldbar {
|
|
239
|
+
display: flex;
|
|
240
|
+
height: 9px;
|
|
241
|
+
margin: 0;
|
|
242
|
+
overflow: hidden;
|
|
243
|
+
border-radius: 999px;
|
|
244
|
+
background: var(--track);
|
|
245
|
+
}
|
|
246
|
+
.oldbar span {
|
|
247
|
+
display: block;
|
|
248
|
+
height: 100%;
|
|
249
|
+
min-width: 0;
|
|
250
|
+
transition: width 0.35s ease;
|
|
251
|
+
}
|
|
252
|
+
.oldbar .useful { width: 100%; background: var(--blue); }
|
|
253
|
+
.oldbar .old { width: 0; background: var(--brick); }
|
|
254
|
+
.alert {
|
|
255
|
+
display: none;
|
|
256
|
+
margin-top: 13px;
|
|
257
|
+
padding: 9px 0 9px 12px;
|
|
258
|
+
border-left: 3px solid rgba(199, 55, 47, 0.42);
|
|
259
|
+
}
|
|
260
|
+
.alert-title {
|
|
261
|
+
margin-bottom: 4px;
|
|
262
|
+
color: var(--brick);
|
|
263
|
+
font-size: 12.5px;
|
|
264
|
+
font-weight: 750;
|
|
265
|
+
}
|
|
266
|
+
.alert-detail {
|
|
267
|
+
color: var(--muted);
|
|
268
|
+
font-size: 12px;
|
|
269
|
+
line-height: 1.45;
|
|
270
|
+
}
|
|
271
|
+
.nudge {
|
|
272
|
+
display: none;
|
|
273
|
+
margin-top: 13px;
|
|
274
|
+
padding: 10px 12px;
|
|
275
|
+
border-radius: 12px;
|
|
276
|
+
font-size: 12.5px;
|
|
277
|
+
line-height: 1.45;
|
|
278
|
+
}
|
|
279
|
+
.nudge.calm {
|
|
280
|
+
border: 1px solid rgba(26, 58, 143, 0.14);
|
|
281
|
+
background: rgba(26, 58, 143, 0.05);
|
|
282
|
+
color: var(--muted);
|
|
283
|
+
}
|
|
284
|
+
.nudge.red {
|
|
285
|
+
border: 1px solid rgba(199, 55, 47, 0.22);
|
|
286
|
+
background: rgba(199, 55, 47, 0.07);
|
|
287
|
+
color: #7a241e;
|
|
288
|
+
}
|
|
289
|
+
button.renew-btn {
|
|
290
|
+
display: none;
|
|
291
|
+
width: 100%;
|
|
292
|
+
margin-top: 12px;
|
|
293
|
+
border: 1px solid rgba(199, 55, 47, 0.3);
|
|
294
|
+
border-radius: 999px;
|
|
295
|
+
padding: 9px 10px;
|
|
296
|
+
background: rgba(199, 55, 47, 0.08);
|
|
297
|
+
color: var(--brick);
|
|
298
|
+
font-weight: 750;
|
|
299
|
+
font-size: 13px;
|
|
300
|
+
cursor: pointer;
|
|
301
|
+
transition: background 0.2s ease, opacity 0.2s ease;
|
|
302
|
+
}
|
|
303
|
+
button.renew-btn:hover { background: rgba(199, 55, 47, 0.14); }
|
|
304
|
+
button.renew-btn:active { background: rgba(199, 55, 47, 0.22); }
|
|
305
|
+
button.renew-btn.loading { opacity: 0.6; cursor: wait; }
|
|
306
|
+
.renew-result {
|
|
307
|
+
display: none;
|
|
308
|
+
margin-top: 10px;
|
|
309
|
+
border: 1px solid rgba(26, 58, 143, 0.18);
|
|
310
|
+
border-radius: 12px;
|
|
311
|
+
background: rgba(26, 58, 143, 0.04);
|
|
312
|
+
padding: 10px 12px;
|
|
313
|
+
}
|
|
314
|
+
.renew-result .renew-msg {
|
|
315
|
+
margin-bottom: 6px;
|
|
316
|
+
color: var(--blue);
|
|
317
|
+
font-size: 11.5px;
|
|
318
|
+
font-weight: 750;
|
|
319
|
+
}
|
|
320
|
+
.renew-result pre {
|
|
321
|
+
max-height: 120px;
|
|
322
|
+
overflow: auto;
|
|
323
|
+
margin: 0;
|
|
324
|
+
white-space: pre-wrap;
|
|
325
|
+
word-break: break-word;
|
|
326
|
+
color: var(--ink);
|
|
327
|
+
font-family: "JetBrains Mono", ui-monospace, monospace;
|
|
328
|
+
font-size: 9.5px;
|
|
329
|
+
}
|
|
330
|
+
.renew-result .copy-btn {
|
|
331
|
+
display: inline-block;
|
|
332
|
+
width: auto;
|
|
333
|
+
margin-top: 6px;
|
|
334
|
+
border: 1px solid rgba(26, 58, 143, 0.22);
|
|
335
|
+
border-radius: 999px;
|
|
336
|
+
padding: 5px 14px;
|
|
337
|
+
background: rgba(26, 58, 143, 0.08);
|
|
338
|
+
color: var(--blue);
|
|
339
|
+
font-weight: 750;
|
|
340
|
+
font-size: 11px;
|
|
341
|
+
cursor: pointer;
|
|
342
|
+
}
|
|
343
|
+
.meta,
|
|
344
|
+
.path,
|
|
345
|
+
.missing {
|
|
346
|
+
overflow: hidden;
|
|
347
|
+
text-overflow: ellipsis;
|
|
348
|
+
white-space: nowrap;
|
|
349
|
+
color: var(--faint);
|
|
350
|
+
font-size: 11px;
|
|
351
|
+
}
|
|
352
|
+
.meta { margin-top: 13px; }
|
|
353
|
+
.path { margin-top: 8px; }
|
|
354
|
+
.missing { margin-top: 6px; }
|
|
355
|
+
#hud.s-green .mascot-stage { filter: drop-shadow(0 2px 6px rgba(67, 209, 122, 0.3)); }
|
|
356
|
+
#hud.s-amber .mascot-stage { filter: drop-shadow(0 2px 7px rgba(233, 185, 73, 0.42)); }
|
|
357
|
+
#hud.s-red .mascot-stage { filter: drop-shadow(0 3px 8px rgba(199, 55, 47, 0.48)); }
|
|
97
358
|
#hud.s-amber .mascot { animation-duration: 5.6s; }
|
|
98
359
|
#hud.s-red .mascot { animation-duration: 6.8s; }
|
|
99
360
|
</style>
|
|
@@ -103,404 +364,309 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
103
364
|
<div class="glance" id="toggle">
|
|
104
365
|
<span class="drag" title="Drag"></span>
|
|
105
366
|
<span class="mascot-stage"><img class="mascot" src="/assets/hud/echo-face-cutout.png" alt="" /></span>
|
|
106
|
-
<div class="
|
|
107
|
-
<div class="paper-bubble-skin" aria-hidden="true">
|
|
108
|
-
<svg class="paper-bubble-outline" preserveAspectRatio="none" focusable="false">
|
|
109
|
-
<path />
|
|
110
|
-
</svg>
|
|
111
|
-
</div>
|
|
367
|
+
<div class="status-bubble">
|
|
112
368
|
<span class="dot" id="dot"></span>
|
|
113
369
|
<span class="copy">
|
|
114
|
-
<span class="main" id="main">Checking context
|
|
370
|
+
<span class="main" id="main">Checking context...</span>
|
|
115
371
|
<span class="client" id="client">EchoMem HUD</span>
|
|
116
372
|
</span>
|
|
117
373
|
</div>
|
|
118
374
|
</div>
|
|
119
375
|
<div class="details">
|
|
120
|
-
<div class="
|
|
121
|
-
|
|
122
|
-
<
|
|
376
|
+
<div class="head"><span class="turn" id="turn"></span></div>
|
|
377
|
+
<div class="state-row">
|
|
378
|
+
<span class="state-dot" id="stateDot"></span>
|
|
379
|
+
<span class="state-word" id="stateWord">Fresh</span>
|
|
123
380
|
</div>
|
|
124
|
-
<
|
|
125
|
-
<
|
|
126
|
-
<div class="
|
|
127
|
-
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
<polyline id="sparkline" fill="none" stroke="var(--echo-ink-primary)" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round" points=""></polyline>
|
|
131
|
-
</svg>
|
|
132
|
-
<div class="trend-empty" id="trendempty">Collecting a few turns…</div>
|
|
381
|
+
<div class="full" id="fullness"></div>
|
|
382
|
+
<div class="lead" id="lead">Clean and roomy</div>
|
|
383
|
+
<div class="evidence" id="evidence">Collecting enough local context to judge the window.</div>
|
|
384
|
+
<div class="oldbar" aria-hidden="true">
|
|
385
|
+
<span class="useful" id="usefulbar"></span>
|
|
386
|
+
<span class="old" id="oldbar"></span>
|
|
133
387
|
</div>
|
|
134
|
-
<div class="
|
|
135
|
-
<div class="
|
|
136
|
-
<div class="
|
|
137
|
-
<div class="tile"><b id="t-reads">—</b><span>files read</span></div>
|
|
138
|
-
<div class="tile"><b id="t-edits">—</b><span>edits</span></div>
|
|
388
|
+
<div class="alert" id="alert">
|
|
389
|
+
<div class="alert-title" id="alertTitle"></div>
|
|
390
|
+
<div class="alert-detail" id="alertDetail"></div>
|
|
139
391
|
</div>
|
|
140
|
-
<div class="
|
|
141
|
-
<div class="section">
|
|
142
|
-
<div class="section-h"><span>Tools used</span><strong id="toolstotal"></strong></div>
|
|
143
|
-
<div class="bars" id="toolbars"></div>
|
|
144
|
-
</div>
|
|
145
|
-
<div class="section" id="bigsec" style="display:none">
|
|
146
|
-
<div class="section-h"><span>Output volume · by tool</span><strong id="bigtotal"></strong></div>
|
|
147
|
-
<div class="bars" id="bigtools"></div>
|
|
148
|
-
</div>
|
|
149
|
-
<p class="note">Tracked, lower-bound estimate — there may be more we can't see.</p>
|
|
150
|
-
<p class="nudge" id="nudge" style="display:none"></p>
|
|
392
|
+
<div class="nudge" id="nudge"></div>
|
|
151
393
|
<button class="renew-btn" id="renewbtn">Renew session</button>
|
|
152
394
|
<div class="renew-result" id="renewresult">
|
|
153
395
|
<div class="renew-msg" id="renewmsg"></div>
|
|
154
396
|
<pre id="renewpre"></pre>
|
|
155
397
|
<button class="copy-btn" id="copybtn">Copy to clipboard</button>
|
|
156
398
|
</div>
|
|
399
|
+
<div class="meta" id="meta"></div>
|
|
157
400
|
<div class="path" id="path"></div>
|
|
158
401
|
<div class="missing" id="missing"></div>
|
|
159
402
|
</div>
|
|
160
403
|
</section>
|
|
161
404
|
<script>
|
|
162
|
-
const hud = document.getElementById(
|
|
163
|
-
const dot = document.getElementById(
|
|
164
|
-
const main = document.getElementById(
|
|
165
|
-
const client = document.getElementById(
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
const
|
|
173
|
-
const
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
const
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
const
|
|
183
|
-
const
|
|
184
|
-
const
|
|
185
|
-
const
|
|
186
|
-
const
|
|
187
|
-
const deltaEl = document.getElementById('delta');
|
|
188
|
-
const renewBtn = document.getElementById('renewbtn');
|
|
189
|
-
const renewResult = document.getElementById('renewresult');
|
|
190
|
-
const renewMsg = document.getElementById('renewmsg');
|
|
191
|
-
const renewPre = document.getElementById('renewpre');
|
|
192
|
-
const copyBtn = document.getElementById('copybtn');
|
|
193
|
-
const history = [];
|
|
194
|
-
let lastSource = '';
|
|
405
|
+
const hud = document.getElementById("hud");
|
|
406
|
+
const dot = document.getElementById("dot");
|
|
407
|
+
const main = document.getElementById("main");
|
|
408
|
+
const client = document.getElementById("client");
|
|
409
|
+
const turnEl = document.getElementById("turn");
|
|
410
|
+
const stateDot = document.getElementById("stateDot");
|
|
411
|
+
const stateWordEl = document.getElementById("stateWord");
|
|
412
|
+
const fullness = document.getElementById("fullness");
|
|
413
|
+
const lead = document.getElementById("lead");
|
|
414
|
+
const evidence = document.getElementById("evidence");
|
|
415
|
+
const usefulbar = document.getElementById("usefulbar");
|
|
416
|
+
const oldbar = document.getElementById("oldbar");
|
|
417
|
+
const alertEl = document.getElementById("alert");
|
|
418
|
+
const alertTitle = document.getElementById("alertTitle");
|
|
419
|
+
const alertDetail = document.getElementById("alertDetail");
|
|
420
|
+
const nudge = document.getElementById("nudge");
|
|
421
|
+
const renewBtn = document.getElementById("renewbtn");
|
|
422
|
+
const renewResult = document.getElementById("renewresult");
|
|
423
|
+
const renewMsg = document.getElementById("renewmsg");
|
|
424
|
+
const renewPre = document.getElementById("renewpre");
|
|
425
|
+
const copyBtn = document.getElementById("copybtn");
|
|
426
|
+
const metaEl = document.getElementById("meta");
|
|
427
|
+
const pathEl = document.getElementById("path");
|
|
428
|
+
const missing = document.getElementById("missing");
|
|
429
|
+
const hudVersion = "v${MCP_PACKAGE_VERSION}";
|
|
195
430
|
let prevTurnState = null;
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
document.getElementById('toggle').addEventListener('click', (event) => {
|
|
203
|
-
if (event.target instanceof HTMLElement && event.target.closest('.drag')) return;
|
|
204
|
-
hud.classList.toggle('open');
|
|
205
|
-
const open = hud.classList.contains('open');
|
|
206
|
-
requestAnimationFrame(() => {
|
|
207
|
-
updateBubbleSkins();
|
|
208
|
-
const height = open ? Math.ceil(hud.getBoundingClientRect().height) + 14 : undefined;
|
|
209
|
-
window.echomemHud && window.echomemHud.setOpen(open, height);
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
if (toolbars) toolbars.addEventListener('click', (event) => {
|
|
213
|
-
const wrap = event.target && event.target.closest ? event.target.closest('.barwrap') : null;
|
|
214
|
-
if (!wrap || !wrap.classList.contains('has-files')) return;
|
|
215
|
-
const open = wrap.classList.toggle('open');
|
|
216
|
-
const nameEl = wrap.querySelector('.bar-name');
|
|
217
|
-
if (nameEl) nameEl.textContent = (open ? '▾ ' : '▸ ') + (wrap.getAttribute('data-tool') || '');
|
|
218
|
-
requestAnimationFrame(() => {
|
|
219
|
-
if (hud.classList.contains('open')) {
|
|
220
|
-
const height = Math.ceil(hud.getBoundingClientRect().height) + 14;
|
|
221
|
-
window.echomemHud && window.echomemHud.setOpen(true, height);
|
|
222
|
-
}
|
|
223
|
-
});
|
|
431
|
+
let lastCapsule = "";
|
|
432
|
+
|
|
433
|
+
document.getElementById("toggle").addEventListener("click", (event) => {
|
|
434
|
+
if (event.target instanceof HTMLElement && event.target.closest(".drag")) return;
|
|
435
|
+
hud.classList.toggle("open");
|
|
436
|
+
syncHeight();
|
|
224
437
|
});
|
|
225
|
-
|
|
226
|
-
if (renewBtn) renewBtn.addEventListener(
|
|
227
|
-
renewBtn.classList.add(
|
|
228
|
-
renewBtn.textContent =
|
|
438
|
+
|
|
439
|
+
if (renewBtn) renewBtn.addEventListener("click", async () => {
|
|
440
|
+
renewBtn.classList.add("loading");
|
|
441
|
+
renewBtn.textContent = "Capturing...";
|
|
229
442
|
try {
|
|
230
|
-
const res = await fetch(
|
|
443
|
+
const res = await fetch("/capsule");
|
|
231
444
|
const data = await res.json();
|
|
232
|
-
if (!data.ok) {
|
|
233
|
-
|
|
445
|
+
if (!data.ok) {
|
|
446
|
+
renewBtn.textContent = "Renew session";
|
|
447
|
+
renewBtn.classList.remove("loading");
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
const prompt = "Continue my previous session. Verify anything that references file state before trusting it.\n\n" + data.capsule;
|
|
234
451
|
lastCapsule = prompt;
|
|
235
452
|
if (renewPre) renewPre.textContent = prompt;
|
|
236
|
-
if (renewMsg) renewMsg.textContent =
|
|
237
|
-
if (renewResult)
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
renewBtn.textContent = 'Renew session';
|
|
247
|
-
renewBtn.classList.remove('loading');
|
|
453
|
+
if (renewMsg) renewMsg.textContent = "Open a new session and paste this:";
|
|
454
|
+
if (renewResult) renewResult.style.display = "block";
|
|
455
|
+
try {
|
|
456
|
+
await navigator.clipboard.writeText(prompt);
|
|
457
|
+
if (renewMsg) renewMsg.textContent = "Copied. Open a new session and paste:";
|
|
458
|
+
} catch {}
|
|
459
|
+
} catch {}
|
|
460
|
+
renewBtn.textContent = "Renew session";
|
|
461
|
+
renewBtn.classList.remove("loading");
|
|
462
|
+
syncHeight();
|
|
248
463
|
});
|
|
249
|
-
|
|
464
|
+
|
|
465
|
+
if (copyBtn) copyBtn.addEventListener("click", async () => {
|
|
250
466
|
if (!lastCapsule) return;
|
|
251
|
-
try {
|
|
467
|
+
try {
|
|
468
|
+
await navigator.clipboard.writeText(lastCapsule);
|
|
469
|
+
copyBtn.textContent = "Copied";
|
|
470
|
+
setTimeout(() => { copyBtn.textContent = "Copy to clipboard"; }, 1500);
|
|
471
|
+
} catch {}
|
|
252
472
|
});
|
|
253
|
-
|
|
473
|
+
|
|
474
|
+
const events = new EventSource("/events");
|
|
254
475
|
events.onmessage = (event) => render(JSON.parse(event.data));
|
|
255
|
-
fetch(
|
|
476
|
+
fetch("/state").then((r) => r.json()).then(render).catch(() => {});
|
|
477
|
+
|
|
256
478
|
function render(state) {
|
|
257
479
|
const score = state.active || state;
|
|
258
|
-
if (!score || typeof score.usefulPct !==
|
|
259
|
-
|
|
260
|
-
hud.classList.remove('s-green', 's-amber', 's-red');
|
|
261
|
-
if (deltaEl) deltaEl.style.display = 'none';
|
|
262
|
-
prevTurnState = null;
|
|
263
|
-
main.textContent = 'No active session yet';
|
|
264
|
-
client.textContent = '';
|
|
265
|
-
if (summary) summary.textContent = 'Start working in Codex or Claude and I’ll check the context here.';
|
|
266
|
-
if (fullness) fullness.textContent = '';
|
|
267
|
-
if (meterfill) meterfill.style.width = '0';
|
|
268
|
-
history.length = 0;
|
|
269
|
-
if (spark) spark.style.display = 'none';
|
|
270
|
-
if (trendempty) trendempty.style.display = 'block';
|
|
271
|
-
if (trendnow) trendnow.textContent = '';
|
|
272
|
-
if (nudge) nudge.style.display = 'none';
|
|
273
|
-
if (toolbars) toolbars.innerHTML = '';
|
|
274
|
-
if (bigsec) bigsec.style.display = 'none';
|
|
275
|
-
if (toolstotal) toolstotal.textContent = '';
|
|
276
|
-
if (metaEl) metaEl.textContent = '';
|
|
277
|
-
if (tReclaim) tReclaim.textContent = '—';
|
|
278
|
-
if (tRepeat) tRepeat.textContent = '—';
|
|
279
|
-
if (tReads) tReads.textContent = '—';
|
|
280
|
-
if (tEdits) tEdits.textContent = '—';
|
|
281
|
-
missing.textContent = formatMissing(state.missing);
|
|
480
|
+
if (!score || typeof score.usefulPct !== "number") {
|
|
481
|
+
renderIdle(state);
|
|
282
482
|
return;
|
|
283
483
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
484
|
+
|
|
485
|
+
const color = score.color || "green";
|
|
486
|
+
const stateName = stateWord(color);
|
|
487
|
+
const sat = fullnessPct(score);
|
|
488
|
+
const oldPct = clamp(score.pollutionPct || 0, 0, 95);
|
|
489
|
+
const repeatCount = score.buckets && score.buckets.range_redundant ? score.buckets.range_redundant.count || 0 : 0;
|
|
490
|
+
const edits = score.stats && score.stats.patchEdits ? Number(score.stats.patchEdits) : 0;
|
|
491
|
+
|
|
492
|
+
dot.className = "dot " + color;
|
|
493
|
+
stateDot.className = "state-dot " + color;
|
|
494
|
+
hud.classList.remove("s-green", "s-amber", "s-red");
|
|
495
|
+
hud.classList.add("s-" + color);
|
|
496
|
+
main.textContent = stateName + " - " + fmt(score.ctTokens) + " ctx";
|
|
497
|
+
client.textContent = label(score.client) + " - " + hudVersion;
|
|
498
|
+
turnEl.textContent = typeof score.turn === "number" && score.turn > 0 ? "turn " + score.turn : "";
|
|
499
|
+
stateWordEl.textContent = stateName;
|
|
500
|
+
fullness.textContent = fullnessLine(sat, score);
|
|
501
|
+
fullness.style.color = tone(color);
|
|
502
|
+
lead.textContent = leadLine(color);
|
|
503
|
+
lead.style.color = tone(color);
|
|
504
|
+
evidence.textContent = evidenceLine(score, oldPct, repeatCount);
|
|
505
|
+
usefulbar.style.width = Math.max(0, 100 - oldPct) + "%";
|
|
506
|
+
oldbar.style.width = oldPct + "%";
|
|
507
|
+
metaEl.textContent = ctSourceLabel(score.ctSource) + " - lower-bound signal - " + edits + " edits";
|
|
508
|
+
pathEl.textContent = score.sourcePath ? "Reading " + score.sourcePath : "";
|
|
509
|
+
missing.textContent = formatMissing(state.missing);
|
|
510
|
+
|
|
511
|
+
renderAlert(score, oldPct, repeatCount);
|
|
512
|
+
renderNudge(color, sat);
|
|
513
|
+
|
|
514
|
+
if (renewBtn) renewBtn.style.display = color === "red" || color === "amber" ? "block" : "none";
|
|
515
|
+
prevTurnState = {
|
|
516
|
+
src: score.sourcePath,
|
|
517
|
+
turn: score.turn,
|
|
518
|
+
ct: score.ctTokens || 0,
|
|
519
|
+
reads: score.reads || 0,
|
|
520
|
+
repeat: repeatCount,
|
|
521
|
+
oldTok: score.pollutionTok || 0,
|
|
522
|
+
};
|
|
523
|
+
syncHeight();
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function renderIdle(state) {
|
|
527
|
+
dot.className = "dot idle";
|
|
528
|
+
stateDot.className = "state-dot";
|
|
529
|
+
hud.classList.remove("s-green", "s-amber", "s-red");
|
|
530
|
+
prevTurnState = null;
|
|
531
|
+
main.textContent = "No active session yet";
|
|
532
|
+
client.textContent = "EchoMem HUD - " + hudVersion;
|
|
533
|
+
turnEl.textContent = "";
|
|
534
|
+
stateWordEl.textContent = "Fresh";
|
|
535
|
+
fullness.textContent = "";
|
|
536
|
+
lead.textContent = "Waiting for a live Codex or Claude session";
|
|
537
|
+
lead.style.color = "#6b675d";
|
|
538
|
+
evidence.textContent = "Start working and EchoMem will show when the context is still clean, drifting, or ready to renew.";
|
|
539
|
+
usefulbar.style.width = "100%";
|
|
540
|
+
oldbar.style.width = "0";
|
|
541
|
+
alertEl.style.display = "none";
|
|
542
|
+
nudge.style.display = "none";
|
|
543
|
+
if (renewBtn) renewBtn.style.display = "none";
|
|
544
|
+
if (renewResult) renewResult.style.display = "none";
|
|
545
|
+
metaEl.textContent = "";
|
|
546
|
+
pathEl.textContent = "";
|
|
547
|
+
missing.textContent = formatMissing(state && state.missing);
|
|
548
|
+
syncHeight();
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function renderAlert(score, oldPct, repeatCount) {
|
|
552
|
+
if (!alertEl) return;
|
|
553
|
+
const turnChanged = prevTurnState && prevTurnState.src === score.sourcePath && score.turn !== prevTurnState.turn;
|
|
554
|
+
const dCt = turnChanged ? (score.ctTokens || 0) - prevTurnState.ct : 0;
|
|
555
|
+
const dReads = turnChanged ? (score.reads || 0) - prevTurnState.reads : 0;
|
|
556
|
+
const dRep = turnChanged ? repeatCount - prevTurnState.repeat : 0;
|
|
557
|
+
const dOld = turnChanged ? (score.pollutionTok || 0) - prevTurnState.oldTok : 0;
|
|
558
|
+
if (dRep > 0 || dOld > 2000) {
|
|
559
|
+
alertTitle.textContent = "this turn re-read what it already had";
|
|
560
|
+
alertDetail.textContent = joinParts([
|
|
561
|
+
dOld > 0 ? "+" + fmt(dOld) + " old weight" : "",
|
|
562
|
+
dReads > 0 ? "+" + dReads + " read" + (dReads === 1 ? "" : "s") : "0 new files",
|
|
563
|
+
dCt !== 0 ? signedFmt(dCt) + " ctx" : "",
|
|
564
|
+
]);
|
|
565
|
+
alertEl.style.display = "block";
|
|
566
|
+
return;
|
|
312
567
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (deltaEl) {
|
|
319
|
-
const repeatNow = bucket ? bucket.count : 0;
|
|
320
|
-
if (prevTurnState && prevTurnState.src === score.sourcePath && score.turn !== prevTurnState.turn) {
|
|
321
|
-
const dCt = (score.ctTokens || 0) - prevTurnState.ct;
|
|
322
|
-
const dReads = (score.reads || 0) - prevTurnState.reads;
|
|
323
|
-
const dRep = repeatNow - prevTurnState.repeat;
|
|
324
|
-
const parts = [(dCt >= 0 ? '+' : '−') + fmt(Math.abs(dCt)) + ' ctx' + (dCt < -20000 ? ' (compacted)' : '')];
|
|
325
|
-
if (dReads > 0) parts.push('+' + dReads + ' read' + (dReads > 1 ? 's' : ''));
|
|
326
|
-
if (dRep > 0) parts.push('+' + dRep + ' repeat' + (dRep > 1 ? 's' : ''));
|
|
327
|
-
deltaEl.textContent = 'last turn ' + parts.join(' · ');
|
|
328
|
-
deltaEl.style.display = 'block';
|
|
329
|
-
} else if (!prevTurnState || prevTurnState.src !== score.sourcePath) {
|
|
330
|
-
deltaEl.style.display = 'none';
|
|
331
|
-
}
|
|
332
|
-
if (!prevTurnState || prevTurnState.src !== score.sourcePath || score.turn !== prevTurnState.turn) {
|
|
333
|
-
prevTurnState = { src: score.sourcePath, turn: score.turn, ct: score.ctTokens || 0, reads: score.reads || 0, repeat: repeatNow };
|
|
334
|
-
}
|
|
568
|
+
if (score.color === "red") {
|
|
569
|
+
alertTitle.textContent = "context has drifted";
|
|
570
|
+
alertDetail.textContent = "at least " + oldPct + "% old weight is still riding in the window";
|
|
571
|
+
alertEl.style.display = "block";
|
|
572
|
+
return;
|
|
335
573
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
574
|
+
alertEl.style.display = "none";
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function renderNudge(color, sat) {
|
|
578
|
+
let text = "";
|
|
579
|
+
let cls = "nudge";
|
|
580
|
+
if (color === "red") {
|
|
581
|
+
text = "A fresh start now beats pushing past this - keep the goal, drop the clutter.";
|
|
582
|
+
cls += " red";
|
|
583
|
+
} else if (color === "amber" || sat >= 75) {
|
|
584
|
+
text = "Still fine - a fresh start soon will keep this snappy.";
|
|
585
|
+
cls += " calm";
|
|
344
586
|
}
|
|
345
|
-
|
|
346
|
-
|
|
587
|
+
nudge.textContent = text;
|
|
588
|
+
nudge.className = cls;
|
|
589
|
+
nudge.style.display = text ? "block" : "none";
|
|
347
590
|
}
|
|
591
|
+
|
|
348
592
|
function stateWord(color) {
|
|
349
|
-
if (color ===
|
|
350
|
-
if (color ===
|
|
351
|
-
return
|
|
593
|
+
if (color === "red") return "Drifted";
|
|
594
|
+
if (color === "amber") return "Drifting";
|
|
595
|
+
return "Fresh";
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function leadLine(color) {
|
|
599
|
+
if (color === "red") return "Working from a cluttered desk";
|
|
600
|
+
if (color === "amber") return "Old context is building up";
|
|
601
|
+
return "Clean and roomy";
|
|
352
602
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
return
|
|
603
|
+
|
|
604
|
+
function evidenceLine(score, oldPct, repeatCount) {
|
|
605
|
+
const repeat = repeatCount > 0 ? repeatCount + " repeated read" + (repeatCount === 1 ? "" : "s") : "nothing has been re-read yet";
|
|
606
|
+
return "at least " + oldPct + "% old weight (" + fmt(score.pollutionTok) + ") - " + repeat;
|
|
357
607
|
}
|
|
608
|
+
|
|
609
|
+
function fullnessLine(sat, score) {
|
|
610
|
+
if (!score.modelContextWindow) return fmt(score.ctTokens) + " context tokens";
|
|
611
|
+
const room = sat >= 85 ? " - compaction near" : sat <= 30 ? " - plenty of room" : "";
|
|
612
|
+
return sat + "% full - " + fmt(score.ctTokens) + " / " + fmt(score.modelContextWindow) + room;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function fullnessPct(score) {
|
|
616
|
+
if (typeof score.saturationPct === "number") return score.saturationPct;
|
|
617
|
+
if (score.modelContextWindow) return Math.round(100 * (score.ctTokens || 0) / score.modelContextWindow);
|
|
618
|
+
return 0;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function tone(color) {
|
|
622
|
+
if (color === "red") return "#c7372f";
|
|
623
|
+
if (color === "amber") return "#8a6a12";
|
|
624
|
+
return "#2f7d4f";
|
|
625
|
+
}
|
|
626
|
+
|
|
358
627
|
function ctSourceLabel(src) {
|
|
359
|
-
if (src ===
|
|
360
|
-
return
|
|
628
|
+
if (src === "token_count" || src === "statusline") return "measured";
|
|
629
|
+
return "estimated";
|
|
361
630
|
}
|
|
631
|
+
|
|
362
632
|
function formatMissing(list) {
|
|
363
|
-
if (!list || !list.length) return
|
|
364
|
-
return
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
function shortFile(f) {
|
|
373
|
-
const parts = String(f).split('/').filter(Boolean);
|
|
374
|
-
return parts.length <= 2 ? String(f) : '…/' + parts.slice(-2).join('/');
|
|
375
|
-
}
|
|
376
|
-
function renderBars(container, map, big, fmtFn, filesByTool) {
|
|
377
|
-
if (!container) return;
|
|
378
|
-
const fmtVal = fmtFn || String;
|
|
379
|
-
const entries = Object.keys(map || {}).map(function (k) { return [k, map[k] || 0]; })
|
|
380
|
-
.filter(function (e) { return e[1] > 0; }).sort(function (a, b) { return b[1] - a[1]; }).slice(0, 6);
|
|
381
|
-
if (!entries.length) { container.innerHTML = '<div class="trend-empty">none yet</div>'; return; }
|
|
382
|
-
const max = entries[0][1];
|
|
383
|
-
container.innerHTML = entries.map(function (e) {
|
|
384
|
-
const w = Math.max(6, Math.round(e[1] / max * 100));
|
|
385
|
-
const files = filesByTool && filesByTool[e[0]] ? filesByTool[e[0]] : null;
|
|
386
|
-
const hasFiles = files && files.length;
|
|
387
|
-
const nameTxt = (hasFiles ? '▸ ' : '') + e[0];
|
|
388
|
-
const filesHTML = hasFiles ? '<div class="files">' + files.map(function (f) {
|
|
389
|
-
return '<div class="file" title="' + escapeHTML(f) + '">' + escapeHTML(shortFile(f)) + '</div>';
|
|
390
|
-
}).join('') + '</div>' : '';
|
|
391
|
-
return '<div class="barwrap' + (hasFiles ? ' has-files' : '') + '" data-tool="' + escapeHTML(e[0]) + '">'
|
|
392
|
-
+ '<div class="bar' + (big ? ' big' : '') + '">'
|
|
393
|
-
+ '<span class="bar-name" title="' + escapeHTML(e[0]) + '">' + escapeHTML(nameTxt) + '</span>'
|
|
394
|
-
+ '<span class="bar-track"><span class="bar-fill" style="width:' + w + '%"></span></span>'
|
|
395
|
-
+ '<span class="bar-val">' + fmtVal(e[1]) + '</span></div>'
|
|
396
|
-
+ filesHTML + '</div>';
|
|
397
|
-
}).join('');
|
|
398
|
-
}
|
|
399
|
-
function pushTrend(score) {
|
|
400
|
-
const src = score.sourcePath || score.client || '';
|
|
401
|
-
if (src !== lastSource) { history.length = 0; lastSource = src; }
|
|
402
|
-
const t = typeof score.turn === 'number' ? score.turn : history.length;
|
|
403
|
-
const p = Math.max(0, Math.min(100, score.pollutionPct || 0));
|
|
404
|
-
if (history.length && history[history.length - 1].t === t) history[history.length - 1].p = p;
|
|
405
|
-
else { history.push({ t: t, p: p }); if (history.length > 40) history.shift(); }
|
|
406
|
-
drawSpark();
|
|
407
|
-
}
|
|
408
|
-
function drawSpark() {
|
|
409
|
-
if (!spark) return;
|
|
410
|
-
const n = history.length;
|
|
411
|
-
if (n < 2) {
|
|
412
|
-
spark.style.display = 'none';
|
|
413
|
-
if (trendempty) trendempty.style.display = 'block';
|
|
414
|
-
if (trendnow) trendnow.textContent = n ? history[0].p + '%' : '';
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
if (trendempty) trendempty.style.display = 'none';
|
|
418
|
-
spark.style.display = 'block';
|
|
419
|
-
const pts = history.map(function (h, i) {
|
|
420
|
-
const x = (i / (n - 1)) * 100;
|
|
421
|
-
const y = 27 - (h.p / 100) * 25;
|
|
422
|
-
return x.toFixed(1) + ',' + y.toFixed(1);
|
|
423
|
-
});
|
|
424
|
-
sparkline.setAttribute('points', pts.join(' '));
|
|
425
|
-
sparkarea.setAttribute('points', pts.join(' ') + ' 100,28 0,28');
|
|
426
|
-
const cur = history[n - 1].p, prev = history[n - 2].p;
|
|
427
|
-
const arrow = cur > prev + 1 ? '↑' : cur < prev - 1 ? '↓' : '→';
|
|
428
|
-
if (trendnow) trendnow.textContent = cur + '% ' + arrow;
|
|
429
|
-
}
|
|
430
|
-
function label(client) {
|
|
431
|
-
if (client === 'codex') return 'Codex';
|
|
432
|
-
if (client === 'claude-code') return 'Claude Code';
|
|
433
|
-
if (client === 'claude-desktop') return 'Claude Desktop';
|
|
434
|
-
return '';
|
|
633
|
+
if (!list || !list.length) return "";
|
|
634
|
+
return "Not seeing " + list.map(label).join(", ") + " yet";
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function label(clientName) {
|
|
638
|
+
if (clientName === "codex") return "Codex";
|
|
639
|
+
if (clientName === "claude-code") return "Claude Code";
|
|
640
|
+
if (clientName === "claude-desktop") return "Claude Desktop";
|
|
641
|
+
return "EchoMem HUD";
|
|
435
642
|
}
|
|
643
|
+
|
|
436
644
|
function fmt(tokens) {
|
|
437
645
|
const n = Number(tokens || 0);
|
|
438
|
-
if (n >= 1000000) return (n / 1000000).toFixed(1) +
|
|
439
|
-
if (n >= 1000) return Math.round(n / 1000) +
|
|
646
|
+
if (n >= 1000000) return (n / 1000000).toFixed(1) + "m";
|
|
647
|
+
if (n >= 1000) return Math.round(n / 1000) + "k";
|
|
440
648
|
return String(Math.round(n));
|
|
441
649
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
const path = bubble.querySelector('path');
|
|
446
|
-
if (!svg || !path) return;
|
|
447
|
-
const rect = bubble.getBoundingClientRect();
|
|
448
|
-
const width = Math.max(96, Math.round(rect.width));
|
|
449
|
-
const height = Math.max(72, Math.round(rect.height));
|
|
450
|
-
svg.setAttribute('viewBox', '0 0 ' + width + ' ' + (height + 16));
|
|
451
|
-
path.setAttribute('d', buildEchoBubblePath(width, height, 0.08));
|
|
452
|
-
});
|
|
650
|
+
|
|
651
|
+
function signedFmt(value) {
|
|
652
|
+
return (value >= 0 ? "+" : "-") + fmt(Math.abs(value));
|
|
453
653
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
let y = top + radius;
|
|
471
|
-
let toggle = 0;
|
|
472
|
-
while (y < bottom - radius - 10) {
|
|
473
|
-
const nextY = Math.min(bottom - radius, y + 72);
|
|
474
|
-
const x1 = right + (toggle % 2 === 0 ? 1.4 : -0.8);
|
|
475
|
-
const x2 = right + (toggle % 2 === 0 ? -1.6 : 1.0);
|
|
476
|
-
rightSegments.push('C ' + x1 + ' ' + (y + 21) + ', ' + x2 + ' ' + (nextY - 21) + ', ' + right + ' ' + nextY);
|
|
477
|
-
y = nextY;
|
|
478
|
-
toggle += 1;
|
|
479
|
-
}
|
|
480
|
-
const leftSegments = [];
|
|
481
|
-
y = bottom - radius;
|
|
482
|
-
toggle = 0;
|
|
483
|
-
while (y > top + radius + 10) {
|
|
484
|
-
const nextY = Math.max(top + radius, y - 72);
|
|
485
|
-
const x1 = left + (toggle % 2 === 0 ? -1.2 : 1.4);
|
|
486
|
-
const x2 = left + (toggle % 2 === 0 ? 1.6 : -1.0);
|
|
487
|
-
leftSegments.push('C ' + x1 + ' ' + (y - 21) + ', ' + x2 + ' ' + (nextY + 21) + ', ' + left + ' ' + nextY);
|
|
488
|
-
y = nextY;
|
|
489
|
-
toggle += 1;
|
|
490
|
-
}
|
|
491
|
-
return [
|
|
492
|
-
'M ' + (left + radius) + ' ' + (top + 0.6),
|
|
493
|
-
'C ' + (w * 0.32) + ' ' + (top - 1.1) + ', ' + (w * 0.58) + ' ' + (top + 1.6) + ', ' + (right - radius) + ' ' + (top + 0.8),
|
|
494
|
-
'C ' + (right - 8) + ' ' + (top + 1.8) + ', ' + (right + 0.5) + ' ' + (top + 9.5) + ', ' + (right - 0.3) + ' ' + (top + radius),
|
|
495
|
-
...rightSegments,
|
|
496
|
-
'C ' + (right + 0.6) + ' ' + (bottom - 14) + ', ' + (right - 8) + ' ' + (bottom - 1.2) + ', ' + (right - radius) + ' ' + bottom,
|
|
497
|
-
'C ' + (w * 0.72) + ' ' + (bottom + 1.2) + ', ' + (w * 0.5) + ' ' + (bottom - 0.6) + ', ' + tailBaseRight + ' ' + bottom,
|
|
498
|
-
'C ' + (tailBaseRight - 3) + ' ' + (bottom + 5) + ', ' + (tailTipX + 4) + ' ' + (tailTipY - 1.5) + ', ' + tailTipX + ' ' + tailTipY,
|
|
499
|
-
'C ' + (tailTipX - 4) + ' ' + (tailTipY - 1.5) + ', ' + (tailBaseLeft - 1) + ' ' + (bottom + 5) + ', ' + tailBaseLeft + ' ' + bottom,
|
|
500
|
-
'C ' + (left + 18) + ' ' + (bottom + 1.0) + ', ' + (left + 5) + ' ' + (bottom - 8) + ', ' + (left + 0.6) + ' ' + (bottom - radius),
|
|
501
|
-
...leftSegments,
|
|
502
|
-
'C ' + (left - 0.8) + ' ' + (top + 14) + ', ' + (left + 7) + ' ' + (top + 2.2) + ', ' + (left + radius) + ' ' + (top + 0.6),
|
|
503
|
-
].join(' ');
|
|
654
|
+
|
|
655
|
+
function joinParts(parts) {
|
|
656
|
+
return parts.filter(Boolean).join(" - ");
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function clamp(value, min, max) {
|
|
660
|
+
return Math.max(min, Math.min(max, Number(value) || 0));
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function syncHeight() {
|
|
664
|
+
requestAnimationFrame(() => {
|
|
665
|
+
if (!window.echomemHud) return;
|
|
666
|
+
const open = hud.classList.contains("open");
|
|
667
|
+
const height = open ? Math.ceil(hud.getBoundingClientRect().height) + 14 : undefined;
|
|
668
|
+
window.echomemHud.setOpen(open, height);
|
|
669
|
+
});
|
|
504
670
|
}
|
|
505
671
|
</script>
|
|
506
672
|
</body>
|