@echomem/mcp 1.4.20 → 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.
@@ -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;
@@ -23,28 +26,46 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
23
26
  var progressPollRunId = 0; // invalidates older progress loops when extraction resumes
24
27
  var dashMounted = false; // dashboard shell (incl. plate iframe) mounted once; persists across /stats polls
25
28
  var reportMounted = false; // report shell (city iframe) mounted once; loading is an overlay on it, not a separate page
26
- var authUrl = "";
27
- var switchAccountUrl = "";
28
29
  var workspacePath = "";
29
30
  var billingStatus = null;
30
31
  var billingStatusLoading = false;
31
32
  var setupPlanChoice = "";
33
+ var setupPlanPreview = "free";
34
+ var billingActivationPendingPlan = "";
32
35
  var selectedSessionKeys = Object.create(null);
33
36
  var sessionSelectionTouched = false;
34
37
  var sessionPickerQuery = "";
35
38
  var sessionPickerSource = "all";
36
39
  var billingPollTimer = null;
37
- var authWindow = null;
38
- var connectionPollStarted = false;
40
+ var localAuthEmail = "";
41
+ var localAuthTermsAccepted = false;
42
+ var localAuthAgeConfirmed = false;
39
43
  var statsPollStarted = false;
40
44
  var reportPollStarted = false;
45
+ var reportEstimateDeadline = 0;
46
+ var reportEstimateTimer = null;
47
+ var readyStage = "";
41
48
  var NIGHT_HOURS = { 22: 1, 23: 1, 0: 1, 1: 1, 2: 1, 3: 1 };
42
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
+
43
57
  function esc(value) {
44
58
  return String(value == null ? "" : value).replace(/[&<>"']/g, function (ch) {
45
59
  return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[ch];
46
60
  });
47
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
+ }
48
69
  function setupIcon(name) {
49
70
  var paths = {
50
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"/>',
@@ -142,10 +163,6 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
142
163
  if (code === "NOT_LOGGED_IN") return "Echo on this machine is not signed in yet. Sign in again and retry.";
143
164
  return code || "Something went wrong. Retry in a moment.";
144
165
  }
145
- function closeAuthWindow() {
146
- try { if (authWindow && !authWindow.closed) authWindow.close(); } catch (_) {}
147
- authWindow = null;
148
- }
149
166
  function notifyOpenerConnected() {
150
167
  try {
151
168
  if (window.opener && window.opener !== window && !window.opener.closed) {
@@ -156,25 +173,268 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
156
173
  } catch (_) {}
157
174
  return false;
158
175
  }
159
- function openAuthWindow(url) {
160
- var targetUrl = url || authUrl;
161
- if (!targetUrl) return false;
176
+ function localAuthLegalHtml() {
177
+ return '<label class="localAuthConsent">' +
178
+ '<input type="checkbox" id="localAuthConsent" ' + (localAuthAgeConfirmed && localAuthTermsAccepted ? "checked" : "") + ' />' +
179
+ '<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>' +
180
+ '</label>';
181
+ }
182
+ function captureLocalAuthConsents() {
183
+ var consent = document.getElementById("localAuthConsent");
184
+ if (consent) {
185
+ localAuthTermsAccepted = !!consent.checked;
186
+ localAuthAgeConfirmed = !!consent.checked;
187
+ return;
188
+ }
189
+ var terms = document.getElementById("localAuthTerms");
190
+ var age = document.getElementById("localAuthAge");
191
+ localAuthTermsAccepted = !!(terms && terms.checked);
192
+ localAuthAgeConfirmed = !!(age && age.checked);
193
+ }
194
+ function setLocalAuthStatus(message, tone) {
195
+ var node = document.getElementById("localAuthStatus");
196
+ if (!node) return;
197
+ node.textContent = message || "";
198
+ node.classList.toggle("is-error", tone === "error");
199
+ }
200
+ function updateLocalAuthSendButton() {
201
+ captureLocalAuthConsents();
202
+ var email = document.getElementById("localAuthEmail");
203
+ var button = document.getElementById("localAuthSend");
204
+ if (!button) return;
205
+ var value = email && email.value ? String(email.value).trim() : "";
206
+ button.disabled = !value || !localAuthTermsAccepted || !localAuthAgeConfirmed;
207
+ }
208
+ function renderLocalLogin(message) {
209
+ setCityMode(false);
210
+ setScanMode(false);
211
+ setExtractMode(false);
212
+ setReadyMode(true);
213
+ setHead("Connect EchoMem", "Local login");
214
+ app.className = "localAuthStage localAuthWelcomeStage";
215
+ app.innerHTML =
216
+ '<section class="localAuthWelcome" aria-labelledby="localAuthWelcomeTitle">' +
217
+ '<div class="localAuthOrbScene" aria-hidden="true"><span class="localAuthOrb"></span><span class="localAuthOrbShadow"></span></div>' +
218
+ '<div class="localAuthWelcomeCopy">' +
219
+ '<h2 id="localAuthWelcomeTitle">Welcome to Echo</h2>' +
220
+ '<p>Your memories, sovereign and shared.</p>' +
221
+ '</div>' +
222
+ '<form class="localAuthWelcomeForm" id="localAuthEmailForm">' +
223
+ '<div class="localAuthEmailCapsule">' +
224
+ '<label class="srOnly" for="localAuthEmail">Email</label>' +
225
+ '<input id="localAuthEmail" type="email" autocomplete="email" placeholder="Enter your email" value="' + esc(localAuthEmail) + '" />' +
226
+ '<button id="localAuthSend" type="submit" aria-label="Send one-time code"><span aria-hidden="true">→</span></button>' +
227
+ '</div>' +
228
+ localAuthLegalHtml() +
229
+ '<p class="localAuthStatus' + (message ? " is-error" : "") + '" id="localAuthStatus" role="status" aria-live="polite">' + esc(message || "") + '</p>' +
230
+ '</form>' +
231
+ '</section>';
232
+ var form = document.getElementById("localAuthEmailForm");
233
+ if (form) form.onsubmit = function (event) { event.preventDefault(); void sendLocalOtp(); };
234
+ var email = document.getElementById("localAuthEmail");
235
+ if (email) {
236
+ email.oninput = function () { localAuthEmail = String(email.value || ""); updateLocalAuthSendButton(); };
237
+ email.focus();
238
+ }
239
+ var consent = document.getElementById("localAuthConsent");
240
+ if (consent) consent.onchange = updateLocalAuthSendButton;
241
+ updateLocalAuthSendButton();
242
+ }
243
+ function renderLocalLoginComplete() {
244
+ setCityMode(false);
245
+ setScanMode(false);
246
+ setExtractMode(false);
247
+ setReadyMode(true);
248
+ setHead("EchoMem connected", "Done");
249
+ app.className = "notice";
250
+ app.innerHTML =
251
+ '<section class="localAuthCard localAuthComplete">' +
252
+ '<div class="localAuthLead">' +
253
+ '<img src="/hud-assets/echo-face-cutout.png" alt="" />' +
254
+ '<p class="consentEyebrow">This device is connected</p>' +
255
+ '<h2 class="siteHeadline">You\'re signed in.</h2>' +
256
+ '<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>' +
257
+ '</div>' +
258
+ '</section>';
259
+ }
260
+ async function finishLocalLogin() {
261
+ connected = true;
262
+ if (setupFlow === "login") {
263
+ renderLocalLoginComplete();
264
+ return;
265
+ }
266
+ rememberSetupPlanChoice("");
267
+ try { await refreshBillingStatus(); } catch (_) {}
268
+ await waitForStats();
269
+ }
270
+ async function sendLocalOtp() {
271
+ var email = document.getElementById("localAuthEmail");
272
+ localAuthEmail = email && email.value ? String(email.value).trim().toLowerCase() : "";
273
+ captureLocalAuthConsents();
274
+ if (!localAuthEmail) { setLocalAuthStatus("Enter your email.", "error"); return; }
275
+ if (!localAuthAgeConfirmed || !localAuthTermsAccepted) {
276
+ setLocalAuthStatus("Confirm your age and accept Echo's terms before continuing.", "error");
277
+ return;
278
+ }
279
+ var button = document.getElementById("localAuthSend");
280
+ if (button) { button.disabled = true; button.innerHTML = '<span class="localAuthSpinner" aria-hidden="true"></span>'; }
281
+ setLocalAuthStatus("Sending verification code...");
162
282
  try {
163
- authWindow = window.open(targetUrl, "echomem-signin-" + nonce, "popup=yes,width=560,height=760");
164
- if (!authWindow) return false;
165
- authWindow.focus();
166
- return true;
167
- } catch (_) { return false; }
168
- }
169
- function showPopupFallback(message, url) {
170
- Array.prototype.forEach.call(document.querySelectorAll("[data-signin-help]"), function (hint) {
171
- hint.textContent = message || "Your browser blocked the secure sign-in window. Use the button below, then this page will resume when sign-in returns.";
172
- });
173
- Array.prototype.forEach.call(document.querySelectorAll("[data-signin-fallback]"), function (fallback) {
174
- fallback.setAttribute("href", url || authUrl);
175
- fallback.classList.remove("hidden");
283
+ var data = await postJson("/local-auth/send-otp", {
284
+ email: localAuthEmail,
285
+ acceptedTerms: localAuthTermsAccepted,
286
+ ageConfirmed: localAuthAgeConfirmed
287
+ }, 12000);
288
+ localAuthEmail = data.email || localAuthEmail;
289
+ renderLocalOtp("Code sent. Check your inbox.");
290
+ } catch (error) {
291
+ if (button) { button.disabled = false; button.innerHTML = '<span aria-hidden="true">→</span>'; }
292
+ setLocalAuthStatus(error && error.message ? error.message : "Could not send the code.", "error");
293
+ }
294
+ }
295
+ function renderLocalOtp(message) {
296
+ setCityMode(false);
297
+ setScanMode(false);
298
+ setExtractMode(false);
299
+ setReadyMode(true);
300
+ setHead("Check your email", "Local login");
301
+ app.className = "localAuthStage localAuthWelcomeStage localAuthOtpStage";
302
+ app.innerHTML =
303
+ '<section class="localAuthWelcome localAuthOtpWelcome" aria-labelledby="localAuthOtpTitle">' +
304
+ '<div class="localAuthOrbScene" aria-hidden="true"><span class="localAuthOrb"></span><span class="localAuthOrbShadow"></span></div>' +
305
+ '<div class="localAuthWelcomeCopy">' +
306
+ '<h2 id="localAuthOtpTitle">Check your inbox</h2>' +
307
+ '<p>We sent a 6-digit code to <strong>' + esc(localAuthEmail) + '</strong></p>' +
308
+ '</div>' +
309
+ '<form class="localAuthWelcomeForm localAuthOtpForm" id="localAuthOtpForm">' +
310
+ '<label class="srOnly" for="localAuthOtp0">6-digit code</label>' +
311
+ '<div class="localAuthOtpCells" aria-label="6-digit verification code">' +
312
+ '<input id="localAuthOtp0" data-otp-cell type="text" inputmode="numeric" autocomplete="one-time-code" maxlength="1" />' +
313
+ '<input data-otp-cell type="text" inputmode="numeric" maxlength="1" />' +
314
+ '<input data-otp-cell type="text" inputmode="numeric" maxlength="1" />' +
315
+ '<input data-otp-cell type="text" inputmode="numeric" 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
+ '</div>' +
319
+ '<button class="primary localAuthVerifyWide" id="localAuthVerify" type="submit">Verify</button>' +
320
+ '<button class="secondary localAuthBackWide" id="localAuthBack" type="button">Use another email</button>' +
321
+ '<p class="localAuthStatus" id="localAuthStatus" role="status" aria-live="polite">' + esc(message || "") + '</p>' +
322
+ '</form>' +
323
+ '</section>';
324
+ var form = document.getElementById("localAuthOtpForm");
325
+ if (form) form.onsubmit = function (event) { event.preventDefault(); void verifyLocalOtp(); };
326
+ var back = document.getElementById("localAuthBack");
327
+ if (back) back.onclick = function () { renderLocalLogin(""); };
328
+ bindLocalOtpCells();
329
+ var otp = document.querySelector("[data-otp-cell]");
330
+ if (otp) otp.focus();
331
+ }
332
+ function localOtpCellValues() {
333
+ return Array.prototype.map.call(document.querySelectorAll("[data-otp-cell]"), function (node) {
334
+ return String(node.value || "").replace(/\\D/g, "").slice(0, 1);
335
+ }).join("");
336
+ }
337
+ function fillLocalOtpCells(value) {
338
+ var digits = String(value || "").replace(/\\D/g, "").slice(0, 6);
339
+ var cells = Array.prototype.slice.call(document.querySelectorAll("[data-otp-cell]"));
340
+ cells.forEach(function (cell, index) { cell.value = digits.charAt(index) || ""; });
341
+ var focusIndex = Math.min(digits.length, cells.length - 1);
342
+ if (cells[focusIndex]) cells[focusIndex].focus();
343
+ }
344
+ function bindLocalOtpCells() {
345
+ var cells = Array.prototype.slice.call(document.querySelectorAll("[data-otp-cell]"));
346
+ cells.forEach(function (cell, index) {
347
+ cell.oninput = function () {
348
+ var value = String(cell.value || "").replace(/\\D/g, "");
349
+ if (value.length > 1) { fillLocalOtpCells(value); return; }
350
+ cell.value = value;
351
+ if (value && cells[index + 1]) cells[index + 1].focus();
352
+ };
353
+ cell.onkeydown = function (event) {
354
+ if (event.key === "Backspace" && !cell.value && cells[index - 1]) cells[index - 1].focus();
355
+ };
356
+ cell.onpaste = function (event) {
357
+ var text = event.clipboardData && event.clipboardData.getData ? event.clipboardData.getData("text") : "";
358
+ if (text) {
359
+ event.preventDefault();
360
+ fillLocalOtpCells(text);
361
+ }
362
+ };
176
363
  });
177
364
  }
365
+ async function verifyLocalOtp() {
366
+ var otp = localOtpCellValues();
367
+ if (otp.length !== 6) { setLocalAuthStatus("Enter the 6-digit code.", "error"); return; }
368
+ var button = document.getElementById("localAuthVerify");
369
+ if (button) { button.disabled = true; button.textContent = "Verifying..."; }
370
+ setLocalAuthStatus("Verifying code...");
371
+ try {
372
+ var data = await postJson("/local-auth/verify-otp", {
373
+ email: localAuthEmail,
374
+ otp: otp,
375
+ acceptedTerms: localAuthTermsAccepted,
376
+ ageConfirmed: localAuthAgeConfirmed
377
+ }, 18000);
378
+ if (data && data.stage === "passphrase") {
379
+ renderLocalPassphrase(data.mode || "unlock", data.email || localAuthEmail);
380
+ return;
381
+ }
382
+ await finishLocalLogin();
383
+ } catch (error) {
384
+ if (button) { button.disabled = false; button.textContent = "Verify code"; }
385
+ setLocalAuthStatus(error && error.message ? error.message : "Could not verify the code.", "error");
386
+ }
387
+ }
388
+ function renderLocalPassphrase(mode, email, message) {
389
+ localAuthEmail = email || localAuthEmail;
390
+ var setupMode = mode === "setup";
391
+ setCityMode(false);
392
+ setScanMode(false);
393
+ setExtractMode(false);
394
+ setReadyMode(true);
395
+ setHead(setupMode ? "Create encrypted vault" : "Unlock encrypted vault", "Local passphrase");
396
+ app.className = "localAuthStage localAuthWelcomeStage localAuthPassphraseStage";
397
+ app.innerHTML =
398
+ '<section class="localAuthWelcome localAuthPassphraseWelcome">' +
399
+ '<div class="localAuthOrbScene" aria-hidden="true"><span class="localAuthOrb"></span><span class="localAuthOrbShadow"></span></div>' +
400
+ '<div class="localAuthWelcomeCopy">' +
401
+ '<h2>' + (setupMode ? 'Create your local vault passphrase.' : 'Enter your vault passphrase.') + '</h2>' +
402
+ '<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>' +
403
+ '</div>' +
404
+ '<form class="localAuthWelcomeForm localAuthPassphraseForm" id="localAuthPassphraseForm">' +
405
+ '<label class="localAuthField"><span>Passphrase</span><input id="localAuthPassphrase" type="password" autocomplete="' + (setupMode ? "new-password" : "current-password") + '" placeholder="Encryption passphrase" /></label>' +
406
+ (setupMode ? '<label class="localAuthField"><span>Confirm passphrase</span><input id="localAuthPassphraseConfirm" type="password" autocomplete="new-password" placeholder="Confirm passphrase" /></label>' : '') +
407
+ '<button class="primary localAuthPassphraseSubmit" id="localAuthUnlock" type="submit">' + (setupMode ? "Create vault" : "Unlock") + '</button>' +
408
+ '<button class="secondary localAuthBackWide" id="localAuthRestart" type="button">Use a different email</button>' +
409
+ '<p class="localAuthStatus' + (message ? " is-error" : "") + '" id="localAuthStatus" role="status" aria-live="polite">' + esc(message || "") + '</p>' +
410
+ '</form>' +
411
+ '</section>';
412
+ var form = document.getElementById("localAuthPassphraseForm");
413
+ if (form) form.onsubmit = function (event) { event.preventDefault(); void submitLocalPassphrase(setupMode); };
414
+ var restart = document.getElementById("localAuthRestart");
415
+ if (restart) restart.onclick = function () { renderLocalLogin(""); };
416
+ var pass = document.getElementById("localAuthPassphrase");
417
+ if (pass) pass.focus();
418
+ }
419
+ async function submitLocalPassphrase(setupMode) {
420
+ var pass = document.getElementById("localAuthPassphrase");
421
+ var confirm = document.getElementById("localAuthPassphraseConfirm");
422
+ var passphrase = pass && typeof pass.value === "string" ? pass.value : "";
423
+ var confirmValue = confirm && typeof confirm.value === "string" ? confirm.value : "";
424
+ if (passphrase.length < 4) { setLocalAuthStatus("Use at least 4 characters.", "error"); return; }
425
+ if (setupMode && passphrase !== confirmValue) { setLocalAuthStatus("Passphrases do not match.", "error"); return; }
426
+ var button = document.getElementById("localAuthUnlock");
427
+ if (button) { button.disabled = true; button.textContent = setupMode ? "Creating..." : "Unlocking..."; }
428
+ setLocalAuthStatus(setupMode ? "Creating encrypted vault..." : "Unlocking vault...");
429
+ try {
430
+ await postJson("/local-auth/passphrase", { passphrase: passphrase }, 30000);
431
+ await finishLocalLogin();
432
+ } catch (error) {
433
+ if (button) { button.disabled = false; button.textContent = setupMode ? "Create vault" : "Unlock"; }
434
+ var msg = error && error.message ? error.message : "Could not unlock encrypted memory.";
435
+ renderLocalPassphrase(setupMode ? "setup" : "unlock", localAuthEmail, msg);
436
+ }
437
+ }
178
438
  function onConnectClick() {
179
439
  if (!localHistoryConsentGranted) {
180
440
  renderLocalScanConsent();
@@ -182,8 +442,8 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
182
442
  return;
183
443
  }
184
444
  if (connected) { void waitForStats(); return; }
185
- if (!openAuthWindow(authUrl)) showPopupFallback("", authUrl);
186
- startConnectionPoll();
445
+ rememberSetupPlanChoice("");
446
+ renderLocalLogin("");
187
447
  }
188
448
  function bindConnect() {
189
449
  Array.prototype.forEach.call(document.querySelectorAll("[data-connect-echo]"), function (signin) {