@remnic/import-claude 0.1.0 → 9.3.517
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/LICENSE +21 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/package.json +21 -13
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Joshua Warren
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.js
CHANGED
|
@@ -49,6 +49,10 @@ function parseClaudeExport(input, options = {}) {
|
|
|
49
49
|
if (raw && typeof raw === "object") {
|
|
50
50
|
const obj = raw;
|
|
51
51
|
let sawKnownSection = false;
|
|
52
|
+
const hasConversationsSection = Object.prototype.hasOwnProperty.call(
|
|
53
|
+
obj,
|
|
54
|
+
"conversations"
|
|
55
|
+
);
|
|
52
56
|
const convs = obj.conversations;
|
|
53
57
|
if (Array.isArray(convs)) {
|
|
54
58
|
sawKnownSection = true;
|
|
@@ -59,7 +63,13 @@ function parseClaudeExport(input, options = {}) {
|
|
|
59
63
|
throw new Error("Non-conversation entry in conversations array");
|
|
60
64
|
}
|
|
61
65
|
}
|
|
66
|
+
} else if (hasConversationsSection && options.strict) {
|
|
67
|
+
throw new Error("Claude export conversations section must be an array.");
|
|
62
68
|
}
|
|
69
|
+
const hasProjectsSection = Object.prototype.hasOwnProperty.call(
|
|
70
|
+
obj,
|
|
71
|
+
"projects"
|
|
72
|
+
);
|
|
63
73
|
const projects = obj.projects;
|
|
64
74
|
if (Array.isArray(projects)) {
|
|
65
75
|
sawKnownSection = true;
|
|
@@ -70,6 +80,8 @@ function parseClaudeExport(input, options = {}) {
|
|
|
70
80
|
throw new Error("Non-project entry in projects array");
|
|
71
81
|
}
|
|
72
82
|
}
|
|
83
|
+
} else if (hasProjectsSection && options.strict) {
|
|
84
|
+
throw new Error("Claude export projects section must be an array.");
|
|
73
85
|
}
|
|
74
86
|
if (!sawKnownSection && options.strict) {
|
|
75
87
|
throw new Error(
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapter.ts","../src/parser.ts","../src/transform.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Claude importer adapter (issue #568 slice 3)\n// ---------------------------------------------------------------------------\n\nimport type {\n ImportedMemory,\n ImporterAdapter,\n ImporterParseOptions,\n ImporterTransformOptions,\n ImporterWriteResult,\n ImporterWriteTarget,\n} from \"@remnic/core\";\nimport { defaultWriteMemoriesToOrchestrator } from \"@remnic/core\";\n\nimport {\n parseClaudeExport,\n type ParsedClaudeExport,\n} from \"./parser.js\";\nimport { CLAUDE_SOURCE_LABEL, transformClaudeExport } from \"./transform.js\";\n\n/**\n * Canonical `ImporterAdapter` exposed by `@remnic/import-claude`.\n *\n * Loaded by `remnic-cli/optional-importer.ts` via a computed-specifier dynamic\n * import. The CLI drives `adapter.parse` → `adapter.transform` →\n * `adapter.writeTo` through the shared `runImporter` helper in `@remnic/core`.\n */\nexport const adapter: ImporterAdapter<ParsedClaudeExport> = {\n name: \"claude\",\n sourceLabel: CLAUDE_SOURCE_LABEL,\n\n parse(input: unknown, options?: ImporterParseOptions): ParsedClaudeExport {\n return parseClaudeExport(input, {\n ...(options?.strict !== undefined ? { strict: options.strict } : {}),\n ...(options?.filePath !== undefined ? { filePath: options.filePath } : {}),\n });\n },\n\n transform(\n parsed: ParsedClaudeExport,\n options?: ImporterTransformOptions,\n ): ImportedMemory[] {\n return transformClaudeExport(parsed, {\n includeConversations: options?.includeConversations === true,\n ...(options?.maxMemories !== undefined\n ? { maxMemories: options.maxMemories }\n : {}),\n });\n },\n\n async writeTo(\n target: ImporterWriteTarget,\n memories: ImportedMemory[],\n ): Promise<ImporterWriteResult> {\n return defaultWriteMemoriesToOrchestrator(target, memories);\n },\n};\n\n/** Alias kept for symmetry with other @remnic/import-* packages. */\nexport const claudeAdapter = adapter;\n","// ---------------------------------------------------------------------------\n// Claude.ai data-export parser (issue #568 slice 3)\n// ---------------------------------------------------------------------------\n//\n// Claude.ai's \"Export data\" feature produces a ZIP containing several JSON\n// files. The two relevant to memory import are:\n//\n// 1. `conversations.json` — array of Conversation objects. Each has a\n// `chat_messages` array with `sender` (human/assistant) and `text` (plus\n// `content` blocks in newer exports). Text content is plain, not the\n// graph shape ChatGPT uses.\n// 2. `projects.json` — array of Project objects, each with an optional\n// `prompt_template` and a list of `docs` (the durable context documents\n// the user attached to the project). These are high-signal personal\n// artifacts and are imported as one memory per doc / project.\n//\n// The parser accepts either file individually (JSON text or object) or a\n// combined bundle object (`{ conversations: [...], projects: [...] }`) used\n// by the future bundle-auto-detect flow (PR 7).\n//\n// We do NOT read ZIP archives here — the CLI reads the file contents and\n// passes them in. Synthetic fixtures in `fixtures/` mirror the real shapes.\n\n// ---------------------------------------------------------------------------\n// Raw export shapes (subset we care about)\n// ---------------------------------------------------------------------------\n\n/**\n * Message inside a Claude conversation. `sender` is either \"human\" or\n * \"assistant\"; older exports used `role` instead.\n */\nexport interface ClaudeConversationMessage {\n uuid?: string;\n sender?: \"human\" | \"assistant\" | string;\n role?: \"human\" | \"assistant\" | string;\n /** Plain-text transcript of this message. */\n text?: string;\n /** Newer exports expose structured content blocks. */\n content?: Array<{ type?: string; text?: string }>;\n created_at?: string;\n updated_at?: string;\n}\n\nexport interface ClaudeConversation {\n uuid?: string;\n name?: string;\n summary?: string;\n created_at?: string;\n updated_at?: string;\n chat_messages?: ClaudeConversationMessage[];\n /** Some exports also expose `messages` alongside chat_messages. */\n messages?: ClaudeConversationMessage[];\n /** Associated project id, when the conversation lives inside a Project. */\n project_uuid?: string;\n}\n\nexport interface ClaudeProjectDoc {\n uuid?: string;\n filename?: string;\n content?: string;\n created_at?: string;\n updated_at?: string;\n}\n\nexport interface ClaudeProject {\n uuid?: string;\n name?: string;\n description?: string;\n /** User-authored free-form instructions for the project. */\n prompt_template?: string;\n docs?: ClaudeProjectDoc[];\n created_at?: string;\n updated_at?: string;\n}\n\n/**\n * Unified parsed shape passed into `transform()`.\n */\nexport interface ParsedClaudeExport {\n conversations: ClaudeConversation[];\n projects: ClaudeProject[];\n /** Source path the export came from (for provenance). */\n filePath?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Public parse API\n// ---------------------------------------------------------------------------\n\nexport interface ClaudeParseOptions {\n strict?: boolean;\n filePath?: string;\n}\n\n/**\n * Parse a raw Claude export payload. Accepts:\n * - a JSON string (`conversations.json`, `projects.json`, or a combined\n * bundle like `{conversations, projects}`)\n * - an already-parsed object or array.\n *\n * Returns the unified `ParsedClaudeExport`. Non-export payloads throw in\n * strict mode; otherwise the returned struct holds empty arrays so the\n * transform layer can no-op.\n */\nexport function parseClaudeExport(\n input: unknown,\n options: ClaudeParseOptions = {},\n): ParsedClaudeExport {\n // Codex review on PR #598 — missing input (undefined / null) must NEVER\n // succeed as an empty import. The CLI passes `undefined` when `--file`\n // is omitted; silently returning a zero-memory success would make\n // `remnic import --adapter claude` without --file look healthy in\n // automation logs while the user's export was never read.\n if (input === undefined || input === null) {\n throw new Error(\n \"Claude import requires a --file argument pointing at conversations.json, \" +\n \"projects.json, or the exported bundle.\",\n );\n }\n const raw = coerceJson(input);\n const result: ParsedClaudeExport = {\n conversations: [],\n projects: [],\n ...(options.filePath !== undefined ? { filePath: options.filePath } : {}),\n };\n\n // Shape 1: a top-level array.\n // - `conversations.json` is an array of conversations.\n // - `projects.json` is an array of projects.\n // We branch on the first element's shape.\n if (Array.isArray(raw)) {\n if (raw.length === 0) return result;\n const first = raw[0];\n if (looksLikeConversation(first)) {\n for (const entry of raw) {\n if (looksLikeConversation(entry)) {\n result.conversations.push(entry as ClaudeConversation);\n } else if (options.strict) {\n throw new Error(\"Non-conversation entry in conversations array\");\n }\n }\n return result;\n }\n if (looksLikeProject(first, { strict: options.strict })) {\n for (const entry of raw) {\n if (looksLikeProject(entry, { strict: options.strict })) {\n result.projects.push(entry as ClaudeProject);\n } else if (options.strict) {\n throw new Error(\"Non-project entry in projects array\");\n }\n }\n return result;\n }\n if (options.strict) {\n throw new Error(\n \"Unknown Claude export array shape (neither conversations nor projects).\",\n );\n }\n return result;\n }\n\n // Shape 2: an object. Look for the known keys.\n if (raw && typeof raw === \"object\") {\n const obj = raw as Record<string, unknown>;\n let sawKnownSection = false;\n const convs = obj.conversations;\n if (Array.isArray(convs)) {\n sawKnownSection = true;\n for (const entry of convs) {\n if (looksLikeConversation(entry)) {\n result.conversations.push(entry as ClaudeConversation);\n } else if (options.strict) {\n throw new Error(\"Non-conversation entry in conversations array\");\n }\n }\n }\n const projects = obj.projects;\n if (Array.isArray(projects)) {\n sawKnownSection = true;\n for (const entry of projects) {\n if (looksLikeProject(entry, { strict: options.strict })) {\n result.projects.push(entry as ClaudeProject);\n } else if (options.strict) {\n throw new Error(\"Non-project entry in projects array\");\n }\n }\n }\n // Strict mode: if the object has neither `conversations` nor `projects`,\n // bail rather than silently returning an empty struct. Non-strict mode\n // keeps the lenient behavior to survive future-shape changes.\n if (!sawKnownSection && options.strict) {\n throw new Error(\n \"Unknown Claude export object shape: expected 'conversations' or 'projects' keys.\",\n );\n }\n return result;\n }\n\n // Codex review on PR #598 — primitive payloads (numbers, booleans,\n // strings, etc.) must always throw regardless of strict mode. A silent\n // empty result on garbage input would let automation mistake a broken\n // import for a healthy zero-memory run.\n throw new Error(\n \"Claude export must be a JSON array or object; received \" + typeof raw,\n );\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction coerceJson(input: unknown): unknown {\n if (typeof input === \"string\") {\n try {\n return JSON.parse(input);\n } catch (err) {\n throw new Error(\n `Claude export is not valid JSON: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n }\n return input;\n}\n\nfunction looksLikeConversation(value: unknown): value is ClaudeConversation {\n if (!value || typeof value !== \"object\") return false;\n const v = value as Record<string, unknown>;\n // Every conversation has either chat_messages or messages.\n if (Array.isArray(v.chat_messages)) return true;\n if (Array.isArray(v.messages)) return true;\n return false;\n}\n\nfunction looksLikeProject(\n value: unknown,\n opts: { strict?: boolean } = {},\n): value is ClaudeProject {\n if (!value || typeof value !== \"object\") return false;\n const v = value as Record<string, unknown>;\n // Projects are recognised by the presence of `prompt_template` OR `docs`\n // — unambiguous structural signals unique to project exports.\n if (typeof v.prompt_template === \"string\") return true;\n if (Array.isArray(v.docs)) return true;\n // Codex review on PR #598 — the `name`-only fallback is too loose for\n // strict mode. An arbitrary array like `[{\"name\": \"foo\"}]` would slip\n // past strict validation and produce empty imports. In strict mode we\n // require an unambiguous project signal (prompt_template or docs) and\n // reject name-only entries. Non-strict mode keeps the lenient fallback\n // for future-shape safety.\n if (opts.strict) return false;\n if (\n typeof v.name === \"string\" &&\n !Array.isArray(v.chat_messages) &&\n !Array.isArray(v.messages)\n ) {\n return true;\n }\n return false;\n}\n\n/**\n * Walk a conversation and return only human-authored user turns in\n * chronological order. Exported so the transform layer can build conversation\n * summaries from the same source of truth.\n */\nexport function collectHumanTurnsFromConversation(\n conversation: ClaudeConversation,\n): Array<{ content: string; createdAt?: string }> {\n const collected: Array<{ content: string; createdAt?: string }> = [];\n // Prefer chat_messages when it has entries; fall back to the legacy\n // `messages` field. `??` alone is insufficient because an empty\n // `chat_messages` array is non-null but has no content — without the\n // length check, we would miss turns that only live in the legacy\n // `messages` array. Cursor review on PR #598.\n let messages: ClaudeConversationMessage[] = [];\n if (Array.isArray(conversation.chat_messages) && conversation.chat_messages.length > 0) {\n messages = conversation.chat_messages;\n } else if (Array.isArray(conversation.messages) && conversation.messages.length > 0) {\n messages = conversation.messages;\n }\n for (const msg of messages) {\n if (!isHumanSender(msg)) continue;\n const text = extractMessageText(msg);\n if (text) {\n collected.push({\n content: text,\n ...(typeof msg.created_at === \"string\" && msg.created_at.length > 0\n ? { createdAt: msg.created_at }\n : {}),\n });\n }\n }\n return collected;\n}\n\nfunction isHumanSender(msg: ClaudeConversationMessage): boolean {\n const sender = msg.sender ?? msg.role;\n return sender === \"human\" || sender === \"user\";\n}\n\nfunction extractMessageText(\n msg: ClaudeConversationMessage,\n): string | undefined {\n // Prefer structured content blocks (newer exports).\n if (Array.isArray(msg.content)) {\n const joined = msg.content\n .filter((b) => !b.type || b.type === \"text\")\n .map((b) => (typeof b.text === \"string\" ? b.text : \"\"))\n .filter((s) => s.length > 0)\n .join(\"\\n\")\n .trim();\n if (joined.length > 0) return joined;\n }\n if (typeof msg.text === \"string\") {\n const trimmed = msg.text.trim();\n if (trimmed.length > 0) return trimmed;\n }\n return undefined;\n}\n","// ---------------------------------------------------------------------------\n// Claude parsed → ImportedMemory transform (issue #568 slice 3)\n// ---------------------------------------------------------------------------\n//\n// Claude exports contain two distinct memory-worthy surfaces:\n//\n// 1. Project docs + `prompt_template` — durable personal context the user\n// explicitly pinned to a project. These are imported 1:1 by default;\n// each doc becomes one memory, and each project's prompt_template (if\n// non-empty) becomes one memory.\n// 2. Conversations — per-conversation summaries, only emitted when the\n// caller opts in via `includeConversations: true`. The summary\n// concatenates human-side turns (user messages) so downstream\n// extraction has coherent content to score. One memory per conversation\n// keeps the footprint bounded.\n\nimport type { ImportedMemory } from \"@remnic/core\";\n\nimport type {\n ClaudeConversation,\n ClaudeProject,\n ClaudeProjectDoc,\n ParsedClaudeExport,\n} from \"./parser.js\";\nimport { collectHumanTurnsFromConversation } from \"./parser.js\";\n\nexport const CLAUDE_SOURCE_LABEL = \"claude\";\n\nexport interface ClaudeTransformOptions {\n /** When true, emit conversation-summary memories. */\n includeConversations?: boolean;\n /** Optional cap on total memories emitted — primarily for tests. */\n maxMemories?: number;\n /** Max characters for a conversation summary. */\n maxConversationSummaryChars?: number;\n}\n\nconst DEFAULT_CONVERSATION_SUMMARY_CHARS = 2000;\n\n/**\n * Transform a parsed Claude export into `ImportedMemory[]`. Project docs are\n * emitted first (in parse order), then project prompt templates, then\n * conversation summaries when opted in.\n */\nexport function transformClaudeExport(\n parsed: ParsedClaudeExport,\n options: ClaudeTransformOptions = {},\n): ImportedMemory[] {\n const out: ImportedMemory[] = [];\n const cap = options.maxMemories;\n\n for (const project of parsed.projects) {\n if (cap !== undefined && out.length >= cap) return out;\n const docs = Array.isArray(project.docs) ? project.docs : [];\n for (const doc of docs) {\n if (cap !== undefined && out.length >= cap) return out;\n const memory = docToImported(project, doc, parsed.filePath);\n if (memory) out.push(memory);\n }\n if (cap !== undefined && out.length >= cap) return out;\n const templateMemory = projectTemplateToImported(project, parsed.filePath);\n if (templateMemory) out.push(templateMemory);\n }\n\n if (options.includeConversations) {\n const maxSummaryChars =\n options.maxConversationSummaryChars ?? DEFAULT_CONVERSATION_SUMMARY_CHARS;\n for (const conversation of parsed.conversations) {\n if (cap !== undefined && out.length >= cap) return out;\n const summary = conversationToSummary(\n conversation,\n parsed.filePath,\n maxSummaryChars,\n );\n if (summary) out.push(summary);\n }\n }\n return out;\n}\n\nfunction docToImported(\n project: ClaudeProject,\n doc: ClaudeProjectDoc,\n filePath: string | undefined,\n): ImportedMemory | undefined {\n const content = typeof doc.content === \"string\" ? doc.content.trim() : \"\";\n if (content.length === 0) return undefined;\n const sourceTimestamp = doc.updated_at ?? doc.created_at;\n const metadata: Record<string, unknown> = { kind: \"project_doc\" };\n if (typeof doc.filename === \"string\" && doc.filename.length > 0) {\n metadata.filename = doc.filename;\n }\n if (typeof project.name === \"string\" && project.name.length > 0) {\n metadata.projectName = project.name;\n }\n if (typeof project.uuid === \"string\") {\n metadata.projectUuid = project.uuid;\n }\n return {\n content,\n sourceLabel: CLAUDE_SOURCE_LABEL,\n ...(doc.uuid !== undefined ? { sourceId: doc.uuid } : {}),\n ...(sourceTimestamp !== undefined ? { sourceTimestamp } : {}),\n ...(filePath !== undefined ? { importedFromPath: filePath } : {}),\n metadata,\n };\n}\n\nfunction projectTemplateToImported(\n project: ClaudeProject,\n filePath: string | undefined,\n): ImportedMemory | undefined {\n const template =\n typeof project.prompt_template === \"string\"\n ? project.prompt_template.trim()\n : \"\";\n if (template.length === 0) return undefined;\n const sourceTimestamp = project.updated_at ?? project.created_at;\n const metadata: Record<string, unknown> = { kind: \"project_prompt_template\" };\n if (typeof project.name === \"string\" && project.name.length > 0) {\n metadata.projectName = project.name;\n }\n if (typeof project.uuid === \"string\") {\n metadata.projectUuid = project.uuid;\n }\n return {\n content: template,\n sourceLabel: CLAUDE_SOURCE_LABEL,\n ...(project.uuid !== undefined ? { sourceId: project.uuid } : {}),\n ...(sourceTimestamp !== undefined ? { sourceTimestamp } : {}),\n ...(filePath !== undefined ? { importedFromPath: filePath } : {}),\n metadata,\n };\n}\n\nfunction conversationToSummary(\n conversation: ClaudeConversation,\n filePath: string | undefined,\n maxChars: number,\n): ImportedMemory | undefined {\n const turns = collectHumanTurnsFromConversation(conversation);\n if (turns.length === 0) return undefined;\n\n const title =\n typeof conversation.name === \"string\" ? conversation.name.trim() : \"\";\n const titleLine = title.length > 0 ? `Conversation: ${title}\\n\\n` : \"\";\n const body = turns.map((t) => `- ${t.content}`).join(\"\\n\");\n let content = titleLine + body;\n if (content.length > maxChars) {\n // Reserve up to 3 chars for the \"...\" suffix, but truncate the suffix\n // itself when `maxChars` is below 3 so the final content.length is\n // strictly ≤ maxChars. Cursor reviews on PR #598 flagged both the\n // long-title case (titleLine alone exceeds maxChars) and the\n // pathologically small cap (maxChars < suffix.length).\n const effectiveSuffix = maxChars >= 3 ? \"...\" : \"\";\n if (titleLine.length + effectiveSuffix.length >= maxChars) {\n content =\n titleLine.slice(0, Math.max(0, maxChars - effectiveSuffix.length)) +\n effectiveSuffix;\n } else {\n const remaining = maxChars - titleLine.length - effectiveSuffix.length;\n const bodyTruncated = body.slice(0, Math.max(0, remaining));\n content = titleLine + bodyTruncated + effectiveSuffix;\n }\n }\n // Codex review on PR #598 — when per-turn timestamps are absent, fall\n // back to the conversation-level `updated_at`/`created_at`. Without\n // this fallback, exports that omit message-level timestamps lose their\n // original time metadata entirely, which makes old conversations look\n // newly imported and skews recency-based retrieval.\n const sourceTimestamp =\n firstTimestamp(turns) ?? conversation.updated_at ?? conversation.created_at;\n const metadata: Record<string, unknown> = {\n kind: \"conversation_summary\",\n humanTurns: turns.length,\n };\n if (title.length > 0) metadata.title = title;\n return {\n content,\n sourceLabel: CLAUDE_SOURCE_LABEL,\n ...(typeof conversation.uuid === \"string\"\n ? { sourceId: conversation.uuid }\n : {}),\n ...(sourceTimestamp !== undefined ? { sourceTimestamp } : {}),\n ...(filePath !== undefined ? { importedFromPath: filePath } : {}),\n metadata,\n };\n}\n\nfunction firstTimestamp(\n turns: Array<{ content: string; createdAt?: string }>,\n): string | undefined {\n for (const turn of turns) {\n if (typeof turn.createdAt === \"string\" && turn.createdAt.length > 0) {\n return turn.createdAt;\n }\n }\n return undefined;\n}\n"],"mappings":";;;AAYA,SAAS,0CAA0C;;;AC4F5C,SAAS,kBACd,OACA,UAA8B,CAAC,GACX;AAMpB,MAAI,UAAU,UAAa,UAAU,MAAM;AACzC,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACA,QAAM,MAAM,WAAW,KAAK;AAC5B,QAAM,SAA6B;AAAA,IACjC,eAAe,CAAC;AAAA,IAChB,UAAU,CAAC;AAAA,IACX,GAAI,QAAQ,aAAa,SAAY,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;AAAA,EACzE;AAMA,MAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,QAAI,IAAI,WAAW,EAAG,QAAO;AAC7B,UAAM,QAAQ,IAAI,CAAC;AACnB,QAAI,sBAAsB,KAAK,GAAG;AAChC,iBAAW,SAAS,KAAK;AACvB,YAAI,sBAAsB,KAAK,GAAG;AAChC,iBAAO,cAAc,KAAK,KAA2B;AAAA,QACvD,WAAW,QAAQ,QAAQ;AACzB,gBAAM,IAAI,MAAM,+CAA+C;AAAA,QACjE;AAAA,MACF;AACA,aAAO;AAAA,IACT;AACA,QAAI,iBAAiB,OAAO,EAAE,QAAQ,QAAQ,OAAO,CAAC,GAAG;AACvD,iBAAW,SAAS,KAAK;AACvB,YAAI,iBAAiB,OAAO,EAAE,QAAQ,QAAQ,OAAO,CAAC,GAAG;AACvD,iBAAO,SAAS,KAAK,KAAsB;AAAA,QAC7C,WAAW,QAAQ,QAAQ;AACzB,gBAAM,IAAI,MAAM,qCAAqC;AAAA,QACvD;AAAA,MACF;AACA,aAAO;AAAA,IACT;AACA,QAAI,QAAQ,QAAQ;AAClB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAGA,MAAI,OAAO,OAAO,QAAQ,UAAU;AAClC,UAAM,MAAM;AACZ,QAAI,kBAAkB;AACtB,UAAM,QAAQ,IAAI;AAClB,QAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,wBAAkB;AAClB,iBAAW,SAAS,OAAO;AACzB,YAAI,sBAAsB,KAAK,GAAG;AAChC,iBAAO,cAAc,KAAK,KAA2B;AAAA,QACvD,WAAW,QAAQ,QAAQ;AACzB,gBAAM,IAAI,MAAM,+CAA+C;AAAA,QACjE;AAAA,MACF;AAAA,IACF;AACA,UAAM,WAAW,IAAI;AACrB,QAAI,MAAM,QAAQ,QAAQ,GAAG;AAC3B,wBAAkB;AAClB,iBAAW,SAAS,UAAU;AAC5B,YAAI,iBAAiB,OAAO,EAAE,QAAQ,QAAQ,OAAO,CAAC,GAAG;AACvD,iBAAO,SAAS,KAAK,KAAsB;AAAA,QAC7C,WAAW,QAAQ,QAAQ;AACzB,gBAAM,IAAI,MAAM,qCAAqC;AAAA,QACvD;AAAA,MACF;AAAA,IACF;AAIA,QAAI,CAAC,mBAAmB,QAAQ,QAAQ;AACtC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAMA,QAAM,IAAI;AAAA,IACR,4DAA4D,OAAO;AAAA,EACrE;AACF;AAMA,SAAS,WAAW,OAAyB;AAC3C,MAAI,OAAO,UAAU,UAAU;AAC7B,QAAI;AACF,aAAO,KAAK,MAAM,KAAK;AAAA,IACzB,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR,oCACE,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CACjD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,sBAAsB,OAA6C;AAC1E,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,IAAI;AAEV,MAAI,MAAM,QAAQ,EAAE,aAAa,EAAG,QAAO;AAC3C,MAAI,MAAM,QAAQ,EAAE,QAAQ,EAAG,QAAO;AACtC,SAAO;AACT;AAEA,SAAS,iBACP,OACA,OAA6B,CAAC,GACN;AACxB,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,IAAI;AAGV,MAAI,OAAO,EAAE,oBAAoB,SAAU,QAAO;AAClD,MAAI,MAAM,QAAQ,EAAE,IAAI,EAAG,QAAO;AAOlC,MAAI,KAAK,OAAQ,QAAO;AACxB,MACE,OAAO,EAAE,SAAS,YAClB,CAAC,MAAM,QAAQ,EAAE,aAAa,KAC9B,CAAC,MAAM,QAAQ,EAAE,QAAQ,GACzB;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,kCACd,cACgD;AAChD,QAAM,YAA4D,CAAC;AAMnE,MAAI,WAAwC,CAAC;AAC7C,MAAI,MAAM,QAAQ,aAAa,aAAa,KAAK,aAAa,cAAc,SAAS,GAAG;AACtF,eAAW,aAAa;AAAA,EAC1B,WAAW,MAAM,QAAQ,aAAa,QAAQ,KAAK,aAAa,SAAS,SAAS,GAAG;AACnF,eAAW,aAAa;AAAA,EAC1B;AACA,aAAW,OAAO,UAAU;AAC1B,QAAI,CAAC,cAAc,GAAG,EAAG;AACzB,UAAM,OAAO,mBAAmB,GAAG;AACnC,QAAI,MAAM;AACR,gBAAU,KAAK;AAAA,QACb,SAAS;AAAA,QACT,GAAI,OAAO,IAAI,eAAe,YAAY,IAAI,WAAW,SAAS,IAC9D,EAAE,WAAW,IAAI,WAAW,IAC5B,CAAC;AAAA,MACP,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,cAAc,KAAyC;AAC9D,QAAM,SAAS,IAAI,UAAU,IAAI;AACjC,SAAO,WAAW,WAAW,WAAW;AAC1C;AAEA,SAAS,mBACP,KACoB;AAEpB,MAAI,MAAM,QAAQ,IAAI,OAAO,GAAG;AAC9B,UAAM,SAAS,IAAI,QAChB,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,SAAS,MAAM,EAC1C,IAAI,CAAC,MAAO,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO,EAAG,EACrD,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,EAC1B,KAAK,IAAI,EACT,KAAK;AACR,QAAI,OAAO,SAAS,EAAG,QAAO;AAAA,EAChC;AACA,MAAI,OAAO,IAAI,SAAS,UAAU;AAChC,UAAM,UAAU,IAAI,KAAK,KAAK;AAC9B,QAAI,QAAQ,SAAS,EAAG,QAAO;AAAA,EACjC;AACA,SAAO;AACT;;;ACtSO,IAAM,sBAAsB;AAWnC,IAAM,qCAAqC;AAOpC,SAAS,sBACd,QACA,UAAkC,CAAC,GACjB;AAClB,QAAM,MAAwB,CAAC;AAC/B,QAAM,MAAM,QAAQ;AAEpB,aAAW,WAAW,OAAO,UAAU;AACrC,QAAI,QAAQ,UAAa,IAAI,UAAU,IAAK,QAAO;AACnD,UAAM,OAAO,MAAM,QAAQ,QAAQ,IAAI,IAAI,QAAQ,OAAO,CAAC;AAC3D,eAAW,OAAO,MAAM;AACtB,UAAI,QAAQ,UAAa,IAAI,UAAU,IAAK,QAAO;AACnD,YAAM,SAAS,cAAc,SAAS,KAAK,OAAO,QAAQ;AAC1D,UAAI,OAAQ,KAAI,KAAK,MAAM;AAAA,IAC7B;AACA,QAAI,QAAQ,UAAa,IAAI,UAAU,IAAK,QAAO;AACnD,UAAM,iBAAiB,0BAA0B,SAAS,OAAO,QAAQ;AACzE,QAAI,eAAgB,KAAI,KAAK,cAAc;AAAA,EAC7C;AAEA,MAAI,QAAQ,sBAAsB;AAChC,UAAM,kBACJ,QAAQ,+BAA+B;AACzC,eAAW,gBAAgB,OAAO,eAAe;AAC/C,UAAI,QAAQ,UAAa,IAAI,UAAU,IAAK,QAAO;AACnD,YAAM,UAAU;AAAA,QACd;AAAA,QACA,OAAO;AAAA,QACP;AAAA,MACF;AACA,UAAI,QAAS,KAAI,KAAK,OAAO;AAAA,IAC/B;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,cACP,SACA,KACA,UAC4B;AAC5B,QAAM,UAAU,OAAO,IAAI,YAAY,WAAW,IAAI,QAAQ,KAAK,IAAI;AACvE,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,QAAM,kBAAkB,IAAI,cAAc,IAAI;AAC9C,QAAM,WAAoC,EAAE,MAAM,cAAc;AAChE,MAAI,OAAO,IAAI,aAAa,YAAY,IAAI,SAAS,SAAS,GAAG;AAC/D,aAAS,WAAW,IAAI;AAAA,EAC1B;AACA,MAAI,OAAO,QAAQ,SAAS,YAAY,QAAQ,KAAK,SAAS,GAAG;AAC/D,aAAS,cAAc,QAAQ;AAAA,EACjC;AACA,MAAI,OAAO,QAAQ,SAAS,UAAU;AACpC,aAAS,cAAc,QAAQ;AAAA,EACjC;AACA,SAAO;AAAA,IACL;AAAA,IACA,aAAa;AAAA,IACb,GAAI,IAAI,SAAS,SAAY,EAAE,UAAU,IAAI,KAAK,IAAI,CAAC;AAAA,IACvD,GAAI,oBAAoB,SAAY,EAAE,gBAAgB,IAAI,CAAC;AAAA,IAC3D,GAAI,aAAa,SAAY,EAAE,kBAAkB,SAAS,IAAI,CAAC;AAAA,IAC/D;AAAA,EACF;AACF;AAEA,SAAS,0BACP,SACA,UAC4B;AAC5B,QAAM,WACJ,OAAO,QAAQ,oBAAoB,WAC/B,QAAQ,gBAAgB,KAAK,IAC7B;AACN,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,QAAM,kBAAkB,QAAQ,cAAc,QAAQ;AACtD,QAAM,WAAoC,EAAE,MAAM,0BAA0B;AAC5E,MAAI,OAAO,QAAQ,SAAS,YAAY,QAAQ,KAAK,SAAS,GAAG;AAC/D,aAAS,cAAc,QAAQ;AAAA,EACjC;AACA,MAAI,OAAO,QAAQ,SAAS,UAAU;AACpC,aAAS,cAAc,QAAQ;AAAA,EACjC;AACA,SAAO;AAAA,IACL,SAAS;AAAA,IACT,aAAa;AAAA,IACb,GAAI,QAAQ,SAAS,SAAY,EAAE,UAAU,QAAQ,KAAK,IAAI,CAAC;AAAA,IAC/D,GAAI,oBAAoB,SAAY,EAAE,gBAAgB,IAAI,CAAC;AAAA,IAC3D,GAAI,aAAa,SAAY,EAAE,kBAAkB,SAAS,IAAI,CAAC;AAAA,IAC/D;AAAA,EACF;AACF;AAEA,SAAS,sBACP,cACA,UACA,UAC4B;AAC5B,QAAM,QAAQ,kCAAkC,YAAY;AAC5D,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,QAAM,QACJ,OAAO,aAAa,SAAS,WAAW,aAAa,KAAK,KAAK,IAAI;AACrE,QAAM,YAAY,MAAM,SAAS,IAAI,iBAAiB,KAAK;AAAA;AAAA,IAAS;AACpE,QAAM,OAAO,MAAM,IAAI,CAAC,MAAM,KAAK,EAAE,OAAO,EAAE,EAAE,KAAK,IAAI;AACzD,MAAI,UAAU,YAAY;AAC1B,MAAI,QAAQ,SAAS,UAAU;AAM7B,UAAM,kBAAkB,YAAY,IAAI,QAAQ;AAChD,QAAI,UAAU,SAAS,gBAAgB,UAAU,UAAU;AACzD,gBACE,UAAU,MAAM,GAAG,KAAK,IAAI,GAAG,WAAW,gBAAgB,MAAM,CAAC,IACjE;AAAA,IACJ,OAAO;AACL,YAAM,YAAY,WAAW,UAAU,SAAS,gBAAgB;AAChE,YAAM,gBAAgB,KAAK,MAAM,GAAG,KAAK,IAAI,GAAG,SAAS,CAAC;AAC1D,gBAAU,YAAY,gBAAgB;AAAA,IACxC;AAAA,EACF;AAMA,QAAM,kBACJ,eAAe,KAAK,KAAK,aAAa,cAAc,aAAa;AACnE,QAAM,WAAoC;AAAA,IACxC,MAAM;AAAA,IACN,YAAY,MAAM;AAAA,EACpB;AACA,MAAI,MAAM,SAAS,EAAG,UAAS,QAAQ;AACvC,SAAO;AAAA,IACL;AAAA,IACA,aAAa;AAAA,IACb,GAAI,OAAO,aAAa,SAAS,WAC7B,EAAE,UAAU,aAAa,KAAK,IAC9B,CAAC;AAAA,IACL,GAAI,oBAAoB,SAAY,EAAE,gBAAgB,IAAI,CAAC;AAAA,IAC3D,GAAI,aAAa,SAAY,EAAE,kBAAkB,SAAS,IAAI,CAAC;AAAA,IAC/D;AAAA,EACF;AACF;AAEA,SAAS,eACP,OACoB;AACpB,aAAW,QAAQ,OAAO;AACxB,QAAI,OAAO,KAAK,cAAc,YAAY,KAAK,UAAU,SAAS,GAAG;AACnE,aAAO,KAAK;AAAA,IACd;AAAA,EACF;AACA,SAAO;AACT;;;AF3KO,IAAM,UAA+C;AAAA,EAC1D,MAAM;AAAA,EACN,aAAa;AAAA,EAEb,MAAM,OAAgB,SAAoD;AACxE,WAAO,kBAAkB,OAAO;AAAA,MAC9B,GAAI,SAAS,WAAW,SAAY,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;AAAA,MAClE,GAAI,SAAS,aAAa,SAAY,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;AAAA,IAC1E,CAAC;AAAA,EACH;AAAA,EAEA,UACE,QACA,SACkB;AAClB,WAAO,sBAAsB,QAAQ;AAAA,MACnC,sBAAsB,SAAS,yBAAyB;AAAA,MACxD,GAAI,SAAS,gBAAgB,SACzB,EAAE,aAAa,QAAQ,YAAY,IACnC,CAAC;AAAA,IACP,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QACJ,QACA,UAC8B;AAC9B,WAAO,mCAAmC,QAAQ,QAAQ;AAAA,EAC5D;AACF;AAGO,IAAM,gBAAgB;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/adapter.ts","../src/parser.ts","../src/transform.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Claude importer adapter (issue #568 slice 3)\n// ---------------------------------------------------------------------------\n\nimport type {\n ImportedMemory,\n ImporterAdapter,\n ImporterParseOptions,\n ImporterTransformOptions,\n ImporterWriteResult,\n ImporterWriteTarget,\n} from \"@remnic/core\";\nimport { defaultWriteMemoriesToOrchestrator } from \"@remnic/core\";\n\nimport {\n parseClaudeExport,\n type ParsedClaudeExport,\n} from \"./parser.js\";\nimport { CLAUDE_SOURCE_LABEL, transformClaudeExport } from \"./transform.js\";\n\n/**\n * Canonical `ImporterAdapter` exposed by `@remnic/import-claude`.\n *\n * Loaded by `remnic-cli/optional-importer.ts` via a computed-specifier dynamic\n * import. The CLI drives `adapter.parse` → `adapter.transform` →\n * `adapter.writeTo` through the shared `runImporter` helper in `@remnic/core`.\n */\nexport const adapter: ImporterAdapter<ParsedClaudeExport> = {\n name: \"claude\",\n sourceLabel: CLAUDE_SOURCE_LABEL,\n\n parse(input: unknown, options?: ImporterParseOptions): ParsedClaudeExport {\n return parseClaudeExport(input, {\n ...(options?.strict !== undefined ? { strict: options.strict } : {}),\n ...(options?.filePath !== undefined ? { filePath: options.filePath } : {}),\n });\n },\n\n transform(\n parsed: ParsedClaudeExport,\n options?: ImporterTransformOptions,\n ): ImportedMemory[] {\n return transformClaudeExport(parsed, {\n includeConversations: options?.includeConversations === true,\n ...(options?.maxMemories !== undefined\n ? { maxMemories: options.maxMemories }\n : {}),\n });\n },\n\n async writeTo(\n target: ImporterWriteTarget,\n memories: ImportedMemory[],\n ): Promise<ImporterWriteResult> {\n return defaultWriteMemoriesToOrchestrator(target, memories);\n },\n};\n\n/** Alias kept for symmetry with other @remnic/import-* packages. */\nexport const claudeAdapter = adapter;\n","// ---------------------------------------------------------------------------\n// Claude.ai data-export parser (issue #568 slice 3)\n// ---------------------------------------------------------------------------\n//\n// Claude.ai's \"Export data\" feature produces a ZIP containing several JSON\n// files. The two relevant to memory import are:\n//\n// 1. `conversations.json` — array of Conversation objects. Each has a\n// `chat_messages` array with `sender` (human/assistant) and `text` (plus\n// `content` blocks in newer exports). Text content is plain, not the\n// graph shape ChatGPT uses.\n// 2. `projects.json` — array of Project objects, each with an optional\n// `prompt_template` and a list of `docs` (the durable context documents\n// the user attached to the project). These are high-signal personal\n// artifacts and are imported as one memory per doc / project.\n//\n// The parser accepts either file individually (JSON text or object) or a\n// combined bundle object (`{ conversations: [...], projects: [...] }`) used\n// by the future bundle-auto-detect flow (PR 7).\n//\n// We do NOT read ZIP archives here — the CLI reads the file contents and\n// passes them in. Synthetic fixtures in `fixtures/` mirror the real shapes.\n\n// ---------------------------------------------------------------------------\n// Raw export shapes (subset we care about)\n// ---------------------------------------------------------------------------\n\n/**\n * Message inside a Claude conversation. `sender` is either \"human\" or\n * \"assistant\"; older exports used `role` instead.\n */\nexport interface ClaudeConversationMessage {\n uuid?: string;\n sender?: \"human\" | \"assistant\" | string;\n role?: \"human\" | \"assistant\" | string;\n /** Plain-text transcript of this message. */\n text?: string;\n /** Newer exports expose structured content blocks. */\n content?: Array<{ type?: string; text?: string }>;\n created_at?: string;\n updated_at?: string;\n}\n\nexport interface ClaudeConversation {\n uuid?: string;\n name?: string;\n summary?: string;\n created_at?: string;\n updated_at?: string;\n chat_messages?: ClaudeConversationMessage[];\n /** Some exports also expose `messages` alongside chat_messages. */\n messages?: ClaudeConversationMessage[];\n /** Associated project id, when the conversation lives inside a Project. */\n project_uuid?: string;\n}\n\nexport interface ClaudeProjectDoc {\n uuid?: string;\n filename?: string;\n content?: string;\n created_at?: string;\n updated_at?: string;\n}\n\nexport interface ClaudeProject {\n uuid?: string;\n name?: string;\n description?: string;\n /** User-authored free-form instructions for the project. */\n prompt_template?: string;\n docs?: ClaudeProjectDoc[];\n created_at?: string;\n updated_at?: string;\n}\n\n/**\n * Unified parsed shape passed into `transform()`.\n */\nexport interface ParsedClaudeExport {\n conversations: ClaudeConversation[];\n projects: ClaudeProject[];\n /** Source path the export came from (for provenance). */\n filePath?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Public parse API\n// ---------------------------------------------------------------------------\n\nexport interface ClaudeParseOptions {\n strict?: boolean;\n filePath?: string;\n}\n\n/**\n * Parse a raw Claude export payload. Accepts:\n * - a JSON string (`conversations.json`, `projects.json`, or a combined\n * bundle like `{conversations, projects}`)\n * - an already-parsed object or array.\n *\n * Returns the unified `ParsedClaudeExport`. Non-export payloads throw in\n * strict mode; otherwise the returned struct holds empty arrays so the\n * transform layer can no-op.\n */\nexport function parseClaudeExport(\n input: unknown,\n options: ClaudeParseOptions = {},\n): ParsedClaudeExport {\n // Codex review on PR #598 — missing input (undefined / null) must NEVER\n // succeed as an empty import. The CLI passes `undefined` when `--file`\n // is omitted; silently returning a zero-memory success would make\n // `remnic import --adapter claude` without --file look healthy in\n // automation logs while the user's export was never read.\n if (input === undefined || input === null) {\n throw new Error(\n \"Claude import requires a --file argument pointing at conversations.json, \" +\n \"projects.json, or the exported bundle.\",\n );\n }\n const raw = coerceJson(input);\n const result: ParsedClaudeExport = {\n conversations: [],\n projects: [],\n ...(options.filePath !== undefined ? { filePath: options.filePath } : {}),\n };\n\n // Shape 1: a top-level array.\n // - `conversations.json` is an array of conversations.\n // - `projects.json` is an array of projects.\n // We branch on the first element's shape.\n if (Array.isArray(raw)) {\n if (raw.length === 0) return result;\n const first = raw[0];\n if (looksLikeConversation(first)) {\n for (const entry of raw) {\n if (looksLikeConversation(entry)) {\n result.conversations.push(entry as ClaudeConversation);\n } else if (options.strict) {\n throw new Error(\"Non-conversation entry in conversations array\");\n }\n }\n return result;\n }\n if (looksLikeProject(first, { strict: options.strict })) {\n for (const entry of raw) {\n if (looksLikeProject(entry, { strict: options.strict })) {\n result.projects.push(entry as ClaudeProject);\n } else if (options.strict) {\n throw new Error(\"Non-project entry in projects array\");\n }\n }\n return result;\n }\n if (options.strict) {\n throw new Error(\n \"Unknown Claude export array shape (neither conversations nor projects).\",\n );\n }\n return result;\n }\n\n // Shape 2: an object. Look for the known keys.\n if (raw && typeof raw === \"object\") {\n const obj = raw as Record<string, unknown>;\n let sawKnownSection = false;\n const hasConversationsSection = Object.prototype.hasOwnProperty.call(\n obj,\n \"conversations\",\n );\n const convs = obj.conversations;\n if (Array.isArray(convs)) {\n sawKnownSection = true;\n for (const entry of convs) {\n if (looksLikeConversation(entry)) {\n result.conversations.push(entry as ClaudeConversation);\n } else if (options.strict) {\n throw new Error(\"Non-conversation entry in conversations array\");\n }\n }\n } else if (hasConversationsSection && options.strict) {\n throw new Error(\"Claude export conversations section must be an array.\");\n }\n const hasProjectsSection = Object.prototype.hasOwnProperty.call(\n obj,\n \"projects\",\n );\n const projects = obj.projects;\n if (Array.isArray(projects)) {\n sawKnownSection = true;\n for (const entry of projects) {\n if (looksLikeProject(entry, { strict: options.strict })) {\n result.projects.push(entry as ClaudeProject);\n } else if (options.strict) {\n throw new Error(\"Non-project entry in projects array\");\n }\n }\n } else if (hasProjectsSection && options.strict) {\n throw new Error(\"Claude export projects section must be an array.\");\n }\n // Strict mode: if the object has neither `conversations` nor `projects`,\n // bail rather than silently returning an empty struct. Non-strict mode\n // keeps the lenient behavior to survive future-shape changes.\n if (!sawKnownSection && options.strict) {\n throw new Error(\n \"Unknown Claude export object shape: expected 'conversations' or 'projects' keys.\",\n );\n }\n return result;\n }\n\n // Codex review on PR #598 — primitive payloads (numbers, booleans,\n // strings, etc.) must always throw regardless of strict mode. A silent\n // empty result on garbage input would let automation mistake a broken\n // import for a healthy zero-memory run.\n throw new Error(\n \"Claude export must be a JSON array or object; received \" + typeof raw,\n );\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction coerceJson(input: unknown): unknown {\n if (typeof input === \"string\") {\n try {\n return JSON.parse(input);\n } catch (err) {\n throw new Error(\n `Claude export is not valid JSON: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n }\n return input;\n}\n\nfunction looksLikeConversation(value: unknown): value is ClaudeConversation {\n if (!value || typeof value !== \"object\") return false;\n const v = value as Record<string, unknown>;\n // Every conversation has either chat_messages or messages.\n if (Array.isArray(v.chat_messages)) return true;\n if (Array.isArray(v.messages)) return true;\n return false;\n}\n\nfunction looksLikeProject(\n value: unknown,\n opts: { strict?: boolean } = {},\n): value is ClaudeProject {\n if (!value || typeof value !== \"object\") return false;\n const v = value as Record<string, unknown>;\n // Projects are recognised by the presence of `prompt_template` OR `docs`\n // — unambiguous structural signals unique to project exports.\n if (typeof v.prompt_template === \"string\") return true;\n if (Array.isArray(v.docs)) return true;\n // Codex review on PR #598 — the `name`-only fallback is too loose for\n // strict mode. An arbitrary array like `[{\"name\": \"foo\"}]` would slip\n // past strict validation and produce empty imports. In strict mode we\n // require an unambiguous project signal (prompt_template or docs) and\n // reject name-only entries. Non-strict mode keeps the lenient fallback\n // for future-shape safety.\n if (opts.strict) return false;\n if (\n typeof v.name === \"string\" &&\n !Array.isArray(v.chat_messages) &&\n !Array.isArray(v.messages)\n ) {\n return true;\n }\n return false;\n}\n\n/**\n * Walk a conversation and return only human-authored user turns in\n * chronological order. Exported so the transform layer can build conversation\n * summaries from the same source of truth.\n */\nexport function collectHumanTurnsFromConversation(\n conversation: ClaudeConversation,\n): Array<{ content: string; createdAt?: string }> {\n const collected: Array<{ content: string; createdAt?: string }> = [];\n // Prefer chat_messages when it has entries; fall back to the legacy\n // `messages` field. `??` alone is insufficient because an empty\n // `chat_messages` array is non-null but has no content — without the\n // length check, we would miss turns that only live in the legacy\n // `messages` array. Cursor review on PR #598.\n let messages: ClaudeConversationMessage[] = [];\n if (Array.isArray(conversation.chat_messages) && conversation.chat_messages.length > 0) {\n messages = conversation.chat_messages;\n } else if (Array.isArray(conversation.messages) && conversation.messages.length > 0) {\n messages = conversation.messages;\n }\n for (const msg of messages) {\n if (!isHumanSender(msg)) continue;\n const text = extractMessageText(msg);\n if (text) {\n collected.push({\n content: text,\n ...(typeof msg.created_at === \"string\" && msg.created_at.length > 0\n ? { createdAt: msg.created_at }\n : {}),\n });\n }\n }\n return collected;\n}\n\nfunction isHumanSender(msg: ClaudeConversationMessage): boolean {\n const sender = msg.sender ?? msg.role;\n return sender === \"human\" || sender === \"user\";\n}\n\nfunction extractMessageText(\n msg: ClaudeConversationMessage,\n): string | undefined {\n // Prefer structured content blocks (newer exports).\n if (Array.isArray(msg.content)) {\n const joined = msg.content\n .filter((b) => !b.type || b.type === \"text\")\n .map((b) => (typeof b.text === \"string\" ? b.text : \"\"))\n .filter((s) => s.length > 0)\n .join(\"\\n\")\n .trim();\n if (joined.length > 0) return joined;\n }\n if (typeof msg.text === \"string\") {\n const trimmed = msg.text.trim();\n if (trimmed.length > 0) return trimmed;\n }\n return undefined;\n}\n","// ---------------------------------------------------------------------------\n// Claude parsed → ImportedMemory transform (issue #568 slice 3)\n// ---------------------------------------------------------------------------\n//\n// Claude exports contain two distinct memory-worthy surfaces:\n//\n// 1. Project docs + `prompt_template` — durable personal context the user\n// explicitly pinned to a project. These are imported 1:1 by default;\n// each doc becomes one memory, and each project's prompt_template (if\n// non-empty) becomes one memory.\n// 2. Conversations — per-conversation summaries, only emitted when the\n// caller opts in via `includeConversations: true`. The summary\n// concatenates human-side turns (user messages) so downstream\n// extraction has coherent content to score. One memory per conversation\n// keeps the footprint bounded.\n\nimport type { ImportedMemory } from \"@remnic/core\";\n\nimport type {\n ClaudeConversation,\n ClaudeProject,\n ClaudeProjectDoc,\n ParsedClaudeExport,\n} from \"./parser.js\";\nimport { collectHumanTurnsFromConversation } from \"./parser.js\";\n\nexport const CLAUDE_SOURCE_LABEL = \"claude\";\n\nexport interface ClaudeTransformOptions {\n /** When true, emit conversation-summary memories. */\n includeConversations?: boolean;\n /** Optional cap on total memories emitted — primarily for tests. */\n maxMemories?: number;\n /** Max characters for a conversation summary. */\n maxConversationSummaryChars?: number;\n}\n\nconst DEFAULT_CONVERSATION_SUMMARY_CHARS = 2000;\n\n/**\n * Transform a parsed Claude export into `ImportedMemory[]`. Project docs are\n * emitted first (in parse order), then project prompt templates, then\n * conversation summaries when opted in.\n */\nexport function transformClaudeExport(\n parsed: ParsedClaudeExport,\n options: ClaudeTransformOptions = {},\n): ImportedMemory[] {\n const out: ImportedMemory[] = [];\n const cap = options.maxMemories;\n\n for (const project of parsed.projects) {\n if (cap !== undefined && out.length >= cap) return out;\n const docs = Array.isArray(project.docs) ? project.docs : [];\n for (const doc of docs) {\n if (cap !== undefined && out.length >= cap) return out;\n const memory = docToImported(project, doc, parsed.filePath);\n if (memory) out.push(memory);\n }\n if (cap !== undefined && out.length >= cap) return out;\n const templateMemory = projectTemplateToImported(project, parsed.filePath);\n if (templateMemory) out.push(templateMemory);\n }\n\n if (options.includeConversations) {\n const maxSummaryChars =\n options.maxConversationSummaryChars ?? DEFAULT_CONVERSATION_SUMMARY_CHARS;\n for (const conversation of parsed.conversations) {\n if (cap !== undefined && out.length >= cap) return out;\n const summary = conversationToSummary(\n conversation,\n parsed.filePath,\n maxSummaryChars,\n );\n if (summary) out.push(summary);\n }\n }\n return out;\n}\n\nfunction docToImported(\n project: ClaudeProject,\n doc: ClaudeProjectDoc,\n filePath: string | undefined,\n): ImportedMemory | undefined {\n const content = typeof doc.content === \"string\" ? doc.content.trim() : \"\";\n if (content.length === 0) return undefined;\n const sourceTimestamp = doc.updated_at ?? doc.created_at;\n const metadata: Record<string, unknown> = { kind: \"project_doc\" };\n if (typeof doc.filename === \"string\" && doc.filename.length > 0) {\n metadata.filename = doc.filename;\n }\n if (typeof project.name === \"string\" && project.name.length > 0) {\n metadata.projectName = project.name;\n }\n if (typeof project.uuid === \"string\") {\n metadata.projectUuid = project.uuid;\n }\n return {\n content,\n sourceLabel: CLAUDE_SOURCE_LABEL,\n ...(doc.uuid !== undefined ? { sourceId: doc.uuid } : {}),\n ...(sourceTimestamp !== undefined ? { sourceTimestamp } : {}),\n ...(filePath !== undefined ? { importedFromPath: filePath } : {}),\n metadata,\n };\n}\n\nfunction projectTemplateToImported(\n project: ClaudeProject,\n filePath: string | undefined,\n): ImportedMemory | undefined {\n const template =\n typeof project.prompt_template === \"string\"\n ? project.prompt_template.trim()\n : \"\";\n if (template.length === 0) return undefined;\n const sourceTimestamp = project.updated_at ?? project.created_at;\n const metadata: Record<string, unknown> = { kind: \"project_prompt_template\" };\n if (typeof project.name === \"string\" && project.name.length > 0) {\n metadata.projectName = project.name;\n }\n if (typeof project.uuid === \"string\") {\n metadata.projectUuid = project.uuid;\n }\n return {\n content: template,\n sourceLabel: CLAUDE_SOURCE_LABEL,\n ...(project.uuid !== undefined ? { sourceId: project.uuid } : {}),\n ...(sourceTimestamp !== undefined ? { sourceTimestamp } : {}),\n ...(filePath !== undefined ? { importedFromPath: filePath } : {}),\n metadata,\n };\n}\n\nfunction conversationToSummary(\n conversation: ClaudeConversation,\n filePath: string | undefined,\n maxChars: number,\n): ImportedMemory | undefined {\n const turns = collectHumanTurnsFromConversation(conversation);\n if (turns.length === 0) return undefined;\n\n const title =\n typeof conversation.name === \"string\" ? conversation.name.trim() : \"\";\n const titleLine = title.length > 0 ? `Conversation: ${title}\\n\\n` : \"\";\n const body = turns.map((t) => `- ${t.content}`).join(\"\\n\");\n let content = titleLine + body;\n if (content.length > maxChars) {\n // Reserve up to 3 chars for the \"...\" suffix, but truncate the suffix\n // itself when `maxChars` is below 3 so the final content.length is\n // strictly ≤ maxChars. Cursor reviews on PR #598 flagged both the\n // long-title case (titleLine alone exceeds maxChars) and the\n // pathologically small cap (maxChars < suffix.length).\n const effectiveSuffix = maxChars >= 3 ? \"...\" : \"\";\n if (titleLine.length + effectiveSuffix.length >= maxChars) {\n content =\n titleLine.slice(0, Math.max(0, maxChars - effectiveSuffix.length)) +\n effectiveSuffix;\n } else {\n const remaining = maxChars - titleLine.length - effectiveSuffix.length;\n const bodyTruncated = body.slice(0, Math.max(0, remaining));\n content = titleLine + bodyTruncated + effectiveSuffix;\n }\n }\n // Codex review on PR #598 — when per-turn timestamps are absent, fall\n // back to the conversation-level `updated_at`/`created_at`. Without\n // this fallback, exports that omit message-level timestamps lose their\n // original time metadata entirely, which makes old conversations look\n // newly imported and skews recency-based retrieval.\n const sourceTimestamp =\n firstTimestamp(turns) ?? conversation.updated_at ?? conversation.created_at;\n const metadata: Record<string, unknown> = {\n kind: \"conversation_summary\",\n humanTurns: turns.length,\n };\n if (title.length > 0) metadata.title = title;\n return {\n content,\n sourceLabel: CLAUDE_SOURCE_LABEL,\n ...(typeof conversation.uuid === \"string\"\n ? { sourceId: conversation.uuid }\n : {}),\n ...(sourceTimestamp !== undefined ? { sourceTimestamp } : {}),\n ...(filePath !== undefined ? { importedFromPath: filePath } : {}),\n metadata,\n };\n}\n\nfunction firstTimestamp(\n turns: Array<{ content: string; createdAt?: string }>,\n): string | undefined {\n for (const turn of turns) {\n if (typeof turn.createdAt === \"string\" && turn.createdAt.length > 0) {\n return turn.createdAt;\n }\n }\n return undefined;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/import-claude",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.3.517",
|
|
4
4
|
"description": "Import conversation history from Claude.ai data exports into Remnic (issue #568)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -11,25 +11,21 @@
|
|
|
11
11
|
"import": "./dist/index.js"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
|
-
"files": [
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"check-types": "tsc --noEmit",
|
|
18
|
-
"test": "tsx --test src/adapter.test.ts src/parser.test.ts src/transform.test.ts",
|
|
19
|
-
"prepublishOnly": "npm run build"
|
|
20
|
-
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
21
17
|
"publishConfig": {
|
|
22
18
|
"access": "public",
|
|
23
19
|
"provenance": true
|
|
24
20
|
},
|
|
25
21
|
"peerDependencies": {
|
|
26
|
-
"@remnic/core": "
|
|
22
|
+
"@remnic/core": "^9.3.517"
|
|
27
23
|
},
|
|
28
24
|
"devDependencies": {
|
|
29
|
-
"@remnic/core": "workspace:*",
|
|
30
25
|
"tsup": "^8.0.0",
|
|
31
26
|
"tsx": "^4.0.0",
|
|
32
|
-
"typescript": "^5.7.0"
|
|
27
|
+
"typescript": "^5.7.0",
|
|
28
|
+
"@remnic/core": "9.3.517"
|
|
33
29
|
},
|
|
34
30
|
"license": "MIT",
|
|
35
31
|
"repository": {
|
|
@@ -37,5 +33,17 @@
|
|
|
37
33
|
"url": "https://github.com/joshuaswarren/remnic.git",
|
|
38
34
|
"directory": "packages/import-claude"
|
|
39
35
|
},
|
|
40
|
-
"keywords": [
|
|
41
|
-
|
|
36
|
+
"keywords": [
|
|
37
|
+
"remnic",
|
|
38
|
+
"memory",
|
|
39
|
+
"claude",
|
|
40
|
+
"anthropic",
|
|
41
|
+
"import"
|
|
42
|
+
],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsup src/index.ts --format esm --dts",
|
|
45
|
+
"precheck-types": "node ../../scripts/ensure-bench-build-deps.mjs",
|
|
46
|
+
"check-types": "tsc --noEmit",
|
|
47
|
+
"test": "tsx --test src/adapter.test.ts src/parser.test.ts src/transform.test.ts"
|
|
48
|
+
}
|
|
49
|
+
}
|