@echomem/mcp 1.4.44 → 1.4.46
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/README.md +28 -25
- package/dist/city/README.md +9 -0
- package/dist/city/echo-ai-city-only.html +2232 -0
- package/dist/city/echo-extraction-plate.html +330 -0
- package/dist/city/echo-face-cutout.png +0 -0
- package/dist/city/personality_stickers/bossy.png +0 -0
- package/dist/city/personality_stickers/ghosty.png +0 -0
- package/dist/city/personality_stickers/loopy.png +0 -0
- package/dist/city/personality_stickers/lusty.png +0 -0
- package/dist/city/personality_stickers/maxxy.png +0 -0
- package/dist/city/personality_stickers/tabby.png +0 -0
- package/dist/city/vendor/OrbitControls.js +1417 -0
- package/dist/city/vendor/RoundedBoxGeometry.js +155 -0
- package/dist/city/vendor/echo_general-file-21.riv +0 -0
- package/dist/city/vendor/rive.js +8139 -0
- package/dist/city/vendor/rive.wasm +0 -0
- package/dist/city/vendor/three.module.min.js +6 -0
- package/dist/context-analysis/claude-native-canonical.js +2 -2
- package/dist/context-analysis/vendored-canonical.js +2 -2
- package/dist/context-analysis/workspace-report.js +3 -3
- package/dist/forensics.js +1531 -0
- package/dist/hud/hooks.js +31 -43
- package/dist/index.js +79 -15
- package/dist/local-data-paths.js +38 -0
- package/dist/migrate.js +140 -70
- package/dist/report.js +721 -0
- package/dist/save-checkpoint-hook.js +1 -1
- package/dist/setup-page/client-core.js +372 -18
- package/dist/setup-page/client-extraction.js +204 -37
- package/dist/setup-page/client-lifecycle.js +101 -31
- package/dist/setup-page/client-report-audit.js +819 -0
- package/dist/setup-page/client-report-city.js +356 -0
- package/dist/setup-page/client-report.js +6 -0
- package/dist/setup-page/client.js +2 -0
- package/dist/setup-page/styles-city-report.js +880 -0
- package/dist/setup-page/styles-context-audit.js +470 -0
- package/dist/setup-page/styles-extraction.js +31 -1
- package/dist/setup-page/styles-foundation.js +89 -0
- package/dist/setup-page/styles-mvp.js +155 -10
- package/dist/setup-page/styles-website-alignment.js +204 -0
- package/dist/setup-page/styles.js +4 -0
- package/dist/setup-page.js +4 -4
- package/dist/setup-preview.js +212 -4
- package/dist/setup.js +793 -300
- package/dist/source-session.js +3 -9
- package/dist/v1-contract.js +8 -0
- package/package.json +7 -9
- package/dist/config-files.js +0 -63
- package/dist/local-jsonl.js +0 -87
- package/dist/onboarding-stats.js +0 -16
package/dist/migrate.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `echomem-mcp migrate` — one-shot bulk back-fill of the user's EXISTING local coding-agent history
|
|
3
|
-
* (Codex + Claude Code) into their Echo cloud. The onboarding
|
|
3
|
+
* (Codex + Claude Code + local Claude Desktop Cowork) into their Echo cloud. The onboarding
|
|
4
|
+
* "see it instantly know my work" moment.
|
|
4
5
|
*
|
|
5
6
|
* Why this lives in the bridge (client-side): the session logs exist ONLY on the user's machine
|
|
6
|
-
* (~/.codex/sessions, ~/.claude/projects). The bridge
|
|
7
|
+
* (~/.codex/sessions, ~/.claude/projects, Claude Desktop local-agent-mode-sessions). The bridge
|
|
8
|
+
* discovers each session, assembles it into a
|
|
7
9
|
* text-turn-only `## ` transcript, and feeds it to the shared durable import queue
|
|
8
10
|
* through MCP-owned policy entrypoints. POST /api/extension/mcp/historical-import-sessions
|
|
9
11
|
* creates one import_jobs row per session, then the bridge POSTs each transcript to
|
|
@@ -27,8 +29,8 @@ import axios from "axios";
|
|
|
27
29
|
import { KeyStore, echoConfigDir } from "./keystore.js";
|
|
28
30
|
import { fetchEncryptionConfig } from "./encryption.js";
|
|
29
31
|
import { discoverCodexSessionFiles } from "./codex-session-files.js";
|
|
30
|
-
import { resolveClaudeProjectsDir } from "./local-data-paths.js";
|
|
31
|
-
import {
|
|
32
|
+
import { resolveClaudeCoworkSessionsDir, resolveClaudeProjectsDir } from "./local-data-paths.js";
|
|
33
|
+
import { walk, eachLine } from "./report.js";
|
|
32
34
|
const API_BASE = (process.env.ECHO_API_BASE_URL || "https://echo-mem-chrome.vercel.app").replace(/\/$/, "");
|
|
33
35
|
const RATE_MAX = 28; // stay under the import-jobs /run limit of 30 / 60s
|
|
34
36
|
const RATE_WINDOW_MS = 60_000;
|
|
@@ -56,8 +58,7 @@ export function assembleCodex(file) {
|
|
|
56
58
|
let firstTs = null;
|
|
57
59
|
let title = "";
|
|
58
60
|
const turns = [];
|
|
59
|
-
|
|
60
|
-
const o = value;
|
|
61
|
+
eachLine(file, (o) => {
|
|
61
62
|
if (o && o.type === "session_meta") {
|
|
62
63
|
sessionId = (o.payload && typeof o.payload.id === "string" && o.payload.id) || sessionId;
|
|
63
64
|
cwd = (o.payload && typeof o.payload.cwd === "string" && o.payload.cwd) || cwd;
|
|
@@ -103,14 +104,13 @@ export function assembleCodex(file) {
|
|
|
103
104
|
};
|
|
104
105
|
}
|
|
105
106
|
/** Claude Code <uuid>.jsonl → one session. User/assistant text turns are sent; tool calls/results/thinking stay local. */
|
|
106
|
-
export function assembleClaude(file) {
|
|
107
|
+
export function assembleClaude(file, opts = {}) {
|
|
107
108
|
let sessionId = null;
|
|
108
109
|
let cwd = null;
|
|
109
110
|
let firstTs = null;
|
|
110
111
|
let title = "";
|
|
111
112
|
const turns = [];
|
|
112
|
-
|
|
113
|
-
const o = value;
|
|
113
|
+
eachLine(file, (o) => {
|
|
114
114
|
if (!cwd && typeof o.cwd === "string")
|
|
115
115
|
cwd = o.cwd;
|
|
116
116
|
if (!sessionId && typeof o.sessionId === "string")
|
|
@@ -159,10 +159,12 @@ export function assembleClaude(file) {
|
|
|
159
159
|
if (!turns.length)
|
|
160
160
|
return null;
|
|
161
161
|
const stat = statSafe(file);
|
|
162
|
+
const source = opts.source ?? "claude-code";
|
|
163
|
+
const conversationId = opts.conversationId ?? sessionId ?? sha16(file);
|
|
162
164
|
return {
|
|
163
165
|
filePath: file,
|
|
164
|
-
source
|
|
165
|
-
conversationKey:
|
|
166
|
+
source,
|
|
167
|
+
conversationKey: `${source}:${conversationId}`,
|
|
166
168
|
cwd: normalizeCwd(cwd),
|
|
167
169
|
firstTs,
|
|
168
170
|
title: title || "Claude text turns",
|
|
@@ -201,7 +203,46 @@ function userCreatedCodexFiles(codexRoot) {
|
|
|
201
203
|
return userCreatedCodexSessionFiles(codexRoot).map((file) => file.path);
|
|
202
204
|
}
|
|
203
205
|
function userCreatedClaudeFiles(claudeRoot) {
|
|
204
|
-
return
|
|
206
|
+
return walk(claudeRoot, (candidate) => candidate.endsWith(".jsonl"), (name) => name === "subagents" || name === "workflows").filter(isUserCreatedClaudeSessionFile);
|
|
207
|
+
}
|
|
208
|
+
function coworkConversationId(filePath, root) {
|
|
209
|
+
const relative = path.relative(root, filePath);
|
|
210
|
+
if (!relative || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))
|
|
211
|
+
return null;
|
|
212
|
+
const parts = relative.split(path.sep);
|
|
213
|
+
for (let index = 0; index + 2 < parts.length; index += 1) {
|
|
214
|
+
if (/^local_[A-Za-z0-9_-]{1,194}$/.test(parts[index] || "")
|
|
215
|
+
&& parts[index + 1] === ".claude"
|
|
216
|
+
&& parts[index + 2] === "projects") {
|
|
217
|
+
return parts[index] || null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
function claudeSessionCandidates(opts = {}) {
|
|
223
|
+
const candidates = [];
|
|
224
|
+
const claudeRoot = opts.claudeRoot ?? resolveClaudeProjectsDir();
|
|
225
|
+
if (claudeRoot) {
|
|
226
|
+
for (const filePath of userCreatedClaudeFiles(claudeRoot)) {
|
|
227
|
+
candidates.push({ filePath, source: "claude-code" });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// Supplying an explicit Claude root is an isolated discovery request (tests, diagnostics, or a
|
|
231
|
+
// moved profile). Callers can still add Cowork roots explicitly. Normal onboarding supplies no
|
|
232
|
+
// roots, so the platform-default Cowork directory is included automatically.
|
|
233
|
+
const defaultCoworkRoot = opts.claudeRoot === undefined
|
|
234
|
+
? resolveClaudeCoworkSessionsDir()
|
|
235
|
+
: null;
|
|
236
|
+
const coworkRoots = opts.coworkRoots ?? (defaultCoworkRoot ? [defaultCoworkRoot] : []);
|
|
237
|
+
for (const root of coworkRoots) {
|
|
238
|
+
for (const filePath of userCreatedClaudeFiles(root)) {
|
|
239
|
+
const conversationId = coworkConversationId(filePath, root);
|
|
240
|
+
if (!conversationId)
|
|
241
|
+
continue;
|
|
242
|
+
candidates.push({ filePath, source: "claude-desktop", conversationId });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return candidates;
|
|
205
246
|
}
|
|
206
247
|
/** Discover every user-created local session, newest first (by first-turn timestamp). */
|
|
207
248
|
export function discoverSessions(opts = {}) {
|
|
@@ -211,17 +252,31 @@ export function discoverSessions(opts = {}) {
|
|
|
211
252
|
if (s)
|
|
212
253
|
out.push(s);
|
|
213
254
|
}
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (s)
|
|
219
|
-
out.push(s);
|
|
220
|
-
}
|
|
255
|
+
for (const candidate of claudeSessionCandidates(opts)) {
|
|
256
|
+
const s = assembleClaude(candidate.filePath, candidate);
|
|
257
|
+
if (s)
|
|
258
|
+
out.push(s);
|
|
221
259
|
}
|
|
222
260
|
out.sort((a, b) => String(b.firstTs || "").localeCompare(String(a.firstTs || "")));
|
|
223
261
|
return out;
|
|
224
262
|
}
|
|
263
|
+
function sourceCounts(sessions, pending) {
|
|
264
|
+
const codexCount = sessions.filter((session) => session.source === "codex").length;
|
|
265
|
+
const claudeCodeCount = sessions.filter((session) => session.source === "claude-code").length;
|
|
266
|
+
const coworkCount = sessions.filter((session) => session.source === "claude-desktop").length;
|
|
267
|
+
const pendingCodex = pending.filter((session) => session.source === "codex").length;
|
|
268
|
+
const pendingClaudeCode = pending.filter((session) => session.source === "claude-code").length;
|
|
269
|
+
const pendingCowork = pending.filter((session) => session.source === "claude-desktop").length;
|
|
270
|
+
return {
|
|
271
|
+
codexCount,
|
|
272
|
+
claudeCount: claudeCodeCount + coworkCount,
|
|
273
|
+
claudeCodeCount,
|
|
274
|
+
coworkCount,
|
|
275
|
+
pendingCodex,
|
|
276
|
+
pendingClaudeCode,
|
|
277
|
+
pendingCowork,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
225
280
|
const IMPORT_STATUS_CHUNK_SIZE = 1000;
|
|
226
281
|
function initialJsonObjects(file, maxBytes = 1024 * 1024) {
|
|
227
282
|
let fd;
|
|
@@ -281,7 +336,7 @@ function isUserCreatedClaudeSessionFile(file) {
|
|
|
281
336
|
});
|
|
282
337
|
}
|
|
283
338
|
function fastSessionInfo(file, source) {
|
|
284
|
-
let conversationKey = `${source
|
|
339
|
+
let conversationKey = `${source}:${sha16(file)}`;
|
|
285
340
|
let hasTextTurn = false;
|
|
286
341
|
let hasRealKey = false;
|
|
287
342
|
for (const obj of initialJsonObjects(file)) {
|
|
@@ -303,11 +358,11 @@ function fastSessionInfo(file, source) {
|
|
|
303
358
|
hasTextTurn = true;
|
|
304
359
|
continue;
|
|
305
360
|
}
|
|
306
|
-
if (
|
|
307
|
-
conversationKey =
|
|
361
|
+
if (typeof obj.sessionId === "string" && obj.sessionId) {
|
|
362
|
+
conversationKey = `${source}:${obj.sessionId}`;
|
|
308
363
|
hasRealKey = true;
|
|
309
364
|
}
|
|
310
|
-
if (
|
|
365
|
+
if (obj.type === "user" || obj.type === "assistant") {
|
|
311
366
|
const message = isRecord(obj.message) ? obj.message : {};
|
|
312
367
|
if (hasClaudeText(message.content))
|
|
313
368
|
hasTextTurn = true;
|
|
@@ -332,19 +387,25 @@ function fastSessionEntries(opts = {}) {
|
|
|
332
387
|
mtimeMs: Math.round(file.mtimeMs),
|
|
333
388
|
});
|
|
334
389
|
}
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
390
|
+
for (const candidate of claudeSessionCandidates(opts)) {
|
|
391
|
+
const stat = statSafe(candidate.filePath);
|
|
392
|
+
const filenameId = path.basename(candidate.filePath, path.extname(candidate.filePath));
|
|
393
|
+
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);
|
|
394
|
+
const stableId = candidate.conversationId
|
|
395
|
+
?? (filenameHasStableId ? filenameId.toLowerCase() : null);
|
|
396
|
+
const info = stableId
|
|
397
|
+
? { conversationKey: `${candidate.source}:${stableId}`, hasTextTurn: true, hasRealKey: true }
|
|
398
|
+
: fastSessionInfo(candidate.filePath, candidate.source);
|
|
399
|
+
// Same rule as codex: include on text OR a real session id so large sessions aren't undercounted,
|
|
400
|
+
// while keeping the key stable. Cowork keys use the outer local_<id>, not the nested CLI UUID.
|
|
401
|
+
if (info.hasTextTurn || info.hasRealKey) {
|
|
402
|
+
out.push({
|
|
403
|
+
filePath: candidate.filePath,
|
|
404
|
+
source: candidate.source,
|
|
405
|
+
conversationKey: info.conversationKey,
|
|
406
|
+
size: stat.size,
|
|
407
|
+
mtimeMs: stat.mtimeMs,
|
|
408
|
+
});
|
|
348
409
|
}
|
|
349
410
|
}
|
|
350
411
|
return out;
|
|
@@ -508,7 +569,7 @@ function printMigrationEstimate(e, useJson) {
|
|
|
508
569
|
console.log("");
|
|
509
570
|
console.log("Migration estimate (local metadata only; no transcripts uploaded)");
|
|
510
571
|
console.log(` Sessions: ${humanNum(e.sessions)} total · ${humanNum(e.pending)} pending · ${humanNum(e.alreadyMigrated)} already processed`);
|
|
511
|
-
console.log(` Sources: ${humanNum(e.pendingCodex)} Codex + ${humanNum(e.pendingClaudeCode)} Claude Code pending`);
|
|
572
|
+
console.log(` Sources: ${humanNum(e.pendingCodex)} Codex + ${humanNum(e.pendingClaudeCode)} Claude Code + ${humanNum(e.pendingCowork)} Cowork pending`);
|
|
512
573
|
console.log(` Assembled transcript size: ${humanNum(e.chars.total)} chars ≈ ${humanNum(e.approxInputTokens.total)} input tokens`);
|
|
513
574
|
console.log(` Size percentiles: p50 ${humanNum(e.chars.p50)} chars · p90 ${humanNum(e.chars.p90)} · p95 ${humanNum(e.chars.p95)} · max ${humanNum(e.chars.max)}`);
|
|
514
575
|
console.log(` Turns: ${humanNum(e.turns.total)} total · p50 ${humanNum(e.turns.p50)} · p90 ${humanNum(e.turns.p90)} · max ${humanNum(e.turns.max)}`);
|
|
@@ -617,7 +678,7 @@ function percentile(nums, p) {
|
|
|
617
678
|
export function estimateMigration(sessions, pending) {
|
|
618
679
|
const chars = pending.map((s) => s.rawData.length);
|
|
619
680
|
const turns = pending.map((s) => s.turnCount);
|
|
620
|
-
const
|
|
681
|
+
const counts = sourceCounts(sessions, pending);
|
|
621
682
|
const bucketDefs = [
|
|
622
683
|
{ label: "small <=10k chars", min: 0, max: 10_000 },
|
|
623
684
|
{ label: "routine 10k-30k", min: 10_000, max: 30_000 },
|
|
@@ -642,8 +703,10 @@ export function estimateMigration(sessions, pending) {
|
|
|
642
703
|
alreadyMigrated: sessions.length - pending.length,
|
|
643
704
|
codex: sessions.filter((s) => s.source === "codex").length,
|
|
644
705
|
claudeCode: sessions.filter((s) => s.source === "claude-code").length,
|
|
645
|
-
|
|
646
|
-
|
|
706
|
+
cowork: counts.coworkCount,
|
|
707
|
+
pendingCodex: counts.pendingCodex,
|
|
708
|
+
pendingClaudeCode: counts.pendingClaudeCode,
|
|
709
|
+
pendingCowork: counts.pendingCowork,
|
|
647
710
|
chars: {
|
|
648
711
|
total: sum(chars),
|
|
649
712
|
p50: percentile(chars, 0.5),
|
|
@@ -725,7 +788,7 @@ export function estimateMigrationEtaFromLengths(lengths, skippedActive = 0, opts
|
|
|
725
788
|
};
|
|
726
789
|
}
|
|
727
790
|
export function summarizeFastMigratableDiscovery(discovery) {
|
|
728
|
-
const
|
|
791
|
+
const counts = sourceCounts(discovery.sessions, discovery.pending);
|
|
729
792
|
return {
|
|
730
793
|
sessions: discovery.sessions.length,
|
|
731
794
|
pending: discovery.pending.length,
|
|
@@ -734,8 +797,11 @@ export function summarizeFastMigratableDiscovery(discovery) {
|
|
|
734
797
|
skippedActive: discovery.skippedActive,
|
|
735
798
|
codexCount: discovery.codexCount,
|
|
736
799
|
claudeCount: discovery.claudeCount,
|
|
737
|
-
|
|
738
|
-
|
|
800
|
+
claudeCodeCount: discovery.claudeCodeCount ?? counts.claudeCodeCount,
|
|
801
|
+
coworkCount: discovery.coworkCount ?? counts.coworkCount,
|
|
802
|
+
pendingCodex: discovery.pendingCodex ?? counts.pendingCodex,
|
|
803
|
+
pendingClaudeCode: discovery.pendingClaudeCode ?? counts.pendingClaudeCode,
|
|
804
|
+
pendingCowork: discovery.pendingCowork ?? counts.pendingCowork,
|
|
739
805
|
eta: estimateMigrationEtaFromLengths(discovery.pending.map((s) => s.size), discovery.skippedActive, {
|
|
740
806
|
secondsPerSession: measuredSecondsPerSession() ?? undefined,
|
|
741
807
|
}),
|
|
@@ -773,8 +839,7 @@ export function discoverMigratableFastDiscovery(opts = {}) {
|
|
|
773
839
|
return !e || e.size !== s.size;
|
|
774
840
|
});
|
|
775
841
|
const pending = opts.limit && opts.limit > 0 ? pendingAll.slice(0, opts.limit) : pendingAll;
|
|
776
|
-
const
|
|
777
|
-
const pendingCodex = pending.filter((s) => s.source === "codex").length;
|
|
842
|
+
const counts = sourceCounts(sessions, pending);
|
|
778
843
|
return {
|
|
779
844
|
sessions,
|
|
780
845
|
pending,
|
|
@@ -782,10 +847,7 @@ export function discoverMigratableFastDiscovery(opts = {}) {
|
|
|
782
847
|
alreadyMigrated: selectable.length - pendingAll.length,
|
|
783
848
|
skippedActive,
|
|
784
849
|
limited: pending.length < pendingAll.length,
|
|
785
|
-
|
|
786
|
-
claudeCount: sessions.length - codexCount,
|
|
787
|
-
pendingCodex,
|
|
788
|
-
pendingClaudeCode: pending.length - pendingCodex,
|
|
850
|
+
...counts,
|
|
789
851
|
};
|
|
790
852
|
}
|
|
791
853
|
export function discoverMigratableSummaryFast(opts = {}) {
|
|
@@ -843,8 +905,7 @@ export function discoverMigratableSessions(opts = {}) {
|
|
|
843
905
|
return !e || e.size !== s.size;
|
|
844
906
|
});
|
|
845
907
|
const pending = opts.limit && opts.limit > 0 ? pendingAll.slice(0, opts.limit) : pendingAll;
|
|
846
|
-
const
|
|
847
|
-
const pendingCodex = pending.filter((s) => s.source === "codex").length;
|
|
908
|
+
const counts = sourceCounts(sessions, pending);
|
|
848
909
|
return {
|
|
849
910
|
sessions,
|
|
850
911
|
pending,
|
|
@@ -852,10 +913,7 @@ export function discoverMigratableSessions(opts = {}) {
|
|
|
852
913
|
alreadyMigrated: selectableSessions.length - pendingAll.length,
|
|
853
914
|
skippedActive,
|
|
854
915
|
limited: pending.length < pendingAll.length,
|
|
855
|
-
|
|
856
|
-
claudeCount: sessions.length - codexCount,
|
|
857
|
-
pendingCodex,
|
|
858
|
-
pendingClaudeCode: pending.length - pendingCodex,
|
|
916
|
+
...counts,
|
|
859
917
|
};
|
|
860
918
|
}
|
|
861
919
|
/**
|
|
@@ -873,11 +931,16 @@ export function discoverPendingSessionsTargeted(processedKeys, opts = {}) {
|
|
|
873
931
|
const filtered = processedKeys ? applyFastAccountImportStatus(fast, processedKeys, opts) : fast;
|
|
874
932
|
const pending = [];
|
|
875
933
|
for (const entry of filtered.pending) {
|
|
876
|
-
const s = entry.source === "codex"
|
|
934
|
+
const s = entry.source === "codex"
|
|
935
|
+
? assembleCodex(entry.filePath)
|
|
936
|
+
: assembleClaude(entry.filePath, {
|
|
937
|
+
source: entry.source,
|
|
938
|
+
conversationId: bareId(entry),
|
|
939
|
+
});
|
|
877
940
|
if (s)
|
|
878
941
|
pending.push(s);
|
|
879
942
|
}
|
|
880
|
-
const
|
|
943
|
+
const pendingCounts = sourceCounts(pending, pending);
|
|
881
944
|
return {
|
|
882
945
|
sessions: pending,
|
|
883
946
|
pending,
|
|
@@ -887,8 +950,11 @@ export function discoverPendingSessionsTargeted(processedKeys, opts = {}) {
|
|
|
887
950
|
limited: filtered.limited,
|
|
888
951
|
codexCount: filtered.codexCount,
|
|
889
952
|
claudeCount: filtered.claudeCount,
|
|
890
|
-
|
|
891
|
-
|
|
953
|
+
claudeCodeCount: filtered.claudeCodeCount,
|
|
954
|
+
coworkCount: filtered.coworkCount,
|
|
955
|
+
pendingCodex: pendingCounts.pendingCodex,
|
|
956
|
+
pendingClaudeCode: pendingCounts.pendingClaudeCode,
|
|
957
|
+
pendingCowork: pendingCounts.pendingCowork,
|
|
892
958
|
accountChecked: filtered.accountChecked,
|
|
893
959
|
accountCheckFailed: filtered.accountCheckFailed,
|
|
894
960
|
accountCheckUnavailable: filtered.accountCheckUnavailable,
|
|
@@ -901,7 +967,7 @@ export function applyAccountImportStatus(discovery, processedKeys, opts = {}) {
|
|
|
901
967
|
const skippedActive = discovery.sessions.length - selectableSessions.length;
|
|
902
968
|
const pendingAll = selectableSessions.filter((s) => !processedKeys.has(s.conversationKey));
|
|
903
969
|
const pending = opts.limit && opts.limit > 0 ? pendingAll.slice(0, opts.limit) : pendingAll;
|
|
904
|
-
const
|
|
970
|
+
const counts = sourceCounts(discovery.sessions, pending);
|
|
905
971
|
return {
|
|
906
972
|
...discovery,
|
|
907
973
|
pending,
|
|
@@ -909,8 +975,9 @@ export function applyAccountImportStatus(discovery, processedKeys, opts = {}) {
|
|
|
909
975
|
alreadyMigrated: selectableSessions.length - pendingAll.length,
|
|
910
976
|
skippedActive,
|
|
911
977
|
limited: pending.length < pendingAll.length,
|
|
912
|
-
pendingCodex,
|
|
913
|
-
pendingClaudeCode:
|
|
978
|
+
pendingCodex: counts.pendingCodex,
|
|
979
|
+
pendingClaudeCode: counts.pendingClaudeCode,
|
|
980
|
+
pendingCowork: counts.pendingCowork,
|
|
914
981
|
accountChecked: true,
|
|
915
982
|
accountCheckFailed: false,
|
|
916
983
|
accountCheckUnavailable: false,
|
|
@@ -923,7 +990,7 @@ export function applyFastAccountImportStatus(discovery, processedKeys, opts = {}
|
|
|
923
990
|
const skippedActive = discovery.sessions.length - selectableSessions.length;
|
|
924
991
|
const pendingAll = selectableSessions.filter((s) => !processedKeys.has(s.conversationKey));
|
|
925
992
|
const pending = opts.limit && opts.limit > 0 ? pendingAll.slice(0, opts.limit) : pendingAll;
|
|
926
|
-
const
|
|
993
|
+
const counts = sourceCounts(discovery.sessions, pending);
|
|
927
994
|
return {
|
|
928
995
|
...discovery,
|
|
929
996
|
pending,
|
|
@@ -931,8 +998,9 @@ export function applyFastAccountImportStatus(discovery, processedKeys, opts = {}
|
|
|
931
998
|
alreadyMigrated: selectableSessions.length - pendingAll.length,
|
|
932
999
|
skippedActive,
|
|
933
1000
|
limited: pending.length < pendingAll.length,
|
|
934
|
-
pendingCodex,
|
|
935
|
-
pendingClaudeCode:
|
|
1001
|
+
pendingCodex: counts.pendingCodex,
|
|
1002
|
+
pendingClaudeCode: counts.pendingClaudeCode,
|
|
1003
|
+
pendingCowork: counts.pendingCowork,
|
|
936
1004
|
accountChecked: true,
|
|
937
1005
|
accountCheckFailed: false,
|
|
938
1006
|
accountCheckUnavailable: false,
|
|
@@ -1238,10 +1306,10 @@ export async function cmdMigrate(flags) {
|
|
|
1238
1306
|
}
|
|
1239
1307
|
}
|
|
1240
1308
|
}
|
|
1241
|
-
const { sessions, pending, pendingTotal, alreadyMigrated, skippedActive, limited, codexCount,
|
|
1309
|
+
const { sessions, pending, pendingTotal, alreadyMigrated, skippedActive, limited, codexCount, claudeCodeCount, coworkCount, accountChecked, accountCheckFailed, accountCheckUnavailable } = discovery;
|
|
1242
1310
|
const estimateSessions = includeActive ? sessions : sessions.filter((s) => !isActiveMigrationSession(s));
|
|
1243
1311
|
if (!sessions.length) {
|
|
1244
|
-
console.log("No local Codex
|
|
1312
|
+
console.log("No local Codex, Claude Code, or Cowork sessions found to migrate.");
|
|
1245
1313
|
return;
|
|
1246
1314
|
}
|
|
1247
1315
|
if (flags.estimate === true && flags.json === true) {
|
|
@@ -1254,7 +1322,7 @@ export async function cmdMigrate(flags) {
|
|
|
1254
1322
|
const pendingText = limited
|
|
1255
1323
|
? `${c.bold(String(pending.length))} of ${c.bold(String(pendingTotal))} ${pendingName} selected`
|
|
1256
1324
|
: `${c.bold(String(pending.length))} ${pendingName} to import`;
|
|
1257
|
-
console.log(`Found ${c.bold(String(sessions.length))} sessions (${codexCount} Codex + ${
|
|
1325
|
+
console.log(`Found ${c.bold(String(sessions.length))} sessions (${codexCount} Codex + ${claudeCodeCount} Claude Code + ${coworkCount} Cowork) · ${pendingText}` +
|
|
1258
1326
|
(alreadyMigrated ? c.dim(`, ${alreadyMigrated} already migrated`) : "") +
|
|
1259
1327
|
(skippedActive ? c.dim(`, ${skippedActive} active skipped`) : ""));
|
|
1260
1328
|
if (accountChecked)
|
|
@@ -1279,7 +1347,8 @@ export async function cmdMigrate(flags) {
|
|
|
1279
1347
|
if (flags["dry-run"] === true) {
|
|
1280
1348
|
console.log(c.dim("\n--dry-run: discovered + assembled only, nothing sent.\n"));
|
|
1281
1349
|
for (const s of pending.slice(0, 50)) {
|
|
1282
|
-
|
|
1350
|
+
const sourceLabel = s.source === "codex" ? "codex " : s.source === "claude-desktop" ? "cowork" : "claude";
|
|
1351
|
+
console.log(` ${sourceLabel} ${(s.firstTs || "").slice(0, 10)} ${humanNum(s.rawData.length)} chars ${s.turnCount} text turns`);
|
|
1283
1352
|
}
|
|
1284
1353
|
if (pending.length > 50)
|
|
1285
1354
|
console.log(c.dim(` … and ${pending.length - 50} more`));
|
|
@@ -1304,7 +1373,8 @@ export async function cmdMigrate(flags) {
|
|
|
1304
1373
|
metricsFile,
|
|
1305
1374
|
selection,
|
|
1306
1375
|
onProgress: (ev) => {
|
|
1307
|
-
const
|
|
1376
|
+
const sourceLabel = ev.session.source === "codex" ? "codex " : ev.session.source === "claude-desktop" ? "cowork" : "claude";
|
|
1377
|
+
const tag = `${c.dim(`[${ev.index}/${ev.total}]`)} ${sourceLabel} ${(ev.session.firstTs || "").slice(0, 10)}`;
|
|
1308
1378
|
if (ev.error) {
|
|
1309
1379
|
console.log(`${tag} ${c.red("✗ failed")} ${c.dim(truncate(ev.error, 60))}` + (ev.durationMs ? c.dim(` ${humanDuration(ev.durationMs)}`) : ""));
|
|
1310
1380
|
return;
|