@echomem/mcp 1.4.21 → 1.4.22
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 +1 -1
- package/assets/hud/echo-pricing-free-sticker.png +0 -0
- package/assets/hud/echo-pricing-power-sticker.png +0 -0
- package/assets/hud/echo-pricing-pro-sticker.png +0 -0
- package/assets/hud/scan-loop/frame-01-ready.png +0 -0
- package/assets/hud/scan-loop/frame-02-open.png +0 -0
- package/assets/hud/scan-loop/frame-03-pull.png +0 -0
- package/assets/hud/scan-loop/frame-04-read.png +0 -0
- package/assets/hud/scan-loop/frame-05-file.png +0 -0
- package/assets/hud/scan-loop/frame-06-close.png +0 -0
- package/dist/setup-page/client-core.js +140 -47
- package/dist/setup-page/client-extraction.js +251 -68
- package/dist/setup-page/client-lifecycle.js +45 -39
- package/dist/setup-page/client-report-audit.js +1 -1
- package/dist/setup-page/client-report-city.js +35 -22
- package/dist/setup-page/document.js +1 -1
- package/dist/setup-page/styles-extraction.js +330 -0
- package/dist/setup-page/styles-mvp.js +976 -0
- package/dist/setup-page/styles-website-alignment.js +237 -5
- package/dist/setup-page/styles.js +2 -0
- package/dist/setup-preview.js +18 -2
- package/dist/setup.js +262 -40
- package/package.json +1 -1
|
@@ -113,6 +113,10 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
113
113
|
if (res.status === 200) {
|
|
114
114
|
stats = await res.json();
|
|
115
115
|
if (decisionMade) return;
|
|
116
|
+
if (connected && !billingStatus && !billingStatusLoading) {
|
|
117
|
+
try { await refreshBillingStatus(); } catch (_) {}
|
|
118
|
+
if (decisionMade) return;
|
|
119
|
+
}
|
|
116
120
|
renderDashboard();
|
|
117
121
|
if (stats && stats.partial) { await sleep(1200); continue; }
|
|
118
122
|
return;
|
|
@@ -135,6 +139,27 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
135
139
|
'<iframe class="plateGl" title="Extraction plate" src="/city/echo-extraction-plate.html?nonce=' + encodeURIComponent(nonce) + '"></iframe>' +
|
|
136
140
|
'</div></div>';
|
|
137
141
|
}
|
|
142
|
+
function renderPlanGate() {
|
|
143
|
+
setCityMode(false);
|
|
144
|
+
setExtractMode(false);
|
|
145
|
+
setReadyMode(true);
|
|
146
|
+
setHead("Choose your Echo", "Plan");
|
|
147
|
+
dashMounted = false;
|
|
148
|
+
if (readyStage === "plan" && app.className === "planGateStage" && document.getElementById("setupPlan")) {
|
|
149
|
+
renderSetupPlan(true);
|
|
150
|
+
renderAccountChoice();
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
readyStage = "plan";
|
|
154
|
+
app.className = "planGateStage";
|
|
155
|
+
app.innerHTML =
|
|
156
|
+
'<section class="planGateDecision">' +
|
|
157
|
+
'<div class="setupPlanSlot" id="setupPlan"></div>' +
|
|
158
|
+
'<aside class="planGateAccount" aria-label="Connected account"><div class="setupAccountSlot" id="setupAccount"></div></aside>' +
|
|
159
|
+
'</section>';
|
|
160
|
+
renderSetupPlan(true);
|
|
161
|
+
renderAccountChoice();
|
|
162
|
+
}
|
|
138
163
|
function renderDashboard(error) {
|
|
139
164
|
setCityMode(false);
|
|
140
165
|
setExtractMode(false);
|
|
@@ -159,6 +184,10 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
159
184
|
var etaLabel = eta.estimatedLabel || migratable.estimatedLabel || "a couple of minutes";
|
|
160
185
|
var pendingCodex = typeof migratable.pendingCodex === "number" ? migratable.pendingCodex : null;
|
|
161
186
|
var pendingClaudeCode = typeof migratable.pendingClaudeCode === "number" ? migratable.pendingClaudeCode : null;
|
|
187
|
+
if (connected && !billingStatusLoading && !setupPlanConfirmed()) {
|
|
188
|
+
renderPlanGate();
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
162
191
|
setHead("Turn coding history into memory", pendingTrusted ? (knownDone ? "Done" : "Ready") : (localScanReady ? "Ready" : "Scanning"));
|
|
163
192
|
var headlineHtml = !pendingTrusted
|
|
164
193
|
? "Counting your <strong>new conversations…</strong>"
|
|
@@ -170,13 +199,14 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
170
199
|
: (knownDone
|
|
171
200
|
? (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.")
|
|
172
201
|
: "Turn them into memory your agents can recall: decisions, fixes, and where you left off. No more re-explaining your project every session.");
|
|
173
|
-
var extractLabel = !pendingTrusted ? "Counting…" : (canExtract ? "Start extraction" : "All caught up");
|
|
202
|
+
var extractLabel = !pendingTrusted ? "Counting…" : (canExtract ? "Start memory extraction" : "All caught up");
|
|
174
203
|
// Mount the dashboard shell ONCE so the WebGL plate iframe is never re-created across /stats polls.
|
|
175
204
|
// (Rebuilding app.innerHTML on every partial-scan poll reloaded the iframe → the city flickered.)
|
|
176
205
|
// Later polls only patch the text/counts in place; the iframe stays mounted.
|
|
177
206
|
var live = dashMounted && document.getElementById("exHeadline");
|
|
178
207
|
if (!live) {
|
|
179
208
|
extractMounted = false;
|
|
209
|
+
readyStage = "sessions";
|
|
180
210
|
app.className = "";
|
|
181
211
|
// One reading line, top to bottom: the find (headline) → the value (sub) → quiet proof
|
|
182
212
|
// (source strip) → the single action with its reassurances attached → optional detail.
|
|
@@ -185,7 +215,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
185
215
|
// not a display-size headline.
|
|
186
216
|
app.innerHTML =
|
|
187
217
|
'<div id="exBanner"></div>' +
|
|
188
|
-
'<section class="readyDecision">' +
|
|
218
|
+
'<section class="readyDecision extractionReady">' +
|
|
189
219
|
'<p class="utilityEyebrow readyEyebrow">History import / review</p>' +
|
|
190
220
|
'<div class="readyHero">' +
|
|
191
221
|
'<img class="readyMascot" src="/hud-assets/echo-face-cutout.png" alt="" />' +
|
|
@@ -195,22 +225,23 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
195
225
|
'</div>' +
|
|
196
226
|
'</div>' +
|
|
197
227
|
'<div class="proofStrip" id="exSources"></div>' +
|
|
198
|
-
'<div class="setupAccountSlot" id="setupAccount"></div>' +
|
|
199
|
-
'<div class="setupPlanSlot" id="setupPlan"></div>' +
|
|
200
|
-
'<div class="sessionSelectionSlot" id="sessionSelection"></div>' +
|
|
201
228
|
'<div class="readyGo">' +
|
|
202
229
|
'<div class="actions">' +
|
|
203
230
|
'<button id="migrate" class="primary"></button>' +
|
|
204
|
-
'<button type="button" id="skipHistoryImport" class="secondary skipHistoryButton">Skip
|
|
231
|
+
'<button type="button" id="skipHistoryImport" class="secondary skipHistoryButton">Skip for now</button>' +
|
|
205
232
|
'</div>' +
|
|
206
233
|
'<p class="ctaMeta" id="exEta"></p>' +
|
|
207
234
|
'</div>' +
|
|
235
|
+
'<aside class="readySettings" aria-label="Account and plan settings">' +
|
|
236
|
+
'<div class="readySetting readyAccountSetting"><span class="readySettingLabel">Account</span><div class="setupAccountSlot" id="setupAccount"></div></div>' +
|
|
237
|
+
'<div class="readySetting readyPlanSetting"><span class="readySettingLabel">Plan</span><div class="setupPlanSlot" id="setupPlan"></div></div>' +
|
|
238
|
+
'</aside>' +
|
|
208
239
|
'<div class="readyFoot">' +
|
|
209
240
|
'<details class="readyExplain" id="readyExplain"></details>' +
|
|
210
241
|
'</div>' +
|
|
211
242
|
'</section>' +
|
|
212
243
|
'<dialog class="sessionPicker" id="sessionPicker" aria-labelledby="sessionPickerTitle"></dialog>';
|
|
213
|
-
document.getElementById("migrate").onclick =
|
|
244
|
+
document.getElementById("migrate").onclick = openSessionPicker;
|
|
214
245
|
dashMounted = true;
|
|
215
246
|
}
|
|
216
247
|
document.getElementById("exBanner").innerHTML =
|
|
@@ -250,7 +281,6 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
250
281
|
'</div>';
|
|
251
282
|
renderSetupPlan(canExtract);
|
|
252
283
|
renderAccountChoice();
|
|
253
|
-
renderSessionSelection(canExtract);
|
|
254
284
|
if (canExtract && !billingStatus && !billingStatusLoading) void refreshBillingStatus();
|
|
255
285
|
// While still counting, hide the primary button entirely; the headline + source split
|
|
256
286
|
// already say it is working, and the ready state should present one clear action.
|
|
@@ -258,9 +288,10 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
258
288
|
var skipHistoryBtn = document.getElementById("skipHistoryImport");
|
|
259
289
|
if (pendingTrusted) {
|
|
260
290
|
migrateBtn.style.display = "";
|
|
291
|
+
if (canExtract) ensureSessionSelection();
|
|
261
292
|
migrateBtn.disabled = canExtract && (!setupPlanConfirmed() || selectedSessionKeyList().length === 0);
|
|
262
|
-
migrateBtn.innerHTML = '<span>' + esc(canExtract ?
|
|
263
|
-
migrateBtn.onclick = canExtract ?
|
|
293
|
+
migrateBtn.innerHTML = '<span>' + esc(canExtract ? "Review " + number(pendN) + " sessions" : "Finish setup") + '</span>' + (canExtract ? setupIcon("arrow-right") : "");
|
|
294
|
+
migrateBtn.onclick = canExtract ? openSessionPicker : skip;
|
|
264
295
|
if (skipHistoryBtn) {
|
|
265
296
|
skipHistoryBtn.style.display = canExtract ? "" : "none";
|
|
266
297
|
skipHistoryBtn.onclick = skip;
|
|
@@ -273,12 +304,14 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
273
304
|
async function startMigrate() {
|
|
274
305
|
if (decisionMade) return;
|
|
275
306
|
if (!setupPlanConfirmed()) {
|
|
276
|
-
setSetupPlanStatus("Choose
|
|
307
|
+
setSetupPlanStatus("Choose Original, Pro, or Power to bring Echo home.");
|
|
277
308
|
return;
|
|
278
309
|
}
|
|
279
310
|
var selectedKeys = selectedSessionKeyList();
|
|
280
311
|
if (!selectedKeys.length) {
|
|
281
|
-
|
|
312
|
+
openSessionPicker();
|
|
313
|
+
var pickerStatus = document.getElementById("sessionPickerStatus");
|
|
314
|
+
if (pickerStatus) pickerStatus.textContent = "Choose at least one conversation to extract.";
|
|
282
315
|
return;
|
|
283
316
|
}
|
|
284
317
|
stopBillingPoll();
|
|
@@ -398,7 +431,6 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
398
431
|
var memoryNoun = extracted === 1 ? "new memory" : "new memories";
|
|
399
432
|
var savedMemoryLabel = extracted === 1 ? "memory saved" : "memories saved";
|
|
400
433
|
var conversationNoun = completed === 1 ? "conversation" : "conversations";
|
|
401
|
-
var timelineUrl = "https://yeahecho.com/memories/timeline";
|
|
402
434
|
var stopDetail = String(progress.error) === "vault-locked"
|
|
403
435
|
? "Your encryption key was not accepted. Re-run extraction; if it keeps failing, run <code>echomem-mcp unlock</code> first. Already-done conversations are skipped."
|
|
404
436
|
: esc(progress.error || "Re-run extraction to finish the rest. Already-done conversations are skipped.");
|
|
@@ -411,7 +443,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
411
443
|
'<div class="resultTop">' +
|
|
412
444
|
'<div>' +
|
|
413
445
|
(done
|
|
414
|
-
? '<h2 class="savedHeading siteHeadline"><span class="savedMain"><strong>' + esc(number(extracted)) + '</strong> ' + esc(savedMemoryLabel) + '</span
|
|
446
|
+
? '<h2 class="savedHeading siteHeadline"><span class="savedMain"><strong>' + esc(number(extracted)) + '</strong> ' + esc(savedMemoryLabel) + '</span></h2>' + completionNote
|
|
415
447
|
: '<h2 class="siteHeadline">Some conversations need <strong>another pass.</strong></h2>' +
|
|
416
448
|
'<p class="exSub">' + stopDetail + '</p>' +
|
|
417
449
|
'<div class="resultNumber"><strong>' + esc(number(extracted)) + '</strong><span>' + esc(memoryNoun) + '</span></div>') +
|
|
@@ -683,7 +715,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
683
715
|
var confirm = dialog.querySelector("#confirmSessions");
|
|
684
716
|
if (confirm) {
|
|
685
717
|
confirm.disabled = selectedCount === 0;
|
|
686
|
-
confirm.textContent = "
|
|
718
|
+
confirm.textContent = "Start memory extraction";
|
|
687
719
|
}
|
|
688
720
|
}
|
|
689
721
|
function sessionPickerLimitNotice(canDismiss) {
|
|
@@ -766,13 +798,13 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
766
798
|
'</div>' +
|
|
767
799
|
'<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>' +
|
|
768
800
|
'<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>' +
|
|
769
|
-
'<footer class="sessionPickerFoot"><p>Only selected conversations will be sent for extraction.</p><div class="sessionPickerFootActions"><button type="button" class="secondary" id="skipSessionImport">Skip history import</button><button type="button" class="primary" id="confirmSessions"' + (selected.length ? "" : " disabled") + '>
|
|
801
|
+
'<footer class="sessionPickerFoot"><p>Only selected conversations will be sent for extraction.</p><div class="sessionPickerFootActions"><button type="button" class="secondary" id="skipSessionImport">Skip history import</button><button type="button" class="primary" id="confirmSessions"' + (selected.length ? "" : " disabled") + '>Start memory extraction</button></div></footer>' +
|
|
770
802
|
'</div>';
|
|
771
803
|
var close = document.getElementById("closeSessionPicker");
|
|
772
804
|
var confirm = document.getElementById("confirmSessions");
|
|
773
805
|
var skipImport = document.getElementById("skipSessionImport");
|
|
774
806
|
if (close) close.onclick = function () { dialog.close(); renderSessionSelection(true); };
|
|
775
|
-
if (confirm) confirm.onclick = function () { dialog.close();
|
|
807
|
+
if (confirm) confirm.onclick = function () { dialog.close(); void startMigrate(); };
|
|
776
808
|
if (skipImport) skipImport.onclick = skip;
|
|
777
809
|
wireSessionPickerUpgrade();
|
|
778
810
|
var search = document.getElementById("sessionPickerSearch");
|
|
@@ -827,6 +859,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
827
859
|
var account = billingStatus && billingStatus.account;
|
|
828
860
|
var name = account && account.displayName ? account.displayName : "Connected EchoMem account";
|
|
829
861
|
var email = account && account.email ? account.email : "This import will be saved to the account connected on this device.";
|
|
862
|
+
var accountLabel = account && account.email ? account.email : name;
|
|
830
863
|
var avatarUrl = account && account.avatarUrl ? String(account.avatarUrl) : "";
|
|
831
864
|
var avatar = '<span class="setupAccountAvatar" aria-hidden="true">' +
|
|
832
865
|
'<span>' + esc(accountInitials(account)) + '</span>' +
|
|
@@ -835,8 +868,8 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
835
868
|
slot.innerHTML =
|
|
836
869
|
'<section class="setupAccountCard" aria-label="Connected EchoMem account">' +
|
|
837
870
|
avatar +
|
|
838
|
-
'<span class="setupAccountCopy"><small>
|
|
839
|
-
'<button type="button" class="textButton setupAccountChange" id="changeSetupAccount">
|
|
871
|
+
'<span class="setupAccountCopy"><small>Logged in as</small><strong>' + esc(accountLabel) + '</strong><span>' + esc(email) + '</span></span>' +
|
|
872
|
+
'<button type="button" class="textButton setupAccountChange" id="changeSetupAccount">Switch</button>' +
|
|
840
873
|
'</section>';
|
|
841
874
|
var change = document.getElementById("changeSetupAccount");
|
|
842
875
|
if (change) change.onclick = switchSetupAccount;
|
|
@@ -872,6 +905,112 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
872
905
|
function setupPlanConfirmed() {
|
|
873
906
|
return setupPlanChoice === "free" || paidRecallPlan(billingStatus && billingStatus.plan);
|
|
874
907
|
}
|
|
908
|
+
function setupPlanDefinition(plan) {
|
|
909
|
+
if (plan === "power") return {
|
|
910
|
+
id: "power",
|
|
911
|
+
name: "Power",
|
|
912
|
+
homeName: "Power",
|
|
913
|
+
promise: "More room. More agents. Much more Echo.",
|
|
914
|
+
price: "$100",
|
|
915
|
+
cadence: "per month · cancel anytime",
|
|
916
|
+
sticker: "/hud-assets/echo-pricing-power-sticker.png",
|
|
917
|
+
stickerAlt: "Power Echo arrives with a gold key and a crew of notebook helpers.",
|
|
918
|
+
features: ["2,000 past chats", "agent-heavy memory", "2,000 context returns / week"]
|
|
919
|
+
};
|
|
920
|
+
if (plan === "pro") return {
|
|
921
|
+
id: "pro",
|
|
922
|
+
name: "Pro",
|
|
923
|
+
homeName: "Pro",
|
|
924
|
+
promise: "A room for the work you return to every day.",
|
|
925
|
+
price: "$20",
|
|
926
|
+
cadence: "per month · cancel anytime",
|
|
927
|
+
sticker: "/hud-assets/echo-pricing-pro-sticker.png",
|
|
928
|
+
stickerAlt: "Pro Echo organizes tabbed notebooks and a daily refresh control.",
|
|
929
|
+
popular: true,
|
|
930
|
+
features: ["500 past chats", "daily memory updates", "500 context returns / week"]
|
|
931
|
+
};
|
|
932
|
+
return {
|
|
933
|
+
id: "free",
|
|
934
|
+
name: "Free",
|
|
935
|
+
homeName: "Original",
|
|
936
|
+
promise: "A little corner is enough to start.",
|
|
937
|
+
price: "$0",
|
|
938
|
+
cadence: "free forever · no card needed",
|
|
939
|
+
sticker: "/hud-assets/echo-pricing-free-sticker.png",
|
|
940
|
+
stickerAlt: "Original Echo hugs one simple memory card.",
|
|
941
|
+
features: ["100 past chats", "a few new chats / week", "100 context returns / week"]
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
function setupDiscoveredSessionCount() {
|
|
945
|
+
var pending = stats && stats.migratable && stats.migratable.pending;
|
|
946
|
+
if (typeof pending === "number" && isFinite(pending)) return Math.max(0, pending);
|
|
947
|
+
var total = stats && stats.sessions && stats.sessions.total;
|
|
948
|
+
return typeof total === "number" && isFinite(total) ? Math.max(0, total) : 0;
|
|
949
|
+
}
|
|
950
|
+
function renderPlanHabitat() {
|
|
951
|
+
setupPlanPreview = setupPlanPreview === "pro" || setupPlanPreview === "power" ? setupPlanPreview : "free";
|
|
952
|
+
var plan = setupPlanDefinition(setupPlanPreview);
|
|
953
|
+
var discovered = setupDiscoveredSessionCount();
|
|
954
|
+
var tabs = ["free", "pro", "power"].map(function (id) {
|
|
955
|
+
var tab = setupPlanDefinition(id);
|
|
956
|
+
var selected = id === plan.id;
|
|
957
|
+
return '<button type="button" class="mvpPlanTab" id="planTab-' + id + '" data-plan-tab="' + id + '" role="tab" aria-selected="' + (selected ? "true" : "false") + '" aria-controls="activePlanHabitat"><span>' + esc(tab.homeName) + '</span><b>' + esc(tab.price) + '</b></button>';
|
|
958
|
+
}).join("");
|
|
959
|
+
var facts = plan.features.map(function (feature) {
|
|
960
|
+
return '<li>' + setupIcon("check") + '<span>' + esc(feature) + '</span></li>';
|
|
961
|
+
}).join("");
|
|
962
|
+
var chooseId = plan.id === "free" ? "chooseFreePlan" : (plan.id === "power" ? "choosePowerPlan" : "chooseProPlan");
|
|
963
|
+
var discovery = discovered > 0
|
|
964
|
+
? '<p class="mvpPricingDiscovery">Echo found <strong>' + esc(number(discovered)) + '</strong> new coding session' + (discovered === 1 ? "" : "s") + ' on this Mac.</p>'
|
|
965
|
+
: "";
|
|
966
|
+
return '<section class="mvpPricing" aria-labelledby="setupPlanHeading">' +
|
|
967
|
+
'<header class="mvpPricingIntro"><span>Plans</span><h2 id="setupPlanHeading">Choose your Echo.</h2>' + discovery + '</header>' +
|
|
968
|
+
'<div class="mvpPlanTabs" role="tablist" aria-label="Choose an Echo plan">' + tabs + '</div>' +
|
|
969
|
+
'<article class="mvpPlanHabitat" id="activePlanHabitat" role="tabpanel" aria-labelledby="planTab-' + plan.id + '" data-plan="' + plan.id + '">' +
|
|
970
|
+
'<div class="mvpHabitatScene">' +
|
|
971
|
+
'<div class="mvpHabitatWindow" aria-hidden="true"></div>' +
|
|
972
|
+
'<div class="mvpHabitatShelf" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i></div>' +
|
|
973
|
+
'<div class="mvpHabitatRug" aria-hidden="true"></div>' +
|
|
974
|
+
'<img class="mvpHabitatEcho" src="' + esc(plan.sticker) + '" alt="' + esc(plan.stickerAlt) + '" />' +
|
|
975
|
+
'</div>' +
|
|
976
|
+
'<div class="mvpHabitatDetails">' +
|
|
977
|
+
'<div class="mvpPlanIdentity"><span>' + esc(plan.name) + '</span>' + (plan.popular ? "<b>Echo’s pick</b>" : "") + '</div>' +
|
|
978
|
+
'<h3>' + esc(plan.homeName) + ' Echo</h3>' +
|
|
979
|
+
'<p class="mvpPlanPromise">' + esc(plan.promise) + '</p>' +
|
|
980
|
+
'<div class="mvpPlanPrice"><strong>' + esc(plan.price) + '</strong><span>' + esc(plan.cadence) + '</span></div>' +
|
|
981
|
+
'<p class="mvpPacksLabel">Echo packs</p>' +
|
|
982
|
+
'<ul class="mvpPlanFacts">' + facts + '</ul>' +
|
|
983
|
+
'<button type="button" class="primary mvpPlanCta" id="' + chooseId + '">Take ' + esc(plan.homeName) + ' home <span aria-hidden="true">→</span></button>' +
|
|
984
|
+
'</div>' +
|
|
985
|
+
'</article>' +
|
|
986
|
+
'<p class="mvpPlanStatus" id="setupPlanStatus" role="status"></p>' +
|
|
987
|
+
'</section>';
|
|
988
|
+
}
|
|
989
|
+
function bindPlanHabitat() {
|
|
990
|
+
Array.prototype.forEach.call(document.querySelectorAll("[data-plan-tab]"), function (tab) {
|
|
991
|
+
tab.onclick = function () {
|
|
992
|
+
setupPlanPreview = tab.getAttribute("data-plan-tab") || "free";
|
|
993
|
+
var slot = document.getElementById("setupPlan");
|
|
994
|
+
if (slot) slot.innerHTML = renderPlanHabitat();
|
|
995
|
+
bindPlanHabitat();
|
|
996
|
+
};
|
|
997
|
+
});
|
|
998
|
+
var free = document.getElementById("chooseFreePlan");
|
|
999
|
+
if (free) free.onclick = function () {
|
|
1000
|
+
rememberSetupPlanChoice("free");
|
|
1001
|
+
renderDashboard();
|
|
1002
|
+
};
|
|
1003
|
+
var pro = document.getElementById("chooseProPlan");
|
|
1004
|
+
if (pro) pro.onclick = function () {
|
|
1005
|
+
rememberSetupPlanChoice("pro");
|
|
1006
|
+
openPaidPricing("pro");
|
|
1007
|
+
};
|
|
1008
|
+
var power = document.getElementById("choosePowerPlan");
|
|
1009
|
+
if (power) power.onclick = function () {
|
|
1010
|
+
rememberSetupPlanChoice("power");
|
|
1011
|
+
openPaidPricing("power");
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
875
1014
|
function setSetupPlanStatus(message) {
|
|
876
1015
|
var node = document.getElementById("setupPlanStatus");
|
|
877
1016
|
if (node) node.textContent = message || "";
|
|
@@ -879,9 +1018,11 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
879
1018
|
function renderSetupPlan(canExtract) {
|
|
880
1019
|
var slot = document.getElementById("setupPlan");
|
|
881
1020
|
if (!slot) return;
|
|
1021
|
+
var readySettings = slot.closest ? slot.closest(".readySettings") : null;
|
|
882
1022
|
if (!canExtract) {
|
|
883
1023
|
slot.innerHTML = "";
|
|
884
1024
|
slot.classList.add("is-hidden");
|
|
1025
|
+
if (readySettings) readySettings.classList.remove("is-plan-open");
|
|
885
1026
|
return;
|
|
886
1027
|
}
|
|
887
1028
|
slot.classList.remove("is-hidden");
|
|
@@ -889,61 +1030,70 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
889
1030
|
var paid = paidRecallPlan(plan);
|
|
890
1031
|
var migrateBtn = document.getElementById("migrate");
|
|
891
1032
|
if (paid) {
|
|
1033
|
+
if (readyStage === "plan") {
|
|
1034
|
+
stopBillingPoll();
|
|
1035
|
+
renderDashboard();
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
if (readySettings) readySettings.classList.remove("is-plan-open");
|
|
1039
|
+
rememberSetupPlanChoice("");
|
|
892
1040
|
setupPlanChoice = "paid";
|
|
893
1041
|
var planLabel = plan.charAt(0).toUpperCase() + plan.slice(1);
|
|
1042
|
+
var checkoutJustConfirmed = !!billingActivationPendingPlan;
|
|
1043
|
+
billingActivationPendingPlan = "";
|
|
894
1044
|
var paidHistory = billingStatus && billingStatus.historicalConversationQuota;
|
|
895
1045
|
var paidUsage = paidHistory && typeof paidHistory.used === "number" && typeof paidHistory.limit === "number"
|
|
896
1046
|
? paidHistory.used + " / " + paidHistory.limit + " bulk history imports used."
|
|
897
1047
|
: "Your bulk history import and weekly new-chat allowance are ready.";
|
|
898
|
-
|
|
1048
|
+
if (checkoutJustConfirmed) document.title = "Echo plan active — continue setup";
|
|
1049
|
+
slot.innerHTML = '<div class="setupPlanConfirmed' + (checkoutJustConfirmed ? ' is-checkout-confirmed' : '') + '"><span class="setupPlanConfirmedIcon" aria-hidden="true">' + setupIcon("check") + '</span><span class="setupPlanConfirmedCopy"><strong>' + (checkoutJustConfirmed ? 'Payment confirmed. ' : '') + esc(planLabel) + ' Echo</strong><span>' + esc(checkoutJustConfirmed ? "Your plan is ready. Continue below—checkout never needed access to this localhost page." : paidUsage) + '</span></span><button type="button" class="textButton" id="changeActivePlan">Change</button></div>';
|
|
1050
|
+
var changeActivePlan = document.getElementById("changeActivePlan");
|
|
1051
|
+
if (changeActivePlan) changeActivePlan.onclick = openHostedPlanOptions;
|
|
899
1052
|
renderSessionSelection(true);
|
|
900
1053
|
stopBillingPoll();
|
|
901
1054
|
return;
|
|
902
1055
|
}
|
|
903
1056
|
if (setupPlanChoice === "free") {
|
|
904
|
-
|
|
1057
|
+
if (readySettings) readySettings.classList.remove("is-plan-open");
|
|
1058
|
+
slot.innerHTML = '<div class="setupPlanConfirmed"><span class="setupPlanConfirmedIcon" aria-hidden="true">' + setupIcon("check") + '</span><span class="setupPlanConfirmedCopy"><strong>Original Echo</strong><span>100 bulk history imports, 25K new-chat tokens each week, and 10 memory searches each week.</span></span><button type="button" class="textButton" id="changeSetupPlan">Change</button></div>';
|
|
905
1059
|
var changeBtn = document.getElementById("changeSetupPlan");
|
|
906
|
-
if (changeBtn) changeBtn.onclick = function () {
|
|
1060
|
+
if (changeBtn) changeBtn.onclick = function () {
|
|
1061
|
+
rememberSetupPlanChoice("");
|
|
1062
|
+
renderDashboard();
|
|
1063
|
+
};
|
|
907
1064
|
renderSessionSelection(true);
|
|
908
1065
|
return;
|
|
909
1066
|
}
|
|
910
|
-
var
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
'</
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
var freeBtn = document.getElementById("chooseFreePlan");
|
|
940
|
-
if (freeBtn) freeBtn.onclick = function () { setupPlanChoice = "free"; renderSetupPlan(true); renderSessionSelection(true); };
|
|
941
|
-
var proBtn = document.getElementById("chooseProPlan");
|
|
942
|
-
if (proBtn) proBtn.onclick = function () { openPaidPricing("pro"); };
|
|
943
|
-
var powerBtn = document.getElementById("choosePowerPlan");
|
|
944
|
-
if (powerBtn) powerBtn.onclick = function () { openPaidPricing("power"); };
|
|
1067
|
+
var pendingPaidPlan = setupPlanChoice === "power" || billingActivationPendingPlan === "power"
|
|
1068
|
+
? "power"
|
|
1069
|
+
: (setupPlanChoice === "pro" || billingActivationPendingPlan === "pro" ? "pro" : "");
|
|
1070
|
+
if (pendingPaidPlan) {
|
|
1071
|
+
if (readySettings) readySettings.classList.remove("is-plan-open");
|
|
1072
|
+
var selectedPaidPlan = pendingPaidPlan === "power" ? "Power" : "Pro";
|
|
1073
|
+
slot.innerHTML =
|
|
1074
|
+
'<section class="setupPlanChoice setupPlanCheckout" aria-labelledby="setupPlanHeading">' +
|
|
1075
|
+
'<div class="setupPlanIntro"><h3 id="setupPlanHeading">' + selectedPaidPlan + ' checkout is open.</h3><p>Finish in the secure Stripe tab. This page will notice automatically when your plan becomes active.</p></div>' +
|
|
1076
|
+
'<button type="button" class="primary" id="continueSelectedPaidPlan">Reopen secure checkout</button>' +
|
|
1077
|
+
'<div class="setupPlanFoot"><button type="button" class="textButton" id="changeSelectedPaidPlan">Choose another Echo</button><span id="setupPlanStatus" role="status"></span></div>' +
|
|
1078
|
+
'</section>';
|
|
1079
|
+
var continuePaid = document.getElementById("continueSelectedPaidPlan");
|
|
1080
|
+
if (continuePaid) continuePaid.onclick = function () { openPaidPricing(pendingPaidPlan); };
|
|
1081
|
+
var changePaid = document.getElementById("changeSelectedPaidPlan");
|
|
1082
|
+
if (changePaid) changePaid.onclick = function () {
|
|
1083
|
+
stopBillingPoll();
|
|
1084
|
+
billingActivationPendingPlan = "";
|
|
1085
|
+
rememberSetupPlanChoice("");
|
|
1086
|
+
renderSetupPlan(true);
|
|
1087
|
+
renderSessionSelection(true);
|
|
1088
|
+
};
|
|
1089
|
+
if (migrateBtn) migrateBtn.disabled = true;
|
|
1090
|
+
renderSessionSelection(true);
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
if (readySettings) readySettings.classList.add("is-plan-open");
|
|
1094
|
+
slot.innerHTML = renderPlanHabitat();
|
|
1095
|
+
bindPlanHabitat();
|
|
945
1096
|
if (migrateBtn) migrateBtn.disabled = true;
|
|
946
|
-
renderSessionSelection(true);
|
|
947
1097
|
}
|
|
948
1098
|
async function refreshBillingStatus() {
|
|
949
1099
|
if (billingStatusLoading) return;
|
|
@@ -953,9 +1103,9 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
953
1103
|
renderSetupPlan(true);
|
|
954
1104
|
renderAccountChoice();
|
|
955
1105
|
renderSessionSelection(true);
|
|
956
|
-
if (billingStatus && billingStatus.plan === "unknown") setSetupPlanStatus("Plan check unavailable.
|
|
1106
|
+
if (billingStatus && billingStatus.plan === "unknown") setSetupPlanStatus("Plan check unavailable. Original still works.");
|
|
957
1107
|
} catch (_) {
|
|
958
|
-
setSetupPlanStatus("Plan check unavailable.
|
|
1108
|
+
setSetupPlanStatus("Plan check unavailable. Original still works.");
|
|
959
1109
|
} finally {
|
|
960
1110
|
billingStatusLoading = false;
|
|
961
1111
|
}
|
|
@@ -971,21 +1121,54 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
971
1121
|
billingPollTimer = null;
|
|
972
1122
|
window.removeEventListener("focus", refreshBillingOnFocus);
|
|
973
1123
|
}
|
|
974
|
-
function
|
|
975
|
-
var
|
|
976
|
-
setSetupPlanStatus("Opening secure checkout…");
|
|
1124
|
+
async function openHostedBilling(path, body, openingMessage, openedMessage) {
|
|
1125
|
+
var opened = null;
|
|
977
1126
|
try {
|
|
978
|
-
|
|
1127
|
+
opened = window.open("about:blank", "_blank");
|
|
979
1128
|
if (!opened) {
|
|
980
|
-
setSetupPlanStatus("Your browser blocked
|
|
981
|
-
return;
|
|
1129
|
+
setSetupPlanStatus("Your browser blocked the secure billing page. Allow pop-ups, then try again.");
|
|
1130
|
+
return false;
|
|
982
1131
|
}
|
|
1132
|
+
try {
|
|
1133
|
+
opened.document.title = "Opening secure Echo billing…";
|
|
1134
|
+
opened.document.body.innerHTML = '<p style="font:16px system-ui;padding:2rem;color:#555">Opening secure Echo billing…</p>';
|
|
1135
|
+
} catch (_) {}
|
|
1136
|
+
setSetupPlanStatus(openingMessage);
|
|
1137
|
+
var response = await postJson(path, body, 18000);
|
|
1138
|
+
if (!response || typeof response.url !== "string") throw new Error("Echo billing did not return a secure URL.");
|
|
983
1139
|
try { opened.opener = null; } catch (_) {}
|
|
1140
|
+
opened.location.href = response.url;
|
|
1141
|
+
setSetupPlanStatus(openedMessage);
|
|
984
1142
|
startBillingPoll();
|
|
985
|
-
|
|
986
|
-
|
|
1143
|
+
return true;
|
|
1144
|
+
} catch (error) {
|
|
1145
|
+
try { if (opened && !opened.closed) opened.close(); } catch (_) {}
|
|
1146
|
+
setSetupPlanStatus(error && error.message ? error.message : "Could not open secure Echo billing.");
|
|
1147
|
+
return false;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
async function openPaidPricing(plan) {
|
|
1151
|
+
setSetupPlanStatus("Opening secure checkout…");
|
|
1152
|
+
var opened = await openHostedBilling(
|
|
1153
|
+
"/billing-checkout",
|
|
1154
|
+
{ plan: plan, trial: !billingStatus || billingStatus.trialAvailable !== false },
|
|
1155
|
+
"Creating your secure checkout…",
|
|
1156
|
+
"Checkout opened in a secure tab. Keep this local page open; Echo will confirm here automatically."
|
|
1157
|
+
);
|
|
1158
|
+
if (opened) {
|
|
1159
|
+
billingActivationPendingPlan = plan;
|
|
1160
|
+
document.title = "Finish checkout — Echo setup is waiting";
|
|
1161
|
+
renderSetupPlan(true);
|
|
987
1162
|
}
|
|
988
1163
|
}
|
|
1164
|
+
async function openHostedPlanOptions() {
|
|
1165
|
+
await openHostedBilling(
|
|
1166
|
+
"/billing-portal",
|
|
1167
|
+
{},
|
|
1168
|
+
"Opening secure plan management…",
|
|
1169
|
+
"Plan management opened in a secure tab. Echo will refresh your plan here automatically."
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
989
1172
|
function tryItCard() {
|
|
990
1173
|
var questions = recallQuestions();
|
|
991
1174
|
var prompts = questions.map(function (question, index) {
|