@echomem/mcp 1.4.47 → 1.4.48
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 +22 -26
- 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/index.js +5 -8
- package/dist/local-jsonl.js +87 -0
- package/dist/migrate.js +6 -4
- package/dist/onboarding-stats.js +16 -0
- package/dist/save-checkpoint-hook.js +1 -1
- package/dist/setup-page/client-core.js +18 -372
- package/dist/setup-page/client-extraction.js +14 -129
- package/dist/setup-page/client-lifecycle.js +28 -101
- package/dist/setup-page/client.js +0 -2
- package/dist/setup-page/styles-extraction.js +1 -31
- package/dist/setup-page/styles-foundation.js +0 -89
- package/dist/setup-page/styles-mvp.js +10 -155
- package/dist/setup-page/styles-website-alignment.js +0 -204
- package/dist/setup-page/styles.js +0 -4
- package/dist/setup-page.js +4 -4
- package/dist/setup-preview.js +4 -212
- package/dist/setup.js +94 -580
- package/dist/v1-contract.js +0 -8
- package/package.json +5 -7
- package/dist/city/README.md +0 -9
- package/dist/city/echo-ai-city-only.html +0 -2232
- package/dist/city/echo-extraction-plate.html +0 -330
- 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 +0 -1417
- package/dist/city/vendor/RoundedBoxGeometry.js +0 -155
- package/dist/city/vendor/echo_general-file-21.riv +0 -0
- package/dist/city/vendor/rive.js +0 -8139
- package/dist/city/vendor/rive.wasm +0 -0
- package/dist/city/vendor/three.module.min.js +0 -6
- package/dist/forensics.js +0 -1531
- package/dist/report.js +0 -721
- package/dist/setup-page/client-report-audit.js +0 -819
- package/dist/setup-page/client-report-city.js +0 -356
- package/dist/setup-page/client-report.js +0 -6
- package/dist/setup-page/styles-city-report.js +0 -880
- package/dist/setup-page/styles-context-audit.js +0 -470
|
@@ -1,97 +1,7 @@
|
|
|
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
|
-
}
|
|
92
3
|
function renderDiscoveryCheck(slow) {
|
|
93
4
|
void slow;
|
|
94
|
-
setCityMode(false);
|
|
95
5
|
setExtractMode(false);
|
|
96
6
|
setReadyMode(true);
|
|
97
7
|
setHead("Loading", "Working");
|
|
@@ -99,7 +9,6 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
99
9
|
app.innerHTML = '<div class="discoverySpinner" role="status" aria-label="Loading"><span aria-hidden="true"></span></div>';
|
|
100
10
|
}
|
|
101
11
|
function renderDiscoveryCheckIssue(error) {
|
|
102
|
-
setCityMode(false);
|
|
103
12
|
setExtractMode(false);
|
|
104
13
|
setReadyMode(true);
|
|
105
14
|
setHead("Session refresh paused", "Needs attention");
|
|
@@ -107,7 +16,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
107
16
|
app.innerHTML =
|
|
108
17
|
'<div style="padding:24px"><h2>Echo could not refresh session status</h2>' +
|
|
109
18
|
'<p class="helper">Keep the terminal open and try the local check again.</p>' +
|
|
110
|
-
(error ? '<details class="
|
|
19
|
+
(error ? '<details class="technicalDetails"><summary>Technical details</summary><code>' + esc(error) + '</code></details>' : '') +
|
|
111
20
|
'<div class="actions"><button id="retryDiscoveryCheck" class="primary">Try again</button></div></div>';
|
|
112
21
|
var retry = document.getElementById("retryDiscoveryCheck");
|
|
113
22
|
if (retry) retry.onclick = function () {
|
|
@@ -116,13 +25,11 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
116
25
|
};
|
|
117
26
|
}
|
|
118
27
|
function renderLogoutPending() {
|
|
119
|
-
setCityMode(false);
|
|
120
28
|
setHead("Signing out locally", "Working");
|
|
121
29
|
app.className = "loading-panel";
|
|
122
30
|
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>';
|
|
123
31
|
}
|
|
124
32
|
function renderLogoutIssue(error) {
|
|
125
|
-
setCityMode(false);
|
|
126
33
|
setHead("Sign out did not finish", "Needs attention");
|
|
127
34
|
app.className = "loading-panel";
|
|
128
35
|
app.innerHTML =
|
|
@@ -136,7 +43,6 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
136
43
|
if (statsPollStarted) return;
|
|
137
44
|
var statsEpoch = accountStateEpoch;
|
|
138
45
|
statsPollStarted = true;
|
|
139
|
-
setCityMode(false);
|
|
140
46
|
setExtractMode(false);
|
|
141
47
|
setReadyMode(true);
|
|
142
48
|
renderDiscoveryCheck(false);
|
|
@@ -169,23 +75,14 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
169
75
|
if (res.status !== 202) throw new Error(await res.text() || ("HTTP " + res.status));
|
|
170
76
|
} catch (e) {
|
|
171
77
|
lastError = e && e.message ? e.message : String(e);
|
|
172
|
-
if (!stats && Date.now() - started > 25000)
|
|
78
|
+
if (!stats && Date.now() - started > 25000) renderDiscoveryCheckIssue(lastError);
|
|
173
79
|
if (Date.now() - started > 20000) { statsSlow = true; if (stats) renderDashboard(); }
|
|
174
80
|
}
|
|
175
81
|
await sleep(statsSlow ? 1500 : 500);
|
|
176
82
|
}
|
|
177
83
|
} finally { statsPollStarted = false; }
|
|
178
84
|
}
|
|
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
|
-
}
|
|
187
85
|
function renderPlanGate() {
|
|
188
|
-
setCityMode(false);
|
|
189
86
|
setExtractMode(false);
|
|
190
87
|
setReadyMode(true);
|
|
191
88
|
setHead("Choose your Echo", "Plan");
|
|
@@ -198,7 +95,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
198
95
|
readyStage = "plan";
|
|
199
96
|
app.className = "planGateStage";
|
|
200
97
|
app.innerHTML =
|
|
201
|
-
setupJourney(
|
|
98
|
+
setupJourney(2) +
|
|
202
99
|
'<section class="planGateDecision">' +
|
|
203
100
|
'<div class="setupPlanSlot" id="setupPlan"></div>' +
|
|
204
101
|
'<aside class="planGateAccount" aria-label="Connected account"><div class="setupAccountSlot" id="setupAccount"></div></aside>' +
|
|
@@ -207,7 +104,6 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
207
104
|
renderAccountChoice();
|
|
208
105
|
}
|
|
209
106
|
function renderDashboard(error) {
|
|
210
|
-
setCityMode(false);
|
|
211
107
|
setExtractMode(false);
|
|
212
108
|
setReadyMode(true);
|
|
213
109
|
var migratable = stats && stats.migratable ? stats.migratable : {};
|
|
@@ -264,9 +160,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
264
160
|
? currentPlanLabel + " can import " + number(importableNow) + " of them. Choose which ones to bring into memory; the other " + number(deferredByPlan) + " stay on this Mac."
|
|
265
161
|
: currentPlanLabel + " has no coding-session imports remaining. These sessions stay on this Mac.")
|
|
266
162
|
: ""));
|
|
267
|
-
// Mount the dashboard shell
|
|
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
|
+
// Mount the dashboard shell once; later polls only patch its text and counts.
|
|
270
164
|
var live = dashMounted && document.getElementById("exHeadline");
|
|
271
165
|
if (!live) {
|
|
272
166
|
extractMounted = false;
|
|
@@ -278,7 +172,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
278
172
|
// one-line strip, not stat cards; the ETA is a commitment-moment note under the button,
|
|
279
173
|
// not a display-size headline.
|
|
280
174
|
app.innerHTML =
|
|
281
|
-
setupJourney(
|
|
175
|
+
setupJourney(3) +
|
|
282
176
|
'<aside class="readyUtilityNav" aria-label="Account and plan settings">' +
|
|
283
177
|
'<div class="planGateAccount readyAccountNav" aria-label="Connected account"><div class="setupAccountSlot" id="setupAccount"></div></div>' +
|
|
284
178
|
'<div class="readyPlanNav" aria-label="Current plan"><span class="readyPlanNavLabel">Plan</span><div class="setupPlanSlot" id="setupPlan"></div></div>' +
|
|
@@ -396,14 +290,12 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
396
290
|
stopBillingPoll();
|
|
397
291
|
decisionMade = true;
|
|
398
292
|
extractionEnded = false;
|
|
399
|
-
setCityMode(false);
|
|
400
|
-
setScanMode(false);
|
|
401
293
|
setReadyMode(false);
|
|
402
294
|
setExtractMode(true);
|
|
403
295
|
setHead("Extracting", "Starting");
|
|
404
296
|
app.className = "extractionStartStage";
|
|
405
297
|
app.innerHTML =
|
|
406
|
-
setupJourney(
|
|
298
|
+
setupJourney(3) +
|
|
407
299
|
'<div class="discoverySpinner" role="status" aria-label="Starting import"><span aria-hidden="true"></span></div>';
|
|
408
300
|
var started;
|
|
409
301
|
try {
|
|
@@ -495,7 +387,6 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
495
387
|
function renderProgress(progress) {
|
|
496
388
|
if (extractionEnded) return;
|
|
497
389
|
lastExtractionProgress = progress;
|
|
498
|
-
setCityMode(false);
|
|
499
390
|
setExtractMode(true);
|
|
500
391
|
var status = progress.status;
|
|
501
392
|
var done = status === "completed";
|
|
@@ -507,18 +398,15 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
507
398
|
var pct = total > 0 ? Math.round((completed / total) * 100) : (done ? 100 : 0);
|
|
508
399
|
var preparing = (status === "starting" || status === "preparing");
|
|
509
400
|
app.className = "";
|
|
510
|
-
// Mount the shell
|
|
511
|
-
// each poll only rebuilds the text column.
|
|
401
|
+
// Mount the shell once; each poll only rebuilds the text column.
|
|
512
402
|
if (!extractMounted) {
|
|
513
403
|
app.innerHTML =
|
|
514
|
-
'<div id="setupJourneySlot">' + setupJourney(
|
|
515
|
-
'<section class="exHero"><div class="exCopy" id="exText"></div>'
|
|
516
|
-
plateBlock() +
|
|
517
|
-
'</section>';
|
|
404
|
+
'<div id="setupJourneySlot">' + setupJourney(3) + '</div>' +
|
|
405
|
+
'<section class="exHero"><div class="exCopy" id="exText"></div></section>';
|
|
518
406
|
extractMounted = true;
|
|
519
407
|
}
|
|
520
408
|
var journeySlot = document.getElementById("setupJourneySlot");
|
|
521
|
-
if (journeySlot) journeySlot.innerHTML = setupJourney(done ?
|
|
409
|
+
if (journeySlot) journeySlot.innerHTML = setupJourney(done ? 4 : 3);
|
|
522
410
|
var shell = app.querySelector(".exHero");
|
|
523
411
|
if (shell) {
|
|
524
412
|
shell.classList.toggle("exHeroResult", done || stopped);
|
|
@@ -1042,13 +930,12 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1042
930
|
? account.email
|
|
1043
931
|
: "the connected account";
|
|
1044
932
|
accountSwitchPending = true;
|
|
1045
|
-
setCityMode(false);
|
|
1046
933
|
setExtractMode(false);
|
|
1047
934
|
setReadyMode(true);
|
|
1048
935
|
setHead("Switch EchoMem account", "Confirm");
|
|
1049
936
|
app.className = "accountSwitchStage";
|
|
1050
937
|
app.innerHTML =
|
|
1051
|
-
setupJourney(
|
|
938
|
+
setupJourney(3) +
|
|
1052
939
|
'<section class="accountSwitchConfirm" aria-labelledby="accountSwitchTitle">' +
|
|
1053
940
|
'<button type="button" class="flowBack" id="cancelAccountSwitch">← Back</button>' +
|
|
1054
941
|
'<h2 id="accountSwitchTitle">Switch account?</h2>' +
|
|
@@ -1606,7 +1493,6 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1606
1493
|
});
|
|
1607
1494
|
}
|
|
1608
1495
|
function renderEnding() {
|
|
1609
|
-
setCityMode(false);
|
|
1610
1496
|
setExtractMode(true);
|
|
1611
1497
|
// This view replaces the live extraction shell. Mark it unmounted so a failed
|
|
1612
1498
|
// pause can rebuild the progress page instead of trying to patch missing DOM.
|
|
@@ -1614,7 +1500,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1614
1500
|
setHead("Ending extraction", "Ending");
|
|
1615
1501
|
app.className = "";
|
|
1616
1502
|
app.innerHTML =
|
|
1617
|
-
setupJourney(
|
|
1503
|
+
setupJourney(3) +
|
|
1618
1504
|
'<section class="lifecycleState lifecyclePending">' +
|
|
1619
1505
|
'<h2>Stopping the remaining queued work…</h2>' +
|
|
1620
1506
|
'<p>Saved memories stay intact.</p>' +
|
|
@@ -1622,13 +1508,12 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1622
1508
|
'</section>';
|
|
1623
1509
|
}
|
|
1624
1510
|
function renderSkipped() {
|
|
1625
|
-
setCityMode(false);
|
|
1626
1511
|
setExtractMode(true);
|
|
1627
1512
|
setHead("Extraction ended for now", "Ended");
|
|
1628
1513
|
app.className = "";
|
|
1629
1514
|
var savedCount = Math.max(0, Number(lastExtractionProgress && lastExtractionProgress.extracted || 0));
|
|
1630
1515
|
app.innerHTML =
|
|
1631
|
-
setupJourney(savedCount > 0 ?
|
|
1516
|
+
setupJourney(savedCount > 0 ? 4 : 3) +
|
|
1632
1517
|
'<section class="lifecycleState lifecyclePaused">' +
|
|
1633
1518
|
'<header class="lifecycleIntro">' +
|
|
1634
1519
|
'<h2>' + (savedCount > 0 ? esc(number(savedCount)) + ' memories are ready to use.' : 'Import ended safely.') + '</h2>' +
|
|
@@ -1692,7 +1577,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1692
1577
|
statsSlow = false;
|
|
1693
1578
|
connected = false;
|
|
1694
1579
|
decisionMade = false;
|
|
1695
|
-
|
|
1580
|
+
renderLocalLogin("Signed out locally. Connect EchoMem again to extract.");
|
|
1696
1581
|
} catch (e) {
|
|
1697
1582
|
decisionMade = false;
|
|
1698
1583
|
renderLogoutIssue(e && e.message ? e.message : String(e));
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
// Generated by splitting the previous inline setup page. Keep this source readable: it is emitted into one browser IIFE.
|
|
2
|
-
export const SETUP_PAGE_CLIENT_LIFECYCLE = String.raw ` /* ----------
|
|
3
|
-
function
|
|
4
|
-
setScanMode(false);
|
|
5
|
-
setCityMode(false);
|
|
2
|
+
export const SETUP_PAGE_CLIENT_LIFECYCLE = String.raw ` /* ---------- local-history permission and setup routing ---------- */
|
|
3
|
+
function renderLocalHistoryConsent() {
|
|
6
4
|
setExtractMode(false);
|
|
7
5
|
setReadyMode(true);
|
|
8
6
|
setHead("Local file access", "Consent");
|
|
@@ -14,15 +12,15 @@ export const SETUP_PAGE_CLIENT_LIFECYCLE = String.raw ` /* ---------- scan-
|
|
|
14
12
|
'<img class="mvpConsentEcho" src="/hud-assets/echo-face-cutout.png" alt="" />' +
|
|
15
13
|
'<div class="mvpConsentPitch">' +
|
|
16
14
|
'<h2 id="mvpConsentTitle">Start with the context you’ve already built.</h2>' +
|
|
17
|
-
'<p class="mvpConsentIntro">
|
|
15
|
+
'<p class="mvpConsentIntro">Choose which local coding conversations to bring into EchoMem.</p>' +
|
|
18
16
|
'</div>' +
|
|
19
17
|
'</div>' +
|
|
20
|
-
'<p class="mvpConsentTrustLine">Your history stays on
|
|
21
|
-
'<button type="button" class="primary mvpPrimary" id="
|
|
18
|
+
'<p class="mvpConsentTrustLine">Your history stays on this computer until you choose what to import. ' + dataHandlingLink("Data handling") + '</p>' +
|
|
19
|
+
'<button type="button" class="primary mvpPrimary" id="allowLocalHistory">Continue <span aria-hidden="true">→</span></button>' +
|
|
22
20
|
'<p class="consentFine" id="consentStatus" role="status" aria-live="polite"></p>' +
|
|
23
21
|
'</section>';
|
|
24
|
-
var allow = document.getElementById("
|
|
25
|
-
if (allow) allow.onclick =
|
|
22
|
+
var allow = document.getElementById("allowLocalHistory");
|
|
23
|
+
if (allow) allow.onclick = allowLocalHistory;
|
|
26
24
|
}
|
|
27
25
|
function setConsentStatus(message, tone) {
|
|
28
26
|
var node = document.getElementById("consentStatus");
|
|
@@ -31,18 +29,26 @@ export const SETUP_PAGE_CLIENT_LIFECYCLE = String.raw ` /* ---------- scan-
|
|
|
31
29
|
node.classList.toggle("is-required", tone === "required");
|
|
32
30
|
}
|
|
33
31
|
}
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
function continueAfterLocalHistoryConsent() {
|
|
33
|
+
if (connected) {
|
|
34
|
+
try { void refreshBillingStatus(); } catch (_) {}
|
|
35
|
+
void waitForStats();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
rememberSetupPlanChoice("");
|
|
39
|
+
renderLocalLogin("");
|
|
40
|
+
}
|
|
41
|
+
async function allowLocalHistory() {
|
|
42
|
+
var allow = document.getElementById("allowLocalHistory");
|
|
36
43
|
if (allow) allow.disabled = true;
|
|
37
|
-
setConsentStatus("
|
|
44
|
+
setConsentStatus("Saving your choice...");
|
|
38
45
|
try {
|
|
39
|
-
await postJson("/
|
|
46
|
+
await postJson("/local-history-consent", { allowed: true }, 5000);
|
|
40
47
|
localHistoryConsentGranted = true;
|
|
41
|
-
|
|
48
|
+
continueAfterLocalHistoryConsent();
|
|
42
49
|
} catch (error) {
|
|
43
50
|
// The localhost bridge may have accepted consent even if the browser loses the POST
|
|
44
|
-
// response while it swaps connections. Recover from the server's authoritative state
|
|
45
|
-
// instead of leaving the user forever on "Starting local scan...".
|
|
51
|
+
// response while it swaps connections. Recover from the server's authoritative state.
|
|
46
52
|
try {
|
|
47
53
|
var configResponse = await fetchWithTimeout(
|
|
48
54
|
"/config?nonce=" + encodeURIComponent(nonce),
|
|
@@ -52,80 +58,14 @@ export const SETUP_PAGE_CLIENT_LIFECYCLE = String.raw ` /* ---------- scan-
|
|
|
52
58
|
var config = configResponse.ok ? await configResponse.json() : null;
|
|
53
59
|
if (config && config.consentGranted === true) {
|
|
54
60
|
localHistoryConsentGranted = true;
|
|
55
|
-
|
|
61
|
+
continueAfterLocalHistoryConsent();
|
|
56
62
|
return;
|
|
57
63
|
}
|
|
58
64
|
} catch (_) {}
|
|
59
|
-
|
|
65
|
+
if (allow) allow.disabled = false;
|
|
66
|
+
setConsentStatus(error && error.message ? error.message : "Could not save local-history access.", "required");
|
|
60
67
|
}
|
|
61
68
|
}
|
|
62
|
-
async function showReport() {
|
|
63
|
-
if (reportPollStarted) return;
|
|
64
|
-
reportPollStarted = true;
|
|
65
|
-
if (!report) renderLoading();
|
|
66
|
-
try {
|
|
67
|
-
var fails = 0;
|
|
68
|
-
for (;;) {
|
|
69
|
-
if (decisionMade) return;
|
|
70
|
-
var res;
|
|
71
|
-
try {
|
|
72
|
-
res = await fetchWithTimeout("/report?nonce=" + encodeURIComponent(nonce), { credentials: "omit", cache: "no-store" }, 12000);
|
|
73
|
-
} catch (_) {
|
|
74
|
-
// Only a RUN of consecutive failures is a real bridge problem. A long scan that keeps
|
|
75
|
-
// answering valid 202 envelopes must never trip this.
|
|
76
|
-
fails++;
|
|
77
|
-
if (fails >= 8) { renderBridgeIssue(); return; }
|
|
78
|
-
await sleep(700);
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
if (res.status === 403) {
|
|
82
|
-
renderReportIssue("SETUP_SESSION_INVALID", "This setup link is missing or has an invalid local session nonce.");
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
var envelope;
|
|
86
|
-
try {
|
|
87
|
-
envelope = await res.json();
|
|
88
|
-
} catch (_) {
|
|
89
|
-
renderReportIssue("REPORT_RESPONSE_INVALID", "The local bridge returned a report response that could not be decoded.");
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
try {
|
|
93
|
-
validateProductionReportEnvelope(envelope);
|
|
94
|
-
} catch (_) {
|
|
95
|
-
renderReportIssue("REPORT_SCHEMA_INVALID", "The local report failed its provenance or ledger consistency checks.");
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
if (res.status === 202 && envelope.kind === "scanning") {
|
|
99
|
-
fails = 0;
|
|
100
|
-
reportEnvelope = envelope;
|
|
101
|
-
scheduleReportToCity();
|
|
102
|
-
renderLoading();
|
|
103
|
-
await sleep(700);
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
if (res.status === 200 && envelope.kind === "empty") {
|
|
107
|
-
renderEmptyReport();
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
if (res.status === 200 && envelope.kind === "ready") {
|
|
111
|
-
reportEnvelope = envelope;
|
|
112
|
-
report = envelope.report;
|
|
113
|
-
try {
|
|
114
|
-
renderForensicReport();
|
|
115
|
-
} catch (_) {
|
|
116
|
-
renderReportIssue("RENDER_FAILED", "Your validated local report could not be rendered safely.");
|
|
117
|
-
}
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
if (envelope.kind === "failed") {
|
|
121
|
-
renderReportIssue(envelope.error.code, envelope.error.message);
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
renderReportIssue("REPORT_STATE_MISMATCH", "The local bridge returned a report state that did not match its HTTP status.");
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
} finally { reportPollStarted = false; }
|
|
128
|
-
}
|
|
129
69
|
|
|
130
70
|
window.addEventListener("message", function (event) {
|
|
131
71
|
if (event.origin !== location.origin) return;
|
|
@@ -140,21 +80,13 @@ export const SETUP_PAGE_CLIENT_LIFECYCLE = String.raw ` /* ---------- scan-
|
|
|
140
80
|
rememberSetupPlanChoice("");
|
|
141
81
|
try { void refreshBillingStatus(); } catch (_) {}
|
|
142
82
|
void waitForStats();
|
|
143
|
-
} else if (data.type === "echomem:city-ready" && data.nonce === nonce) {
|
|
144
|
-
scheduleReportToCity();
|
|
145
|
-
} else if (data.type === "echomem:connect-click") {
|
|
146
|
-
onConnectClick();
|
|
147
83
|
}
|
|
148
84
|
});
|
|
149
85
|
async function init() {
|
|
150
86
|
if (!nonce) throw new Error("Missing setup nonce. Re-run echomem-mcp init.");
|
|
151
|
-
// Older builds cached a report under the nonce. It is never an authority now; the current
|
|
152
|
-
// server envelope and scan identity are the only inputs allowed to unlock personalized UI.
|
|
153
|
-
try { sessionStorage.removeItem("echomem:setup-report:" + nonce); } catch (_) {}
|
|
154
87
|
var configLoaded = false;
|
|
155
88
|
var consentRequired = true;
|
|
156
89
|
var consentGranted = false;
|
|
157
|
-
// Learn the connection state and server-side consent gate before routing.
|
|
158
90
|
try {
|
|
159
91
|
var cfg = await getJson("/config");
|
|
160
92
|
configLoaded = true;
|
|
@@ -173,32 +105,27 @@ export const SETUP_PAGE_CLIENT_LIFECYCLE = String.raw ` /* ---------- scan-
|
|
|
173
105
|
if (!connected) rememberSetupPlanChoice("");
|
|
174
106
|
if (params.get("connected") === "1") {
|
|
175
107
|
if (!configLoaded || (consentRequired && !consentGranted)) {
|
|
176
|
-
|
|
108
|
+
renderLocalHistoryConsent();
|
|
177
109
|
setConsentStatus("Local history access is required before you can connect EchoMem or continue setup.", "required");
|
|
178
110
|
return;
|
|
179
111
|
}
|
|
180
112
|
connected = true;
|
|
181
113
|
if (notifyOpenerConnected()) {
|
|
182
|
-
// Popup flow: hand the main tab back control, then this popup closes.
|
|
183
114
|
setHead("EchoMem connected", "Done");
|
|
184
115
|
app.className = "notice";
|
|
185
116
|
app.innerHTML = '<h2 style="color:#203820">This device is connected</h2><p style="margin-top:10px">Returning to the local setup page...</p>';
|
|
186
|
-
// Fallback: if window.close() was blocked (auth opened as a tab, or the browser refused),
|
|
187
|
-
// this window would otherwise hang on "Returning..." forever. After a beat, take over here
|
|
188
|
-
// so the user always lands on the dashboard instead of a dead page.
|
|
189
117
|
setTimeout(function () { if (!window.closed) void waitForStats(); }, 1500);
|
|
190
118
|
} else {
|
|
191
|
-
// Same-tab fallback (popup was blocked): go straight to the extraction dashboard.
|
|
192
119
|
try { void refreshBillingStatus(); } catch (_) {}
|
|
193
120
|
void waitForStats();
|
|
194
121
|
}
|
|
195
122
|
return;
|
|
196
123
|
}
|
|
197
124
|
if (consentGranted) {
|
|
198
|
-
|
|
125
|
+
continueAfterLocalHistoryConsent();
|
|
199
126
|
return;
|
|
200
127
|
}
|
|
201
|
-
|
|
128
|
+
renderLocalHistoryConsent();
|
|
202
129
|
}
|
|
203
130
|
__ECHO_SETUP_STARTUP__;
|
|
204
131
|
`;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { SETUP_PAGE_CLIENT_CORE } from "./client-core.js";
|
|
2
|
-
import { SETUP_PAGE_CLIENT_REPORT } from "./client-report.js";
|
|
3
2
|
import { SETUP_PAGE_CLIENT_EXTRACTION } from "./client-extraction.js";
|
|
4
3
|
import { SETUP_PAGE_CLIENT_LIFECYCLE } from "./client-lifecycle.js";
|
|
5
4
|
export function renderSetupPageClient(previewState, startupScript) {
|
|
6
5
|
const body = [
|
|
7
6
|
SETUP_PAGE_CLIENT_CORE,
|
|
8
|
-
SETUP_PAGE_CLIENT_REPORT,
|
|
9
7
|
SETUP_PAGE_CLIENT_EXTRACTION,
|
|
10
8
|
SETUP_PAGE_CLIENT_LIFECYCLE,
|
|
11
9
|
].join("\n")
|
|
@@ -598,13 +598,8 @@ export const SETUP_PAGE_STYLES_EXTRACTION = String.raw ` /* ---- dashboard (p
|
|
|
598
598
|
.consentActions .primary { width: 100%; }
|
|
599
599
|
.localAuthActions .primary,
|
|
600
600
|
.localAuthForm > .primary { width: 100%; }
|
|
601
|
-
.cityHero { min-height: auto; }
|
|
602
|
-
.cityShell {
|
|
603
|
-
width: 100vw;
|
|
604
|
-
height: 100vh;
|
|
605
|
-
}
|
|
606
601
|
}
|
|
607
|
-
/* extraction dashboard
|
|
602
|
+
/* extraction dashboard */
|
|
608
603
|
.exHero { display: grid; grid-template-columns: minmax(0, 1fr) minmax(300px, 0.95fr); gap: clamp(20px, 3vw, 40px); align-items: center; margin-top: 8px; }
|
|
609
604
|
/* Centered composition: mascot on top, then everything stacked and centered on one axis. */
|
|
610
605
|
.readyDecision {
|
|
@@ -674,7 +669,6 @@ export const SETUP_PAGE_STYLES_EXTRACTION = String.raw ` /* ---- dashboard (p
|
|
|
674
669
|
justify-items: stretch;
|
|
675
670
|
margin: 0 auto;
|
|
676
671
|
}
|
|
677
|
-
.exHeroResult .exPlate { display: none; }
|
|
678
672
|
.exHeroResult .exCopy { width: 100%; margin: 0 auto; }
|
|
679
673
|
.exCopy h2 { max-width: 620px; margin: 8px 0 0; font-size: clamp(34px, 4.8vw, 58px); line-height: 1.01; }
|
|
680
674
|
.exCopy .exSub { margin: 14px 0 0; max-width: 590px; font-size: clamp(15px, 1.3vw, 17px); line-height: 1.6; color: var(--echo-ink-mute); }
|
|
@@ -896,19 +890,6 @@ export const SETUP_PAGE_STYLES_EXTRACTION = String.raw ` /* ---- dashboard (p
|
|
|
896
890
|
font-size: 12px;
|
|
897
891
|
line-height: 1.35;
|
|
898
892
|
}
|
|
899
|
-
.exPlate { width: 100%; }
|
|
900
|
-
/* WebGL clay city (echo-extraction-plate.html) — the only plate; no SVG layer */
|
|
901
|
-
.plateStack { position: relative; width: 100%; aspect-ratio: 640 / 470; }
|
|
902
|
-
.plateStack .plateGl { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; background: transparent; }
|
|
903
|
-
.exHeroActive {
|
|
904
|
-
grid-template-columns: minmax(0, 640px);
|
|
905
|
-
justify-content: center;
|
|
906
|
-
justify-items: stretch;
|
|
907
|
-
width: 100%;
|
|
908
|
-
max-width: min(920px, calc(100vw - clamp(36px, 8vw, 120px)));
|
|
909
|
-
margin: 0 auto;
|
|
910
|
-
}
|
|
911
|
-
.exHeroActive .exPlate { display: none; }
|
|
912
893
|
.exHeroActive .exCopy { width: 100%; }
|
|
913
894
|
.workbench { display: grid; gap: 14px; max-width: 640px; }
|
|
914
895
|
.workbench h2 {
|
|
@@ -2538,15 +2519,4 @@ export const SETUP_PAGE_STYLES_EXTRACTION = String.raw ` /* ---- dashboard (p
|
|
|
2538
2519
|
.sessionPickerFoot button { width: 100%; }
|
|
2539
2520
|
}
|
|
2540
2521
|
|
|
2541
|
-
/* ===== workspace readout dashboard (Part 2 — replaces explainPage) ===== */
|
|
2542
|
-
@keyframes rdoRise { from { opacity: 0; translate: 0 14px; } to { opacity: 1; translate: 0 0; } }
|
|
2543
|
-
@keyframes rdoGrow { from { transform: scaleY(0); } to { transform: scaleY(1); } }
|
|
2544
|
-
@keyframes rdoDraw { from { transform: scaleX(0); } to { transform: scaleX(1); } }
|
|
2545
|
-
@keyframes rdoWipe { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }
|
|
2546
|
-
/* Pull the report up toward the city figure — the canvas bottom fades out now,
|
|
2547
|
-
so the document can tuck into that empty band without touching the board. */
|
|
2548
|
-
body.cityMode .detailsWrap { background: var(--echo-paper-canvas); margin-top: clamp(-120px, -7vw, -56px); }
|
|
2549
|
-
|
|
2550
|
-
/* Context audit — a printed report sheet on the desk: serif document type, tight rhythm. */
|
|
2551
|
-
.context-audit {
|
|
2552
2522
|
`;
|