@echomem/mcp 1.4.26 → 1.4.27
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/dist/context-analysis/vendored-canonical.js +54 -11
- package/dist/forensics.js +24 -4
- package/dist/hud/cli.js +18 -1
- package/dist/hud/hooks.js +59 -0
- package/dist/index.js +111 -16
- package/dist/package-metadata.js +5 -3
- package/dist/save-checkpoint-hook.js +111 -0
- package/dist/setup-page/client-core.js +86 -15
- package/dist/setup-page/client-extraction.js +313 -145
- package/dist/setup-page/client-lifecycle.js +10 -11
- package/dist/setup-page/client-report-audit.js +3 -2
- package/dist/setup-page/client-report-city.js +36 -40
- package/dist/setup-page/styles-foundation.js +3 -1
- package/dist/setup-page/styles-mvp.js +568 -150
- package/dist/setup-preview.js +46 -6
- package/dist/setup.js +143 -10
- package/dist/v1-contract.js +39 -6
- package/package.json +3 -2
- package/templates/codex-skills/echomem-save/SKILL.md +10 -0
- package/templates/echomem-recall.md +10 -2
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
const SAVE_TOOL_RE = /^(?:mcp__[\w.-]+__)?save_conversation$/i;
|
|
3
|
+
const MUTATION_TOOL_RE = /(?:^|[._])(?:apply_patch|Edit|Write)$/i;
|
|
4
|
+
const DURABLE_COMMAND_RE = /\b(?:git\s+commit|npm\s+(?:run\s+)?(?:test|build|lint)|pnpm\s+(?:test|build|lint)|yarn\s+(?:test|build|lint)|pytest|cargo\s+test|go\s+test|deploy|publish)\b/i;
|
|
5
|
+
const DURABLE_LANGUAGE_RE = /\b(?:implemented|fixed|resolved|shipped|deployed|published|committed|milestone|decision|architecture|root cause|tests? pass(?:ed|ing)?|build pass(?:ed|ing)?)\b|(?:实现|修复|解决|发布|提交|里程碑|决定|架构|根因|测试通过|构建通过)/i;
|
|
6
|
+
const CHECKPOINT_REASON = [
|
|
7
|
+
"Run EchoMem's private completion checkpoint before finishing this turn.",
|
|
8
|
+
"Review the work that just completed. If it produced a durable decision, confirmed preference, implementation, resolved bug, commit, passing verification, release, or project milestone, call `save_conversation` now with a concise structured summary of the outcome, reasoning, relevant files, and constraints.",
|
|
9
|
+
"Do not save secrets or incidental chatter. If the work was not durable, do not call the tool and finish normally.",
|
|
10
|
+
"Private persistence happens first. For a user with a company group, call `get_group_session_sharing` near session start or after this first qualifying private save.",
|
|
11
|
+
"If this exact session has no decision, ask once whether memories saved from the session should be shared with the current group, then call `set_group_session_sharing` only with the explicit Yes/No answer. A Yes decision automatically syncs eligible memories after later private saves; a No decision keeps later saves private.",
|
|
12
|
+
"Flagged memories are withheld from automatic session sync and remain private.",
|
|
13
|
+
"If EchoMem reports that the encrypted vault is locked, tell the user to run `echomem-mcp unlock`; never silently skip a qualifying checkpoint.",
|
|
14
|
+
].join(" ");
|
|
15
|
+
function currentTurnSlice(transcript) {
|
|
16
|
+
const lines = transcript.split(/\r?\n/);
|
|
17
|
+
let start = 0;
|
|
18
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
19
|
+
const line = lines[index];
|
|
20
|
+
if (/"type"\s*:\s*"user_message"/.test(line)
|
|
21
|
+
|| /"type"\s*:\s*"user"/.test(line)
|
|
22
|
+
|| /"role"\s*:\s*"user"/.test(line)) {
|
|
23
|
+
start = index;
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return lines.slice(start).join("\n");
|
|
28
|
+
}
|
|
29
|
+
function visibleTextSize(value) {
|
|
30
|
+
return value
|
|
31
|
+
.replace(/"[^"]+"\s*:/g, "")
|
|
32
|
+
.replace(/[{}\[\]",\\]/g, "")
|
|
33
|
+
.trim()
|
|
34
|
+
.length;
|
|
35
|
+
}
|
|
36
|
+
function toolNamesFromTranscript(transcript) {
|
|
37
|
+
const names = [];
|
|
38
|
+
const visit = (value) => {
|
|
39
|
+
if (Array.isArray(value)) {
|
|
40
|
+
value.forEach(visit);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (typeof value !== "object" || value === null)
|
|
44
|
+
return;
|
|
45
|
+
const record = value;
|
|
46
|
+
const type = typeof record.type === "string" ? record.type : "";
|
|
47
|
+
if ((type === "function_call" || type === "tool_use" || type === "mcp_tool_call")
|
|
48
|
+
&& typeof record.name === "string") {
|
|
49
|
+
names.push(record.name);
|
|
50
|
+
}
|
|
51
|
+
Object.values(record).forEach(visit);
|
|
52
|
+
};
|
|
53
|
+
for (const line of transcript.split(/\r?\n/)) {
|
|
54
|
+
try {
|
|
55
|
+
visit(JSON.parse(line));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Hook transcripts are JSONL. Ignore a partial final line while Codex is still flushing it.
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return names;
|
|
62
|
+
}
|
|
63
|
+
export function evaluateSaveCheckpoint(payload, transcript) {
|
|
64
|
+
if (payload.stop_hook_active === true) {
|
|
65
|
+
return { decision: "allow", reason: "already_checked" };
|
|
66
|
+
}
|
|
67
|
+
if (!transcript) {
|
|
68
|
+
return { decision: "allow", reason: "missing_transcript" };
|
|
69
|
+
}
|
|
70
|
+
const turn = currentTurnSlice(transcript);
|
|
71
|
+
const toolNames = toolNamesFromTranscript(turn);
|
|
72
|
+
if (toolNames.some((name) => SAVE_TOOL_RE.test(name))) {
|
|
73
|
+
return { decision: "allow", reason: "already_saved" };
|
|
74
|
+
}
|
|
75
|
+
const assistant = typeof payload.last_assistant_message === "string" ? payload.last_assistant_message : "";
|
|
76
|
+
const durable = toolNames.some((name) => MUTATION_TOOL_RE.test(name))
|
|
77
|
+
|| DURABLE_COMMAND_RE.test(turn)
|
|
78
|
+
|| DURABLE_LANGUAGE_RE.test(assistant)
|
|
79
|
+
|| DURABLE_LANGUAGE_RE.test(turn)
|
|
80
|
+
|| (visibleTextSize(turn) >= 1_200 && assistant.trim().length >= 240);
|
|
81
|
+
if (!durable) {
|
|
82
|
+
return { decision: "allow", reason: "not_durable" };
|
|
83
|
+
}
|
|
84
|
+
return { decision: "block", reason: CHECKPOINT_REASON };
|
|
85
|
+
}
|
|
86
|
+
export function runSaveCheckpointHook(input) {
|
|
87
|
+
let payload = {};
|
|
88
|
+
try {
|
|
89
|
+
const parsed = JSON.parse(input);
|
|
90
|
+
if (typeof parsed === "object" && parsed !== null)
|
|
91
|
+
payload = parsed;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return JSON.stringify({ continue: true });
|
|
95
|
+
}
|
|
96
|
+
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
|
|
97
|
+
let transcript = null;
|
|
98
|
+
if (transcriptPath) {
|
|
99
|
+
try {
|
|
100
|
+
transcript = fs.readFileSync(transcriptPath, "utf8");
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
transcript = null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const result = evaluateSaveCheckpoint(payload, transcript);
|
|
107
|
+
if (result.decision === "block") {
|
|
108
|
+
return JSON.stringify({ decision: "block", reason: result.reason });
|
|
109
|
+
}
|
|
110
|
+
return JSON.stringify({ continue: true });
|
|
111
|
+
}
|
|
@@ -32,6 +32,13 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
32
32
|
var setupPlanChoice = "";
|
|
33
33
|
var setupPlanPreview = "free";
|
|
34
34
|
var billingActivationPendingPlan = "";
|
|
35
|
+
var pendingCheckoutSessionId = "";
|
|
36
|
+
var billingSyncLastAttemptAt = 0;
|
|
37
|
+
var billingSyncAttempts = 0;
|
|
38
|
+
var billingLastCheckedAt = 0;
|
|
39
|
+
var billingCheckMessage = "";
|
|
40
|
+
var firstRecallPollTimer = null;
|
|
41
|
+
var firstRecallPollStartedAt = 0;
|
|
35
42
|
var selectedSessionKeys = Object.create(null);
|
|
36
43
|
var sessionSelectionTouched = false;
|
|
37
44
|
var sessionPickerQuery = "";
|
|
@@ -40,10 +47,9 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
40
47
|
var localAuthEmail = "";
|
|
41
48
|
var localAuthTermsAccepted = false;
|
|
42
49
|
var localAuthAgeConfirmed = false;
|
|
50
|
+
var accountSwitchPending = false;
|
|
43
51
|
var statsPollStarted = false;
|
|
44
52
|
var reportPollStarted = false;
|
|
45
|
-
var reportEstimateDeadline = 0;
|
|
46
|
-
var reportEstimateTimer = null;
|
|
47
53
|
var readyStage = "";
|
|
48
54
|
var NIGHT_HOURS = { 22: 1, 23: 1, 0: 1, 1: 1, 2: 1, 3: 1 };
|
|
49
55
|
|
|
@@ -52,6 +58,10 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
52
58
|
if (storedSetupPlanChoice === "free" || storedSetupPlanChoice === "pro" || storedSetupPlanChoice === "power") {
|
|
53
59
|
setupPlanChoice = storedSetupPlanChoice;
|
|
54
60
|
}
|
|
61
|
+
var storedCheckoutSessionId = sessionStorage.getItem("echomem:checkout-session:" + nonce) || "";
|
|
62
|
+
if (/^cs_(?:test_|live_)?[A-Za-z0-9_]+$/.test(storedCheckoutSessionId)) {
|
|
63
|
+
pendingCheckoutSessionId = storedCheckoutSessionId;
|
|
64
|
+
}
|
|
55
65
|
} catch (_) {}
|
|
56
66
|
|
|
57
67
|
function esc(value) {
|
|
@@ -59,6 +69,11 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
59
69
|
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[ch];
|
|
60
70
|
});
|
|
61
71
|
}
|
|
72
|
+
function dataHandlingLink(label) {
|
|
73
|
+
return '<a class="dataHandlingLink" href="https://echoknows.com/privacy-policy" target="_blank" rel="noopener noreferrer">' +
|
|
74
|
+
esc(label || "How Echo handles your coding history") +
|
|
75
|
+
'</a>';
|
|
76
|
+
}
|
|
62
77
|
function rememberSetupPlanChoice(choice) {
|
|
63
78
|
setupPlanChoice = choice === "free" || choice === "pro" || choice === "power" ? choice : "";
|
|
64
79
|
try {
|
|
@@ -66,6 +81,18 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
66
81
|
else sessionStorage.removeItem("echomem:setup-plan:" + nonce);
|
|
67
82
|
} catch (_) {}
|
|
68
83
|
}
|
|
84
|
+
function rememberCheckoutSessionId(sessionId) {
|
|
85
|
+
pendingCheckoutSessionId = /^cs_(?:test_|live_)?[A-Za-z0-9_]+$/.test(String(sessionId || ""))
|
|
86
|
+
? String(sessionId)
|
|
87
|
+
: "";
|
|
88
|
+
try {
|
|
89
|
+
if (pendingCheckoutSessionId) {
|
|
90
|
+
sessionStorage.setItem("echomem:checkout-session:" + nonce, pendingCheckoutSessionId);
|
|
91
|
+
} else {
|
|
92
|
+
sessionStorage.removeItem("echomem:checkout-session:" + nonce);
|
|
93
|
+
}
|
|
94
|
+
} catch (_) {}
|
|
95
|
+
}
|
|
69
96
|
function setupIcon(name) {
|
|
70
97
|
var paths = {
|
|
71
98
|
"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"/>',
|
|
@@ -80,6 +107,18 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
80
107
|
};
|
|
81
108
|
return '<svg class="setupGlyph" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><g stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">' + (paths[name] || "") + '</g></svg>';
|
|
82
109
|
}
|
|
110
|
+
function setupJourney(currentStep) {
|
|
111
|
+
var labels = ["Scan", "Report", "Connect", "Import", "Try Echo"];
|
|
112
|
+
var items = labels.map(function (label, index) {
|
|
113
|
+
var step = index + 1;
|
|
114
|
+
var state = step < currentStep ? " is-complete" : (step === currentStep ? " is-current" : "");
|
|
115
|
+
return '<li class="setupJourneyStep' + state + '"' + (step === currentStep ? ' aria-current="step"' : "") + '>' +
|
|
116
|
+
'<span>' + (step < currentStep ? setupIcon("check") : esc(step)) + '</span>' +
|
|
117
|
+
'<b>' + esc(label) + '</b>' +
|
|
118
|
+
'</li>';
|
|
119
|
+
}).join("");
|
|
120
|
+
return '<nav class="setupJourney" aria-label="EchoMem setup progress"><ol>' + items + '</ol></nav>';
|
|
121
|
+
}
|
|
83
122
|
function setHead(nextTitle, nextStatus) {
|
|
84
123
|
title.textContent = nextTitle;
|
|
85
124
|
status.textContent = nextStatus;
|
|
@@ -179,7 +218,7 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
179
218
|
function localAuthLegalHtml() {
|
|
180
219
|
return '<label class="localAuthConsent">' +
|
|
181
220
|
'<input type="checkbox" id="localAuthConsent" ' + (localAuthAgeConfirmed && localAuthTermsAccepted ? "checked" : "") + ' />' +
|
|
182
|
-
'<span>I
|
|
221
|
+
'<span>I’m at least 18 and agree to Echo\'s <a href="https://echoknows.com/terms-of-use" target="_blank" rel="noopener noreferrer">Terms</a>, <a href="https://echoknows.com/memory-terms" target="_blank" rel="noopener noreferrer">Memory Terms</a>, and <a href="https://echoknows.com/privacy-policy" target="_blank" rel="noopener noreferrer">Privacy Policy</a>. <a href="https://echoknows.com/subprocessor-list" target="_blank" rel="noopener noreferrer">Subprocessors</a></span>' +
|
|
183
222
|
'</label>';
|
|
184
223
|
}
|
|
185
224
|
function captureLocalAuthConsents() {
|
|
@@ -209,24 +248,28 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
209
248
|
button.disabled = !value || !localAuthTermsAccepted || !localAuthAgeConfirmed;
|
|
210
249
|
}
|
|
211
250
|
function renderLocalLogin(message) {
|
|
251
|
+
var onboardingConnect = setupFlow !== "login";
|
|
212
252
|
setCityMode(false);
|
|
213
253
|
setScanMode(false);
|
|
214
254
|
setExtractMode(false);
|
|
215
255
|
setReadyMode(true);
|
|
216
256
|
setHead("Connect EchoMem", "Local login");
|
|
217
|
-
app.className = "localAuthStage localAuthWelcomeStage";
|
|
257
|
+
app.className = "localAuthStage localAuthWelcomeStage localAuthEmailStage";
|
|
218
258
|
app.innerHTML =
|
|
259
|
+
setupJourney(3) +
|
|
219
260
|
'<section class="localAuthWelcome" aria-labelledby="localAuthWelcomeTitle">' +
|
|
220
261
|
'<div class="localAuthOrbScene" aria-hidden="true"><span class="localAuthOrb"></span><span class="localAuthOrbShadow"></span></div>' +
|
|
221
262
|
'<div class="localAuthWelcomeCopy">' +
|
|
222
|
-
'<h2 id="localAuthWelcomeTitle">Welcome to Echo</h2>' +
|
|
223
|
-
|
|
263
|
+
'<h2 id="localAuthWelcomeTitle">' + (onboardingConnect ? 'Take your context with you.' : 'Welcome to Echo') + '</h2>' +
|
|
264
|
+
(onboardingConnect
|
|
265
|
+
? '<p class="localAuthConnectPurpose">Connect this Mac to Echo to use the memories you choose across your AI tools.</p>'
|
|
266
|
+
: '') +
|
|
224
267
|
'</div>' +
|
|
225
268
|
'<form class="localAuthWelcomeForm" id="localAuthEmailForm">' +
|
|
226
269
|
'<div class="localAuthEmailCapsule">' +
|
|
227
270
|
'<label class="srOnly" for="localAuthEmail">Email</label>' +
|
|
228
271
|
'<input id="localAuthEmail" type="email" autocomplete="email" placeholder="Enter your email" value="' + esc(localAuthEmail) + '" />' +
|
|
229
|
-
'<button id="localAuthSend" type="submit"
|
|
272
|
+
'<button id="localAuthSend" type="submit">Email me a code</button>' +
|
|
230
273
|
'</div>' +
|
|
231
274
|
localAuthLegalHtml() +
|
|
232
275
|
'<p class="localAuthStatus' + (message ? " is-error" : "") + '" id="localAuthStatus" role="status" aria-live="polite">' + esc(message || "") + '</p>' +
|
|
@@ -254,9 +297,8 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
254
297
|
'<section class="localAuthCard localAuthComplete">' +
|
|
255
298
|
'<div class="localAuthLead">' +
|
|
256
299
|
'<img src="/hud-assets/echo-face-cutout.png" alt="" />' +
|
|
257
|
-
'<p class="consentEyebrow">This device is connected</p>' +
|
|
258
300
|
'<h2 class="siteHeadline">You\'re signed in.</h2>' +
|
|
259
|
-
'<p>
|
|
301
|
+
'<p>Return to Terminal, or run <code>echomem-mcp init</code> to start onboarding.</p>' +
|
|
260
302
|
'</div>' +
|
|
261
303
|
'</section>';
|
|
262
304
|
}
|
|
@@ -280,7 +322,7 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
280
322
|
return;
|
|
281
323
|
}
|
|
282
324
|
var button = document.getElementById("localAuthSend");
|
|
283
|
-
if (button) { button.disabled = true; button.innerHTML = '<span class="localAuthSpinner" aria-hidden="true"></span>'; }
|
|
325
|
+
if (button) { button.disabled = true; button.innerHTML = '<span class="localAuthSpinner" aria-hidden="true"></span><span>Sending…</span>'; }
|
|
284
326
|
setLocalAuthStatus("Sending verification code...");
|
|
285
327
|
try {
|
|
286
328
|
var data = await postJson("/local-auth/send-otp", {
|
|
@@ -291,7 +333,7 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
291
333
|
localAuthEmail = data.email || localAuthEmail;
|
|
292
334
|
renderLocalOtp("Code sent. Check your inbox.");
|
|
293
335
|
} catch (error) {
|
|
294
|
-
if (button) { button.disabled = false; button.
|
|
336
|
+
if (button) { button.disabled = false; button.textContent = "Email me a code"; }
|
|
295
337
|
setLocalAuthStatus(error && error.message ? error.message : "Could not send the code.", "error");
|
|
296
338
|
}
|
|
297
339
|
}
|
|
@@ -303,6 +345,7 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
303
345
|
setHead("Check your email", "Local login");
|
|
304
346
|
app.className = "localAuthStage localAuthWelcomeStage localAuthOtpStage";
|
|
305
347
|
app.innerHTML =
|
|
348
|
+
setupJourney(3) +
|
|
306
349
|
'<section class="localAuthWelcome localAuthOtpWelcome" aria-labelledby="localAuthOtpTitle">' +
|
|
307
350
|
'<div class="localAuthOrbScene" aria-hidden="true"><span class="localAuthOrb"></span><span class="localAuthOrbShadow"></span></div>' +
|
|
308
351
|
'<div class="localAuthWelcomeCopy">' +
|
|
@@ -398,17 +441,22 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
398
441
|
setHead(setupMode ? "Create encrypted vault" : "Unlock encrypted vault", "Local passphrase");
|
|
399
442
|
app.className = "localAuthStage localAuthWelcomeStage localAuthPassphraseStage";
|
|
400
443
|
app.innerHTML =
|
|
444
|
+
setupJourney(3) +
|
|
401
445
|
'<section class="localAuthWelcome localAuthPassphraseWelcome">' +
|
|
402
446
|
'<div class="localAuthOrbScene" aria-hidden="true"><span class="localAuthOrb"></span><span class="localAuthOrbShadow"></span></div>' +
|
|
403
447
|
'<div class="localAuthWelcomeCopy">' +
|
|
404
|
-
'<h2>' + (setupMode ? '
|
|
405
|
-
'<p>' + (setupMode ? '
|
|
448
|
+
'<h2>' + (setupMode ? 'Protect your memories with a passphrase.' : 'Enter your vault passphrase.') + '</h2>' +
|
|
449
|
+
'<p>' + (setupMode ? 'It encrypts memories before upload and unlocks them on your trusted devices. Echo can’t recover it, so save it in your password manager.' : 'Verified only on this device. It is never sent to EchoMem.') + '</p>' +
|
|
406
450
|
'</div>' +
|
|
407
451
|
'<form class="localAuthWelcomeForm localAuthPassphraseForm" id="localAuthPassphraseForm">' +
|
|
408
452
|
'<label class="localAuthField"><span>Passphrase</span><input id="localAuthPassphrase" type="password" autocomplete="' + (setupMode ? "new-password" : "current-password") + '" placeholder="Encryption passphrase" /></label>' +
|
|
409
453
|
(setupMode ? '<label class="localAuthField"><span>Confirm passphrase</span><input id="localAuthPassphraseConfirm" type="password" autocomplete="new-password" placeholder="Confirm passphrase" /></label>' : '') +
|
|
410
454
|
'<button class="primary localAuthPassphraseSubmit" id="localAuthUnlock" type="submit">' + (setupMode ? "Create vault" : "Unlock") + '</button>' +
|
|
411
|
-
|
|
455
|
+
(setupMode
|
|
456
|
+
? '<p class="localAuthVaultDeferNote">Import won’t start until you create your vault.</p>' +
|
|
457
|
+
'<button class="localAuthTextAction" id="localAuthSkipVault" type="button">Skip for now</button>'
|
|
458
|
+
: '') +
|
|
459
|
+
'<button class="localAuthTextAction localAuthRestartAction" id="localAuthRestart" type="button">Use a different email</button>' +
|
|
412
460
|
'<p class="localAuthStatus' + (message ? " is-error" : "") + '" id="localAuthStatus" role="status" aria-live="polite">' + esc(message || "") + '</p>' +
|
|
413
461
|
'</form>' +
|
|
414
462
|
'</section>';
|
|
@@ -416,9 +464,32 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
416
464
|
if (form) form.onsubmit = function (event) { event.preventDefault(); void submitLocalPassphrase(setupMode); };
|
|
417
465
|
var restart = document.getElementById("localAuthRestart");
|
|
418
466
|
if (restart) restart.onclick = function () { renderLocalLogin(""); };
|
|
467
|
+
var skipVault = document.getElementById("localAuthSkipVault");
|
|
468
|
+
if (skipVault) skipVault.onclick = function () { void skipLocalVaultSetup(); };
|
|
419
469
|
var pass = document.getElementById("localAuthPassphrase");
|
|
420
470
|
if (pass) pass.focus();
|
|
421
471
|
}
|
|
472
|
+
async function skipLocalVaultSetup() {
|
|
473
|
+
var button = document.getElementById("localAuthSkipVault");
|
|
474
|
+
if (button) { button.disabled = true; button.textContent = "Leaving setup…"; }
|
|
475
|
+
setLocalAuthStatus("Leaving setup. Your coding history will stay on this Mac.");
|
|
476
|
+
try {
|
|
477
|
+
await postJson("/skip", {});
|
|
478
|
+
setHead("Setup paused", "Vault deferred");
|
|
479
|
+
app.className = "localAuthStage localAuthWelcomeStage localAuthPassphraseStage";
|
|
480
|
+
app.innerHTML =
|
|
481
|
+
'<section class="localAuthWelcome localAuthDeferred" aria-labelledby="localAuthDeferredTitle">' +
|
|
482
|
+
'<div class="localAuthWelcomeCopy">' +
|
|
483
|
+
'<h2 id="localAuthDeferredTitle">Setup paused.</h2>' +
|
|
484
|
+
'<p>Your coding history is unchanged. Run <code>echomem-mcp init</code> when you’re ready to create your vault and continue.</p>' +
|
|
485
|
+
'</div>' +
|
|
486
|
+
'</section>';
|
|
487
|
+
try { window.close(); } catch (_) {}
|
|
488
|
+
} catch (error) {
|
|
489
|
+
if (button) { button.disabled = false; button.textContent = "Skip for now"; }
|
|
490
|
+
setLocalAuthStatus(error && error.message ? error.message : "Could not leave setup safely.", "error");
|
|
491
|
+
}
|
|
492
|
+
}
|
|
422
493
|
async function submitLocalPassphrase(setupMode) {
|
|
423
494
|
var pass = document.getElementById("localAuthPassphrase");
|
|
424
495
|
var confirm = document.getElementById("localAuthPassphraseConfirm");
|
|
@@ -441,7 +512,7 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
441
512
|
function onConnectClick() {
|
|
442
513
|
if (!localHistoryConsentGranted) {
|
|
443
514
|
renderLocalScanConsent();
|
|
444
|
-
setConsentStatus("
|
|
515
|
+
setConsentStatus("Allow the local scan to continue.", "required");
|
|
445
516
|
return;
|
|
446
517
|
}
|
|
447
518
|
if (connected) { void waitForStats(); return; }
|