@husk-ai/sessions 0.1.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.
Files changed (69) hide show
  1. package/dist/chat.d.ts +11 -0
  2. package/dist/chat.d.ts.map +1 -0
  3. package/dist/chat.js +2 -0
  4. package/dist/chat.js.map +1 -0
  5. package/dist/distill.d.ts +130 -0
  6. package/dist/distill.d.ts.map +1 -0
  7. package/dist/distill.js +773 -0
  8. package/dist/distill.js.map +1 -0
  9. package/dist/distiller.d.ts +18 -0
  10. package/dist/distiller.d.ts.map +1 -0
  11. package/dist/distiller.js +23 -0
  12. package/dist/distiller.js.map +1 -0
  13. package/dist/formatter.d.ts +8 -0
  14. package/dist/formatter.d.ts.map +1 -0
  15. package/dist/formatter.js +11 -0
  16. package/dist/formatter.js.map +1 -0
  17. package/dist/importers/chatgpt.d.ts +12 -0
  18. package/dist/importers/chatgpt.d.ts.map +1 -0
  19. package/dist/importers/chatgpt.js +223 -0
  20. package/dist/importers/chatgpt.js.map +1 -0
  21. package/dist/importers/claude.d.ts +37 -0
  22. package/dist/importers/claude.d.ts.map +1 -0
  23. package/dist/importers/claude.js +512 -0
  24. package/dist/importers/claude.js.map +1 -0
  25. package/dist/importers/cursor.d.ts +12 -0
  26. package/dist/importers/cursor.d.ts.map +1 -0
  27. package/dist/importers/cursor.js +229 -0
  28. package/dist/importers/cursor.js.map +1 -0
  29. package/dist/importers/fsutil.d.ts +22 -0
  30. package/dist/importers/fsutil.d.ts.map +1 -0
  31. package/dist/importers/fsutil.js +79 -0
  32. package/dist/importers/fsutil.js.map +1 -0
  33. package/dist/importers/gemini.d.ts +13 -0
  34. package/dist/importers/gemini.d.ts.map +1 -0
  35. package/dist/importers/gemini.js +228 -0
  36. package/dist/importers/gemini.js.map +1 -0
  37. package/dist/importers/index.d.ts +70 -0
  38. package/dist/importers/index.d.ts.map +1 -0
  39. package/dist/importers/index.js +258 -0
  40. package/dist/importers/index.js.map +1 -0
  41. package/dist/importers/markdown.d.ts +14 -0
  42. package/dist/importers/markdown.d.ts.map +1 -0
  43. package/dist/importers/markdown.js +188 -0
  44. package/dist/importers/markdown.js.map +1 -0
  45. package/dist/importers/universal.d.ts +16 -0
  46. package/dist/importers/universal.d.ts.map +1 -0
  47. package/dist/importers/universal.js +106 -0
  48. package/dist/importers/universal.js.map +1 -0
  49. package/dist/index.d.ts +17 -0
  50. package/dist/index.d.ts.map +1 -0
  51. package/dist/index.js +16 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/prompts.d.ts +151 -0
  54. package/dist/prompts.d.ts.map +1 -0
  55. package/dist/prompts.js +109 -0
  56. package/dist/prompts.js.map +1 -0
  57. package/dist/redactor.d.ts +38 -0
  58. package/dist/redactor.d.ts.map +1 -0
  59. package/dist/redactor.js +144 -0
  60. package/dist/redactor.js.map +1 -0
  61. package/dist/scaffold.d.ts +40 -0
  62. package/dist/scaffold.d.ts.map +1 -0
  63. package/dist/scaffold.js +0 -0
  64. package/dist/scaffold.js.map +1 -0
  65. package/dist/serialize.d.ts +15 -0
  66. package/dist/serialize.d.ts.map +1 -0
  67. package/dist/serialize.js +122 -0
  68. package/dist/serialize.js.map +1 -0
  69. package/package.json +28 -0
@@ -0,0 +1,70 @@
1
+ import type { ImportInput, Transcript, TranscriptImporter, TranscriptSource } from '@husk-ai/core';
2
+ import { UniversalImporter } from './universal.js';
3
+ export * from './claude.js';
4
+ export * from './chatgpt.js';
5
+ export * from './cursor.js';
6
+ export * from './gemini.js';
7
+ export * from './markdown.js';
8
+ export * from './universal.js';
9
+ export * from './fsutil.js';
10
+ /** Every importer that works without a model, in no particular order. */
11
+ export declare function builtinImporters(): TranscriptImporter[];
12
+ export interface DetectResult {
13
+ importer: TranscriptImporter;
14
+ confidence: number;
15
+ }
16
+ /** Score every importer against the input. Never throws; a thrown detect scores 0. */
17
+ export declare function detectAll(input: {
18
+ path?: string;
19
+ content?: string;
20
+ }, importers?: TranscriptImporter[]): Promise<DetectResult[]>;
21
+ export interface DetectAndParseResult {
22
+ transcripts: Transcript[];
23
+ source: TranscriptSource;
24
+ confidence: number;
25
+ /** Every importer that scored above zero, best first. Useful in a picker. */
26
+ candidates: Array<{
27
+ source: TranscriptSource;
28
+ confidence: number;
29
+ }>;
30
+ }
31
+ /**
32
+ * Work out what a file is and read it.
33
+ *
34
+ * `source` on the input short-circuits detection. Otherwise every importer is
35
+ * scored in parallel and tried best-first, because a confident detect can still
36
+ * find zero messages -- a `.jsonl` that turns out to be a log, say.
37
+ */
38
+ export declare function detectAndParse(input: ImportInput, importers?: TranscriptImporter[]): Promise<DetectAndParseResult>;
39
+ export interface DiscoveredTranscript {
40
+ id: string;
41
+ source: TranscriptSource;
42
+ title: string;
43
+ messageCount: number;
44
+ updatedAt: string;
45
+ origin: string;
46
+ /** true when messageCount was extrapolated from a head/tail sample. */
47
+ approximate?: boolean;
48
+ bytes?: number;
49
+ }
50
+ export interface DiscoverOptions {
51
+ /** Restrict to these sources. Default: claude-code, chatgpt, gemini, cursor. */
52
+ sources?: TranscriptSource[];
53
+ /** Skip anything with fewer messages than this. Default 2. */
54
+ minMessages?: number;
55
+ /** Cap the result set. Newest first, so a cap keeps the useful ones. */
56
+ limit?: number;
57
+ concurrency?: number;
58
+ /** Extra directories to scan on top of each importer's defaults. */
59
+ extraLocations?: string[];
60
+ }
61
+ /**
62
+ * Everything importable on this machine, newest first.
63
+ *
64
+ * Stats and two window reads per file. It does not open a transcript to count
65
+ * its messages, because there are thousands of them and the picker has to paint
66
+ * before the user gets bored.
67
+ */
68
+ export declare function discover(opts?: DiscoverOptions): Promise<DiscoveredTranscript[]>;
69
+ export { UniversalImporter as ModelAssistedImporter };
70
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/importers/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAMnG,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAGnD,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAE5B,yEAAyE;AACzE,wBAAgB,gBAAgB,IAAI,kBAAkB,EAAE,CAQvD;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,sFAAsF;AACtF,wBAAsB,SAAS,CAC7B,KAAK,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,EAC1C,SAAS,GAAE,kBAAkB,EAAuB,GACnD,OAAO,CAAC,YAAY,EAAE,CAAC,CAYzB;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,MAAM,EAAE,gBAAgB,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,UAAU,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,gBAAgB,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACrE;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,WAAW,EAClB,SAAS,GAAE,kBAAkB,EAAuB,GACnD,OAAO,CAAC,oBAAoB,CAAC,CA8C/B;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,gBAAgB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,gFAAgF;IAChF,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC7B,8DAA8D;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAyGD;;;;;;GAMG;AACH,wBAAsB,QAAQ,CAAC,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAwD1F;AAED,OAAO,EAAE,iBAAiB,IAAI,qBAAqB,EAAE,CAAC"}
@@ -0,0 +1,258 @@
1
+ import { basename, extname, join } from 'node:path';
2
+ import { mapLimit } from '@husk-ai/core';
3
+ import { ClaudeCodeImporter, parseClaudeJsonl } from './claude.js';
4
+ import { ChatGPTImporter } from './chatgpt.js';
5
+ import { CursorImporter } from './cursor.js';
6
+ import { GeminiImporter } from './gemini.js';
7
+ import { MarkdownImporter } from './markdown.js';
8
+ import { UniversalImporter } from './universal.js';
9
+ import { DEFAULT_WINDOW, listDirs, listFiles, readHeadTail, wholeLines } from './fsutil.js';
10
+ export * from './claude.js';
11
+ export * from './chatgpt.js';
12
+ export * from './cursor.js';
13
+ export * from './gemini.js';
14
+ export * from './markdown.js';
15
+ export * from './universal.js';
16
+ export * from './fsutil.js';
17
+ /** Every importer that works without a model, in no particular order. */
18
+ export function builtinImporters() {
19
+ return [
20
+ new ClaudeCodeImporter(),
21
+ new ChatGPTImporter(),
22
+ new CursorImporter(),
23
+ new GeminiImporter(),
24
+ new MarkdownImporter(),
25
+ ];
26
+ }
27
+ /** Score every importer against the input. Never throws; a thrown detect scores 0. */
28
+ export async function detectAll(input, importers = builtinImporters()) {
29
+ const scored = await Promise.all(importers.map(async (importer) => {
30
+ try {
31
+ const confidence = await importer.detect(input);
32
+ return { importer, confidence: Number.isFinite(confidence) ? confidence : 0 };
33
+ }
34
+ catch {
35
+ return { importer, confidence: 0 };
36
+ }
37
+ }));
38
+ return scored.sort((a, b) => b.confidence - a.confidence);
39
+ }
40
+ /**
41
+ * Work out what a file is and read it.
42
+ *
43
+ * `source` on the input short-circuits detection. Otherwise every importer is
44
+ * scored in parallel and tried best-first, because a confident detect can still
45
+ * find zero messages -- a `.jsonl` that turns out to be a log, say.
46
+ */
47
+ export async function detectAndParse(input, importers = builtinImporters()) {
48
+ if (input.source) {
49
+ const forced = importers.find((i) => i.id === input.source);
50
+ if (forced) {
51
+ return {
52
+ transcripts: await forced.parse(input),
53
+ source: forced.id,
54
+ confidence: 1,
55
+ candidates: [{ source: forced.id, confidence: 1 }],
56
+ };
57
+ }
58
+ }
59
+ // Detection wants content. Read a window rather than the whole file when we
60
+ // only have a path, so sniffing an 80 MB transcript stays cheap.
61
+ let probe = input.content;
62
+ if (probe === undefined && input.path) {
63
+ try {
64
+ const ht = await readHeadTail(input.path, 32 * 1024);
65
+ probe = ht.head;
66
+ }
67
+ catch {
68
+ probe = undefined;
69
+ }
70
+ }
71
+ const detectInput = {
72
+ ...(input.path ? { path: input.path } : {}),
73
+ ...(probe !== undefined ? { content: probe } : {}),
74
+ };
75
+ const scored = await detectAll(detectInput, importers);
76
+ const candidates = scored
77
+ .filter((s) => s.confidence > 0)
78
+ .map((s) => ({ source: s.importer.id, confidence: s.confidence }));
79
+ for (const { importer, confidence } of scored) {
80
+ if (confidence <= 0)
81
+ continue;
82
+ try {
83
+ const transcripts = await importer.parse(input);
84
+ if (transcripts.some((t) => t.messages.length > 0)) {
85
+ return { transcripts, source: importer.id, confidence, candidates };
86
+ }
87
+ }
88
+ catch {
89
+ // Try the next importer. A confident-but-wrong guess is common.
90
+ }
91
+ }
92
+ return { transcripts: [], source: 'unknown', confidence: 0, candidates };
93
+ }
94
+ const TITLE_RE = /"(?:customTitle|aiTitle|summary)"\s*:\s*"((?:[^"\\]|\\.)*)"/g;
95
+ const TIMESTAMP_RE = /"timestamp"\s*:\s*"((?:[^"\\]|\\.)*)"/g;
96
+ function lastMatch(text, re) {
97
+ re.lastIndex = 0;
98
+ let out;
99
+ let m;
100
+ while ((m = re.exec(text)) !== null)
101
+ out = m[1];
102
+ if (out === undefined)
103
+ return undefined;
104
+ try {
105
+ return JSON.parse(`"${out}"`);
106
+ }
107
+ catch {
108
+ return out;
109
+ }
110
+ }
111
+ /**
112
+ * Summarise a Claude JSONL from its first and last 96 KB.
113
+ *
114
+ * Exact when the file fits in the window. Otherwise the message count is a
115
+ * density extrapolation and `approximate` says so -- a picker may not lie about
116
+ * a number it did not count.
117
+ */
118
+ async function summariseClaudeFile(path) {
119
+ let ht = await readHeadTail(path, DEFAULT_WINDOW);
120
+ if (ht.size === 0)
121
+ return undefined;
122
+ let headLines = wholeLines(ht.head, false, !ht.complete);
123
+ if (!headLines.length && !ht.complete) {
124
+ // A single line longer than the window. Widen once, then give up on exactness.
125
+ ht = await readHeadTail(path, 1024 * 1024);
126
+ headLines = wholeLines(ht.head, false, !ht.complete);
127
+ }
128
+ const tailLines = ht.complete ? [] : wholeLines(ht.tail, true, false);
129
+ if (ht.complete) {
130
+ const parsed = parseClaudeJsonl(ht.head, path);
131
+ if (!parsed)
132
+ return undefined;
133
+ const t = parsed.transcript;
134
+ return {
135
+ id: t.id,
136
+ source: 'claude-code',
137
+ title: t.title ?? basename(path, extname(path)),
138
+ messageCount: t.messages.length,
139
+ updatedAt: t.updatedAt ?? ht.mtime.toISOString(),
140
+ origin: path,
141
+ bytes: ht.size,
142
+ };
143
+ }
144
+ const sample = [...headLines, ...tailLines];
145
+ let bearing = 0;
146
+ let sampledBytes = 0;
147
+ let sessionId;
148
+ for (const line of sample) {
149
+ sampledBytes += Buffer.byteLength(line, 'utf8') + 1;
150
+ try {
151
+ const obj = JSON.parse(line);
152
+ if (typeof obj !== 'object' || obj === null)
153
+ continue;
154
+ const rec = obj;
155
+ if (typeof rec.sessionId === 'string')
156
+ sessionId ??= rec.sessionId;
157
+ if ((rec.type === 'user' || rec.type === 'assistant') && rec.message)
158
+ bearing++;
159
+ }
160
+ catch {
161
+ // Partial line at a window edge.
162
+ }
163
+ }
164
+ const density = sampledBytes > 0 ? bearing / sampledBytes : 0;
165
+ const estimate = Math.max(bearing, Math.round(density * ht.size));
166
+ const window = ht.head + '\n' + ht.tail;
167
+ return {
168
+ id: sessionId ?? basename(path, extname(path)),
169
+ source: 'claude-code',
170
+ title: lastMatch(window, TITLE_RE) ?? basename(path, extname(path)),
171
+ messageCount: estimate,
172
+ updatedAt: lastMatch(ht.tail, TIMESTAMP_RE) ?? ht.mtime.toISOString(),
173
+ origin: path,
174
+ approximate: true,
175
+ bytes: ht.size,
176
+ };
177
+ }
178
+ async function summariseGenericJson(path, source) {
179
+ const ht = await readHeadTail(path, DEFAULT_WINDOW);
180
+ if (ht.size === 0)
181
+ return undefined;
182
+ const window = ht.head + ht.tail;
183
+ const titleMatch = /"title"\s*:\s*"((?:[^"\\]|\\.)*)"/.exec(window);
184
+ // Counting turns needs the whole document; the picker gets a marker instead.
185
+ return {
186
+ id: basename(path, extname(path)),
187
+ source,
188
+ title: titleMatch?.[1] ?? basename(path),
189
+ messageCount: 0,
190
+ updatedAt: ht.mtime.toISOString(),
191
+ origin: path,
192
+ approximate: true,
193
+ bytes: ht.size,
194
+ };
195
+ }
196
+ /**
197
+ * Everything importable on this machine, newest first.
198
+ *
199
+ * Stats and two window reads per file. It does not open a transcript to count
200
+ * its messages, because there are thousands of them and the picker has to paint
201
+ * before the user gets bored.
202
+ */
203
+ export async function discover(opts = {}) {
204
+ const sources = new Set(opts.sources ?? ['claude-code', 'chatgpt', 'gemini', 'cursor']);
205
+ const minMessages = opts.minMessages ?? 2;
206
+ const concurrency = opts.concurrency ?? 24;
207
+ const jobs = [];
208
+ if (sources.has('claude-code')) {
209
+ const claude = new ClaudeCodeImporter();
210
+ for (const root of [...claude.defaultLocations(), ...(opts.extraLocations ?? [])]) {
211
+ for (const dir of await listDirs(root)) {
212
+ for (const f of await listFiles(dir, '.jsonl'))
213
+ jobs.push({ path: f, source: 'claude-code' });
214
+ }
215
+ for (const f of await listFiles(root, '.jsonl'))
216
+ jobs.push({ path: f, source: 'claude-code' });
217
+ }
218
+ }
219
+ if (sources.has('chatgpt')) {
220
+ for (const root of new ChatGPTImporter().defaultLocations()) {
221
+ for (const f of await listFiles(root, 'conversations.json'))
222
+ jobs.push({ path: f, source: 'chatgpt' });
223
+ }
224
+ }
225
+ if (sources.has('gemini')) {
226
+ for (const root of new GeminiImporter().defaultLocations()) {
227
+ for (const dir of [root, ...(await listDirs(root))]) {
228
+ for (const f of await listFiles(join(dir, 'chats'), '.json'))
229
+ jobs.push({ path: f, source: 'gemini' });
230
+ for (const f of await listFiles(dir, '.json'))
231
+ jobs.push({ path: f, source: 'gemini' });
232
+ }
233
+ }
234
+ }
235
+ if (sources.has('cursor')) {
236
+ for (const root of new CursorImporter().defaultLocations()) {
237
+ for (const f of await listFiles(root, '.vscdb'))
238
+ jobs.push({ path: f, source: 'cursor' });
239
+ }
240
+ }
241
+ const results = await mapLimit(jobs, concurrency, async (job) => {
242
+ try {
243
+ return job.source === 'claude-code'
244
+ ? await summariseClaudeFile(job.path)
245
+ : await summariseGenericJson(job.path, job.source);
246
+ }
247
+ catch {
248
+ return undefined;
249
+ }
250
+ });
251
+ const out = results
252
+ .filter((r) => r !== undefined)
253
+ .filter((r) => r.messageCount >= minMessages || r.source !== 'claude-code')
254
+ .sort((a, b) => (a.updatedAt < b.updatedAt ? 1 : a.updatedAt > b.updatedAt ? -1 : 0));
255
+ return opts.limit ? out.slice(0, opts.limit) : out;
256
+ }
257
+ export { UniversalImporter as ModelAssistedImporter };
258
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/importers/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { ImportInput, Transcript, TranscriptImporter } from '@husk-ai/core';
2
+ export declare function parseMarkdownChat(content: string, origin?: string): Transcript | undefined;
3
+ /** The inverse of `parseMarkdownChat`. Round-trips through it unchanged. */
4
+ export declare function toMarkdownChat(transcript: Transcript): string;
5
+ export declare class MarkdownImporter implements TranscriptImporter {
6
+ readonly id: "markdown";
7
+ readonly displayName = "Markdown chat";
8
+ detect(input: {
9
+ path?: string;
10
+ content?: string;
11
+ }): Promise<number>;
12
+ parse(input: ImportInput): Promise<Transcript[]>;
13
+ }
14
+ //# sourceMappingURL=markdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/importers/markdown.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,kBAAkB,EAAqB,MAAM,eAAe,CAAC;AAwDpG,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CA6D1F;AASD,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAO7D;AAED,qBAAa,gBAAiB,YAAW,kBAAkB;IACzD,QAAQ,CAAC,EAAE,EAAG,UAAU,CAAU;IAClC,QAAQ,CAAC,WAAW,mBAAmB;IAEjC,MAAM,CAAC,KAAK,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAuBnE,KAAK,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;CAyBvD"}
@@ -0,0 +1,188 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { basename, extname } from 'node:path';
3
+ import { HuskError, id } from '@husk-ai/core';
4
+ import { isDirectory, listFiles } from './fsutil.js';
5
+ /**
6
+ * Markdown, and specifically the markdown a person produces by pasting a chat
7
+ * into a file. Three spellings are common and all three have to work:
8
+ *
9
+ * ## User / ### Assistant
10
+ * **Assistant:** / **User:**
11
+ * User: / Assistant:
12
+ */
13
+ const ROLE_WORDS = {
14
+ user: 'user',
15
+ human: 'user',
16
+ me: 'user',
17
+ you: 'assistant',
18
+ assistant: 'assistant',
19
+ ai: 'assistant',
20
+ bot: 'assistant',
21
+ claude: 'assistant',
22
+ chatgpt: 'assistant',
23
+ gpt: 'assistant',
24
+ model: 'assistant',
25
+ system: 'system',
26
+ tool: 'tool',
27
+ function: 'tool',
28
+ };
29
+ const ROLE_ALTERNATION = Object.keys(ROLE_WORDS).join('|');
30
+ /** `## User`, `### Assistant:`, `#### Tool` */
31
+ const HEADING = new RegExp(`^\\s{0,3}#{1,6}\\s+(?:\\*\\*)?(${ROLE_ALTERNATION})(?:\\*\\*)?\\s*:?\\s*$`, 'i');
32
+ /** `**Assistant:**`, `__User__:` */
33
+ const BOLD = new RegExp(`^\\s{0,3}(?:\\*\\*|__)(${ROLE_ALTERNATION})\\s*:?(?:\\*\\*|__)\\s*:?\\s*(.*)$`, 'i');
34
+ /** `User: hello` */
35
+ const PLAIN = new RegExp(`^\\s{0,3}(${ROLE_ALTERNATION})\\s*:\\s*(.*)$`, 'i');
36
+ /** `# My conversation` -- a heading that is not a role is the title. */
37
+ const TITLE = /^\s{0,3}#\s+(.+?)\s*$/;
38
+ function matchMarker(line, insideFence) {
39
+ if (insideFence)
40
+ return undefined;
41
+ const h = HEADING.exec(line);
42
+ if (h)
43
+ return { role: ROLE_WORDS[(h[1] ?? '').toLowerCase()] ?? 'user', rest: '' };
44
+ const b = BOLD.exec(line);
45
+ if (b)
46
+ return { role: ROLE_WORDS[(b[1] ?? '').toLowerCase()] ?? 'user', rest: (b[2] ?? '').trim() };
47
+ const p = PLAIN.exec(line);
48
+ if (p)
49
+ return { role: ROLE_WORDS[(p[1] ?? '').toLowerCase()] ?? 'user', rest: (p[2] ?? '').trim() };
50
+ return undefined;
51
+ }
52
+ export function parseMarkdownChat(content, origin) {
53
+ const lines = content.split(/\r?\n/);
54
+ const messages = [];
55
+ let title;
56
+ let role;
57
+ let buf = [];
58
+ let fence;
59
+ const flush = () => {
60
+ if (!role) {
61
+ buf = [];
62
+ return;
63
+ }
64
+ const text = buf.join('\n').trim();
65
+ buf = [];
66
+ if (text)
67
+ messages.push({ role, content: text });
68
+ };
69
+ for (const line of lines) {
70
+ const fenceMatch = /^\s{0,3}(`{3,}|~{3,})/.exec(line);
71
+ if (fenceMatch) {
72
+ const token = fenceMatch[1];
73
+ if (!fence)
74
+ fence = token[0] === '`' ? '`' : '~';
75
+ else if (token[0] === fence)
76
+ fence = undefined;
77
+ buf.push(line);
78
+ continue;
79
+ }
80
+ const marker = matchMarker(line, fence !== undefined);
81
+ if (marker) {
82
+ flush();
83
+ role = marker.role;
84
+ if (marker.rest)
85
+ buf.push(marker.rest);
86
+ continue;
87
+ }
88
+ if (!role && title === undefined) {
89
+ const t = TITLE.exec(line);
90
+ if (t) {
91
+ title = (t[1] ?? '').trim();
92
+ continue;
93
+ }
94
+ }
95
+ if (!role) {
96
+ // Text before the first marker is the opening user turn.
97
+ if (line.trim() === '')
98
+ continue;
99
+ role = 'user';
100
+ }
101
+ buf.push(line);
102
+ }
103
+ flush();
104
+ if (!messages.length)
105
+ return undefined;
106
+ return {
107
+ id: origin ? basename(origin, extname(origin)) : id('md'),
108
+ source: 'markdown',
109
+ messages,
110
+ ...(title ? { title } : {}),
111
+ ...(origin ? { origin } : {}),
112
+ };
113
+ }
114
+ const ROLE_HEADING = {
115
+ user: 'User',
116
+ assistant: 'Assistant',
117
+ system: 'System',
118
+ tool: 'Tool',
119
+ };
120
+ /** The inverse of `parseMarkdownChat`. Round-trips through it unchanged. */
121
+ export function toMarkdownChat(transcript) {
122
+ const out = [];
123
+ if (transcript.title)
124
+ out.push(`# ${transcript.title}`, '');
125
+ for (const m of transcript.messages) {
126
+ out.push(`## ${ROLE_HEADING[m.role]}`, '', m.content.trim(), '');
127
+ }
128
+ return out.join('\n').replace(/\n{3,}/g, '\n\n').trimEnd() + '\n';
129
+ }
130
+ export class MarkdownImporter {
131
+ id = 'markdown';
132
+ displayName = 'Markdown chat';
133
+ async detect(input) {
134
+ try {
135
+ let score = 0;
136
+ const path = input.path?.toLowerCase();
137
+ if (path?.endsWith('.md') || path?.endsWith('.markdown') || path?.endsWith('.txt'))
138
+ score = 0.3;
139
+ const probe = input.content?.slice(0, 32 * 1024);
140
+ if (probe) {
141
+ const hits = probe
142
+ .split(/\r?\n/)
143
+ .filter((l) => matchMarker(l, false) !== undefined).length;
144
+ if (hits >= 4)
145
+ score = Math.max(score, 0.85);
146
+ else if (hits >= 2)
147
+ score = Math.max(score, 0.6);
148
+ else if (hits === 1)
149
+ score = Math.max(score, 0.25);
150
+ // JSON is never markdown, whatever the extension says.
151
+ if (/^\s*[[{]/.test(probe))
152
+ score = Math.min(score, 0.05);
153
+ }
154
+ return score;
155
+ }
156
+ catch {
157
+ return 0;
158
+ }
159
+ }
160
+ async parse(input) {
161
+ if (input.content !== undefined) {
162
+ const t = parseMarkdownChat(input.content, input.path);
163
+ return t ? [t] : [];
164
+ }
165
+ if (!input.path) {
166
+ throw new HuskError('E_IMPORT_FAILED', 'Markdown import needs a path or content.', {
167
+ hint: 'Pass { path } to a .md file, or { content } with the pasted chat.',
168
+ });
169
+ }
170
+ const files = (await isDirectory(input.path)) ? await listFiles(input.path, '.md') : [input.path];
171
+ const out = [];
172
+ for (const f of files) {
173
+ try {
174
+ const t = parseMarkdownChat(await readFile(f, 'utf8'), f);
175
+ if (t)
176
+ out.push(t);
177
+ }
178
+ catch (cause) {
179
+ throw new HuskError('E_IMPORT_FAILED', `Cannot read ${f}.`, {
180
+ hint: 'Check the path exists and is readable.',
181
+ cause,
182
+ });
183
+ }
184
+ }
185
+ return out;
186
+ }
187
+ }
188
+ //# sourceMappingURL=markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../src/importers/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAErD;;;;;;;GAOG;AAEH,MAAM,UAAU,GAA8C;IAC5D,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,MAAM;IACb,EAAE,EAAE,MAAM;IACV,GAAG,EAAE,WAAW;IAChB,SAAS,EAAE,WAAW;IACtB,EAAE,EAAE,WAAW;IACf,GAAG,EAAE,WAAW;IAChB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,WAAW;IACpB,GAAG,EAAE,WAAW;IAChB,KAAK,EAAE,WAAW;IAClB,MAAM,EAAE,QAAQ;IAChB,IAAI,EAAE,MAAM;IACZ,QAAQ,EAAE,MAAM;CACjB,CAAC;AAEF,MAAM,gBAAgB,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE3D,+CAA+C;AAC/C,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,kCAAkC,gBAAgB,yBAAyB,EAAE,GAAG,CAAC,CAAC;AAC7G,oCAAoC;AACpC,MAAM,IAAI,GAAG,IAAI,MAAM,CAAC,0BAA0B,gBAAgB,qCAAqC,EAAE,GAAG,CAAC,CAAC;AAC9G,oBAAoB;AACpB,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,aAAa,gBAAgB,iBAAiB,EAAE,GAAG,CAAC,CAAC;AAC9E,wEAAwE;AACxE,MAAM,KAAK,GAAG,uBAAuB,CAAC;AAOtC,SAAS,WAAW,CAAC,IAAY,EAAE,WAAoB;IACrD,IAAI,WAAW;QAAE,OAAO,SAAS,CAAC;IAClC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IACnF,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,IAAI,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;IACpG,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;IACpG,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAe,EAAE,MAAe;IAChE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAwB,EAAE,CAAC;IACzC,IAAI,KAAyB,CAAC;IAC9B,IAAI,IAA2C,CAAC;IAChD,IAAI,GAAG,GAAa,EAAE,CAAC;IACvB,IAAI,KAAyB,CAAC;IAE9B,MAAM,KAAK,GAAG,GAAG,EAAE;QACjB,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,GAAG,GAAG,EAAE,CAAC;YACT,OAAO;QACT,CAAC;QACD,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;QACnC,GAAG,GAAG,EAAE,CAAC;QACT,IAAI,IAAI;YAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,UAAU,GAAG,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtD,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,KAAK,GAAG,UAAU,CAAC,CAAC,CAAW,CAAC;YACtC,IAAI,CAAC,KAAK;gBAAE,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;iBAC5C,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK;gBAAE,KAAK,GAAG,SAAS,CAAC;YAC/C,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACf,SAAS;QACX,CAAC;QAED,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC;QACtD,IAAI,MAAM,EAAE,CAAC;YACX,KAAK,EAAE,CAAC;YACR,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;YACnB,IAAI,MAAM,CAAC,IAAI;gBAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACvC,SAAS;QACX,CAAC;QAED,IAAI,CAAC,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACjC,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC3B,IAAI,CAAC,EAAE,CAAC;gBACN,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;gBAC5B,SAAS;YACX,CAAC;QACH,CAAC;QACD,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,yDAAyD;YACzD,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;gBAAE,SAAS;YACjC,IAAI,GAAG,MAAM,CAAC;QAChB,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC;IACD,KAAK,EAAE,CAAC;IAER,IAAI,CAAC,QAAQ,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAEvC,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC;QACzD,MAAM,EAAE,UAAU;QAClB,QAAQ;QACR,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3B,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC9B,CAAC;AACJ,CAAC;AAED,MAAM,YAAY,GAA8C;IAC9D,IAAI,EAAE,MAAM;IACZ,SAAS,EAAE,WAAW;IACtB,MAAM,EAAE,QAAQ;IAChB,IAAI,EAAE,MAAM;CACb,CAAC;AAEF,4EAA4E;AAC5E,MAAM,UAAU,cAAc,CAAC,UAAsB;IACnD,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,IAAI,UAAU,CAAC,KAAK;QAAE,GAAG,CAAC,IAAI,CAAC,KAAK,UAAU,CAAC,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;IAC5D,KAAK,MAAM,CAAC,IAAI,UAAU,CAAC,QAAQ,EAAE,CAAC;QACpC,GAAG,CAAC,IAAI,CAAC,MAAM,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC;AACpE,CAAC;AAED,MAAM,OAAO,gBAAgB;IAClB,EAAE,GAAG,UAAmB,CAAC;IACzB,WAAW,GAAG,eAAe,CAAC;IAEvC,KAAK,CAAC,MAAM,CAAC,KAA0C;QACrD,IAAI,CAAC;YACH,IAAI,KAAK,GAAG,CAAC,CAAC;YACd,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC;YACvC,IAAI,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,QAAQ,CAAC,WAAW,CAAC,IAAI,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC;gBAAE,KAAK,GAAG,GAAG,CAAC;YAEhG,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC;YACjD,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,IAAI,GAAG,KAAK;qBACf,KAAK,CAAC,OAAO,CAAC;qBACd,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,SAAS,CAAC,CAAC,MAAM,CAAC;gBAC7D,IAAI,IAAI,IAAI,CAAC;oBAAE,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;qBACxC,IAAI,IAAI,IAAI,CAAC;oBAAE,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;qBAC5C,IAAI,IAAI,KAAK,CAAC;oBAAE,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;gBACnD,uDAAuD;gBACvD,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;oBAAE,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YAC5D,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,CAAC;QACX,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,KAAkB;QAC5B,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAChC,MAAM,CAAC,GAAG,iBAAiB,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACvD,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACtB,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;YAChB,MAAM,IAAI,SAAS,CAAC,iBAAiB,EAAE,0CAA0C,EAAE;gBACjF,IAAI,EAAE,mEAAmE;aAC1E,CAAC,CAAC;QACL,CAAC;QACD,MAAM,KAAK,GAAG,CAAC,MAAM,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAClG,MAAM,GAAG,GAAiB,EAAE,CAAC;QAC7B,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACtB,IAAI,CAAC;gBACH,MAAM,CAAC,GAAG,iBAAiB,CAAC,MAAM,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;gBAC1D,IAAI,CAAC;oBAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACrB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,IAAI,SAAS,CAAC,iBAAiB,EAAE,eAAe,CAAC,GAAG,EAAE;oBAC1D,IAAI,EAAE,wCAAwC;oBAC9C,KAAK;iBACN,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;CACF"}
@@ -0,0 +1,16 @@
1
+ import type { ImportInput, Transcript, TranscriptImporter } from '@husk-ai/core';
2
+ import type { ChatLike } from '../chat.js';
3
+ export declare class UniversalImporter implements TranscriptImporter {
4
+ private readonly model?;
5
+ private readonly opts;
6
+ readonly id: "universal";
7
+ readonly displayName = "Universal (model-assisted)";
8
+ /** Injected so tests never touch a network and callers control the budget. */
9
+ constructor(model?: ChatLike | undefined, opts?: {
10
+ model?: string;
11
+ maxBytes?: number;
12
+ });
13
+ detect(): Promise<number>;
14
+ parse(input: ImportInput): Promise<Transcript[]>;
15
+ }
16
+ //# sourceMappingURL=universal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"universal.d.ts","sourceRoot":"","sources":["../../src/importers/universal.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,kBAAkB,EAAqB,MAAM,eAAe,CAAC;AACpG,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAoC3C,qBAAa,iBAAkB,YAAW,kBAAkB;IAMxD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI;IANvB,QAAQ,CAAC,EAAE,EAAG,WAAW,CAAU;IACnC,QAAQ,CAAC,WAAW,gCAAgC;IAEpD,8EAA8E;gBAE3D,KAAK,CAAC,EAAE,QAAQ,YAAA,EAChB,IAAI,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO;IAG7D,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;IAMzB,KAAK,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;CAwDvD"}