decant-core 1.0.0 → 1.0.1

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/ai/claude.js CHANGED
@@ -1,29 +1,31 @@
1
- import { ChatParser } from './base.js';
2
- import { convertToMarkdown } from '../utils/html-to-markdown.js';
1
+ import { ChatParser } from "./base.js";
2
+ import { convertToMarkdown } from "../utils/html-to-markdown.js";
3
3
 
4
4
  async function getOrganizationId() {
5
5
  try {
6
- const response = await fetch('https://claude.ai/api/organizations', {
7
- credentials: 'include',
6
+ const response = await fetch("https://claude.ai/api/organizations", {
7
+ credentials: "include",
8
8
  headers: {
9
- Accept: 'application/json',
9
+ Accept: "application/json",
10
10
  },
11
11
  });
12
12
  if (!response.ok) return null;
13
13
  const orgs = await response.json();
14
14
  if (Array.isArray(orgs) && orgs.length > 0) {
15
- const chatOrg = orgs.find((org) => org.capabilities && org.capabilities.includes('chat'));
15
+ const chatOrg = orgs.find(
16
+ (org) => org.capabilities && org.capabilities.includes("chat"),
17
+ );
16
18
  return chatOrg ? chatOrg.uuid : orgs[0].uuid;
17
19
  }
18
20
  } catch (e) {
19
- console.error('[AI Exporter] Failed to detect org ID:', e);
21
+ console.error("[AI Exporter] Failed to detect org ID:", e);
20
22
  }
21
23
  return null;
22
24
  }
23
25
 
24
26
  function getConversationId() {
25
27
  try {
26
- if (typeof window === 'undefined' || !window.location) return null;
28
+ if (typeof window === "undefined" || !window.location) return null;
27
29
  return window.location.pathname.match(/\/chat\/([^/?#]+)/)?.[1] ?? null;
28
30
  } catch {
29
31
  return null;
@@ -33,9 +35,9 @@ function getConversationId() {
33
35
  async function fetchConversation(orgId, conversationId) {
34
36
  const url = `https://claude.ai/api/organizations/${orgId}/chat_conversations/${conversationId}?tree=True&rendering_mode=messages&render_all_tools=true`;
35
37
  const response = await fetch(url, {
36
- credentials: 'include',
38
+ credentials: "include",
37
39
  headers: {
38
- Accept: 'application/json',
40
+ Accept: "application/json",
39
41
  },
40
42
  });
41
43
  if (!response.ok) {
@@ -80,8 +82,8 @@ function extractArtifactsFromText(text) {
80
82
  const languageMatch = fullTag.match(/language="([^"]*)"/);
81
83
 
82
84
  artifacts.push({
83
- title: titleMatch ? titleMatch[1] : 'Artifact',
84
- language: languageMatch ? languageMatch[1] : 'text',
85
+ title: titleMatch ? titleMatch[1] : "Artifact",
86
+ language: languageMatch ? languageMatch[1] : "text",
85
87
  content: content.trim(),
86
88
  });
87
89
  }
@@ -93,39 +95,45 @@ function extractArtifacts(message) {
93
95
  if (message.content && Array.isArray(message.content)) {
94
96
  for (const content of message.content) {
95
97
  if (
96
- content.type === 'tool_use' &&
97
- (content.name === 'artifacts' || content.name === 'create_file') &&
98
+ content.type === "tool_use" &&
99
+ (content.name === "artifacts" || content.name === "create_file") &&
98
100
  content.display_content
99
101
  ) {
100
102
  const displayContent = content.display_content;
101
- if (displayContent.type === 'code_block' && displayContent.code) {
102
- const filename = displayContent.filename || 'artifact';
103
+ if (displayContent.type === "code_block" && displayContent.code) {
104
+ const filename = displayContent.filename || "artifact";
103
105
  const title = filename
104
- .split('/')
106
+ .split("/")
105
107
  .pop()
106
- .replace(/\.[^.]+$/, '');
108
+ .replace(/\.[^.]+$/, "");
107
109
  artifacts.push({
108
- title: title || 'Artifact',
109
- language: displayContent.language || 'text',
110
+ title: title || "Artifact",
111
+ language: displayContent.language || "text",
110
112
  content: displayContent.code.trim(),
111
113
  });
112
- } else if (displayContent.type === 'json_block' && displayContent.json_block) {
114
+ } else if (
115
+ displayContent.type === "json_block" &&
116
+ displayContent.json_block
117
+ ) {
113
118
  try {
114
119
  const data = JSON.parse(displayContent.json_block);
115
120
  if (data.filename) {
116
121
  const filename = data.filename;
117
122
  const title = filename
118
- .split('/')
123
+ .split("/")
119
124
  .pop()
120
- .replace(/\.[^.]+$/, '');
125
+ .replace(/\.[^.]+$/, "");
121
126
  artifacts.push({
122
- title: title || 'Artifact',
123
- language: data.language || 'text',
124
- content: (data.code || '').trim(),
127
+ title: title || "Artifact",
128
+ language: data.language || "text",
129
+ content: (data.code || "").trim(),
125
130
  });
126
131
  }
127
132
  } catch (e) {
128
- console.warn('[AI Exporter] Failed to parse tool use artifact json:', e);
133
+ console.warn(
134
+ "[AI Exporter] Failed to parse tool use artifact json:",
135
+ e,
136
+ );
129
137
  }
130
138
  }
131
139
  }
@@ -141,24 +149,24 @@ function extractArtifacts(message) {
141
149
  }
142
150
 
143
151
  export class ClaudeParser extends ChatParser {
144
- name = 'Claude';
152
+ name = "Claude";
145
153
  constructor() {
146
154
  super();
147
155
  this.lastFetch = null;
148
156
  }
149
157
 
150
158
  isAvailable(url) {
151
- return url.includes('claude.ai');
159
+ return url.includes("claude.ai");
152
160
  }
153
161
 
154
162
  async parse(options = {}) {
155
- const title = document.title || 'Claude Chat';
163
+ const title = document.title || "Claude Chat";
156
164
  const messages = [];
157
165
 
158
166
  const conversationId = getConversationId();
159
- const parserMode = options.parserMode || 'auto';
167
+ const parserMode = options.parserMode || "auto";
160
168
 
161
- if (conversationId && parserMode !== 'prefer_dom') {
169
+ if (conversationId && parserMode !== "prefer_dom") {
162
170
  const orgId = await getOrganizationId();
163
171
  if (orgId) {
164
172
  try {
@@ -185,18 +193,18 @@ export class ClaudeParser extends ChatParser {
185
193
  const convTitle = data.name || title;
186
194
 
187
195
  for (const message of branch) {
188
- const role = message.sender === 'human' ? 'User' : 'Claude';
196
+ const role = message.sender === "human" ? "User" : "Claude";
189
197
 
190
- let contentStr = '';
198
+ let contentStr = "";
191
199
 
192
200
  // Construct content
193
201
  if (message.content && Array.isArray(message.content)) {
194
202
  for (const block of message.content) {
195
- if (block.type === 'thinking' && block.thinking) {
196
- contentStr += `> **Thinking Process:**\n> \n> ${block.thinking.replace(/\n/g, '\n> ')}\n\n`;
197
- } else if (block.type === 'text' && block.text) {
203
+ if (block.type === "thinking" && block.thinking) {
204
+ contentStr += `> **Thinking Process:**\n> \n> ${block.thinking.replace(/\n/g, "\n> ")}\n\n`;
205
+ } else if (block.type === "text" && block.text) {
198
206
  const cleanText = block.text
199
- .replace(/<antArtifact[^>]*>[\s\S]*?<\/antArtifact>/g, '')
207
+ .replace(/<antArtifact[^>]*>[\s\S]*?<\/antArtifact>/g, "")
200
208
  .trim();
201
209
  if (cleanText) {
202
210
  contentStr += `${cleanText}\n\n`;
@@ -205,7 +213,7 @@ export class ClaudeParser extends ChatParser {
205
213
  }
206
214
  } else if (message.text) {
207
215
  const cleanText = message.text
208
- .replace(/<antArtifact[^>]*>[\s\S]*?<\/antArtifact>/g, '')
216
+ .replace(/<antArtifact[^>]*>[\s\S]*?<\/antArtifact>/g, "")
209
217
  .trim();
210
218
  if (cleanText) {
211
219
  contentStr += `${cleanText}\n\n`;
@@ -225,7 +233,7 @@ export class ClaudeParser extends ChatParser {
225
233
  meta.push(attachment.file_type);
226
234
  }
227
235
  if (meta.length > 0) {
228
- header += ` _(${meta.join(', ')})_`;
236
+ header += ` _(${meta.join(", ")})_`;
229
237
  }
230
238
  contentStr += `\n\n${header}\n`;
231
239
  if (attachment.extracted_content) {
@@ -245,49 +253,54 @@ export class ClaudeParser extends ChatParser {
245
253
  // Extract and push artifacts
246
254
  const artifacts = extractArtifacts(message);
247
255
  for (const artifact of artifacts) {
248
- let artContent = '';
249
- const artTitle = artifact.title || 'Artifact';
250
- const artText = artifact.content || '';
251
- const artLang = artifact.language || 'text';
256
+ let artContent = "";
257
+ const artTitle = artifact.title || "Artifact";
258
+ const artText = artifact.content || "";
259
+ const artLang = artifact.language || "text";
252
260
 
253
- if (artLang === 'markdown' || artLang === 'text') {
261
+ if (artLang === "markdown" || artLang === "text") {
254
262
  const quotedContent = artText
255
- .split('\n')
263
+ .split("\n")
256
264
  .map((line) => `> ${line}`)
257
- .join('\n');
265
+ .join("\n");
258
266
  artContent = `\n\n> **Artifact: ${artTitle}**\n\n${quotedContent}\n\n`;
259
267
  } else {
260
268
  artContent = `\n\n> **Artifact: ${artTitle}**\n\`\`\`${artLang}\n${artText}\n\`\`\`\n\n`;
261
269
  }
262
270
 
263
271
  messages.push({
264
- role: 'Claude Artifact',
272
+ role: "Claude Artifact",
265
273
  content: artContent.trim(),
266
274
  });
267
275
  }
268
276
  }
269
277
 
270
278
  const currentUrl =
271
- typeof window !== 'undefined' && window.location ? window.location.href || '' : '';
279
+ typeof window !== "undefined" && window.location
280
+ ? window.location.href || ""
281
+ : "";
272
282
  const metadata = {
273
- Source: 'Claude',
283
+ Source: "Claude",
274
284
  Date: new Date().toLocaleString(),
275
285
  Link: currentUrl,
276
- Model: data.model || 'Claude',
286
+ Model: data.model || "Claude",
277
287
  };
278
288
 
279
289
  return { title: convTitle, messages, url: currentUrl, metadata };
280
290
  } catch (e) {
281
- console.error('[AI Exporter] Claude API parse failed, falling back to DOM:', e);
291
+ console.error(
292
+ "[AI Exporter] Claude API parse failed, falling back to DOM:",
293
+ e,
294
+ );
282
295
  }
283
296
  }
284
297
  }
285
298
 
286
299
  // Inject the React reader script if not already injected (DOM Fallback)
287
- if (!document.getElementById('ai-export-claude-reader')) {
288
- const script = document.createElement('script');
289
- script.src = chrome.runtime.getURL('content/claude_react_reader.js');
290
- script.id = 'ai-export-claude-reader';
300
+ if (!document.getElementById("ai-export-claude-reader")) {
301
+ const script = document.createElement("script");
302
+ script.src = chrome.runtime.getURL("content/claude_react_reader.js");
303
+ script.id = "ai-export-claude-reader";
291
304
  script.onload = function () {
292
305
  this.remove(); // Clean up script tag
293
306
  };
@@ -300,17 +313,20 @@ export class ClaudeParser extends ChatParser {
300
313
  const getArtifactInfo = (index) => {
301
314
  return new Promise((resolve) => {
302
315
  const handler = (event) => {
303
- if (event.data.type === 'RspAtftInfo' && event.data.idx === index) {
304
- window.removeEventListener('message', handler);
316
+ if (event.data.type === "RspAtftInfo" && event.data.idx === index) {
317
+ window.removeEventListener("message", handler);
305
318
  resolve(event.data.atftInfo);
306
319
  }
307
320
  };
308
- window.addEventListener('message', handler);
309
- window.postMessage({ type: 'ReqAtftInfo', idx: index }, window.location.origin);
321
+ window.addEventListener("message", handler);
322
+ window.postMessage(
323
+ { type: "ReqAtftInfo", idx: index },
324
+ window.location.origin,
325
+ );
310
326
 
311
327
  // Timeout fallback
312
328
  setTimeout(() => {
313
- window.removeEventListener('message', handler);
329
+ window.removeEventListener("message", handler);
314
330
  resolve(null);
315
331
  }, 1000); // 1s timeout
316
332
  });
@@ -318,15 +334,19 @@ export class ClaudeParser extends ChatParser {
318
334
 
319
335
  const strictSelectors = [
320
336
  '[data-testid="user-message"]',
321
- '.font-claude-message',
322
- '.font-claude-response',
323
- '.artifact-block-cell',
324
- ].join(', ');
337
+ ".font-claude-message",
338
+ ".font-claude-response",
339
+ ".artifact-block-cell",
340
+ ].join(", ");
325
341
 
326
- const fallbackSelectors = ['div.font-serif'].join(', ');
342
+ const fallbackSelectors = ["div.font-serif"].join(", ");
327
343
 
328
- const strictCandidates = Array.from(document.querySelectorAll(strictSelectors));
329
- const fallbackCandidates = Array.from(document.querySelectorAll(fallbackSelectors));
344
+ const strictCandidates = Array.from(
345
+ document.querySelectorAll(strictSelectors),
346
+ );
347
+ const fallbackCandidates = Array.from(
348
+ document.querySelectorAll(fallbackSelectors),
349
+ );
330
350
 
331
351
  const validFallbacks = fallbackCandidates.filter((fallback) => {
332
352
  const overlapsWithError = strictCandidates.some(
@@ -338,54 +358,59 @@ export class ClaudeParser extends ChatParser {
338
358
  const combined = [...new Set([...strictCandidates, ...validFallbacks])];
339
359
 
340
360
  const allElements = combined.sort((a, b) => {
341
- return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
361
+ return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING
362
+ ? -1
363
+ : 1;
342
364
  });
343
365
 
344
- const artifactElements = document.querySelectorAll('.artifact-block-cell');
366
+ const artifactElements = document.querySelectorAll(".artifact-block-cell");
345
367
  const artifactMap = new Map();
346
368
  artifactElements.forEach((el, index) => artifactMap.set(el, index));
347
369
 
348
370
  for (const el of allElements) {
349
- let role = 'Unknown';
350
- let content = '';
371
+ let role = "Unknown";
372
+ let content = "";
351
373
 
352
374
  if (el.matches('[data-testid="user-message"]')) {
353
- role = 'User';
375
+ role = "User";
354
376
  const clone = el.cloneNode(true);
355
- clone.querySelectorAll('button').forEach((btn) => btn.remove());
377
+ clone.querySelectorAll("button").forEach((btn) => btn.remove());
356
378
  content = convertToMarkdown(clone);
357
379
  } else if (
358
- el.matches('.font-claude-message') ||
359
- el.matches('.font-claude-response') ||
360
- el.matches('div.font-serif')
380
+ el.matches(".font-claude-message") ||
381
+ el.matches(".font-claude-response") ||
382
+ el.matches("div.font-serif")
361
383
  ) {
362
- role = 'Claude';
384
+ role = "Claude";
363
385
  const clone = el.cloneNode(true);
364
- clone.querySelectorAll('button').forEach((btn) => btn.remove());
386
+ clone.querySelectorAll("button").forEach((btn) => btn.remove());
365
387
  content = convertToMarkdown(clone);
366
- } else if (el.matches('.artifact-block-cell')) {
367
- role = 'Claude Artifact';
388
+ } else if (el.matches(".artifact-block-cell")) {
389
+ role = "Claude Artifact";
368
390
 
369
391
  const index = artifactMap.get(el);
370
392
  if (index !== undefined) {
371
393
  const info = await getArtifactInfo(index);
372
394
  if (info) {
373
- const artTitle = info.title || 'Artifact';
374
- const artContent = info.content || '';
375
- const artLang = info.language || 'text';
376
- if (artLang === 'markdown' || artLang === 'text') {
395
+ const artTitle = info.title || "Artifact";
396
+ const artContent = info.content || "";
397
+ const artLang = info.language || "text";
398
+ if (artLang === "markdown" || artLang === "text") {
377
399
  const quotedContent = artContent
378
- .split('\n')
400
+ .split("\n")
379
401
  .map((line) => `> ${line}`)
380
- .join('\n');
402
+ .join("\n");
381
403
  content = `\n\n> **Artifact: ${artTitle}**\n\n${quotedContent}\n\n`;
382
404
  } else {
383
405
  content = `\n\n> **Artifact: ${artTitle}**\n\`\`\`${artLang}\n${artContent}\n\`\`\`\n\n`;
384
406
  }
385
407
  } else {
386
408
  const header =
387
- el.querySelector('.flex.items-center.gap-2') || el.querySelector('.font-bold');
388
- const fallbackTitle = header ? header.innerText.split('\n')[0] : 'Unknown Artifact';
409
+ el.querySelector(".flex.items-center.gap-2") ||
410
+ el.querySelector(".font-bold");
411
+ const fallbackTitle = header
412
+ ? header.innerText.split("\n")[0]
413
+ : "Unknown Artifact";
389
414
  content = `\n> [Artifact: ${fallbackTitle} - content extraction failed]\n`;
390
415
  }
391
416
  }
@@ -397,12 +422,14 @@ export class ClaudeParser extends ChatParser {
397
422
  }
398
423
 
399
424
  const currentUrl =
400
- typeof window !== 'undefined' && window.location ? window.location.href || '' : '';
425
+ typeof window !== "undefined" && window.location
426
+ ? window.location.href || ""
427
+ : "";
401
428
  const metadata = {
402
- Source: 'Claude',
429
+ Source: "Claude",
403
430
  Date: new Date().toLocaleString(),
404
431
  Link: currentUrl,
405
- Model: 'Claude',
432
+ Model: "Claude",
406
433
  };
407
434
 
408
435
  return { title, messages, url: currentUrl, metadata };
@@ -1,12 +1,12 @@
1
1
  (() => {
2
2
  // Listen for messages from the content script
3
- window.addEventListener('message', (event) => {
3
+ window.addEventListener("message", (event) => {
4
4
  // Security check: ensure message is from same origin
5
5
  if (event.origin !== window.location.origin) return;
6
6
 
7
- if (event.data.type === 'ReqAtftInfo') {
7
+ if (event.data.type === "ReqAtftInfo") {
8
8
  const index = event.data.idx;
9
- const artifacts = document.querySelectorAll('div.artifact-block-cell');
9
+ const artifacts = document.querySelectorAll("div.artifact-block-cell");
10
10
  const artifactElement = artifacts[index];
11
11
 
12
12
  let artifactInfo = null;
@@ -14,7 +14,9 @@
14
14
  if (artifactElement) {
15
15
  try {
16
16
  // Try to find the React Fiber key
17
- const key = Object.keys(artifactElement).find((k) => k.startsWith('__reactFiber'));
17
+ const key = Object.keys(artifactElement).find((k) =>
18
+ k.startsWith("__reactFiber"),
19
+ );
18
20
  if (key) {
19
21
  const fiber = artifactElement[key];
20
22
  // Navigate React props structure to find the artifact data
@@ -42,13 +44,13 @@
42
44
  }
43
45
  }
44
46
  } catch (e) {
45
- console.error('[AI Export] Error reading React internals:', e);
47
+ console.error("[AI Export] Error reading React internals:", e);
46
48
  }
47
49
  }
48
50
 
49
51
  window.postMessage(
50
52
  {
51
- type: 'RspAtftInfo',
53
+ type: "RspAtftInfo",
52
54
  idx: index,
53
55
  atftInfo: artifactInfo,
54
56
  },