@echomem/mcp 1.4.44 → 1.4.45
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 +28 -25
- package/dist/city/README.md +9 -0
- package/dist/city/echo-ai-city-only.html +2232 -0
- package/dist/city/echo-extraction-plate.html +330 -0
- package/dist/city/echo-face-cutout.png +0 -0
- package/dist/city/personality_stickers/bossy.png +0 -0
- package/dist/city/personality_stickers/ghosty.png +0 -0
- package/dist/city/personality_stickers/loopy.png +0 -0
- package/dist/city/personality_stickers/lusty.png +0 -0
- package/dist/city/personality_stickers/maxxy.png +0 -0
- package/dist/city/personality_stickers/tabby.png +0 -0
- package/dist/city/vendor/OrbitControls.js +1417 -0
- package/dist/city/vendor/RoundedBoxGeometry.js +155 -0
- package/dist/city/vendor/echo_general-file-21.riv +0 -0
- package/dist/city/vendor/rive.js +8139 -0
- package/dist/city/vendor/rive.wasm +0 -0
- package/dist/city/vendor/three.module.min.js +6 -0
- package/dist/context-analysis/claude-native-canonical.js +2 -2
- package/dist/context-analysis/vendored-canonical.js +2 -2
- package/dist/context-analysis/workspace-report.js +3 -3
- package/dist/forensics.js +1531 -0
- package/dist/hud/hooks.js +31 -43
- package/dist/index.js +79 -15
- package/dist/local-data-paths.js +38 -0
- package/dist/migrate.js +140 -70
- package/dist/report.js +721 -0
- package/dist/save-checkpoint-hook.js +1 -1
- package/dist/setup-page/client-core.js +372 -18
- package/dist/setup-page/client-extraction.js +204 -37
- package/dist/setup-page/client-lifecycle.js +101 -31
- package/dist/setup-page/client-report-audit.js +819 -0
- package/dist/setup-page/client-report-city.js +356 -0
- package/dist/setup-page/client-report.js +6 -0
- package/dist/setup-page/client.js +2 -0
- package/dist/setup-page/styles-city-report.js +880 -0
- package/dist/setup-page/styles-context-audit.js +470 -0
- package/dist/setup-page/styles-extraction.js +31 -1
- package/dist/setup-page/styles-foundation.js +89 -0
- package/dist/setup-page/styles-mvp.js +155 -10
- package/dist/setup-page/styles-website-alignment.js +204 -0
- package/dist/setup-page/styles.js +4 -0
- package/dist/setup-page.js +4 -4
- package/dist/setup-preview.js +212 -4
- package/dist/setup.js +702 -321
- package/dist/source-session.js +3 -9
- package/dist/v1-contract.js +8 -0
- package/package.json +7 -9
- package/dist/config-files.js +0 -63
- package/dist/local-jsonl.js +0 -87
- package/dist/onboarding-stats.js +0 -16
|
@@ -1,7 +1,97 @@
|
|
|
1
1
|
// Generated by splitting the previous inline setup page. Keep this source readable: it is emitted into one browser IIFE.
|
|
2
2
|
export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dashboard (post-auth extraction) ---------- */
|
|
3
|
+
function acceptReportScanId(envelope) {
|
|
4
|
+
var scanId = envelope && typeof envelope.scanId === "string" ? envelope.scanId : "";
|
|
5
|
+
if (!scanId) throw new Error("Report response is missing its scan identity.");
|
|
6
|
+
if (activeScanId && activeScanId !== scanId) throw new Error("The local scan identity changed before the report completed.");
|
|
7
|
+
activeScanId = scanId;
|
|
8
|
+
}
|
|
9
|
+
function validateProductionReportEnvelope(envelope) {
|
|
10
|
+
if (!envelope || typeof envelope !== "object" || envelope.schemaVersion !== 1 || envelope.mode !== "production") {
|
|
11
|
+
throw new Error("Report response has an unsupported production schema.");
|
|
12
|
+
}
|
|
13
|
+
acceptReportScanId(envelope);
|
|
14
|
+
if (["scanning", "ready", "empty", "failed"].indexOf(envelope.kind) === -1) {
|
|
15
|
+
throw new Error("Report response has an unknown state.");
|
|
16
|
+
}
|
|
17
|
+
if (envelope.kind === "scanning") return envelope;
|
|
18
|
+
if (envelope.kind === "failed") {
|
|
19
|
+
if (!envelope.error || typeof envelope.error.code !== "string" || typeof envelope.error.message !== "string") {
|
|
20
|
+
throw new Error("Report failure response is malformed.");
|
|
21
|
+
}
|
|
22
|
+
return envelope;
|
|
23
|
+
}
|
|
24
|
+
var r = envelope.report;
|
|
25
|
+
if (!r || typeof r !== "object" || r.schemaVersion !== 1 || r.dataOrigin !== "local-workspace-scan") {
|
|
26
|
+
throw new Error("Report did not prove that it came from this local workspace scan.");
|
|
27
|
+
}
|
|
28
|
+
var scale = r.scale || {};
|
|
29
|
+
var sessions = Number(scale.sessionCount);
|
|
30
|
+
var gs = gsClean(r.canonicalGoldenStandard);
|
|
31
|
+
var summary = (gs && gs.summary) || {};
|
|
32
|
+
var canonicalSessions = Number(summary.sessionsAnalyzed || 0);
|
|
33
|
+
var canonicalInput = Number(summary.officialInputTokens || 0);
|
|
34
|
+
var skipped = Number((gs && gs.diagnostics && gs.diagnostics.skippedSessions) || 0);
|
|
35
|
+
if (envelope.kind === "empty") {
|
|
36
|
+
if (sessions !== 0 || canonicalSessions !== 0 || canonicalInput !== 0 || Number(scale.totalInputTokens || 0) !== 0) {
|
|
37
|
+
throw new Error("An empty report contained non-empty usage data.");
|
|
38
|
+
}
|
|
39
|
+
return envelope;
|
|
40
|
+
}
|
|
41
|
+
if (
|
|
42
|
+
!gs ||
|
|
43
|
+
!Number.isFinite(sessions) || sessions <= 0 ||
|
|
44
|
+
!Number.isFinite(canonicalSessions) || canonicalSessions <= 0 || canonicalSessions > sessions ||
|
|
45
|
+
!Number.isFinite(skipped) || skipped < 0 || canonicalSessions + skipped !== sessions ||
|
|
46
|
+
!Number.isFinite(canonicalInput) || canonicalInput <= 0
|
|
47
|
+
) {
|
|
48
|
+
throw new Error("Canonical analysis is incomplete or does not match the provider session cohort.");
|
|
49
|
+
}
|
|
50
|
+
if (!Array.isArray(r.repos) || r.repos.length === 0) {
|
|
51
|
+
throw new Error("The local report has no workspace rows to render.");
|
|
52
|
+
}
|
|
53
|
+
gsBillingProjection(r, gs);
|
|
54
|
+
return envelope;
|
|
55
|
+
}
|
|
56
|
+
function renderReportIssue(code, message) {
|
|
57
|
+
void message;
|
|
58
|
+
report = null;
|
|
59
|
+
reportEnvelope = null;
|
|
60
|
+
resetReportSurface();
|
|
61
|
+
var canContinue = String(code || "").indexOf("REPORT_") === 0 || code === "RENDER_FAILED";
|
|
62
|
+
setHead("We couldn't finish this scan", "Needs attention");
|
|
63
|
+
app.className = "reportMessageStage";
|
|
64
|
+
app.innerHTML =
|
|
65
|
+
'<section class="reportMessage" data-report-state="failed">' +
|
|
66
|
+
'<h2>We couldn’t finish this scan.</h2>' +
|
|
67
|
+
'<p>Your coding history is unchanged.</p>' +
|
|
68
|
+
(canContinue
|
|
69
|
+
? '<p>This optional report can be retried later.</p><div class="actions"><button type="button" class="primary" data-connect-echo>Continue without report</button></div>'
|
|
70
|
+
: '<p>Close this tab and run <code>echomem-mcp init</code> again.</p>') +
|
|
71
|
+
'<details class="reportTechnical"><summary>Technical details</summary><code>' + esc(code || "REPORT_FAILED") + '</code></details>' +
|
|
72
|
+
'</section>';
|
|
73
|
+
if (canContinue) bindConnect();
|
|
74
|
+
}
|
|
75
|
+
function renderEmptyReport() {
|
|
76
|
+
report = null;
|
|
77
|
+
reportEnvelope = null;
|
|
78
|
+
resetReportSurface();
|
|
79
|
+
setHead("No local coding history found", "Local scan complete");
|
|
80
|
+
app.className = "reportMessageStage";
|
|
81
|
+
app.innerHTML =
|
|
82
|
+
'<section class="reportMessage" data-report-state="empty">' +
|
|
83
|
+
'<h2>No coding history found.</h2>' +
|
|
84
|
+
'<p>You can continue now and import local coding history later.</p>' +
|
|
85
|
+
'<div class="actions"><button type="button" class="primary" data-connect-echo>Continue setup</button></div>' +
|
|
86
|
+
'</section>';
|
|
87
|
+
bindConnect();
|
|
88
|
+
}
|
|
89
|
+
function renderBridgeIssue() {
|
|
90
|
+
renderReportIssue("BRIDGE_UNREACHABLE", "The local bridge stopped answering before your report was ready. Your terminal may have closed or the process may have stopped.");
|
|
91
|
+
}
|
|
3
92
|
function renderDiscoveryCheck(slow) {
|
|
4
93
|
void slow;
|
|
94
|
+
setCityMode(false);
|
|
5
95
|
setExtractMode(false);
|
|
6
96
|
setReadyMode(true);
|
|
7
97
|
setHead("Loading", "Working");
|
|
@@ -9,6 +99,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
9
99
|
app.innerHTML = '<div class="discoverySpinner" role="status" aria-label="Loading"><span aria-hidden="true"></span></div>';
|
|
10
100
|
}
|
|
11
101
|
function renderDiscoveryCheckIssue(error) {
|
|
102
|
+
setCityMode(false);
|
|
12
103
|
setExtractMode(false);
|
|
13
104
|
setReadyMode(true);
|
|
14
105
|
setHead("Session refresh paused", "Needs attention");
|
|
@@ -16,7 +107,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
16
107
|
app.innerHTML =
|
|
17
108
|
'<div style="padding:24px"><h2>Echo could not refresh session status</h2>' +
|
|
18
109
|
'<p class="helper">Keep the terminal open and try the local check again.</p>' +
|
|
19
|
-
(error ? '<details class="
|
|
110
|
+
(error ? '<details class="reportTechnical"><summary>Technical details</summary><code>' + esc(error) + '</code></details>' : '') +
|
|
20
111
|
'<div class="actions"><button id="retryDiscoveryCheck" class="primary">Try again</button></div></div>';
|
|
21
112
|
var retry = document.getElementById("retryDiscoveryCheck");
|
|
22
113
|
if (retry) retry.onclick = function () {
|
|
@@ -25,11 +116,13 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
25
116
|
};
|
|
26
117
|
}
|
|
27
118
|
function renderLogoutPending() {
|
|
119
|
+
setCityMode(false);
|
|
28
120
|
setHead("Signing out locally", "Working");
|
|
29
121
|
app.className = "loading-panel";
|
|
30
122
|
app.innerHTML = '<div style="padding:24px"><h2>Signing this device out</h2><p class="helper">If Echo is scanning local files, this can take a few seconds to answer.</p></div>';
|
|
31
123
|
}
|
|
32
124
|
function renderLogoutIssue(error) {
|
|
125
|
+
setCityMode(false);
|
|
33
126
|
setHead("Sign out did not finish", "Needs attention");
|
|
34
127
|
app.className = "loading-panel";
|
|
35
128
|
app.innerHTML =
|
|
@@ -43,6 +136,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
43
136
|
if (statsPollStarted) return;
|
|
44
137
|
var statsEpoch = accountStateEpoch;
|
|
45
138
|
statsPollStarted = true;
|
|
139
|
+
setCityMode(false);
|
|
46
140
|
setExtractMode(false);
|
|
47
141
|
setReadyMode(true);
|
|
48
142
|
renderDiscoveryCheck(false);
|
|
@@ -75,14 +169,23 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
75
169
|
if (res.status !== 202) throw new Error(await res.text() || ("HTTP " + res.status));
|
|
76
170
|
} catch (e) {
|
|
77
171
|
lastError = e && e.message ? e.message : String(e);
|
|
78
|
-
if (!stats && Date.now() - started > 25000)
|
|
172
|
+
if (!stats && Date.now() - started > 25000) renderBridgeIssue(lastError);
|
|
79
173
|
if (Date.now() - started > 20000) { statsSlow = true; if (stats) renderDashboard(); }
|
|
80
174
|
}
|
|
81
175
|
await sleep(statsSlow ? 1500 : 500);
|
|
82
176
|
}
|
|
83
177
|
} finally { statsPollStarted = false; }
|
|
84
178
|
}
|
|
179
|
+
// The plate slot: ONLY the WebGL clay city (echo-extraction-plate.html). It self-fetches /report
|
|
180
|
+
// (your repos) and polls /progress (extraction state), growing as memories are created. The old SVG
|
|
181
|
+
// fallback board was removed — it was bleeding through behind the transparent canvas (the double board).
|
|
182
|
+
function plateBlock() {
|
|
183
|
+
return '<div class="exPlate"><div class="plateStack">' +
|
|
184
|
+
'<iframe class="plateGl" title="Extraction plate" src="/city/echo-extraction-plate.html?nonce=' + encodeURIComponent(nonce) + '"></iframe>' +
|
|
185
|
+
'</div></div>';
|
|
186
|
+
}
|
|
85
187
|
function renderPlanGate() {
|
|
188
|
+
setCityMode(false);
|
|
86
189
|
setExtractMode(false);
|
|
87
190
|
setReadyMode(true);
|
|
88
191
|
setHead("Choose your Echo", "Plan");
|
|
@@ -95,7 +198,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
95
198
|
readyStage = "plan";
|
|
96
199
|
app.className = "planGateStage";
|
|
97
200
|
app.innerHTML =
|
|
98
|
-
setupJourney(
|
|
201
|
+
setupJourney(3) +
|
|
99
202
|
'<section class="planGateDecision">' +
|
|
100
203
|
'<div class="setupPlanSlot" id="setupPlan"></div>' +
|
|
101
204
|
'<aside class="planGateAccount" aria-label="Connected account"><div class="setupAccountSlot" id="setupAccount"></div></aside>' +
|
|
@@ -104,6 +207,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
104
207
|
renderAccountChoice();
|
|
105
208
|
}
|
|
106
209
|
function renderDashboard(error) {
|
|
210
|
+
setCityMode(false);
|
|
107
211
|
setExtractMode(false);
|
|
108
212
|
setReadyMode(true);
|
|
109
213
|
var migratable = stats && stats.migratable ? stats.migratable : {};
|
|
@@ -127,6 +231,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
127
231
|
var pendN = hasPendingCount ? pending : 0;
|
|
128
232
|
var pendingCodex = typeof migratable.pendingCodex === "number" ? migratable.pendingCodex : null;
|
|
129
233
|
var pendingClaudeCode = typeof migratable.pendingClaudeCode === "number" ? migratable.pendingClaudeCode : null;
|
|
234
|
+
var pendingCowork = typeof migratable.pendingCowork === "number" ? migratable.pendingCowork : null;
|
|
130
235
|
if (connected && !billingStatusLoading && !setupPlanConfirmed()) {
|
|
131
236
|
renderPlanGate();
|
|
132
237
|
return;
|
|
@@ -149,17 +254,19 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
149
254
|
? "<strong>" + esc(number(pendN)) + " sessions</strong> found."
|
|
150
255
|
: "<strong>" + esc(number(pendN)) + " sessions</strong> are ready to review."));
|
|
151
256
|
var sub = degradedWithoutCounts
|
|
152
|
-
? "You can finish setup now. Your conversations stay on this
|
|
257
|
+
? "You can finish setup now. Your conversations stay on this Mac, and you can retry the history import later."
|
|
153
258
|
: !pendingTrusted
|
|
154
259
|
? "Echo is matching your local history against what is already in memory."
|
|
155
260
|
: (knownDone
|
|
156
261
|
? (skippedActive ? number(skippedActive) + " active conversation" + (skippedActive === 1 ? " is" : "s are") + " still changing, so Echo will pick them up later." : "Your history is already in EchoMem. Nothing new to extract.")
|
|
157
262
|
: (planLimited
|
|
158
263
|
? (importableNow > 0
|
|
159
|
-
? currentPlanLabel + " can import " + number(importableNow) + " of them. Choose which ones to bring into memory; the other " + number(deferredByPlan) + " stay on this
|
|
160
|
-
: currentPlanLabel + " has no coding-session imports remaining. These sessions stay on this
|
|
264
|
+
? currentPlanLabel + " can import " + number(importableNow) + " of them. Choose which ones to bring into memory; the other " + number(deferredByPlan) + " stay on this Mac."
|
|
265
|
+
: currentPlanLabel + " has no coding-session imports remaining. These sessions stay on this Mac.")
|
|
161
266
|
: ""));
|
|
162
|
-
// Mount the dashboard shell
|
|
267
|
+
// Mount the dashboard shell ONCE so the WebGL plate iframe is never re-created across /stats polls.
|
|
268
|
+
// (Rebuilding app.innerHTML on every partial-scan poll reloaded the iframe → the city flickered.)
|
|
269
|
+
// Later polls only patch the text/counts in place; the iframe stays mounted.
|
|
163
270
|
var live = dashMounted && document.getElementById("exHeadline");
|
|
164
271
|
if (!live) {
|
|
165
272
|
extractMounted = false;
|
|
@@ -171,7 +278,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
171
278
|
// one-line strip, not stat cards; the ETA is a commitment-moment note under the button,
|
|
172
279
|
// not a display-size headline.
|
|
173
280
|
app.innerHTML =
|
|
174
|
-
setupJourney(
|
|
281
|
+
setupJourney(4) +
|
|
175
282
|
'<aside class="readyUtilityNav" aria-label="Account and plan settings">' +
|
|
176
283
|
'<div class="planGateAccount readyAccountNav" aria-label="Connected account"><div class="setupAccountSlot" id="setupAccount"></div></div>' +
|
|
177
284
|
'<div class="readyPlanNav" aria-label="Current plan"><span class="readyPlanNavLabel">Plan</span><div class="setupPlanSlot" id="setupPlan"></div></div>' +
|
|
@@ -206,6 +313,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
206
313
|
document.getElementById("exSub").textContent = sub;
|
|
207
314
|
var codexN = pendingTrusted && pendingCodex !== null ? pendingCodex : (sessions.codex || 0);
|
|
208
315
|
var claudeN = pendingTrusted && pendingClaudeCode !== null ? pendingClaudeCode : (sessions.claudeCode || 0);
|
|
316
|
+
var coworkN = pendingTrusted && pendingCowork !== null ? pendingCowork : (sessions.cowork || 0);
|
|
209
317
|
// Proof strip: one quiet line of evidence that the scan is real. Hidden when there is nothing to show.
|
|
210
318
|
var srcIcon = function (id, fallback) {
|
|
211
319
|
var asset = id === "claude-desktop" ? "claude" : "codex";
|
|
@@ -219,6 +327,8 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
219
327
|
? '<span class="pf">' + srcIcon("codex", "CX") + '<strong>' + esc(number(codexN)) + '</strong> Codex</span>' +
|
|
220
328
|
'<span class="pfDot">·</span>' +
|
|
221
329
|
'<span class="pf">' + srcIcon("claude-desktop", "CL") + '<strong>' + esc(number(claudeN)) + '</strong> Claude Code</span>' +
|
|
330
|
+
'<span class="pfDot">·</span>' +
|
|
331
|
+
'<span class="pf">' + srcIcon("claude-desktop", "CW") + '<strong>' + esc(number(coworkN)) + '</strong> Cowork</span>' +
|
|
222
332
|
'<span class="pfNote">found on this computer</span>'
|
|
223
333
|
: "");
|
|
224
334
|
// Reassurances live at the moment of commitment — right under the button.
|
|
@@ -286,12 +396,14 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
286
396
|
stopBillingPoll();
|
|
287
397
|
decisionMade = true;
|
|
288
398
|
extractionEnded = false;
|
|
399
|
+
setCityMode(false);
|
|
400
|
+
setScanMode(false);
|
|
289
401
|
setReadyMode(false);
|
|
290
402
|
setExtractMode(true);
|
|
291
403
|
setHead("Extracting", "Starting");
|
|
292
404
|
app.className = "extractionStartStage";
|
|
293
405
|
app.innerHTML =
|
|
294
|
-
setupJourney(
|
|
406
|
+
setupJourney(4) +
|
|
295
407
|
'<div class="discoverySpinner" role="status" aria-label="Starting import"><span aria-hidden="true"></span></div>';
|
|
296
408
|
var started;
|
|
297
409
|
try {
|
|
@@ -383,6 +495,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
383
495
|
function renderProgress(progress) {
|
|
384
496
|
if (extractionEnded) return;
|
|
385
497
|
lastExtractionProgress = progress;
|
|
498
|
+
setCityMode(false);
|
|
386
499
|
setExtractMode(true);
|
|
387
500
|
var status = progress.status;
|
|
388
501
|
var done = status === "completed";
|
|
@@ -394,15 +507,18 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
394
507
|
var pct = total > 0 ? Math.round((completed / total) * 100) : (done ? 100 : 0);
|
|
395
508
|
var preparing = (status === "starting" || status === "preparing");
|
|
396
509
|
app.className = "";
|
|
397
|
-
// Mount the shell
|
|
510
|
+
// Mount the shell ONCE so the WebGL plate iframe survives every poll (it self-polls /progress);
|
|
511
|
+
// each poll only rebuilds the text column.
|
|
398
512
|
if (!extractMounted) {
|
|
399
513
|
app.innerHTML =
|
|
400
|
-
'<div id="setupJourneySlot">' + setupJourney(
|
|
401
|
-
'<section class="exHero"><div class="exCopy" id="exText"></div
|
|
514
|
+
'<div id="setupJourneySlot">' + setupJourney(4) + '</div>' +
|
|
515
|
+
'<section class="exHero"><div class="exCopy" id="exText"></div>' +
|
|
516
|
+
plateBlock() +
|
|
517
|
+
'</section>';
|
|
402
518
|
extractMounted = true;
|
|
403
519
|
}
|
|
404
520
|
var journeySlot = document.getElementById("setupJourneySlot");
|
|
405
|
-
if (journeySlot) journeySlot.innerHTML = setupJourney(done ?
|
|
521
|
+
if (journeySlot) journeySlot.innerHTML = setupJourney(done ? 5 : 4);
|
|
406
522
|
var shell = app.querySelector(".exHero");
|
|
407
523
|
if (shell) {
|
|
408
524
|
shell.classList.toggle("exHeroResult", done || stopped);
|
|
@@ -664,7 +780,9 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
664
780
|
}
|
|
665
781
|
}
|
|
666
782
|
function sessionSourceLabel(source) {
|
|
667
|
-
|
|
783
|
+
if (source === "claude-code") return "Claude Code";
|
|
784
|
+
if (source === "claude-desktop") return "Cowork";
|
|
785
|
+
return "Codex";
|
|
668
786
|
}
|
|
669
787
|
function sessionDateLabel(value) {
|
|
670
788
|
if (!value) return "Date unavailable";
|
|
@@ -819,11 +937,12 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
819
937
|
var key = String(session.key || "");
|
|
820
938
|
var encodedKey = encodeURIComponent(key);
|
|
821
939
|
var checked = !!selectedSessionKeys[key];
|
|
822
|
-
var
|
|
823
|
-
var
|
|
940
|
+
var isClaude = session.source === "claude-code" || session.source === "claude-desktop";
|
|
941
|
+
var sourceIcon = isClaude ? "claude" : "codex";
|
|
942
|
+
var sourceFallback = session.source === "claude-desktop" ? "CW" : (session.source === "claude-code" ? "CL" : "CX");
|
|
824
943
|
return '<label class="sessionPickerRow' + (checked ? " is-selected" : "") + '">' +
|
|
825
944
|
'<input type="checkbox" data-session-key="' + esc(encodedKey) + '"' + (checked ? " checked" : "") + ' />' +
|
|
826
|
-
'<span class="sessionPickerSource ' + (
|
|
945
|
+
'<span class="sessionPickerSource ' + (isClaude ? "is-claude" : "is-codex") + '"><img src="/hud-assets/' + sourceIcon + '.svg" alt="" onerror="this.style.display="none";this.nextElementSibling.style.display="grid";" /><span class="sessionPickerSourceFallback">' + sourceFallback + '</span></span>' +
|
|
827
946
|
'<span class="sessionPickerMain"><strong>' + esc(session.title || "Untitled coding session") + '</strong><small>' + esc(session.project || "No project detected") + ' · ' + esc(sessionDateLabel(session.date)) + '</small></span>' +
|
|
828
947
|
'<span class="sessionPickerMeta">' + esc(compact(Number(session.approxInputTokens) || 0)) + ' tok</span>' +
|
|
829
948
|
'</label>';
|
|
@@ -835,7 +954,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
835
954
|
'<div class="sessionPickerShell">' +
|
|
836
955
|
'<header class="sessionPickerHead"><div><h2 id="sessionPickerTitle">Choose conversations</h2><p class="sessionPickerSummary"><span class="sessionPickerQuota">Up to <strong>' + esc(number(limit)) + '</strong></span>' + inlineUpgrade + '<span class="sessionPickerOrder">· newest selected first</span></p></div><button type="button" class="sessionPickerClose" id="closeSessionPicker" aria-label="Close">×</button></header>' +
|
|
837
956
|
'<div class="sessionPickerToolbar"><label class="sessionSearch"><span>Search</span><input type="search" id="sessionPickerSearch" placeholder="Title or project" value="' + esc(sessionPickerQuery) + '" /></label>' +
|
|
838
|
-
'<label class="sessionSourceFilter"><span>Source</span><select id="sessionPickerSource"><option value="all"' + (sessionPickerSource === "all" ? " selected" : "") + '>All sources</option><option value="codex"' + (sessionPickerSource === "codex" ? " selected" : "") + '>Codex</option><option value="claude-code"' + (sessionPickerSource === "claude-code" ? " selected" : "") + '>Claude Code</option></select></label>' +
|
|
957
|
+
'<label class="sessionSourceFilter"><span>Source</span><select id="sessionPickerSource"><option value="all"' + (sessionPickerSource === "all" ? " selected" : "") + '>All sources</option><option value="codex"' + (sessionPickerSource === "codex" ? " selected" : "") + '>Codex</option><option value="claude-code"' + (sessionPickerSource === "claude-code" ? " selected" : "") + '>Claude Code</option><option value="claude-desktop"' + (sessionPickerSource === "claude-desktop" ? " selected" : "") + '>Cowork</option></select></label>' +
|
|
839
958
|
'</div>' +
|
|
840
959
|
'<div class="sessionPickerActions"><strong>' + esc(number(selected.length)) + ' / ' + esc(number(limit)) + ' selected</strong><span id="sessionPickerStatus" class="sessionPickerStatus" role="status" aria-live="polite">' + (limit === 0 ? "Plan limit reached. Upgrade to import more." : "") + '</span><button type="button" class="textButton" id="selectNewestSessions">Select newest</button><button type="button" class="textButton" id="clearSessions">Clear</button></div>' +
|
|
841
960
|
'<div class="sessionPickerListWrap' + (limit === 0 ? " is-limit-blocked" : "") + '" id="sessionPickerListWrap"><div class="sessionPickerList"' + (limit === 0 ? " inert" : "") + '>' + (rows || '<div class="sessionPickerEmpty">No conversations match these filters.</div>') + overflow + '</div><div class="sessionPickerLimitOverlay" id="sessionPickerLimitOverlay" role="alert"' + (limit === 0 ? "" : " hidden") + '>' + (limit === 0 ? sessionPickerLimitNotice(false) : "") + '</div></div>' +
|
|
@@ -923,12 +1042,13 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
923
1042
|
? account.email
|
|
924
1043
|
: "the connected account";
|
|
925
1044
|
accountSwitchPending = true;
|
|
1045
|
+
setCityMode(false);
|
|
926
1046
|
setExtractMode(false);
|
|
927
1047
|
setReadyMode(true);
|
|
928
1048
|
setHead("Switch EchoMem account", "Confirm");
|
|
929
1049
|
app.className = "accountSwitchStage";
|
|
930
1050
|
app.innerHTML =
|
|
931
|
-
setupJourney(
|
|
1051
|
+
setupJourney(4) +
|
|
932
1052
|
'<section class="accountSwitchConfirm" aria-labelledby="accountSwitchTitle">' +
|
|
933
1053
|
'<button type="button" class="flowBack" id="cancelAccountSwitch">← Back</button>' +
|
|
934
1054
|
'<h2 id="accountSwitchTitle">Switch account?</h2>' +
|
|
@@ -977,6 +1097,41 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
977
1097
|
function paidRecallPlan(plan) {
|
|
978
1098
|
return ["pro", "power", "team", "enterprise"].indexOf(String(plan || "").toLowerCase()) >= 0;
|
|
979
1099
|
}
|
|
1100
|
+
var planQuotaCatalog = null;
|
|
1101
|
+
var planQuotaCatalogLastFetchedAt = 0;
|
|
1102
|
+
function quotaLimitsForPlan(plan) {
|
|
1103
|
+
var normalized = String(plan || "free").toLowerCase();
|
|
1104
|
+
if (normalized === "team") normalized = "pro";
|
|
1105
|
+
if (normalized === "enterprise") normalized = "power";
|
|
1106
|
+
var limits = planQuotaCatalog && planQuotaCatalog[normalized];
|
|
1107
|
+
if (!limits || typeof limits !== "object") return null;
|
|
1108
|
+
if (
|
|
1109
|
+
typeof limits.historicalConversationLimit !== "number" ||
|
|
1110
|
+
typeof limits.memoryProcessingInputTokensWeeklyLimit !== "number" ||
|
|
1111
|
+
typeof limits.memorySearchWeeklyLimit !== "number"
|
|
1112
|
+
) return null;
|
|
1113
|
+
return limits;
|
|
1114
|
+
}
|
|
1115
|
+
function quotaNumber(value) {
|
|
1116
|
+
return number(Math.max(0, Math.floor(value)));
|
|
1117
|
+
}
|
|
1118
|
+
function quotaTokens(value) {
|
|
1119
|
+
var normalized = Math.max(0, Math.floor(value));
|
|
1120
|
+
if (normalized > 0 && normalized % 1000000 === 0) return String(normalized / 1000000) + "M";
|
|
1121
|
+
return normalized > 0 && normalized % 1000 === 0 ? String(normalized / 1000) + "K" : quotaNumber(normalized);
|
|
1122
|
+
}
|
|
1123
|
+
async function refreshPlanQuotaCatalog() {
|
|
1124
|
+
if (Date.now() - planQuotaCatalogLastFetchedAt < 60000) return;
|
|
1125
|
+
try {
|
|
1126
|
+
var response = await fetch("https://echo-mem-chrome.vercel.app/api/public/plan-quotas", { cache: "no-store" });
|
|
1127
|
+
var payload = response.ok ? await response.json() : null;
|
|
1128
|
+
if (!payload || !payload.plans || typeof payload.plans !== "object") return;
|
|
1129
|
+
planQuotaCatalog = payload.plans;
|
|
1130
|
+
planQuotaCatalogLastFetchedAt = Date.now();
|
|
1131
|
+
} catch (_) {
|
|
1132
|
+
// Generic labels remain visible until the managed catalog is available.
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
980
1135
|
function setupPlanPrice(plan) {
|
|
981
1136
|
return plan === "power" ? "$100" : "$20";
|
|
982
1137
|
}
|
|
@@ -1038,7 +1193,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1038
1193
|
var statusLabel = canceled ? "Canceled" : (state === "trialing" ? "Trial active" : "Active");
|
|
1039
1194
|
var lifecycle = paidPlanLifecycleText(plan);
|
|
1040
1195
|
var quota = billingStatus && billingStatus.historicalConversationQuota;
|
|
1041
|
-
var limit = quota && typeof quota.limit === "number" ? quota.limit :
|
|
1196
|
+
var limit = quota && typeof quota.limit === "number" ? quota.limit : null;
|
|
1042
1197
|
var moneyFact = canceled
|
|
1043
1198
|
? "No future charge"
|
|
1044
1199
|
: (state === "trialing" ? setupPlanPrice(plan) + "/month after trial" : setupPlanPrice(plan) + "/month");
|
|
@@ -1047,7 +1202,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1047
1202
|
'<section class="billingCommitment' + (canceled ? ' is-canceled' : '') + '" aria-label="Current plan commitment">' +
|
|
1048
1203
|
'<div class="billingCommitmentIdentity"><span>' + esc(statusLabel) + '</span><strong>' + esc(planLabel) + '</strong></div>' +
|
|
1049
1204
|
'<div class="billingCommitmentFacts">' +
|
|
1050
|
-
'<span><b>' + esc(
|
|
1205
|
+
'<span><b>' + esc(limit === null ? "Plan import allowance" : quotaNumber(limit)) + '</b>' + (limit === null ? "" : " coding-session imports") + '</span>' +
|
|
1051
1206
|
'<span><b>' + esc(moneyFact) + '</b></span>' +
|
|
1052
1207
|
'</div>' +
|
|
1053
1208
|
'<p>' + esc(lifecycle || "Your paid plan is active.") + '</p>' +
|
|
@@ -1058,6 +1213,19 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1058
1213
|
}
|
|
1059
1214
|
function setupPlanDefinition(plan) {
|
|
1060
1215
|
var trialAvailable = !billingStatus || billingStatus.trialAvailable !== false;
|
|
1216
|
+
var limits = quotaLimitsForPlan(plan);
|
|
1217
|
+
var fallbackFeatures = plan === "power"
|
|
1218
|
+
? ["2,000 past chats", "agent-heavy memory", "2,000 memory recalls / week"]
|
|
1219
|
+
: plan === "pro"
|
|
1220
|
+
? ["500 past chats", "daily memory updates", "500 memory recalls / week"]
|
|
1221
|
+
: ["100 coding sessions", "a few new sessions / week", "100 memory recalls / week"];
|
|
1222
|
+
var features = limits
|
|
1223
|
+
? [
|
|
1224
|
+
quotaNumber(limits.historicalConversationLimit) + " past chats",
|
|
1225
|
+
quotaTokens(limits.memoryProcessingInputTokensWeeklyLimit) + " new-chat tokens / week",
|
|
1226
|
+
quotaNumber(limits.memorySearchWeeklyLimit) + " memory recalls / week"
|
|
1227
|
+
]
|
|
1228
|
+
: fallbackFeatures;
|
|
1061
1229
|
if (plan === "power") return {
|
|
1062
1230
|
id: "power",
|
|
1063
1231
|
name: "Power",
|
|
@@ -1067,11 +1235,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1067
1235
|
cadence: trialAvailable ? "per month · 14-day trial" : "per month · billed immediately",
|
|
1068
1236
|
sticker: "/hud-assets/echo-pricing-power-sticker.png",
|
|
1069
1237
|
stickerAlt: "Power Echo arrives with a gold key and a crew of notebook helpers.",
|
|
1070
|
-
features:
|
|
1071
|
-
"2,000 past chats",
|
|
1072
|
-
"agent-heavy memory",
|
|
1073
|
-
"2,000 memory recalls / week"
|
|
1074
|
-
]
|
|
1238
|
+
features: features
|
|
1075
1239
|
};
|
|
1076
1240
|
if (plan === "pro") return {
|
|
1077
1241
|
id: "pro",
|
|
@@ -1083,11 +1247,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1083
1247
|
sticker: "/hud-assets/echo-pricing-pro-sticker.png",
|
|
1084
1248
|
stickerAlt: "Pro Echo organizes tabbed notebooks and a daily refresh control.",
|
|
1085
1249
|
popular: true,
|
|
1086
|
-
features:
|
|
1087
|
-
"500 past chats",
|
|
1088
|
-
"daily memory updates",
|
|
1089
|
-
"500 memory recalls / week"
|
|
1090
|
-
]
|
|
1250
|
+
features: features
|
|
1091
1251
|
};
|
|
1092
1252
|
return {
|
|
1093
1253
|
id: "free",
|
|
@@ -1098,7 +1258,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1098
1258
|
cadence: "free forever · no card needed",
|
|
1099
1259
|
sticker: "/hud-assets/echo-pricing-free-sticker.png",
|
|
1100
1260
|
stickerAlt: "Original Echo hugs one simple memory card.",
|
|
1101
|
-
features:
|
|
1261
|
+
features: features
|
|
1102
1262
|
};
|
|
1103
1263
|
}
|
|
1104
1264
|
function renderPlanHabitat() {
|
|
@@ -1220,7 +1380,11 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1220
1380
|
if (setupPlanChoice === "free") {
|
|
1221
1381
|
if (readySettings) readySettings.classList.remove("is-plan-open", "is-checkout-pending");
|
|
1222
1382
|
if (planGateDecision) planGateDecision.classList.remove("is-checkout-pending");
|
|
1223
|
-
|
|
1383
|
+
var freeLimits = quotaLimitsForPlan("free");
|
|
1384
|
+
var freePlanCopy = freeLimits
|
|
1385
|
+
? quotaNumber(freeLimits.historicalConversationLimit) + " coding sessions and " + quotaNumber(freeLimits.memorySearchWeeklyLimit) + " memory recalls each week."
|
|
1386
|
+
: "Your managed import and weekly memory allowances are ready.";
|
|
1387
|
+
slot.innerHTML = '<div class="setupPlanConfirmed"><span class="setupPlanConfirmedIcon" aria-hidden="true">' + setupIcon("check") + '</span><span class="setupPlanConfirmedCopy"><strong>Original Echo</strong><span>' + esc(freePlanCopy) + '</span></span><button type="button" class="textButton" id="changeSetupPlan">Change</button></div>';
|
|
1224
1388
|
renderBillingCommitment("");
|
|
1225
1389
|
var changeBtn = document.getElementById("changeSetupPlan");
|
|
1226
1390
|
if (changeBtn) changeBtn.onclick = function () {
|
|
@@ -1321,6 +1485,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1321
1485
|
var nextBillingStatus = await getJson("/billing-status");
|
|
1322
1486
|
if (billingEpoch !== accountStateEpoch || !connected) return;
|
|
1323
1487
|
billingStatus = nextBillingStatus;
|
|
1488
|
+
await refreshPlanQuotaCatalog();
|
|
1324
1489
|
billingLastCheckedAt = Date.now();
|
|
1325
1490
|
billingCheckMessage = "";
|
|
1326
1491
|
if (readyStage === "sessions" && setupPlanConfirmed()) {
|
|
@@ -1423,7 +1588,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1423
1588
|
'<p class="recallPromptText">' + esc(primary) + '</p>' +
|
|
1424
1589
|
'<div class="recallPromptActions" aria-label="Try your first memory recall">' +
|
|
1425
1590
|
'<button type="button" class="recallAgent recallAgentPrimary" data-recall-codex><span class="recallAgentIcon"><img src="/hud-assets/codex.svg" alt="" /></span><span>Ask in Codex</span></button>' +
|
|
1426
|
-
|
|
1591
|
+
'<button type="button" class="recallAgent is-claude" data-recall-claude><span class="recallAgentIcon"><img src="/hud-assets/claude.svg" alt="" /></span><span>Use Claude instead</span></button>' +
|
|
1427
1592
|
'</div>' +
|
|
1428
1593
|
'</article>' +
|
|
1429
1594
|
'<p class="launchStatus" id="launchStatus"></p>' +
|
|
@@ -1441,6 +1606,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1441
1606
|
});
|
|
1442
1607
|
}
|
|
1443
1608
|
function renderEnding() {
|
|
1609
|
+
setCityMode(false);
|
|
1444
1610
|
setExtractMode(true);
|
|
1445
1611
|
// This view replaces the live extraction shell. Mark it unmounted so a failed
|
|
1446
1612
|
// pause can rebuild the progress page instead of trying to patch missing DOM.
|
|
@@ -1448,7 +1614,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1448
1614
|
setHead("Ending extraction", "Ending");
|
|
1449
1615
|
app.className = "";
|
|
1450
1616
|
app.innerHTML =
|
|
1451
|
-
setupJourney(
|
|
1617
|
+
setupJourney(4) +
|
|
1452
1618
|
'<section class="lifecycleState lifecyclePending">' +
|
|
1453
1619
|
'<h2>Stopping the remaining queued work…</h2>' +
|
|
1454
1620
|
'<p>Saved memories stay intact.</p>' +
|
|
@@ -1456,12 +1622,13 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1456
1622
|
'</section>';
|
|
1457
1623
|
}
|
|
1458
1624
|
function renderSkipped() {
|
|
1625
|
+
setCityMode(false);
|
|
1459
1626
|
setExtractMode(true);
|
|
1460
1627
|
setHead("Extraction ended for now", "Ended");
|
|
1461
1628
|
app.className = "";
|
|
1462
1629
|
var savedCount = Math.max(0, Number(lastExtractionProgress && lastExtractionProgress.extracted || 0));
|
|
1463
1630
|
app.innerHTML =
|
|
1464
|
-
setupJourney(savedCount > 0 ?
|
|
1631
|
+
setupJourney(savedCount > 0 ? 5 : 4) +
|
|
1465
1632
|
'<section class="lifecycleState lifecyclePaused">' +
|
|
1466
1633
|
'<header class="lifecycleIntro">' +
|
|
1467
1634
|
'<h2>' + (savedCount > 0 ? esc(number(savedCount)) + ' memories are ready to use.' : 'Import ended safely.') + '</h2>' +
|
|
@@ -1525,7 +1692,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1525
1692
|
statsSlow = false;
|
|
1526
1693
|
connected = false;
|
|
1527
1694
|
decisionMade = false;
|
|
1528
|
-
|
|
1695
|
+
renderForensicReport("Signed out locally. Connect EchoMem again to extract.");
|
|
1529
1696
|
} catch (e) {
|
|
1530
1697
|
decisionMade = false;
|
|
1531
1698
|
renderLogoutIssue(e && e.message ? e.message : String(e));
|