@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.
@@ -8,6 +8,9 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
8
8
  var status = document.getElementById("status");
9
9
  var report = null;
10
10
  var reportEnvelope = null;
11
+ // The local bridge is authoritative. Login never gets access to scan/import routes;
12
+ // onboarding may request the separate local-history permission.
13
+ var setupFlow = "onboarding";
11
14
  var activeScanId = "";
12
15
  var stats = null;
13
16
  var statsSlow = false;
@@ -27,6 +30,8 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
27
30
  var billingStatus = null;
28
31
  var billingStatusLoading = false;
29
32
  var setupPlanChoice = "";
33
+ var setupPlanPreview = "free";
34
+ var billingActivationPendingPlan = "";
30
35
  var selectedSessionKeys = Object.create(null);
31
36
  var sessionSelectionTouched = false;
32
37
  var sessionPickerQuery = "";
@@ -37,13 +42,30 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
37
42
  var localAuthAgeConfirmed = false;
38
43
  var statsPollStarted = false;
39
44
  var reportPollStarted = false;
45
+ var reportEstimateDeadline = 0;
46
+ var reportEstimateTimer = null;
47
+ var readyStage = "";
40
48
  var NIGHT_HOURS = { 22: 1, 23: 1, 0: 1, 1: 1, 2: 1, 3: 1 };
41
49
 
50
+ try {
51
+ var storedSetupPlanChoice = sessionStorage.getItem("echomem:setup-plan:" + nonce) || "";
52
+ if (storedSetupPlanChoice === "free" || storedSetupPlanChoice === "pro" || storedSetupPlanChoice === "power") {
53
+ setupPlanChoice = storedSetupPlanChoice;
54
+ }
55
+ } catch (_) {}
56
+
42
57
  function esc(value) {
43
58
  return String(value == null ? "" : value).replace(/[&<>"']/g, function (ch) {
44
59
  return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[ch];
45
60
  });
46
61
  }
62
+ function rememberSetupPlanChoice(choice) {
63
+ setupPlanChoice = choice === "free" || choice === "pro" || choice === "power" ? choice : "";
64
+ try {
65
+ if (setupPlanChoice) sessionStorage.setItem("echomem:setup-plan:" + nonce, setupPlanChoice);
66
+ else sessionStorage.removeItem("echomem:setup-plan:" + nonce);
67
+ } catch (_) {}
68
+ }
47
69
  function setupIcon(name) {
48
70
  var paths = {
49
71
  "shield-check": '<path d="M12 3 5 6v5c0 4.6 2.9 8.1 7 10 4.1-1.9 7-5.4 7-10V6l-7-3Z"/><path d="m8.8 12.1 2 2 4.4-4.5"/>',
@@ -109,7 +131,10 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
109
131
  }
110
132
  async function getJson(path) {
111
133
  var sep = path.indexOf("?") >= 0 ? "&" : "?";
112
- var res = await fetch(path + sep + "nonce=" + encodeURIComponent(nonce), { credentials: "omit" });
134
+ var res = await fetch(path + sep + "nonce=" + encodeURIComponent(nonce), {
135
+ credentials: "omit",
136
+ cache: "no-store"
137
+ });
113
138
  if (!res.ok) throw new Error(await res.text() || ("HTTP " + res.status));
114
139
  return await res.json();
115
140
  }
@@ -152,14 +177,18 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
152
177
  return false;
153
178
  }
154
179
  function localAuthLegalHtml() {
155
- return '<div class="localAuthLegal">' +
156
- '<label><input type="checkbox" id="localAuthAge" ' + (localAuthAgeConfirmed ? "checked" : "") + ' />' +
157
- '<span>I confirm that I am at least 18 years old.</span></label>' +
158
- '<label><input type="checkbox" id="localAuthTerms" ' + (localAuthTermsAccepted ? "checked" : "") + ' />' +
159
- '<span>I have read and agree to the Echo <a href="https://echoknows.com/terms-of-use" target="_blank" rel="noopener noreferrer">Terms of Use</a>, <a href="https://echoknows.com/memory-terms" target="_blank" rel="noopener noreferrer">Memory Usage Terms</a>, and <a href="https://echoknows.com/privacy-policy" target="_blank" rel="noopener noreferrer">Privacy Policy</a>. I understand Echo uses subprocessors listed <a href="https://echoknows.com/subprocessor-list" target="_blank" rel="noopener noreferrer">here</a>.</span></label>' +
160
- '</div>';
180
+ return '<label class="localAuthConsent">' +
181
+ '<input type="checkbox" id="localAuthConsent" ' + (localAuthAgeConfirmed && localAuthTermsAccepted ? "checked" : "") + ' />' +
182
+ '<span>I confirm that I am at least 18 years old and that I have read and agree to Echo\'s <a href="https://echoknows.com/terms-of-use" target="_blank" rel="noopener noreferrer">Terms of Use</a>, <a href="https://echoknows.com/memory-terms" target="_blank" rel="noopener noreferrer">Memory Usage Terms</a>, and <a href="https://echoknows.com/privacy-policy" target="_blank" rel="noopener noreferrer">Privacy Policy</a>. See our <a href="https://echoknows.com/subprocessor-list" target="_blank" rel="noopener noreferrer">Subprocessor List</a>.</span>' +
183
+ '</label>';
161
184
  }
162
185
  function captureLocalAuthConsents() {
186
+ var consent = document.getElementById("localAuthConsent");
187
+ if (consent) {
188
+ localAuthTermsAccepted = !!consent.checked;
189
+ localAuthAgeConfirmed = !!consent.checked;
190
+ return;
191
+ }
163
192
  var terms = document.getElementById("localAuthTerms");
164
193
  var age = document.getElementById("localAuthAge");
165
194
  localAuthTermsAccepted = !!(terms && terms.checked);
@@ -185,19 +214,21 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
185
214
  setExtractMode(false);
186
215
  setReadyMode(true);
187
216
  setHead("Connect EchoMem", "Local login");
188
- app.className = "localAuthStage";
217
+ app.className = "localAuthStage localAuthWelcomeStage";
189
218
  app.innerHTML =
190
- '<section class="localAuthCard">' +
191
- '<div class="localAuthLead">' +
192
- '<img src="/hud-assets/echo-face-cutout.png" alt="" />' +
193
- '<p class="consentEyebrow">EchoMem account / local login</p>' +
194
- '<h2 class="siteHeadline">Sign in without leaving this device.</h2>' +
195
- '<p>Echo sends a one-time code to your email. The browser stays on localhost; your device token and encryption key are stored only in the local EchoMem keystore.</p>' +
219
+ '<section class="localAuthWelcome" aria-labelledby="localAuthWelcomeTitle">' +
220
+ '<div class="localAuthOrbScene" aria-hidden="true"><span class="localAuthOrb"></span><span class="localAuthOrbShadow"></span></div>' +
221
+ '<div class="localAuthWelcomeCopy">' +
222
+ '<h2 id="localAuthWelcomeTitle">Welcome to Echo</h2>' +
223
+ '<p>Your memories, sovereign and shared.</p>' +
196
224
  '</div>' +
197
- '<form class="localAuthForm" id="localAuthEmailForm">' +
198
- '<label class="localAuthField"><span>Email</span><input id="localAuthEmail" type="email" autocomplete="email" placeholder="you@example.com" value="' + esc(localAuthEmail) + '" /></label>' +
225
+ '<form class="localAuthWelcomeForm" id="localAuthEmailForm">' +
226
+ '<div class="localAuthEmailCapsule">' +
227
+ '<label class="srOnly" for="localAuthEmail">Email</label>' +
228
+ '<input id="localAuthEmail" type="email" autocomplete="email" placeholder="Enter your email" value="' + esc(localAuthEmail) + '" />' +
229
+ '<button id="localAuthSend" type="submit" aria-label="Send one-time code"><span aria-hidden="true">→</span></button>' +
230
+ '</div>' +
199
231
  localAuthLegalHtml() +
200
- '<button class="primary" id="localAuthSend" type="submit">Send code</button>' +
201
232
  '<p class="localAuthStatus' + (message ? " is-error" : "") + '" id="localAuthStatus" role="status" aria-live="polite">' + esc(message || "") + '</p>' +
202
233
  '</form>' +
203
234
  '</section>';
@@ -208,12 +239,37 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
208
239
  email.oninput = function () { localAuthEmail = String(email.value || ""); updateLocalAuthSendButton(); };
209
240
  email.focus();
210
241
  }
211
- var terms = document.getElementById("localAuthTerms");
212
- var age = document.getElementById("localAuthAge");
213
- if (terms) terms.onchange = updateLocalAuthSendButton;
214
- if (age) age.onchange = updateLocalAuthSendButton;
242
+ var consent = document.getElementById("localAuthConsent");
243
+ if (consent) consent.onchange = updateLocalAuthSendButton;
215
244
  updateLocalAuthSendButton();
216
245
  }
246
+ function renderLocalLoginComplete() {
247
+ setCityMode(false);
248
+ setScanMode(false);
249
+ setExtractMode(false);
250
+ setReadyMode(true);
251
+ setHead("EchoMem connected", "Done");
252
+ app.className = "notice";
253
+ app.innerHTML =
254
+ '<section class="localAuthCard localAuthComplete">' +
255
+ '<div class="localAuthLead">' +
256
+ '<img src="/hud-assets/echo-face-cutout.png" alt="" />' +
257
+ '<p class="consentEyebrow">This device is connected</p>' +
258
+ '<h2 class="siteHeadline">You\'re signed in.</h2>' +
259
+ '<p>Your local device token and encryption key are ready. Return to Terminal, or run <code>echomem-mcp init</code> when you want to start local-history onboarding.</p>' +
260
+ '</div>' +
261
+ '</section>';
262
+ }
263
+ async function finishLocalLogin() {
264
+ connected = true;
265
+ if (setupFlow === "login") {
266
+ renderLocalLoginComplete();
267
+ return;
268
+ }
269
+ rememberSetupPlanChoice("");
270
+ try { await refreshBillingStatus(); } catch (_) {}
271
+ await waitForStats();
272
+ }
217
273
  async function sendLocalOtp() {
218
274
  var email = document.getElementById("localAuthEmail");
219
275
  localAuthEmail = email && email.value ? String(email.value).trim().toLowerCase() : "";
@@ -224,7 +280,7 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
224
280
  return;
225
281
  }
226
282
  var button = document.getElementById("localAuthSend");
227
- if (button) { button.disabled = true; button.textContent = "Sending..."; }
283
+ if (button) { button.disabled = true; button.innerHTML = '<span class="localAuthSpinner" aria-hidden="true"></span>'; }
228
284
  setLocalAuthStatus("Sending verification code...");
229
285
  try {
230
286
  var data = await postJson("/local-auth/send-otp", {
@@ -235,7 +291,7 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
235
291
  localAuthEmail = data.email || localAuthEmail;
236
292
  renderLocalOtp("Code sent. Check your inbox.");
237
293
  } catch (error) {
238
- if (button) { button.disabled = false; button.textContent = "Send code"; }
294
+ if (button) { button.disabled = false; button.innerHTML = '<span aria-hidden="true">→</span>'; }
239
295
  setLocalAuthStatus(error && error.message ? error.message : "Could not send the code.", "error");
240
296
  }
241
297
  }
@@ -245,18 +301,26 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
245
301
  setExtractMode(false);
246
302
  setReadyMode(true);
247
303
  setHead("Check your email", "Local login");
248
- app.className = "localAuthStage";
304
+ app.className = "localAuthStage localAuthWelcomeStage localAuthOtpStage";
249
305
  app.innerHTML =
250
- '<section class="localAuthCard localAuthCardOtp">' +
251
- '<div class="localAuthLead">' +
252
- '<img src="/hud-assets/echo-face-cutout.png" alt="" />' +
253
- '<p class="consentEyebrow">Verification code</p>' +
254
- '<h2 class="siteHeadline">Enter the code sent to <strong>' + esc(localAuthEmail) + '</strong>.</h2>' +
255
- '<p>This code signs in this local EchoMem bridge only. The next step asks for your encryption passphrase.</p>' +
306
+ '<section class="localAuthWelcome localAuthOtpWelcome" aria-labelledby="localAuthOtpTitle">' +
307
+ '<div class="localAuthOrbScene" aria-hidden="true"><span class="localAuthOrb"></span><span class="localAuthOrbShadow"></span></div>' +
308
+ '<div class="localAuthWelcomeCopy">' +
309
+ '<h2 id="localAuthOtpTitle">Check your inbox</h2>' +
310
+ '<p>We sent a 6-digit code to <strong>' + esc(localAuthEmail) + '</strong></p>' +
256
311
  '</div>' +
257
- '<form class="localAuthForm" id="localAuthOtpForm">' +
258
- '<label class="localAuthField"><span>6-digit code</span><input id="localAuthOtp" type="text" inputmode="numeric" autocomplete="one-time-code" maxlength="6" placeholder="123456" /></label>' +
259
- '<div class="localAuthActions"><button class="primary" id="localAuthVerify" type="submit">Verify code</button><button class="textButton" id="localAuthBack" type="button">Use a different email</button></div>' +
312
+ '<form class="localAuthWelcomeForm localAuthOtpForm" id="localAuthOtpForm">' +
313
+ '<label class="srOnly" for="localAuthOtp0">6-digit code</label>' +
314
+ '<div class="localAuthOtpCells" aria-label="6-digit verification code">' +
315
+ '<input id="localAuthOtp0" data-otp-cell type="text" inputmode="numeric" autocomplete="one-time-code" maxlength="1" />' +
316
+ '<input data-otp-cell type="text" inputmode="numeric" maxlength="1" />' +
317
+ '<input data-otp-cell type="text" inputmode="numeric" maxlength="1" />' +
318
+ '<input data-otp-cell type="text" inputmode="numeric" maxlength="1" />' +
319
+ '<input data-otp-cell type="text" inputmode="numeric" maxlength="1" />' +
320
+ '<input data-otp-cell type="text" inputmode="numeric" maxlength="1" />' +
321
+ '</div>' +
322
+ '<button class="primary localAuthVerifyWide" id="localAuthVerify" type="submit">Verify</button>' +
323
+ '<button class="secondary localAuthBackWide" id="localAuthBack" type="button">Use another email</button>' +
260
324
  '<p class="localAuthStatus" id="localAuthStatus" role="status" aria-live="polite">' + esc(message || "") + '</p>' +
261
325
  '</form>' +
262
326
  '</section>';
@@ -264,12 +328,45 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
264
328
  if (form) form.onsubmit = function (event) { event.preventDefault(); void verifyLocalOtp(); };
265
329
  var back = document.getElementById("localAuthBack");
266
330
  if (back) back.onclick = function () { renderLocalLogin(""); };
267
- var otp = document.getElementById("localAuthOtp");
331
+ bindLocalOtpCells();
332
+ var otp = document.querySelector("[data-otp-cell]");
268
333
  if (otp) otp.focus();
269
334
  }
335
+ function localOtpCellValues() {
336
+ return Array.prototype.map.call(document.querySelectorAll("[data-otp-cell]"), function (node) {
337
+ return String(node.value || "").replace(/\\D/g, "").slice(0, 1);
338
+ }).join("");
339
+ }
340
+ function fillLocalOtpCells(value) {
341
+ var digits = String(value || "").replace(/\\D/g, "").slice(0, 6);
342
+ var cells = Array.prototype.slice.call(document.querySelectorAll("[data-otp-cell]"));
343
+ cells.forEach(function (cell, index) { cell.value = digits.charAt(index) || ""; });
344
+ var focusIndex = Math.min(digits.length, cells.length - 1);
345
+ if (cells[focusIndex]) cells[focusIndex].focus();
346
+ }
347
+ function bindLocalOtpCells() {
348
+ var cells = Array.prototype.slice.call(document.querySelectorAll("[data-otp-cell]"));
349
+ cells.forEach(function (cell, index) {
350
+ cell.oninput = function () {
351
+ var value = String(cell.value || "").replace(/\\D/g, "");
352
+ if (value.length > 1) { fillLocalOtpCells(value); return; }
353
+ cell.value = value;
354
+ if (value && cells[index + 1]) cells[index + 1].focus();
355
+ };
356
+ cell.onkeydown = function (event) {
357
+ if (event.key === "Backspace" && !cell.value && cells[index - 1]) cells[index - 1].focus();
358
+ };
359
+ cell.onpaste = function (event) {
360
+ var text = event.clipboardData && event.clipboardData.getData ? event.clipboardData.getData("text") : "";
361
+ if (text) {
362
+ event.preventDefault();
363
+ fillLocalOtpCells(text);
364
+ }
365
+ };
366
+ });
367
+ }
270
368
  async function verifyLocalOtp() {
271
- var otpInput = document.getElementById("localAuthOtp");
272
- var otp = otpInput && otpInput.value ? String(otpInput.value).replace(/\\D/g, "") : "";
369
+ var otp = localOtpCellValues();
273
370
  if (otp.length !== 6) { setLocalAuthStatus("Enter the 6-digit code.", "error"); return; }
274
371
  var button = document.getElementById("localAuthVerify");
275
372
  if (button) { button.disabled = true; button.textContent = "Verifying..."; }
@@ -285,8 +382,7 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
285
382
  renderLocalPassphrase(data.mode || "unlock", data.email || localAuthEmail);
286
383
  return;
287
384
  }
288
- connected = true;
289
- await waitForStats();
385
+ await finishLocalLogin();
290
386
  } catch (error) {
291
387
  if (button) { button.disabled = false; button.textContent = "Verify code"; }
292
388
  setLocalAuthStatus(error && error.message ? error.message : "Could not verify the code.", "error");
@@ -300,19 +396,19 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
300
396
  setExtractMode(false);
301
397
  setReadyMode(true);
302
398
  setHead(setupMode ? "Create encrypted vault" : "Unlock encrypted vault", "Local passphrase");
303
- app.className = "localAuthStage";
399
+ app.className = "localAuthStage localAuthWelcomeStage localAuthPassphraseStage";
304
400
  app.innerHTML =
305
- '<section class="localAuthCard localAuthCardPassphrase">' +
306
- '<div class="localAuthLead">' +
307
- '<img src="/hud-assets/echo-face-cutout.png" alt="" />' +
308
- '<p class="consentEyebrow">Encryption passphrase</p>' +
309
- '<h2 class="siteHeadline">' + (setupMode ? 'Create your local vault passphrase.' : 'Enter your vault passphrase.') + '</h2>' +
401
+ '<section class="localAuthWelcome localAuthPassphraseWelcome">' +
402
+ '<div class="localAuthOrbScene" aria-hidden="true"><span class="localAuthOrb"></span><span class="localAuthOrbShadow"></span></div>' +
403
+ '<div class="localAuthWelcomeCopy">' +
404
+ '<h2>' + (setupMode ? 'Create your local vault passphrase.' : 'Enter your vault passphrase.') + '</h2>' +
310
405
  '<p>' + (setupMode ? 'Echo derives your encryption key on this device. The passphrase and key are never sent to EchoMem. If you forget it, encrypted memories cannot be recovered.' : 'Echo verifies this passphrase locally against your account encryption token. The passphrase is never sent to EchoMem.') + '</p>' +
311
406
  '</div>' +
312
- '<form class="localAuthForm" id="localAuthPassphraseForm">' +
407
+ '<form class="localAuthWelcomeForm localAuthPassphraseForm" id="localAuthPassphraseForm">' +
313
408
  '<label class="localAuthField"><span>Passphrase</span><input id="localAuthPassphrase" type="password" autocomplete="' + (setupMode ? "new-password" : "current-password") + '" placeholder="Encryption passphrase" /></label>' +
314
409
  (setupMode ? '<label class="localAuthField"><span>Confirm passphrase</span><input id="localAuthPassphraseConfirm" type="password" autocomplete="new-password" placeholder="Confirm passphrase" /></label>' : '') +
315
- '<div class="localAuthActions"><button class="primary" id="localAuthUnlock" type="submit">' + (setupMode ? "Create vault" : "Unlock") + '</button><button class="textButton" id="localAuthRestart" type="button">Use a different email</button></div>' +
410
+ '<button class="primary localAuthPassphraseSubmit" id="localAuthUnlock" type="submit">' + (setupMode ? "Create vault" : "Unlock") + '</button>' +
411
+ '<button class="secondary localAuthBackWide" id="localAuthRestart" type="button">Use a different email</button>' +
316
412
  '<p class="localAuthStatus' + (message ? " is-error" : "") + '" id="localAuthStatus" role="status" aria-live="polite">' + esc(message || "") + '</p>' +
317
413
  '</form>' +
318
414
  '</section>';
@@ -335,8 +431,7 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
335
431
  setLocalAuthStatus(setupMode ? "Creating encrypted vault..." : "Unlocking vault...");
336
432
  try {
337
433
  await postJson("/local-auth/passphrase", { passphrase: passphrase }, 30000);
338
- connected = true;
339
- await waitForStats();
434
+ await finishLocalLogin();
340
435
  } catch (error) {
341
436
  if (button) { button.disabled = false; button.textContent = setupMode ? "Create vault" : "Unlock"; }
342
437
  var msg = error && error.message ? error.message : "Could not unlock encrypted memory.";
@@ -350,6 +445,7 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
350
445
  return;
351
446
  }
352
447
  if (connected) { void waitForStats(); return; }
448
+ rememberSetupPlanChoice("");
353
449
  renderLocalLogin("");
354
450
  }
355
451
  function bindConnect() {