@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.
@@ -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 history import</button>' +
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 = startMigrate;
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 ? extractLabel : "Finish setup") + '</span>' + (canExtract ? setupIcon("arrow-right") : "");
263
- migrateBtn.onclick = canExtract ? startMigrate : skip;
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 Free, Pro, or Power before extraction starts.");
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
- renderSessionSelection(true, "Choose at least one conversation to extract.");
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><a class="viewEchoLink" href="' + timelineUrl + '" target="_blank" rel="noopener noreferrer">(view in yeahecho)</a></h2>' + completionNote
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 = "Use " + number(selectedCount) + " selected";
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") + '>Use ' + esc(number(selected.length)) + ' selected</button></div></footer>' +
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(); renderSessionSelection(true); };
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>Saving to</small><strong>' + esc(name) + '</strong><span>' + esc(email) + '</span></span>' +
839
- '<button type="button" class="textButton setupAccountChange" id="changeSetupAccount">Change account</button>' +
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&rsquo;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
- slot.innerHTML = '<div class="setupPlanConfirmed"><span class="setupPlanConfirmedIcon" aria-hidden="true">' + setupIcon("check") + '</span><span class="setupPlanConfirmedCopy"><strong>' + esc(planLabel) + ' is active.</strong><span>' + esc(paidUsage) + '</span></span></div>';
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
- slot.innerHTML = '<div class="setupPlanConfirmed"><span class="setupPlanConfirmedIcon" aria-hidden="true">' + setupIcon("check") + '</span><span class="setupPlanConfirmedCopy"><strong>Free selected.</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>';
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 () { setupPlanChoice = ""; renderSetupPlan(true); renderSessionSelection(true); };
1060
+ if (changeBtn) changeBtn.onclick = function () {
1061
+ rememberSetupPlanChoice("");
1062
+ renderDashboard();
1063
+ };
907
1064
  renderSessionSelection(true);
908
1065
  return;
909
1066
  }
910
- var trialAvailable = !billingStatus || billingStatus.trialAvailable !== false;
911
- slot.innerHTML =
912
- '<section class="setupPlanChoice" aria-labelledby="setupPlanHeading">' +
913
- '<div class="setupPlanIntro"><h3 id="setupPlanHeading">Choose a plan before extraction starts.</h3><p>Plans set how much history you can import and how much new conversation text Echo processes each week. There is no memory-count limit.</p></div>' +
914
- '<div class="setupPlanGrid">' +
915
- '<article class="setupPlanCard">' +
916
- '<div class="setupPlanHead"><strong>Free</strong><span>$0 forever</span></div>' +
917
- '<p>Start with the essentials.</p>' +
918
- '<ul><li>' + setupIcon("check") + '<span>100 bulk history imports</span></li><li>' + setupIcon("check") + '<span>25K new-chat tokens each week</span></li><li>' + setupIcon("check") + '<span>10 memory searches each week</span></li><li>' + setupIcon("check") + '<span>No memory-count limit</span></li></ul>' +
919
- '<button type="button" class="secondary" id="chooseFreePlan">Continue with Free</button>' +
920
- '</article>' +
921
- '<article class="setupPlanCard is-pro">' +
922
- '<div class="setupPlanHead"><strong>Pro</strong><span>$40 / month</span></div>' +
923
- '<p>More recall for serious work.</p>' +
924
- '<ul><li>' + setupIcon("check") + '<span>250 bulk history imports</span></li><li>' + setupIcon("check") + '<span>200K new-chat tokens each week</span></li><li>' + setupIcon("check") + '<span>100 memory searches each week</span></li><li>' + setupIcon("check") + '<span>No memory-count limit</span></li></ul>' +
925
- '<button type="button" class="primary" id="chooseProPlan">' + (trialAvailable ? "Start 14-day trial" : "Choose Pro") + '</button>' +
926
- '<small>' + (trialAvailable ? "Card required. Cancel anytime." : "Your trial was already used.") + '</small>' +
927
- '</article>' +
928
- '<article class="setupPlanCard">' +
929
- '<div class="setupPlanHead"><strong>Power</strong><span>$100 / month</span></div>' +
930
- '<p>Higher limits for people who use Echo every day.</p>' +
931
- // Future Power feature: API and automation access.
932
- '<ul><li>' + setupIcon("check") + '<span>500 bulk history imports</span></li><li>' + setupIcon("check") + '<span>750K new-chat tokens each week</span></li><li>' + setupIcon("check") + '<span>250 memory searches each week</span></li><li>' + setupIcon("check") + '<span>No memory-count limit</span></li></ul>' +
933
- '<button type="button" class="secondary" id="choosePowerPlan">' + (trialAvailable ? "Start 14-day trial" : "Choose Power") + '</button>' +
934
- '<small>' + (trialAvailable ? "Card required. Cancel anytime." : "Your trial was already used.") + '</small>' +
935
- '</article>' +
936
- '</div>' +
937
- '<div class="setupPlanFoot"><a href="' + esc(setupPricingUrl("pro", false)) + '" target="_blank" rel="noopener noreferrer">See all plan details</a><span id="setupPlanStatus" role="status"></span></div>' +
938
- '</section>';
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. Free still works.");
1106
+ if (billingStatus && billingStatus.plan === "unknown") setSetupPlanStatus("Plan check unavailable. Original still works.");
957
1107
  } catch (_) {
958
- setSetupPlanStatus("Plan check unavailable. Free still works.");
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 openPaidPricing(plan) {
975
- var url = setupPricingUrl(plan, true);
976
- setSetupPlanStatus("Opening secure checkout…");
1124
+ async function openHostedBilling(path, body, openingMessage, openedMessage) {
1125
+ var opened = null;
977
1126
  try {
978
- var opened = window.open(url, "_blank");
1127
+ opened = window.open("about:blank", "_blank");
979
1128
  if (!opened) {
980
- setSetupPlanStatus("Your browser blocked checkout. Allow pop-ups, then try again.");
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
- } catch (_) {
986
- setSetupPlanStatus("Could not open checkout. Allow pop-ups, then try again.");
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) {