@echomem/mcp 1.4.21 → 1.4.23
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/hud/web.js +3 -3
- package/dist/index.js +264 -7
- package/dist/migrate.js +6 -5
- package/dist/package-metadata.js +2 -0
- package/dist/setup-page/client-core.js +144 -48
- package/dist/setup-page/client-extraction.js +260 -70
- 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 +1079 -0
- package/dist/setup-page/styles-website-alignment.js +237 -5
- package/dist/setup-page/styles.js +2 -0
- package/dist/setup-preview.js +22 -6
- package/dist/setup.js +272 -40
- package/dist/v1-contract.js +226 -11
- 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>') +
|
|
@@ -595,8 +627,8 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
595
627
|
return Math.max(0, Math.floor(quota.remaining));
|
|
596
628
|
}
|
|
597
629
|
var plan = String(billingStatus && billingStatus.plan || "").toLowerCase();
|
|
598
|
-
if (plan === "power" || plan === "enterprise") return
|
|
599
|
-
if (paidRecallPlan(plan)) return
|
|
630
|
+
if (plan === "power" || plan === "enterprise") return 2000;
|
|
631
|
+
if (paidRecallPlan(plan)) return 500;
|
|
600
632
|
return 100;
|
|
601
633
|
}
|
|
602
634
|
function selectedSessionKeyList() {
|
|
@@ -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,113 @@ 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 · 14-day trial",
|
|
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 · 14-day trial",
|
|
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 chooseLabel = plan.id === "free" ? "Take " + plan.homeName + " home" : "Start 14-day trial";
|
|
964
|
+
var discovery = discovered > 0
|
|
965
|
+
? '<p class="mvpPricingDiscovery">Echo found <strong>' + esc(number(discovered)) + '</strong> new coding session' + (discovered === 1 ? "" : "s") + ' on this Mac.</p>'
|
|
966
|
+
: "";
|
|
967
|
+
return '<section class="mvpPricing" aria-labelledby="setupPlanHeading">' +
|
|
968
|
+
'<header class="mvpPricingIntro"><span>Plans</span><h2 id="setupPlanHeading">Choose your Echo.</h2>' + discovery + '</header>' +
|
|
969
|
+
'<div class="mvpPlanTabs" role="tablist" aria-label="Choose an Echo plan">' + tabs + '</div>' +
|
|
970
|
+
'<article class="mvpPlanHabitat" id="activePlanHabitat" role="tabpanel" aria-labelledby="planTab-' + plan.id + '" data-plan="' + plan.id + '">' +
|
|
971
|
+
'<div class="mvpHabitatScene">' +
|
|
972
|
+
'<div class="mvpHabitatWindow" aria-hidden="true"></div>' +
|
|
973
|
+
'<div class="mvpHabitatShelf" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i></div>' +
|
|
974
|
+
'<div class="mvpHabitatRug" aria-hidden="true"></div>' +
|
|
975
|
+
'<img class="mvpHabitatEcho" src="' + esc(plan.sticker) + '" alt="' + esc(plan.stickerAlt) + '" />' +
|
|
976
|
+
'</div>' +
|
|
977
|
+
'<div class="mvpHabitatDetails">' +
|
|
978
|
+
'<div class="mvpPlanIdentity"><span>' + esc(plan.name) + '</span>' + (plan.popular ? "<b>Echo’s pick</b>" : "") + '</div>' +
|
|
979
|
+
'<h3>' + esc(plan.homeName) + ' Echo</h3>' +
|
|
980
|
+
'<p class="mvpPlanPromise">' + esc(plan.promise) + '</p>' +
|
|
981
|
+
'<div class="mvpPlanPrice"><strong>' + esc(plan.price) + '</strong><span>' + esc(plan.cadence) + '</span></div>' +
|
|
982
|
+
'<p class="mvpPacksLabel">Echo packs</p>' +
|
|
983
|
+
'<ul class="mvpPlanFacts">' + facts + '</ul>' +
|
|
984
|
+
'<button type="button" class="primary mvpPlanCta" id="' + chooseId + '">' + esc(chooseLabel) + ' <span aria-hidden="true">→</span></button>' +
|
|
985
|
+
'</div>' +
|
|
986
|
+
'</article>' +
|
|
987
|
+
'<p class="mvpPlanStatus" id="setupPlanStatus" role="status"></p>' +
|
|
988
|
+
'</section>';
|
|
989
|
+
}
|
|
990
|
+
function bindPlanHabitat() {
|
|
991
|
+
Array.prototype.forEach.call(document.querySelectorAll("[data-plan-tab]"), function (tab) {
|
|
992
|
+
tab.onclick = function () {
|
|
993
|
+
setupPlanPreview = tab.getAttribute("data-plan-tab") || "free";
|
|
994
|
+
var slot = document.getElementById("setupPlan");
|
|
995
|
+
if (slot) slot.innerHTML = renderPlanHabitat();
|
|
996
|
+
bindPlanHabitat();
|
|
997
|
+
};
|
|
998
|
+
});
|
|
999
|
+
var free = document.getElementById("chooseFreePlan");
|
|
1000
|
+
if (free) free.onclick = function () {
|
|
1001
|
+
rememberSetupPlanChoice("free");
|
|
1002
|
+
renderDashboard();
|
|
1003
|
+
};
|
|
1004
|
+
var pro = document.getElementById("chooseProPlan");
|
|
1005
|
+
if (pro) pro.onclick = function () {
|
|
1006
|
+
rememberSetupPlanChoice("pro");
|
|
1007
|
+
openPaidPricing("pro");
|
|
1008
|
+
};
|
|
1009
|
+
var power = document.getElementById("choosePowerPlan");
|
|
1010
|
+
if (power) power.onclick = function () {
|
|
1011
|
+
rememberSetupPlanChoice("power");
|
|
1012
|
+
openPaidPricing("power");
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
875
1015
|
function setSetupPlanStatus(message) {
|
|
876
1016
|
var node = document.getElementById("setupPlanStatus");
|
|
877
1017
|
if (node) node.textContent = message || "";
|
|
@@ -879,9 +1019,11 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
879
1019
|
function renderSetupPlan(canExtract) {
|
|
880
1020
|
var slot = document.getElementById("setupPlan");
|
|
881
1021
|
if (!slot) return;
|
|
1022
|
+
var readySettings = slot.closest ? slot.closest(".readySettings") : null;
|
|
882
1023
|
if (!canExtract) {
|
|
883
1024
|
slot.innerHTML = "";
|
|
884
1025
|
slot.classList.add("is-hidden");
|
|
1026
|
+
if (readySettings) readySettings.classList.remove("is-plan-open", "is-checkout-pending");
|
|
885
1027
|
return;
|
|
886
1028
|
}
|
|
887
1029
|
slot.classList.remove("is-hidden");
|
|
@@ -889,61 +1031,76 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
889
1031
|
var paid = paidRecallPlan(plan);
|
|
890
1032
|
var migrateBtn = document.getElementById("migrate");
|
|
891
1033
|
if (paid) {
|
|
1034
|
+
if (readyStage === "plan") {
|
|
1035
|
+
stopBillingPoll();
|
|
1036
|
+
renderDashboard();
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
if (readySettings) readySettings.classList.remove("is-plan-open", "is-checkout-pending");
|
|
1040
|
+
rememberSetupPlanChoice("");
|
|
892
1041
|
setupPlanChoice = "paid";
|
|
893
1042
|
var planLabel = plan.charAt(0).toUpperCase() + plan.slice(1);
|
|
1043
|
+
var checkoutJustConfirmed = !!billingActivationPendingPlan;
|
|
1044
|
+
billingActivationPendingPlan = "";
|
|
894
1045
|
var paidHistory = billingStatus && billingStatus.historicalConversationQuota;
|
|
895
1046
|
var paidUsage = paidHistory && typeof paidHistory.used === "number" && typeof paidHistory.limit === "number"
|
|
896
1047
|
? paidHistory.used + " / " + paidHistory.limit + " bulk history imports used."
|
|
897
1048
|
: "Your bulk history import and weekly new-chat allowance are ready.";
|
|
898
|
-
|
|
1049
|
+
if (checkoutJustConfirmed) document.title = "Echo plan active — continue setup";
|
|
1050
|
+
slot.innerHTML = '<div class="setupPlanConfirmed' + (checkoutJustConfirmed ? ' is-checkout-confirmed' : '') + '"><span class="setupPlanConfirmedIcon" aria-hidden="true">' + setupIcon("check") + '</span><span class="setupPlanConfirmedCopy"><strong>' + (checkoutJustConfirmed ? 'Your Echo is ready. ' : '') + esc(planLabel) + ' Echo</strong><span>' + esc(checkoutJustConfirmed ? "Continue below." : paidUsage) + '</span></span><button type="button" class="textButton" id="changeActivePlan">Change</button></div>';
|
|
1051
|
+
var changeActivePlan = document.getElementById("changeActivePlan");
|
|
1052
|
+
if (changeActivePlan) changeActivePlan.onclick = openHostedPlanOptions;
|
|
899
1053
|
renderSessionSelection(true);
|
|
900
1054
|
stopBillingPoll();
|
|
901
1055
|
return;
|
|
902
1056
|
}
|
|
903
1057
|
if (setupPlanChoice === "free") {
|
|
904
|
-
|
|
1058
|
+
if (readySettings) readySettings.classList.remove("is-plan-open", "is-checkout-pending");
|
|
1059
|
+
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 100 memory searches each week.</span></span><button type="button" class="textButton" id="changeSetupPlan">Change</button></div>';
|
|
905
1060
|
var changeBtn = document.getElementById("changeSetupPlan");
|
|
906
|
-
if (changeBtn) changeBtn.onclick = function () {
|
|
1061
|
+
if (changeBtn) changeBtn.onclick = function () {
|
|
1062
|
+
rememberSetupPlanChoice("");
|
|
1063
|
+
renderDashboard();
|
|
1064
|
+
};
|
|
907
1065
|
renderSessionSelection(true);
|
|
908
1066
|
return;
|
|
909
1067
|
}
|
|
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
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1068
|
+
var pendingPaidPlan = setupPlanChoice === "power" || billingActivationPendingPlan === "power"
|
|
1069
|
+
? "power"
|
|
1070
|
+
: (setupPlanChoice === "pro" || billingActivationPendingPlan === "pro" ? "pro" : "");
|
|
1071
|
+
if (pendingPaidPlan) {
|
|
1072
|
+
if (readySettings) {
|
|
1073
|
+
readySettings.classList.remove("is-plan-open");
|
|
1074
|
+
readySettings.classList.add("is-checkout-pending");
|
|
1075
|
+
}
|
|
1076
|
+
var selectedPaidPlan = pendingPaidPlan === "power" ? "Power" : "Pro";
|
|
1077
|
+
slot.innerHTML =
|
|
1078
|
+
'<section class="setupPlanChoice setupPlanCheckout" aria-labelledby="setupPlanHeading">' +
|
|
1079
|
+
'<div class="setupPlanIntro"><h3 id="setupPlanHeading">Start your 14-day trial.</h3><p>Finish in the secure Stripe tab, then return here. Echo will update automatically.</p></div>' +
|
|
1080
|
+
'<button type="button" class="primary" id="continueSelectedPaidPlan">Reopen secure checkout</button>' +
|
|
1081
|
+
'<div class="setupPlanFoot"><button type="button" class="textButton" id="changeSelectedPaidPlan">Choose another Echo</button><span id="setupPlanStatus" role="status"></span></div>' +
|
|
1082
|
+
'</section>';
|
|
1083
|
+
var continuePaid = document.getElementById("continueSelectedPaidPlan");
|
|
1084
|
+
if (continuePaid) continuePaid.onclick = function () { openPaidPricing(pendingPaidPlan); };
|
|
1085
|
+
var changePaid = document.getElementById("changeSelectedPaidPlan");
|
|
1086
|
+
if (changePaid) changePaid.onclick = function () {
|
|
1087
|
+
stopBillingPoll();
|
|
1088
|
+
billingActivationPendingPlan = "";
|
|
1089
|
+
rememberSetupPlanChoice("");
|
|
1090
|
+
renderSetupPlan(true);
|
|
1091
|
+
renderSessionSelection(true);
|
|
1092
|
+
};
|
|
1093
|
+
if (migrateBtn) migrateBtn.disabled = true;
|
|
1094
|
+
renderSessionSelection(true);
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
if (readySettings) {
|
|
1098
|
+
readySettings.classList.remove("is-checkout-pending");
|
|
1099
|
+
readySettings.classList.add("is-plan-open");
|
|
1100
|
+
}
|
|
1101
|
+
slot.innerHTML = renderPlanHabitat();
|
|
1102
|
+
bindPlanHabitat();
|
|
945
1103
|
if (migrateBtn) migrateBtn.disabled = true;
|
|
946
|
-
renderSessionSelection(true);
|
|
947
1104
|
}
|
|
948
1105
|
async function refreshBillingStatus() {
|
|
949
1106
|
if (billingStatusLoading) return;
|
|
@@ -953,9 +1110,9 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
953
1110
|
renderSetupPlan(true);
|
|
954
1111
|
renderAccountChoice();
|
|
955
1112
|
renderSessionSelection(true);
|
|
956
|
-
if (billingStatus && billingStatus.plan === "unknown") setSetupPlanStatus("Plan check unavailable.
|
|
1113
|
+
if (billingStatus && billingStatus.plan === "unknown") setSetupPlanStatus("Plan check unavailable. Original still works.");
|
|
957
1114
|
} catch (_) {
|
|
958
|
-
setSetupPlanStatus("Plan check unavailable.
|
|
1115
|
+
setSetupPlanStatus("Plan check unavailable. Original still works.");
|
|
959
1116
|
} finally {
|
|
960
1117
|
billingStatusLoading = false;
|
|
961
1118
|
}
|
|
@@ -971,21 +1128,54 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
971
1128
|
billingPollTimer = null;
|
|
972
1129
|
window.removeEventListener("focus", refreshBillingOnFocus);
|
|
973
1130
|
}
|
|
974
|
-
function
|
|
975
|
-
var
|
|
976
|
-
setSetupPlanStatus("Opening secure checkout…");
|
|
1131
|
+
async function openHostedBilling(path, body, openingMessage, openedMessage) {
|
|
1132
|
+
var opened = null;
|
|
977
1133
|
try {
|
|
978
|
-
|
|
1134
|
+
opened = window.open("about:blank", "_blank");
|
|
979
1135
|
if (!opened) {
|
|
980
|
-
setSetupPlanStatus("Your browser blocked
|
|
981
|
-
return;
|
|
1136
|
+
setSetupPlanStatus("Your browser blocked the secure billing page. Allow pop-ups, then try again.");
|
|
1137
|
+
return false;
|
|
982
1138
|
}
|
|
1139
|
+
try {
|
|
1140
|
+
opened.document.title = "Opening secure Echo billing…";
|
|
1141
|
+
opened.document.body.innerHTML = '<p style="font:16px system-ui;padding:2rem;color:#555">Opening secure Echo billing…</p>';
|
|
1142
|
+
} catch (_) {}
|
|
1143
|
+
setSetupPlanStatus(openingMessage);
|
|
1144
|
+
var response = await postJson(path, body, 18000);
|
|
1145
|
+
if (!response || typeof response.url !== "string") throw new Error("Echo billing did not return a secure URL.");
|
|
983
1146
|
try { opened.opener = null; } catch (_) {}
|
|
1147
|
+
opened.location.href = response.url;
|
|
1148
|
+
setSetupPlanStatus(openedMessage);
|
|
984
1149
|
startBillingPoll();
|
|
985
|
-
|
|
986
|
-
|
|
1150
|
+
return true;
|
|
1151
|
+
} catch (error) {
|
|
1152
|
+
try { if (opened && !opened.closed) opened.close(); } catch (_) {}
|
|
1153
|
+
setSetupPlanStatus(error && error.message ? error.message : "Could not open secure Echo billing.");
|
|
1154
|
+
return false;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
async function openPaidPricing(plan) {
|
|
1158
|
+
setSetupPlanStatus("Opening secure checkout…");
|
|
1159
|
+
var opened = await openHostedBilling(
|
|
1160
|
+
"/billing-checkout",
|
|
1161
|
+
{ plan: plan, trial: !billingStatus || billingStatus.trialAvailable !== false },
|
|
1162
|
+
"Creating your secure checkout…",
|
|
1163
|
+
"Checkout opened in a secure tab. Keep this local page open; Echo will confirm here automatically."
|
|
1164
|
+
);
|
|
1165
|
+
if (opened) {
|
|
1166
|
+
billingActivationPendingPlan = plan;
|
|
1167
|
+
document.title = "Finish checkout — Echo setup is waiting";
|
|
1168
|
+
renderSetupPlan(true);
|
|
987
1169
|
}
|
|
988
1170
|
}
|
|
1171
|
+
async function openHostedPlanOptions() {
|
|
1172
|
+
await openHostedBilling(
|
|
1173
|
+
"/billing-portal",
|
|
1174
|
+
{},
|
|
1175
|
+
"Opening secure plan management…",
|
|
1176
|
+
"Plan management opened in a secure tab. Echo will refresh your plan here automatically."
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
989
1179
|
function tryItCard() {
|
|
990
1180
|
var questions = recallQuestions();
|
|
991
1181
|
var prompts = questions.map(function (question, index) {
|