@echomem/mcp 1.4.27 → 1.4.28
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/migrate.js +24 -11
- package/dist/setup-page/client-core.js +18 -17
- package/dist/setup-page/client-extraction.js +207 -74
- 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 +189 -7
- package/dist/setup-page/styles-website-alignment.js +8 -3
- package/dist/setup.js +260 -35
- package/package.json +1 -1
|
@@ -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/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)
|
|
@@ -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
|
|
|
@@ -453,8 +463,8 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
453
463
|
(setupMode ? '<label class="localAuthField"><span>Confirm passphrase</span><input id="localAuthPassphraseConfirm" type="password" autocomplete="new-password" placeholder="Confirm passphrase" /></label>' : '') +
|
|
454
464
|
'<button class="primary localAuthPassphraseSubmit" id="localAuthUnlock" type="submit">' + (setupMode ? "Create vault" : "Unlock") + '</button>' +
|
|
455
465
|
(setupMode
|
|
456
|
-
? '<p class="localAuthVaultDeferNote">
|
|
457
|
-
'<button class="localAuthTextAction" id="localAuthSkipVault" type="button">Skip for now</button>'
|
|
466
|
+
? '<p class="localAuthVaultDeferNote">You can turn on encryption later.</p>' +
|
|
467
|
+
'<button class="localAuthTextAction" id="localAuthSkipVault" type="button">Skip encryption for now</button>'
|
|
458
468
|
: '') +
|
|
459
469
|
'<button class="localAuthTextAction localAuthRestartAction" id="localAuthRestart" type="button">Use a different email</button>' +
|
|
460
470
|
'<p class="localAuthStatus' + (message ? " is-error" : "") + '" id="localAuthStatus" role="status" aria-live="polite">' + esc(message || "") + '</p>' +
|
|
@@ -471,23 +481,14 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
471
481
|
}
|
|
472
482
|
async function skipLocalVaultSetup() {
|
|
473
483
|
var button = document.getElementById("localAuthSkipVault");
|
|
474
|
-
if (button) { button.disabled = true; button.textContent = "
|
|
475
|
-
setLocalAuthStatus("
|
|
484
|
+
if (button) { button.disabled = true; button.textContent = "Continuing…"; }
|
|
485
|
+
setLocalAuthStatus("Continuing without encryption…");
|
|
476
486
|
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 (_) {}
|
|
487
|
+
await postJson("/local-auth/skip-encryption", {});
|
|
488
|
+
await finishLocalLogin();
|
|
488
489
|
} catch (error) {
|
|
489
|
-
if (button) { button.disabled = false; button.textContent = "Skip for now"; }
|
|
490
|
-
setLocalAuthStatus(error && error.message ? error.message : "Could not
|
|
490
|
+
if (button) { button.disabled = false; button.textContent = "Skip encryption for now"; }
|
|
491
|
+
setLocalAuthStatus(error && error.message ? error.message : "Could not continue without encryption.", "error");
|
|
491
492
|
}
|
|
492
493
|
}
|
|
493
494
|
async function submitLocalPassphrase(setupMode) {
|