agentel 0.2.8 → 0.3.0
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 +222 -68
- package/docs/code-reference.md +121 -37
- package/docs/history-source-handling.md +555 -124
- package/docs/release.md +35 -8
- package/npm-shrinkwrap.json +478 -0
- package/package.json +18 -5
- package/scripts/postinstall.js +156 -0
- package/src/archive.js +1174 -65
- package/src/canonical-events.js +346 -35
- package/src/cli.js +7121 -819
- package/src/collector.js +42 -4
- package/src/config.js +15 -4
- package/src/diffs.js +156 -0
- package/src/doctor.js +48 -5
- package/src/importers/claude.js +51 -4
- package/src/importers/copilot.js +385 -0
- package/src/importers/cursor-recovery.js +22 -0
- package/src/importers/factory.js +396 -0
- package/src/importers/gemini.js +39 -0
- package/src/importers/grok.js +367 -0
- package/src/importers/pi.js +422 -0
- package/src/importers/providers.js +64 -5
- package/src/importers.js +4524 -383
- package/src/mcp.js +1 -0
- package/src/memory-sources.js +671 -0
- package/src/memory-store.js +0 -0
- package/src/parser-versions.js +13 -0
- package/src/pricing.js +84 -0
- package/src/search.js +256 -70
- package/src/session-store.js +405 -0
- package/src/source-watch.js +293 -0
- package/src/sources.js +60 -11
- package/src/supervisor.js +197 -9
- package/src/sync.js +6 -0
- package/src/unavailable-sources.js +358 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const os = require("os");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const { isWebChatProvider, listSessions } = require("./archive");
|
|
7
|
+
const { ensureDir, readJson } = require("./paths");
|
|
8
|
+
const { HISTORY_PROVIDER_OPTIONS, IMPORT_SOURCE_ORDER } = require("./sources");
|
|
9
|
+
|
|
10
|
+
const CLAUDE_CODE_REPAIR_COMMAND = "agentlog repair claude-code-backups";
|
|
11
|
+
const CLAUDE_CODE_REPAIR_PREVIEW_COMMAND = `${CLAUDE_CODE_REPAIR_COMMAND} --dry-run`;
|
|
12
|
+
|
|
13
|
+
function preservedArchiveSession(session) {
|
|
14
|
+
return preservedWebChatSession(session) || sessionHasUnavailableSource(session);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function preservedWebChatSession(session) {
|
|
18
|
+
if (isWebChatProvider(session?.provider, session?.sourceType)) return true;
|
|
19
|
+
return /^\[(chatgpt|claude)\]conversations\//i.test(String(session?.scopeCanonical || ""));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function unavailableSourceSessions(env = process.env) {
|
|
23
|
+
return listSessions(env).filter((session) => !preservedWebChatSession(session) && sessionHasUnavailableSource(session));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function sessionHasUnavailableSource(session) {
|
|
27
|
+
return unavailableSourcePaths(session).length > 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function unavailableSourcePaths(session) {
|
|
31
|
+
return requiredSourcePaths(session).filter((sourcePath) => !safeSourceStat(sourcePath));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function requiredSourcePaths(session) {
|
|
35
|
+
const values = [filesystemSourcePath(session?.sourcePath)];
|
|
36
|
+
if (sessionMayNeedAuxiliaryTranscripts(session)) {
|
|
37
|
+
for (const record of sessionRawRecords(session)) {
|
|
38
|
+
const sourcePath = filesystemSourcePath(record?.originalPath || record?.sourcePath);
|
|
39
|
+
if (auxiliaryTranscriptSourcePath(sourcePath)) values.push(sourcePath);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return [...new Set(values.filter(Boolean).map((value) => path.resolve(value)))];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function sessionMayNeedAuxiliaryTranscripts(session) {
|
|
46
|
+
const provider = String(session?.provider || "");
|
|
47
|
+
const sourceType = String(session?.sourceType || "");
|
|
48
|
+
return provider === "claude_code" || provider === "claude_desktop" || provider === "claude_sdk" || sourceType.startsWith("claude-");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function auxiliaryTranscriptSourcePath(sourcePath) {
|
|
52
|
+
if (!sourcePath) return false;
|
|
53
|
+
return /(?:^|[\\/])audit\.jsonl$/i.test(sourcePath) || /\.jsonl(?:\.zst)?$/i.test(sourcePath);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function filesystemSourcePath(value) {
|
|
57
|
+
const text = String(value || "").trim();
|
|
58
|
+
if (!text) return "";
|
|
59
|
+
const hashIndex = text.indexOf("#");
|
|
60
|
+
const base = hashIndex > 0 ? text.slice(0, hashIndex) : text;
|
|
61
|
+
if (!base) return "";
|
|
62
|
+
if (path.isAbsolute(base)) return path.resolve(base);
|
|
63
|
+
if (path.win32.isAbsolute(base)) return base;
|
|
64
|
+
return "";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function sessionRawRecords(session) {
|
|
68
|
+
const records = Array.isArray(session?.rawFiles) ? [...session.rawFiles] : [];
|
|
69
|
+
if (session?.rawPath) {
|
|
70
|
+
const manifest = readJson(path.join(session.rawPath, "manifest.json"), null);
|
|
71
|
+
for (const record of Array.isArray(manifest?.files) ? manifest.files : []) {
|
|
72
|
+
records.push(record);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return records;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function summarizeUnavailableSourceSessions(sessions, options = {}) {
|
|
79
|
+
const selected = (Array.isArray(sessions) ? sessions : []).filter((session) =>
|
|
80
|
+
!preservedWebChatSession(session) && (options.includeAvailable || sessionHasUnavailableSource(session))
|
|
81
|
+
);
|
|
82
|
+
const breakdown = unavailableSourceBreakdown(selected);
|
|
83
|
+
const totalMissingSources = breakdown.reduce((sum, row) => sum + row.missingSources, 0);
|
|
84
|
+
const claudeCodeRepair = summarizeClaudeCodeBackupRepair(selected, options.env || process.env);
|
|
85
|
+
return {
|
|
86
|
+
totalSessions: selected.length,
|
|
87
|
+
totalMissingSources,
|
|
88
|
+
breakdown,
|
|
89
|
+
claudeCodeRepair
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function unavailableSourceBreakdown(sessions) {
|
|
94
|
+
const byKey = new Map();
|
|
95
|
+
for (const session of sessions || []) {
|
|
96
|
+
const source = sourceForSession(session);
|
|
97
|
+
const provider = String(session?.provider || "unknown");
|
|
98
|
+
const sourceType = String(session?.sourceType || "unknown");
|
|
99
|
+
const key = `${source}\0${provider}\0${sourceType}`;
|
|
100
|
+
const existing = byKey.get(key) || {
|
|
101
|
+
source,
|
|
102
|
+
provider,
|
|
103
|
+
sourceType,
|
|
104
|
+
sessions: 0,
|
|
105
|
+
missingSources: 0
|
|
106
|
+
};
|
|
107
|
+
existing.sessions++;
|
|
108
|
+
existing.missingSources += unavailableSourcePaths(session).length;
|
|
109
|
+
byKey.set(key, existing);
|
|
110
|
+
}
|
|
111
|
+
return [...byKey.values()].sort(compareSourceBreakdownRows);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function sourceForSession(session) {
|
|
115
|
+
const provider = String(session?.provider || "");
|
|
116
|
+
const sourceType = String(session?.sourceType || "");
|
|
117
|
+
const exact = SOURCE_BY_PROVIDER_AND_TYPE.get(`${provider}\0${sourceType}`);
|
|
118
|
+
if (exact) return exact;
|
|
119
|
+
const sourceOption = HISTORY_PROVIDER_OPTIONS.find((item) => item.provider === provider && item.sourceType === sourceType);
|
|
120
|
+
if (sourceOption?.source) return sourceOption.source;
|
|
121
|
+
return SOURCE_BY_PROVIDER.get(provider) || sourceType || provider || "unknown";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function compareSourceBreakdownRows(a, b) {
|
|
125
|
+
const left = sourceOrder(a.source);
|
|
126
|
+
const right = sourceOrder(b.source);
|
|
127
|
+
return left - right
|
|
128
|
+
|| a.source.localeCompare(b.source)
|
|
129
|
+
|| a.provider.localeCompare(b.provider)
|
|
130
|
+
|| a.sourceType.localeCompare(b.sourceType);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function sourceOrder(source) {
|
|
134
|
+
const index = IMPORT_SOURCE_ORDER.indexOf(source);
|
|
135
|
+
return index === -1 ? 999 : index;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function summarizeClaudeCodeBackupRepair(sessions, env = process.env) {
|
|
139
|
+
const plan = planClaudeCodeBackupRepair(env, { sessions, dryRun: true });
|
|
140
|
+
return {
|
|
141
|
+
candidateSessions: plan.candidateSessions,
|
|
142
|
+
restoreSessions: plan.restoreSessions,
|
|
143
|
+
restoreCount: plan.restoreCount,
|
|
144
|
+
existingCount: plan.existingCount,
|
|
145
|
+
missingBackupCount: plan.missingBackupCount,
|
|
146
|
+
command: CLAUDE_CODE_REPAIR_COMMAND,
|
|
147
|
+
previewCommand: CLAUDE_CODE_REPAIR_PREVIEW_COMMAND
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function planClaudeCodeBackupRepair(env = process.env, options = {}) {
|
|
152
|
+
const roots = claudeCodeSourceRoots(env);
|
|
153
|
+
const sessions = (Array.isArray(options.sessions) ? options.sessions : unavailableSourceSessions(env))
|
|
154
|
+
.filter(claudeCodeBackupRepairSession);
|
|
155
|
+
const seenTargets = new Set();
|
|
156
|
+
const actions = [];
|
|
157
|
+
const skipped = [];
|
|
158
|
+
for (const session of sessions) {
|
|
159
|
+
let sessionActionCount = 0;
|
|
160
|
+
for (const record of sessionRawRecords(session)) {
|
|
161
|
+
const targetPath = filesystemSourcePath(record?.originalPath || record?.sourcePath);
|
|
162
|
+
if (!targetPath || !isInsideAnyRoot(targetPath, roots)) continue;
|
|
163
|
+
const targetKey = path.resolve(targetPath);
|
|
164
|
+
if (seenTargets.has(targetKey)) {
|
|
165
|
+
skipped.push(skipRecord(session, record, targetPath, "duplicate-target"));
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
seenTargets.add(targetKey);
|
|
169
|
+
const backupPath = backupPathForRawRecord(record);
|
|
170
|
+
if (!backupPath || !safeSourceStat(backupPath)?.isFile()) {
|
|
171
|
+
skipped.push(skipRecord(session, record, targetPath, "missing-backup"));
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const targetStat = safeSourceStat(targetPath);
|
|
175
|
+
if (targetStat && !options.overwrite) {
|
|
176
|
+
skipped.push(skipRecord(session, record, targetPath, "target-exists", backupPath));
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
actions.push({
|
|
180
|
+
sessionId: session.sessionId || "",
|
|
181
|
+
title: session.title || "",
|
|
182
|
+
sourceType: session.sourceType || "",
|
|
183
|
+
provider: session.provider || "",
|
|
184
|
+
targetPath,
|
|
185
|
+
backupPath,
|
|
186
|
+
size: Number(record?.size || 0) || safeSourceStat(backupPath)?.size || 0,
|
|
187
|
+
mtime: record?.mtime || ""
|
|
188
|
+
});
|
|
189
|
+
sessionActionCount++;
|
|
190
|
+
}
|
|
191
|
+
if (!sessionActionCount) {
|
|
192
|
+
const sourcePath = filesystemSourcePath(session?.sourcePath);
|
|
193
|
+
if (sourcePath && isInsideAnyRoot(sourcePath, roots)) {
|
|
194
|
+
skipped.push({
|
|
195
|
+
sessionId: session.sessionId || "",
|
|
196
|
+
targetPath: sourcePath,
|
|
197
|
+
reason: "no-restorable-raw-backup"
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const restoreSessionIds = new Set(actions.map((item) => item.sessionId).filter(Boolean));
|
|
203
|
+
return {
|
|
204
|
+
dryRun: Boolean(options.dryRun),
|
|
205
|
+
overwrite: Boolean(options.overwrite),
|
|
206
|
+
sourceRoots: roots,
|
|
207
|
+
candidateSessions: sessions.length,
|
|
208
|
+
restoreSessions: restoreSessionIds.size,
|
|
209
|
+
restoreCount: actions.length,
|
|
210
|
+
existingCount: skipped.filter((item) => item.reason === "target-exists").length,
|
|
211
|
+
missingBackupCount: skipped.filter((item) => item.reason === "missing-backup" || item.reason === "no-restorable-raw-backup").length,
|
|
212
|
+
duplicateCount: skipped.filter((item) => item.reason === "duplicate-target").length,
|
|
213
|
+
actions,
|
|
214
|
+
skipped
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function repairClaudeCodeBackups(env = process.env, options = {}) {
|
|
219
|
+
const plan = planClaudeCodeBackupRepair(env, options);
|
|
220
|
+
if (options.dryRun) return { ...plan, restored: 0 };
|
|
221
|
+
let restored = 0;
|
|
222
|
+
for (const action of plan.actions) {
|
|
223
|
+
if (!isInsideAnyRoot(action.targetPath, plan.sourceRoots)) continue;
|
|
224
|
+
if (safeSourceStat(action.targetPath) && !options.overwrite) continue;
|
|
225
|
+
ensureDir(path.dirname(action.targetPath));
|
|
226
|
+
fs.copyFileSync(action.backupPath, action.targetPath);
|
|
227
|
+
try {
|
|
228
|
+
fs.chmodSync(action.targetPath, 0o600);
|
|
229
|
+
} catch {
|
|
230
|
+
// Best-effort permissions normalization.
|
|
231
|
+
}
|
|
232
|
+
if (action.mtime) {
|
|
233
|
+
const mtime = new Date(action.mtime);
|
|
234
|
+
if (Number.isFinite(mtime.getTime())) {
|
|
235
|
+
try {
|
|
236
|
+
fs.utimesSync(action.targetPath, mtime, mtime);
|
|
237
|
+
} catch {
|
|
238
|
+
// Best-effort timestamp preservation.
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
restored++;
|
|
243
|
+
}
|
|
244
|
+
return { ...plan, restored };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function claudeCodeBackupRepairSession(session) {
|
|
248
|
+
return String(session?.provider || "") === "claude_code";
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function claudeCodeSourceRoots(env = process.env) {
|
|
252
|
+
const home = env && env.HOME ? env.HOME : os.homedir();
|
|
253
|
+
return [
|
|
254
|
+
path.join(home, ".claude", "projects"),
|
|
255
|
+
path.join(home, ".claude", "file-history")
|
|
256
|
+
];
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function backupPathForRawRecord(record) {
|
|
260
|
+
const candidates = [record?.archivedPath, record?.sharedRawPath]
|
|
261
|
+
.map(filesystemSourcePath)
|
|
262
|
+
.filter(Boolean);
|
|
263
|
+
return candidates.find((candidate) => safeSourceStat(candidate)?.isFile()) || "";
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function skipRecord(session, record, targetPath, reason, backupPath = "") {
|
|
267
|
+
return {
|
|
268
|
+
sessionId: session?.sessionId || "",
|
|
269
|
+
title: session?.title || "",
|
|
270
|
+
sourceType: session?.sourceType || "",
|
|
271
|
+
provider: session?.provider || "",
|
|
272
|
+
targetPath,
|
|
273
|
+
backupPath: backupPath || backupPathForRawRecord(record),
|
|
274
|
+
reason
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function isInsideAnyRoot(value, roots) {
|
|
279
|
+
return roots.some((root) => isSameOrInside(value, root));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function isSameOrInside(value, root) {
|
|
283
|
+
const relative = path.relative(path.resolve(root), path.resolve(value));
|
|
284
|
+
if (relative === "") return true;
|
|
285
|
+
// Entry names may legitimately start with ".." (e.g. "..foo"), so only an
|
|
286
|
+
// exact ".." segment marks the path as outside the root.
|
|
287
|
+
return relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function safeSourceStat(target) {
|
|
291
|
+
try {
|
|
292
|
+
return fs.statSync(target);
|
|
293
|
+
} catch (error) {
|
|
294
|
+
if (["ENOENT", "ENOTDIR", "EACCES", "EPERM"].includes(error.code)) return null;
|
|
295
|
+
throw error;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const SOURCE_BY_PROVIDER_AND_TYPE = new Map([
|
|
300
|
+
["codex\0cli-history", "codex-cli"],
|
|
301
|
+
["claude_code\0cli-history", "claude"],
|
|
302
|
+
["claude_sdk\0claude-sdk-history", "claude-sdk"],
|
|
303
|
+
["claude_desktop\0claude-code-desktop-metadata", "claude-code-desktop"],
|
|
304
|
+
["claude_desktop\0claude-workspace-desktop", "claude-cowork"],
|
|
305
|
+
["cursor\0cursor-workspace-sqlite", "cursor"],
|
|
306
|
+
["cursor\0cursor-global-sqlite", "cursor"],
|
|
307
|
+
["cursor\0cursor-raw-sqlite-salvage", "cursor"],
|
|
308
|
+
["cursor\0cursor-agent-transcripts", "cursor"],
|
|
309
|
+
["opencode\0opencode-history", "opencode-cli"],
|
|
310
|
+
["opencode\0opencode-sqlite-history", "opencode-cli"],
|
|
311
|
+
["opencode\0opencode-cli-sqlite-history", "opencode-cli"],
|
|
312
|
+
["opencode\0opencode-desktop-sqlite-history", "opencode-desktop"],
|
|
313
|
+
["opencode\0opencode-web-sqlite-history", "opencode-web"],
|
|
314
|
+
["antigravity_cli\0antigravity-cli-brain", "antigravity-cli"],
|
|
315
|
+
["antigravity_ide\0antigravity-ide-brain", "antigravity-ide"],
|
|
316
|
+
...HISTORY_PROVIDER_OPTIONS
|
|
317
|
+
.filter((item) => item.provider && item.sourceType)
|
|
318
|
+
.map((item) => [`${item.provider}\0${item.sourceType}`, item.source])
|
|
319
|
+
]);
|
|
320
|
+
|
|
321
|
+
const SOURCE_BY_PROVIDER = {
|
|
322
|
+
aider: "aider",
|
|
323
|
+
antigravity: "antigravity",
|
|
324
|
+
antigravity_cli: "antigravity-cli",
|
|
325
|
+
antigravity_ide: "antigravity-ide",
|
|
326
|
+
chatgpt: "chatgpt",
|
|
327
|
+
claude_code: "claude",
|
|
328
|
+
claude_sdk: "claude-sdk",
|
|
329
|
+
claude_web: "claude-web",
|
|
330
|
+
cline: "cline",
|
|
331
|
+
copilot: "copilot-cli",
|
|
332
|
+
cursor: "cursor",
|
|
333
|
+
devin: "devin-cli",
|
|
334
|
+
factory: "factory",
|
|
335
|
+
gemini_cli: "gemini-cli",
|
|
336
|
+
grok: "grok-build",
|
|
337
|
+
pi: "pi",
|
|
338
|
+
windsurf: "windsurf"
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
module.exports = {
|
|
342
|
+
CLAUDE_CODE_REPAIR_COMMAND,
|
|
343
|
+
CLAUDE_CODE_REPAIR_PREVIEW_COMMAND,
|
|
344
|
+
claudeCodeSourceRoots,
|
|
345
|
+
filesystemSourcePath,
|
|
346
|
+
planClaudeCodeBackupRepair,
|
|
347
|
+
preservedArchiveSession,
|
|
348
|
+
preservedWebChatSession,
|
|
349
|
+
repairClaudeCodeBackups,
|
|
350
|
+
requiredSourcePaths,
|
|
351
|
+
sessionHasUnavailableSource,
|
|
352
|
+
sessionRawRecords,
|
|
353
|
+
sourceForSession,
|
|
354
|
+
summarizeUnavailableSourceSessions,
|
|
355
|
+
unavailableSourceBreakdown,
|
|
356
|
+
unavailableSourcePaths,
|
|
357
|
+
unavailableSourceSessions
|
|
358
|
+
};
|