agentel 0.2.6 → 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 +260 -79
- package/docs/code-reference.md +130 -42
- package/docs/history-source-handling.md +685 -153
- package/docs/release.md +35 -8
- package/npm-shrinkwrap.json +478 -0
- package/package.json +20 -4
- package/scripts/postinstall.js +156 -0
- package/src/archive.js +1342 -50
- package/src/canonical-events.js +346 -35
- package/src/cli.js +8835 -843
- package/src/collector.js +42 -4
- package/src/config.js +26 -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 +41 -1
- 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 +6429 -747
- 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 +641 -215
- 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
- package/src/web-export-instructions.js +6 -4
|
@@ -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
|
+
};
|
|
@@ -7,17 +7,19 @@ const WEB_EXPORT_INSTRUCTIONS = {
|
|
|
7
7
|
label: "ChatGPT",
|
|
8
8
|
requestUrl: "https://privacy.openai.com/policies/en/",
|
|
9
9
|
helpUrl: "https://help.openai.com/en/articles/7260999-how-do-i-export-my-chatgpt-history-and-data",
|
|
10
|
-
fileDescription: "official
|
|
11
|
-
importCommand: "agentlog import chatgpt
|
|
10
|
+
fileDescription: "official OpenAI export ZIP, then unzip it to the OpenAI-export/User Online Activity folder",
|
|
11
|
+
importCommand: "agentlog import chatgpt",
|
|
12
12
|
steps: [
|
|
13
13
|
"Open the OpenAI Privacy Portal, choose Make a Privacy Request, select I have a consumer ChatGPT account, then select Download my data.",
|
|
14
14
|
"Or in ChatGPT, open your profile menu, choose Settings, open Data Controls, then use Export Data.",
|
|
15
15
|
"Complete account verification and wait for the export email.",
|
|
16
|
-
"Download the ZIP before the email link expires
|
|
16
|
+
"Download the ZIP before the email link expires.",
|
|
17
|
+
"For privacy-portal exports named OpenAI-export, unzip the outer download and pass the parent User Online Activity folder so split Conversations__...part-0001/0002 ZIPs or folders, manifests, and attached files are imported together."
|
|
17
18
|
],
|
|
18
19
|
notes: [
|
|
19
20
|
"OpenAI says ChatGPT Business and Enterprise chat exports are not available through the ChatGPT account export flow.",
|
|
20
|
-
"OpenAI export emails can take time to arrive, and the download link expires after 24 hours."
|
|
21
|
+
"OpenAI export emails can take time to arrive, and the download link expires after 24 hours.",
|
|
22
|
+
"If your export has multiple Conversations__...chatgpt...part ZIPs or folders, import their parent folder rather than one part at a time."
|
|
21
23
|
]
|
|
22
24
|
},
|
|
23
25
|
"claude-web": {
|