@echomem/mcp 1.4.27 → 1.4.29
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/codex-session-files.js +20 -0
- package/dist/forensics.js +1 -1
- package/dist/index.js +26 -10
- package/dist/migrate.js +24 -11
- package/dist/package-metadata.js +2 -0
- package/dist/setup-page/client-core.js +24 -24
- package/dist/setup-page/client-extraction.js +217 -76
- package/dist/setup-page/client-report-city.js +142 -8
- package/dist/setup-page/styles-extraction.js +37 -0
- package/dist/setup-page/styles-mvp.js +239 -7
- package/dist/setup-page/styles-website-alignment.js +8 -3
- package/dist/setup-preview.js +11 -0
- package/dist/setup.js +261 -35
- package/dist/v1-contract.js +10 -9
- package/package.json +1 -1
- package/templates/codex-skills/echomem-search/SKILL.md +3 -2
|
@@ -5,6 +5,21 @@ const ROLLOUT_FILE_RE = /^rollout-.*\.jsonl$/;
|
|
|
5
5
|
const UUID_RE = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i;
|
|
6
6
|
const FIRST_LINE_CHUNK_BYTES = 64 * 1024;
|
|
7
7
|
const MAX_FIRST_LINE_BYTES = 2 * 1024 * 1024;
|
|
8
|
+
function includesCompleteSessionMetadataLine(buffers) {
|
|
9
|
+
const text = Buffer.concat(buffers).toString("utf8");
|
|
10
|
+
const lines = text.split(/\r?\n/);
|
|
11
|
+
if (!text.endsWith("\n"))
|
|
12
|
+
lines.pop();
|
|
13
|
+
return lines.some((line) => {
|
|
14
|
+
try {
|
|
15
|
+
const row = JSON.parse(line);
|
|
16
|
+
return isRecord(row) && row.type === "session_meta" && isRecord(row.payload);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
8
23
|
function initialJsonLines(file) {
|
|
9
24
|
const fd = fs.openSync(file, "r");
|
|
10
25
|
try {
|
|
@@ -19,6 +34,11 @@ function initialJsonLines(file) {
|
|
|
19
34
|
const piece = Buffer.from(chunk.subarray(0, bytesRead));
|
|
20
35
|
buffers.push(piece);
|
|
21
36
|
total += bytesRead;
|
|
37
|
+
// Normal Codex rollouts put session metadata in the first block. Stop as soon as that
|
|
38
|
+
// complete row is available, while preserving the old 2MB fallback for unusual/legacy
|
|
39
|
+
// files whose metadata appears later.
|
|
40
|
+
if (includesCompleteSessionMetadataLine(buffers))
|
|
41
|
+
break;
|
|
22
42
|
}
|
|
23
43
|
if (!buffers.length)
|
|
24
44
|
return [];
|
package/dist/forensics.js
CHANGED
|
@@ -1391,7 +1391,7 @@ export async function buildForensicReport(opts) {
|
|
|
1391
1391
|
return band[0] + (band[1] - band[0]) * ratio;
|
|
1392
1392
|
};
|
|
1393
1393
|
const progress = (stage, detail, stageDone = done, stageTotal = total) => {
|
|
1394
|
-
opts?.onProgress?.(done, total, stage, detail, overallFor(stage, stageDone, stageTotal));
|
|
1394
|
+
opts?.onProgress?.(done, total, stage, detail, overallFor(stage, stageDone, stageTotal), stageDone, stageTotal);
|
|
1395
1395
|
};
|
|
1396
1396
|
progress("reading-transcripts", "finding local Codex and Claude transcript files");
|
|
1397
1397
|
const tick = (stage = "reading-transcripts") => {
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import { contextHealthMarkdown, recomposeCapsuleMarkdown } from "./hud/api.js";
|
|
|
12
12
|
import { createHash, randomUUID } from "node:crypto";
|
|
13
13
|
import { fetchEncryptionConfig, decryptMemoryFields, verifyKeyB64, } from "./encryption.js";
|
|
14
14
|
import { runCli } from "./setup.js";
|
|
15
|
-
import { MCP_PACKAGE_VERSION, MCP_SERVER_INSTRUCTIONS } from "./package-metadata.js";
|
|
15
|
+
import { MCP_PACKAGE_VERSION, MCP_SERVER_INSTRUCTIONS, MEMORY_CITATION_INSTRUCTION, } from "./package-metadata.js";
|
|
16
16
|
import { clearBillingAlert, writeBillingAlert } from "./billing-alert.js";
|
|
17
17
|
import { checkLatestUpdateStatus, formatUpdateNotice, formatUpdateStatusText, startBackgroundUpdateCheck, } from "./update-check.js";
|
|
18
18
|
const ECHO_API_BASE_URL = process.env.ECHO_API_BASE_URL || "https://echo-mem-chrome.vercel.app";
|
|
@@ -32,6 +32,9 @@ function memoryMarkdownLink(url, keys, description) {
|
|
|
32
32
|
.replace(/\]/g, "\\]");
|
|
33
33
|
return `[${label}](${url})`;
|
|
34
34
|
}
|
|
35
|
+
function withMemoryCitationInstruction(text) {
|
|
36
|
+
return `${text}\n\n${MEMORY_CITATION_INSTRUCTION}`;
|
|
37
|
+
}
|
|
35
38
|
/** Thrown when no API token is present yet — the model gets a "run login" nudge, not a hard error. */
|
|
36
39
|
class NoTokenError extends Error {
|
|
37
40
|
}
|
|
@@ -1446,7 +1449,12 @@ class EchoMemMCPServer {
|
|
|
1446
1449
|
].filter(Boolean).join("\n");
|
|
1447
1450
|
})
|
|
1448
1451
|
.join("\n\n");
|
|
1449
|
-
return {
|
|
1452
|
+
return {
|
|
1453
|
+
content: [{
|
|
1454
|
+
type: "text",
|
|
1455
|
+
text: withMemoryCitationInstruction(`Retrieved ${memories.length} memories:\n\n${formattedResults}`),
|
|
1456
|
+
}],
|
|
1457
|
+
};
|
|
1450
1458
|
}
|
|
1451
1459
|
// Fallback: untuned / time-range shape.
|
|
1452
1460
|
const { success, memories, error } = result;
|
|
@@ -1463,7 +1471,12 @@ Category: ${m.category} | Object: ${m.object} | Emotion: ${m.emotion}
|
|
|
1463
1471
|
Description: ${m.description}
|
|
1464
1472
|
Details: ${m.details || "N/A"}`)
|
|
1465
1473
|
.join("\n\n");
|
|
1466
|
-
return {
|
|
1474
|
+
return {
|
|
1475
|
+
content: [{
|
|
1476
|
+
type: "text",
|
|
1477
|
+
text: withMemoryCitationInstruction(`Found ${memories.length} relevant memories:\n\n${formattedResults}`),
|
|
1478
|
+
}],
|
|
1479
|
+
};
|
|
1467
1480
|
}
|
|
1468
1481
|
async handleSave(args, rec) {
|
|
1469
1482
|
const sourceFallback = this.getMcpClientAnalytics().platform_source;
|
|
@@ -1565,7 +1578,7 @@ Details: ${m.details || "N/A"}`)
|
|
|
1565
1578
|
content: [
|
|
1566
1579
|
{
|
|
1567
1580
|
type: "text",
|
|
1568
|
-
text: `Found ${memories.length} memories between ${parsed.startDate} and ${parsed.endDate}:\n\n${formattedResults}
|
|
1581
|
+
text: withMemoryCitationInstruction(`Found ${memories.length} memories between ${parsed.startDate} and ${parsed.endDate}:\n\n${formattedResults}`),
|
|
1569
1582
|
},
|
|
1570
1583
|
],
|
|
1571
1584
|
};
|
|
@@ -1591,7 +1604,7 @@ Details: ${m.details || "N/A"}`)
|
|
|
1591
1604
|
content: [
|
|
1592
1605
|
{
|
|
1593
1606
|
type: "text",
|
|
1594
|
-
text: `Recalled ${memories.length} memories from context ${parsed.contextId} (deterministic full batch):\n\n${formattedResults}
|
|
1607
|
+
text: withMemoryCitationInstruction(`Recalled ${memories.length} memories from context ${parsed.contextId} (deterministic full batch):\n\n${formattedResults}`),
|
|
1595
1608
|
},
|
|
1596
1609
|
],
|
|
1597
1610
|
};
|
|
@@ -1636,7 +1649,10 @@ Details: ${m.details || "N/A"}`)
|
|
|
1636
1649
|
const desc = readString(memory, "description") ?? "";
|
|
1637
1650
|
const details = compactOneLine(readString(memory, "details"), 260);
|
|
1638
1651
|
const id = readString(memory, "id");
|
|
1639
|
-
|
|
1652
|
+
const linkedTitle = id
|
|
1653
|
+
? memoryMarkdownLink(personalMemoryWebUrl(id), title, desc)
|
|
1654
|
+
: title;
|
|
1655
|
+
lines.push(`- ${linkedTitle}${id ? ` (${id})` : ""}: ${desc}${details ? ` — ${details}` : ""}`);
|
|
1640
1656
|
}
|
|
1641
1657
|
lines.push("");
|
|
1642
1658
|
}
|
|
@@ -1645,7 +1661,7 @@ Details: ${m.details || "N/A"}`)
|
|
|
1645
1661
|
content: [
|
|
1646
1662
|
{
|
|
1647
1663
|
type: "text",
|
|
1648
|
-
text: lines.join("\n"),
|
|
1664
|
+
text: withMemoryCitationInstruction(lines.join("\n")),
|
|
1649
1665
|
},
|
|
1650
1666
|
],
|
|
1651
1667
|
};
|
|
@@ -1672,7 +1688,7 @@ Details: ${m.details || "N/A"}`)
|
|
|
1672
1688
|
content: [
|
|
1673
1689
|
{
|
|
1674
1690
|
type: "text",
|
|
1675
|
-
text: `Found ${memories.length} memories matching keywords:\n\n${formattedResults}
|
|
1691
|
+
text: withMemoryCitationInstruction(`Found ${memories.length} memories matching keywords:\n\n${formattedResults}`),
|
|
1676
1692
|
},
|
|
1677
1693
|
],
|
|
1678
1694
|
};
|
|
@@ -1729,7 +1745,7 @@ Details: ${m.details || "N/A"}`;
|
|
|
1729
1745
|
content: [
|
|
1730
1746
|
{
|
|
1731
1747
|
type: "text",
|
|
1732
|
-
text: `Found ${memories.length} others' public memories${metadata ? ` (${metadata})` : ""}:\n\n${formattedResults}
|
|
1748
|
+
text: withMemoryCitationInstruction(`Found ${memories.length} others' public memories${metadata ? ` (${metadata})` : ""}:\n\n${formattedResults}`),
|
|
1733
1749
|
},
|
|
1734
1750
|
],
|
|
1735
1751
|
};
|
|
@@ -1857,7 +1873,7 @@ Details: ${m.details || "N/A"}`;
|
|
|
1857
1873
|
: "",
|
|
1858
1874
|
].filter(Boolean).join("\n");
|
|
1859
1875
|
return {
|
|
1860
|
-
content: [{ type: "text", text }],
|
|
1876
|
+
content: [{ type: "text", text: withMemoryCitationInstruction(text) }],
|
|
1861
1877
|
};
|
|
1862
1878
|
}
|
|
1863
1879
|
async handleGroupContext(args) {
|
package/dist/migrate.js
CHANGED
|
@@ -186,14 +186,17 @@ export function normalizeCwd(cwd) {
|
|
|
186
186
|
const m = cwd.match(/worktrees\/[^/]+\/(.+)$/);
|
|
187
187
|
return m ? m[1] : cwd;
|
|
188
188
|
}
|
|
189
|
-
function
|
|
189
|
+
function userCreatedCodexSessionFiles(codexRoot) {
|
|
190
190
|
return discoverCodexSessionFiles({
|
|
191
191
|
...(codexRoot
|
|
192
192
|
? { roots: [{ kind: "active", path: codexRoot, priority: 0 }] }
|
|
193
193
|
: {}),
|
|
194
194
|
includeArchived: false,
|
|
195
195
|
userInitiatedOnly: true,
|
|
196
|
-
}).files
|
|
196
|
+
}).files;
|
|
197
|
+
}
|
|
198
|
+
function userCreatedCodexFiles(codexRoot) {
|
|
199
|
+
return userCreatedCodexSessionFiles(codexRoot).map((file) => file.path);
|
|
197
200
|
}
|
|
198
201
|
function userCreatedClaudeFiles(claudeRoot) {
|
|
199
202
|
return walk(claudeRoot, (candidate) => candidate.endsWith(".jsonl"), (name) => name === "subagents" || name === "workflows").filter(isUserCreatedClaudeSessionFile);
|
|
@@ -312,20 +315,30 @@ function fastSessionInfo(file, source) {
|
|
|
312
315
|
}
|
|
313
316
|
function fastSessionEntries(opts = {}) {
|
|
314
317
|
const out = [];
|
|
315
|
-
for (const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
318
|
+
for (const file of userCreatedCodexSessionFiles(opts.codexRoot)) {
|
|
319
|
+
// Codex discovery has already classified the rollout as user-created and resolved its stable
|
|
320
|
+
// session key. Do not reread another 1MB of transcript just to rediscover the same ID.
|
|
321
|
+
const stableId = file.sessionKey.startsWith("session:")
|
|
322
|
+
? file.sessionKey.slice("session:".length)
|
|
323
|
+
: null;
|
|
324
|
+
const fallback = stableId ? null : fastSessionInfo(file.path, "codex");
|
|
325
|
+
out.push({
|
|
326
|
+
filePath: file.path,
|
|
327
|
+
source: "codex",
|
|
328
|
+
conversationKey: stableId ? `codex:${stableId}` : fallback?.conversationKey || `codex:${sha16(file.path)}`,
|
|
329
|
+
size: file.size,
|
|
330
|
+
mtimeMs: Math.round(file.mtimeMs),
|
|
331
|
+
});
|
|
323
332
|
}
|
|
324
333
|
const claudeRoot = opts.claudeRoot ?? resolveClaudeProjectsDir();
|
|
325
334
|
if (claudeRoot) {
|
|
326
335
|
for (const filePath of userCreatedClaudeFiles(claudeRoot)) {
|
|
327
336
|
const stat = statSafe(filePath);
|
|
328
|
-
const
|
|
337
|
+
const filenameId = path.basename(filePath, path.extname(filePath));
|
|
338
|
+
const filenameHasStableId = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(filenameId);
|
|
339
|
+
const info = filenameHasStableId
|
|
340
|
+
? { conversationKey: `claude-code:${filenameId.toLowerCase()}`, hasTextTurn: true, hasRealKey: true }
|
|
341
|
+
: fastSessionInfo(filePath, "claude-code");
|
|
329
342
|
// Same rule as codex: include on text OR a real session id so large sessions aren't undercounted,
|
|
330
343
|
// while keeping the key stable (claude-code sessionId appears on every line, so hasRealKey is reliable).
|
|
331
344
|
if (info.hasTextTurn || info.hasRealKey)
|
package/dist/package-metadata.js
CHANGED
|
@@ -23,6 +23,7 @@ export const MCP_PACKAGE_DESCRIPTION = stringOrFallback(packageJson.description,
|
|
|
23
23
|
export const MCP_PACKAGE_LABEL = `${MCP_PACKAGE_NAME}@${MCP_PACKAGE_VERSION}`;
|
|
24
24
|
export const MCP_UPDATE_COMMAND = `npx -y ${MCP_PACKAGE_NAME}@latest update`;
|
|
25
25
|
export const MCP_UPDATE_ALL_COMMAND = `${MCP_UPDATE_COMMAND} --all`;
|
|
26
|
+
export const MEMORY_CITATION_INSTRUCTION = 'If the user-facing answer materially relies on one or more EchoMem memories, end it with a compact "EchoMem sources:" list containing only the memories actually used. Use each memory key as the Markdown label and its canonical https://echoknows.com/memory/<memory-id> URL. Do not cite memories that were merely retrieved, and omit the section when no memory informed the answer.';
|
|
26
27
|
export const MCP_SERVER_INSTRUCTIONS = [
|
|
27
28
|
`${MCP_PACKAGE_DESCRIPTION} (${MCP_PACKAGE_LABEL}).`,
|
|
28
29
|
`If the user or local config expects a newer EchoMem MCP version than ${MCP_PACKAGE_VERSION}, update once with \`${MCP_UPDATE_ALL_COMMAND}\` and start a new MCP session.`,
|
|
@@ -34,6 +35,7 @@ export const MCP_SERVER_INSTRUCTIONS = [
|
|
|
34
35
|
"After joining or when profile fields are missing, use candidate memory evidence to propose a title and responsibility summary. Ask the user to confirm that proposal together with the publication preview, then call update_group_profile and complete_group_publication.",
|
|
35
36
|
"Use one canonical https://echoknows.com/memory/<memory-id> link for private, group, and friend evidence. Label it with the memory key; the site resolves the authorized representation.",
|
|
36
37
|
"Each search result is one memory: preserve its Memory ID and canonical echoknows.com link when citing it.",
|
|
38
|
+
MEMORY_CITATION_INSTRUCTION,
|
|
37
39
|
"During a publication preview, if an unflagged candidate appears sensitive, proactively ask whether the user wants to mark its exact ID for publication attention first. Explain that marking does not publish or change encryption; it means the agent will call it out and ask for detailed confirmation whenever a later publication includes it. Never auto-flag inferred sensitivity. For sensitive-topic flags, search and preview exact owned memories before confirmed flag_memories_for_publication_attention. Separate already-flagged candidates, state that nothing has been published yet, and offer to exclude them, review them separately, or first search for and mark similar sensitive owned memories.",
|
|
38
40
|
"Never save an inferred group profile. Manual prepared publication requires explicit preview confirmation; flagged memories still require separate exact-memory confirmation. Never store or log an echo_grp_ invite code.",
|
|
39
41
|
].join(" ");
|
|
@@ -48,8 +48,18 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
48
48
|
var localAuthTermsAccepted = false;
|
|
49
49
|
var localAuthAgeConfirmed = false;
|
|
50
50
|
var accountSwitchPending = false;
|
|
51
|
+
// Invalidates async work started for a previously connected account.
|
|
52
|
+
var accountStateEpoch = 0;
|
|
51
53
|
var statsPollStarted = false;
|
|
52
54
|
var reportPollStarted = false;
|
|
55
|
+
var reportEtaDeadline = 0;
|
|
56
|
+
var reportEtaDisplaySeconds = 0;
|
|
57
|
+
var reportEtaUpdatedAt = 0;
|
|
58
|
+
var reportEtaStage = "";
|
|
59
|
+
var reportEtaStageDone = 0;
|
|
60
|
+
var reportEtaStageElapsed = 0;
|
|
61
|
+
var reportEtaObservedRate = 0;
|
|
62
|
+
var reportEtaScanId = "";
|
|
53
63
|
var readyStage = "";
|
|
54
64
|
var NIGHT_HOURS = { 22: 1, 23: 1, 0: 1, 1: 1, 2: 1, 3: 1 };
|
|
55
65
|
|
|
@@ -292,14 +302,13 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
292
302
|
setExtractMode(false);
|
|
293
303
|
setReadyMode(true);
|
|
294
304
|
setHead("EchoMem connected", "Done");
|
|
295
|
-
app.className = "
|
|
305
|
+
app.className = "localAuthCompleteStage";
|
|
296
306
|
app.innerHTML =
|
|
297
|
-
'<section class="
|
|
298
|
-
'<
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
'</div>' +
|
|
307
|
+
'<section class="localAuthComplete" aria-labelledby="localAuthCompleteTitle">' +
|
|
308
|
+
'<img src="/hud-assets/echo-face-cutout.png" alt="" />' +
|
|
309
|
+
'<h2 id="localAuthCompleteTitle">You\'re signed in.</h2>' +
|
|
310
|
+
'<p>Return to Terminal.</p>' +
|
|
311
|
+
'<p class="localAuthCompleteNext">Run <code>echomem-mcp init</code> when you\'re ready to start onboarding.</p>' +
|
|
303
312
|
'</section>';
|
|
304
313
|
}
|
|
305
314
|
async function finishLocalLogin() {
|
|
@@ -453,8 +462,8 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
453
462
|
(setupMode ? '<label class="localAuthField"><span>Confirm passphrase</span><input id="localAuthPassphraseConfirm" type="password" autocomplete="new-password" placeholder="Confirm passphrase" /></label>' : '') +
|
|
454
463
|
'<button class="primary localAuthPassphraseSubmit" id="localAuthUnlock" type="submit">' + (setupMode ? "Create vault" : "Unlock") + '</button>' +
|
|
455
464
|
(setupMode
|
|
456
|
-
? '<p class="localAuthVaultDeferNote">
|
|
457
|
-
'<button class="localAuthTextAction" id="localAuthSkipVault" type="button">Skip for now</button>'
|
|
465
|
+
? '<p class="localAuthVaultDeferNote">You can turn on encryption later.</p>' +
|
|
466
|
+
'<button class="localAuthTextAction" id="localAuthSkipVault" type="button">Skip encryption for now</button>'
|
|
458
467
|
: '') +
|
|
459
468
|
'<button class="localAuthTextAction localAuthRestartAction" id="localAuthRestart" type="button">Use a different email</button>' +
|
|
460
469
|
'<p class="localAuthStatus' + (message ? " is-error" : "") + '" id="localAuthStatus" role="status" aria-live="polite">' + esc(message || "") + '</p>' +
|
|
@@ -471,23 +480,14 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
471
480
|
}
|
|
472
481
|
async function skipLocalVaultSetup() {
|
|
473
482
|
var button = document.getElementById("localAuthSkipVault");
|
|
474
|
-
if (button) { button.disabled = true; button.textContent = "
|
|
475
|
-
setLocalAuthStatus("
|
|
483
|
+
if (button) { button.disabled = true; button.textContent = "Continuing…"; }
|
|
484
|
+
setLocalAuthStatus("Continuing without encryption…");
|
|
476
485
|
try {
|
|
477
|
-
await postJson("/skip", {});
|
|
478
|
-
|
|
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 (_) {}
|
|
486
|
+
await postJson("/local-auth/skip-encryption", {});
|
|
487
|
+
await finishLocalLogin();
|
|
488
488
|
} catch (error) {
|
|
489
|
-
if (button) { button.disabled = false; button.textContent = "Skip for now"; }
|
|
490
|
-
setLocalAuthStatus(error && error.message ? error.message : "Could not
|
|
489
|
+
if (button) { button.disabled = false; button.textContent = "Skip encryption for now"; }
|
|
490
|
+
setLocalAuthStatus(error && error.message ? error.message : "Could not continue without encryption.", "error");
|
|
491
491
|
}
|
|
492
492
|
}
|
|
493
493
|
async function submitLocalPassphrase(setupMode) {
|