thread-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +497 -0
  3. package/dist/formatters/index.d.ts +5 -0
  4. package/dist/formatters/index.d.ts.map +1 -0
  5. package/dist/formatters/index.js +12 -0
  6. package/dist/formatters/index.js.map +1 -0
  7. package/dist/formatters/json.d.ts +3 -0
  8. package/dist/formatters/json.d.ts.map +1 -0
  9. package/dist/formatters/json.js +25 -0
  10. package/dist/formatters/json.js.map +1 -0
  11. package/dist/formatters/markdown.d.ts +3 -0
  12. package/dist/formatters/markdown.d.ts.map +1 -0
  13. package/dist/formatters/markdown.js +134 -0
  14. package/dist/formatters/markdown.js.map +1 -0
  15. package/dist/index.d.ts +3 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +7 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/server.d.ts +4 -0
  20. package/dist/server.d.ts.map +1 -0
  21. package/dist/server.js +175 -0
  22. package/dist/server.js.map +1 -0
  23. package/dist/storage/index.d.ts +3 -0
  24. package/dist/storage/index.d.ts.map +1 -0
  25. package/dist/storage/index.js +3 -0
  26. package/dist/storage/index.js.map +1 -0
  27. package/dist/storage/local.d.ts +23 -0
  28. package/dist/storage/local.d.ts.map +1 -0
  29. package/dist/storage/local.js +126 -0
  30. package/dist/storage/local.js.map +1 -0
  31. package/dist/storage/remote.d.ts +15 -0
  32. package/dist/storage/remote.d.ts.map +1 -0
  33. package/dist/storage/remote.js +91 -0
  34. package/dist/storage/remote.js.map +1 -0
  35. package/dist/tools/delete-thread.d.ts +71 -0
  36. package/dist/tools/delete-thread.d.ts.map +1 -0
  37. package/dist/tools/delete-thread.js +74 -0
  38. package/dist/tools/delete-thread.js.map +1 -0
  39. package/dist/tools/find-threads.d.ts +175 -0
  40. package/dist/tools/find-threads.d.ts.map +1 -0
  41. package/dist/tools/find-threads.js +265 -0
  42. package/dist/tools/find-threads.js.map +1 -0
  43. package/dist/tools/index.d.ts +270 -0
  44. package/dist/tools/index.d.ts.map +1 -0
  45. package/dist/tools/index.js +18 -0
  46. package/dist/tools/index.js.map +1 -0
  47. package/dist/tools/resume-thread.d.ts +138 -0
  48. package/dist/tools/resume-thread.d.ts.map +1 -0
  49. package/dist/tools/resume-thread.js +191 -0
  50. package/dist/tools/resume-thread.js.map +1 -0
  51. package/dist/tools/save-thread.d.ts +155 -0
  52. package/dist/tools/save-thread.d.ts.map +1 -0
  53. package/dist/tools/save-thread.js +116 -0
  54. package/dist/tools/save-thread.js.map +1 -0
  55. package/dist/tools/update-thread.d.ts +180 -0
  56. package/dist/tools/update-thread.d.ts.map +1 -0
  57. package/dist/tools/update-thread.js +159 -0
  58. package/dist/tools/update-thread.js.map +1 -0
  59. package/dist/types.d.ts +177 -0
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +41 -0
  62. package/dist/types.js.map +1 -0
  63. package/package.json +67 -0
package/README.md ADDED
@@ -0,0 +1,497 @@
1
+ # Thread MCP
2
+
3
+ An MCP (Model Context Protocol) server for saving AI conversation threads to local files or remote servers. This tool enables you to preserve, update, search, and resume your conversations with AI applications like Claude, ChatGPT, and others.
4
+
5
+ ## Features
6
+
7
+ - **Unified Saving**: Store conversations locally (Markdown/JSON) or remotely via REST API
8
+ - **Smart Search**: Find threads by ID, title, tags, or full-text search with relevance scoring
9
+ - **Easy Updates**: Append messages by ID or title - no need to track IDs manually
10
+ - **Resume Conversations**: Load previous threads with context optimized for AI continuation
11
+ - **Rich Metadata**: Include timestamps, tags, summaries, and source application info
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install thread-mcp
17
+ ```
18
+
19
+ Or install from source:
20
+
21
+ ```bash
22
+ git clone https://github.com/your-username/thread-mcp.git
23
+ cd thread-mcp
24
+ npm install
25
+ npm run build
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ### Configure with Claude Desktop
31
+
32
+ Add to your Claude Desktop configuration file:
33
+
34
+ **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
35
+ **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
36
+
37
+ ```json
38
+ {
39
+ "mcpServers": {
40
+ "thread-mcp": {
41
+ "command": "npx",
42
+ "args": ["thread-mcp"]
43
+ }
44
+ }
45
+ }
46
+ ```
47
+
48
+ Or if installed from source:
49
+
50
+ ```json
51
+ {
52
+ "mcpServers": {
53
+ "thread-mcp": {
54
+ "command": "node",
55
+ "args": ["/path/to/thread-mcp/dist/index.js"]
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ ## Available Tools
62
+
63
+ ### `save_thread`
64
+
65
+ Save a new conversation thread to local storage or a remote server.
66
+
67
+ **Parameters:**
68
+ | Parameter | Type | Required | Default | Description |
69
+ |-----------|------|----------|---------|-------------|
70
+ | `title` | string | Yes | - | Title for the thread |
71
+ | `messages` | array | Yes | - | Array of messages with `role` and `content` |
72
+ | `destination` | string | No | `"local"` | Where to save: `"local"` or `"remote"` |
73
+ | `format` | string | No | `"markdown"` | Output format: `"markdown"` or `"json"` |
74
+ | `sourceApp` | string | No | - | Name of the AI application |
75
+ | `tags` | string[] | No | - | Tags for categorization |
76
+ | `summary` | string | No | - | Summary of the conversation |
77
+ | `outputDir` | string | No | `~/.thread-mcp` | Custom directory for local storage |
78
+ | `remoteUrl` | string | Conditional | - | Required when destination is `"remote"` |
79
+ | `apiKey` | string | No | - | API key for remote authentication |
80
+ | `headers` | object | No | - | Additional HTTP headers for remote |
81
+
82
+ **Example:**
83
+
84
+ ```json
85
+ {
86
+ "title": "Code Review Discussion",
87
+ "messages": [
88
+ { "role": "user", "content": "Can you review this Python function?" },
89
+ { "role": "assistant", "content": "Sure! Here are my suggestions..." }
90
+ ],
91
+ "sourceApp": "Claude",
92
+ "tags": ["code-review", "python"],
93
+ "format": "markdown"
94
+ }
95
+ ```
96
+
97
+ ---
98
+
99
+ ### `find_threads`
100
+
101
+ Find saved threads by ID, title, search query, or list all threads. Supports filtering and relevance scoring.
102
+
103
+ **Parameters:**
104
+ | Parameter | Type | Required | Default | Description |
105
+ |-----------|------|----------|---------|-------------|
106
+ | `id` | string | No | - | Get a specific thread by ID (returns full details) |
107
+ | `title` | string | No | - | Find thread by exact title match |
108
+ | `query` | string | No | - | Search in titles, summaries, and content |
109
+ | `tags` | string[] | No | - | Filter by tags (must have ALL specified) |
110
+ | `sourceApp` | string | No | - | Filter by source application |
111
+ | `dateFrom` | string | No | - | Filter by creation date (ISO format) |
112
+ | `dateTo` | string | No | - | Filter by creation date (ISO format) |
113
+ | `includeMessages` | boolean | No | `false` | Include full message content |
114
+ | `limit` | number | No | `50` | Maximum results to return |
115
+ | `source` | string | No | `"local"` | Source: `"local"` or `"remote"` |
116
+ | `outputDir` | string | No | `~/.thread-mcp` | Directory for local storage |
117
+ | `remoteUrl` | string | Conditional | - | Required when source is `"remote"` |
118
+
119
+ **Examples:**
120
+
121
+ List all threads:
122
+ ```json
123
+ {
124
+ "source": "local"
125
+ }
126
+ ```
127
+
128
+ Find by ID:
129
+ ```json
130
+ {
131
+ "id": "abc123-def456"
132
+ }
133
+ ```
134
+
135
+ Search with filters:
136
+ ```json
137
+ {
138
+ "query": "Python debugging",
139
+ "tags": ["code"],
140
+ "includeMessages": true,
141
+ "limit": 5
142
+ }
143
+ ```
144
+
145
+ **Response includes relevance metadata:**
146
+
147
+ ```json
148
+ {
149
+ "totalResults": 3,
150
+ "threads": [
151
+ {
152
+ "id": "abc123",
153
+ "title": "Python Debugging Session",
154
+ "summary": "Discussion about debugging techniques",
155
+ "tags": ["python", "debugging"],
156
+ "messageCount": 12,
157
+ "createdAt": "2024-01-15T10:00:00.000Z",
158
+ "relevance": {
159
+ "score": 85,
160
+ "matchedFields": ["title", "content"],
161
+ "topicHints": ["python", "debugging", "error handling"]
162
+ }
163
+ }
164
+ ]
165
+ }
166
+ ```
167
+
168
+ ---
169
+
170
+ ### `update_thread`
171
+
172
+ Update an existing thread by appending new messages. Find thread by ID or title.
173
+
174
+ **Parameters:**
175
+ | Parameter | Type | Required | Default | Description |
176
+ |-----------|------|----------|---------|-------------|
177
+ | `id` | string | Conditional | - | Thread ID (use this OR title) |
178
+ | `title` | string | Conditional | - | Find thread by exact title (use this OR id) |
179
+ | `messages` | array | Yes | - | New messages to add |
180
+ | `mode` | string | No | `"append"` | `"append"` to add messages, `"replace"` to overwrite |
181
+ | `deduplicateMessages` | boolean | No | `true` | Skip duplicate messages |
182
+ | `newTitle` | string | No | - | Update the thread title |
183
+ | `tags` | string[] | No | - | Update tags |
184
+ | `summary` | string | No | - | Update summary |
185
+ | `source` | string | No | `"local"` | Source: `"local"` or `"remote"` |
186
+ | `outputDir` | string | No | `~/.thread-mcp` | Directory for local storage |
187
+ | `remoteUrl` | string | Conditional | - | Required when source is `"remote"` |
188
+
189
+ **Example - Append by title:**
190
+
191
+ ```json
192
+ {
193
+ "title": "Code Review Discussion",
194
+ "messages": [
195
+ { "role": "user", "content": "What about error handling?" },
196
+ { "role": "assistant", "content": "Good point! You should..." }
197
+ ]
198
+ }
199
+ ```
200
+
201
+ **Example - Update by ID with new metadata:**
202
+
203
+ ```json
204
+ {
205
+ "id": "abc123-def456",
206
+ "messages": [
207
+ { "role": "user", "content": "Follow-up question..." }
208
+ ],
209
+ "tags": ["code-review", "python", "error-handling"],
210
+ "summary": "Extended discussion including error handling"
211
+ }
212
+ ```
213
+
214
+ ---
215
+
216
+ ### `delete_thread`
217
+
218
+ Delete a saved thread by ID or title.
219
+
220
+ **Parameters:**
221
+ | Parameter | Type | Required | Default | Description |
222
+ |-----------|------|----------|---------|-------------|
223
+ | `id` | string | Conditional | - | Thread ID (use this OR title) |
224
+ | `title` | string | Conditional | - | Find thread by exact title (use this OR id) |
225
+ | `source` | string | No | `"local"` | Source: `"local"` or `"remote"` |
226
+ | `outputDir` | string | No | `~/.thread-mcp` | Directory for local storage |
227
+ | `remoteUrl` | string | Conditional | - | Required when source is `"remote"` |
228
+
229
+ **Examples:**
230
+
231
+ ```json
232
+ {
233
+ "id": "abc123-def456"
234
+ }
235
+ ```
236
+
237
+ ```json
238
+ {
239
+ "title": "Old Discussion to Remove"
240
+ }
241
+ ```
242
+
243
+ ---
244
+
245
+ ### `resume_thread`
246
+
247
+ Load a saved thread to continue the conversation. Returns context optimized for AI continuation.
248
+
249
+ **Parameters:**
250
+ | Parameter | Type | Required | Default | Description |
251
+ |-----------|------|----------|---------|-------------|
252
+ | `id` | string | Conditional | - | Thread ID |
253
+ | `title` | string | Conditional | - | Find by exact title match |
254
+ | `titleContains` | string | Conditional | - | Find most recent thread with title containing this |
255
+ | `format` | string | No | `"structured"` | Output format (see below) |
256
+ | `maxMessages` | number | No | all | Limit to last N messages |
257
+ | `includeSummary` | boolean | No | `true` | Include thread summary |
258
+ | `source` | string | No | `"local"` | Source: `"local"` or `"remote"` |
259
+ | `outputDir` | string | No | `~/.thread-mcp` | Directory for local storage |
260
+ | `remoteUrl` | string | Conditional | - | Required when source is `"remote"` |
261
+
262
+ **Output Formats:**
263
+
264
+ - `"structured"` - Organized context with metadata, messages, and continuation hints
265
+ - `"narrative"` - Human-readable summary suitable for context injection
266
+ - `"messages"` - Raw message array only
267
+
268
+ **Example - Resume by title:**
269
+
270
+ ```json
271
+ {
272
+ "title": "Code Review Discussion",
273
+ "format": "structured",
274
+ "maxMessages": 10
275
+ }
276
+ ```
277
+
278
+ **Structured Response:**
279
+
280
+ ```json
281
+ {
282
+ "found": true,
283
+ "id": "abc123",
284
+ "format": "structured",
285
+ "context": {
286
+ "title": "Code Review Discussion",
287
+ "summary": "Discussion about Python best practices",
288
+ "tags": ["code-review", "python"],
289
+ "messageCount": 15,
290
+ "startedAt": "2024-01-15T10:00:00.000Z"
291
+ },
292
+ "messages": [...],
293
+ "continuationHint": "The assistant last responded. The user may have follow-up questions.",
294
+ "totalMessages": 15
295
+ }
296
+ ```
297
+
298
+ ## Typical Workflow
299
+
300
+ 1. **Save a new thread** after an important conversation:
301
+ ```json
302
+ { "title": "Project Planning", "messages": [...], "tags": ["planning"] }
303
+ ```
304
+
305
+ 2. **Continue later** - find and resume:
306
+ ```json
307
+ { "title": "Project Planning" } // resume_thread
308
+ ```
309
+
310
+ 3. **Add new messages** as the conversation continues:
311
+ ```json
312
+ { "title": "Project Planning", "messages": [new messages...] } // update_thread
313
+ ```
314
+
315
+ 4. **Search across threads** to find relevant context:
316
+ ```json
317
+ { "query": "database schema", "tags": ["planning"] } // find_threads
318
+ ```
319
+
320
+ ## Output Formats
321
+
322
+ ### Markdown
323
+
324
+ Produces a human-readable Markdown file with YAML frontmatter:
325
+
326
+ ```markdown
327
+ ---
328
+ id: abc123-def456
329
+ title: "Code Review Discussion"
330
+ created_at: 2024-01-15T10:00:00.000Z
331
+ source_app: Claude
332
+ tags: ["code-review", "python"]
333
+ ---
334
+
335
+ # Code Review Discussion
336
+
337
+ > A discussion about Python best practices
338
+
339
+ ## Conversation
340
+
341
+ ### User _(1/15/2024, 10:00:00 AM)_
342
+
343
+ Can you review this Python function?
344
+
345
+ ### Assistant _(1/15/2024, 10:00:05 AM)_
346
+
347
+ Sure! Here are my suggestions...
348
+ ```
349
+
350
+ ### JSON
351
+
352
+ Produces a structured JSON file:
353
+
354
+ ```json
355
+ {
356
+ "id": "abc123-def456",
357
+ "metadata": {
358
+ "title": "Code Review Discussion",
359
+ "sourceApp": "Claude",
360
+ "createdAt": "2024-01-15T10:00:00.000Z",
361
+ "tags": ["code-review", "python"]
362
+ },
363
+ "messages": [
364
+ {
365
+ "role": "user",
366
+ "content": "Can you review this Python function?",
367
+ "timestamp": "2024-01-15T10:00:00.000Z"
368
+ }
369
+ ]
370
+ }
371
+ ```
372
+
373
+ ## Remote Server API
374
+
375
+ When using remote storage, your server should implement these endpoints:
376
+
377
+ ### `POST /conversations`
378
+
379
+ Create a new conversation.
380
+
381
+ **Request Body:**
382
+
383
+ ```json
384
+ {
385
+ "id": "string",
386
+ "title": "string",
387
+ "content": "string (formatted content)",
388
+ "format": "markdown | json",
389
+ "metadata": { ... }
390
+ }
391
+ ```
392
+
393
+ **Response:**
394
+
395
+ ```json
396
+ {
397
+ "url": "https://your-server.com/conversations/id"
398
+ }
399
+ ```
400
+
401
+ ### `GET /conversations`
402
+
403
+ List all conversations.
404
+
405
+ ### `GET /conversations/:id`
406
+
407
+ Get a specific conversation.
408
+
409
+ ### `PUT /conversations/:id`
410
+
411
+ Update a conversation (for update_thread).
412
+
413
+ ### `DELETE /conversations/:id`
414
+
415
+ Delete a conversation.
416
+
417
+ ## Development
418
+
419
+ ### Prerequisites
420
+
421
+ - Node.js 22+
422
+ - npm
423
+
424
+ ### Setup
425
+
426
+ ```bash
427
+ npm install
428
+ ```
429
+
430
+ ### Build
431
+
432
+ ```bash
433
+ npm run build
434
+ ```
435
+
436
+ ### Run Tests
437
+
438
+ ```bash
439
+ npm test # Run all tests
440
+ npm run test:watch # Watch mode
441
+ npm run test:coverage # With coverage report
442
+ ```
443
+
444
+ ### Development Server
445
+
446
+ ```bash
447
+ npm run dev
448
+ ```
449
+
450
+ ### Linting & Formatting
451
+
452
+ ```bash
453
+ npm run lint # ESLint
454
+ npm run format # Prettier format
455
+ npm run format:check # Check formatting
456
+ npm run typecheck # TypeScript type checking
457
+ ```
458
+
459
+ ## Project Structure
460
+
461
+ ```
462
+ thread-mcp/
463
+ ├── src/
464
+ │ ├── index.ts # Entry point
465
+ │ ├── server.ts # MCP server setup
466
+ │ ├── types.ts # TypeScript types & Zod schemas
467
+ │ ├── tools/ # MCP tool implementations
468
+ │ │ ├── save-thread.ts # Save new threads
469
+ │ │ ├── find-threads.ts # Search/list/get threads
470
+ │ │ ├── update-thread.ts # Update existing threads
471
+ │ │ ├── delete-thread.ts # Delete threads
472
+ │ │ └── resume-thread.ts # Load threads for continuation
473
+ │ ├── formatters/ # Output formatters
474
+ │ │ ├── markdown.ts
475
+ │ │ └── json.ts
476
+ │ └── storage/ # Storage providers
477
+ │ ├── local.ts
478
+ │ └── remote.ts
479
+ ├── tests/
480
+ │ ├── unit/ # Unit tests
481
+ │ └── e2e/ # End-to-end tests
482
+ ├── package.json
483
+ ├── tsconfig.json
484
+ └── vitest.config.ts
485
+ ```
486
+
487
+ ## License
488
+
489
+ MIT
490
+
491
+ ## Contributing
492
+
493
+ 1. Fork the repository
494
+ 2. Create a feature branch
495
+ 3. Make your changes
496
+ 4. Run tests: `npm test`
497
+ 5. Submit a pull request
@@ -0,0 +1,5 @@
1
+ import type { Formatter, OutputFormat } from "../types.js";
2
+ export { markdownFormatter } from "./markdown.js";
3
+ export { jsonFormatter } from "./json.js";
4
+ export declare function getFormatter(format: OutputFormat): Formatter;
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/formatters/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAI3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAO1C,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,CAE5D"}
@@ -0,0 +1,12 @@
1
+ import { markdownFormatter } from "./markdown.js";
2
+ import { jsonFormatter } from "./json.js";
3
+ export { markdownFormatter } from "./markdown.js";
4
+ export { jsonFormatter } from "./json.js";
5
+ const formatters = {
6
+ markdown: markdownFormatter,
7
+ json: jsonFormatter,
8
+ };
9
+ export function getFormatter(format) {
10
+ return formatters[format];
11
+ }
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/formatters/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,UAAU,GAAoC;IAClD,QAAQ,EAAE,iBAAiB;IAC3B,IAAI,EAAE,aAAa;CACpB,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,MAAoB;IAC/C,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type Formatter } from "../types.js";
2
+ export declare const jsonFormatter: Formatter;
3
+ //# sourceMappingURL=json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/formatters/json.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EAGf,MAAM,aAAa,CAAC;AAErB,eAAO,MAAM,aAAa,EAAE,SA2B3B,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { ConversationSchema, } from "../types.js";
2
+ export const jsonFormatter = {
3
+ extension: ".json",
4
+ format(conversation, options) {
5
+ const output = {
6
+ id: conversation.id,
7
+ };
8
+ if (options.includeMetadata) {
9
+ output.metadata = conversation.metadata;
10
+ }
11
+ output.messages = conversation.messages.map((msg) => {
12
+ if (!options.includeTimestamps) {
13
+ const { timestamp, ...rest } = msg;
14
+ return rest;
15
+ }
16
+ return msg;
17
+ });
18
+ return JSON.stringify(output, null, 2);
19
+ },
20
+ parse(content) {
21
+ const data = JSON.parse(content);
22
+ return ConversationSchema.parse(data);
23
+ },
24
+ };
25
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/formatters/json.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAErB,MAAM,CAAC,MAAM,aAAa,GAAc;IACtC,SAAS,EAAE,OAAO;IAElB,MAAM,CAAC,YAA0B,EAAE,OAAoB;QACrD,MAAM,MAAM,GAA4B;YACtC,EAAE,EAAE,YAAY,CAAC,EAAE;SACpB,CAAC;QAEF,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;YAC5B,MAAM,CAAC,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC;QAC1C,CAAC;QAED,MAAM,CAAC,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;YAClD,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC;gBAC/B,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,GAAG,GAAG,CAAC;gBACnC,OAAO,IAAI,CAAC;YACd,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC,CAAC,CAAC;QAEH,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,OAAe;QACnB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACjC,OAAO,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACxC,CAAC;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Formatter } from "../types.js";
2
+ export declare const markdownFormatter: Formatter;
3
+ //# sourceMappingURL=markdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/formatters/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,SAAS,EAAwB,MAAM,aAAa,CAAC;AAmHjF,eAAO,MAAM,iBAAiB,EAAE,SAwD/B,CAAC"}
@@ -0,0 +1,134 @@
1
+ function formatMessage(message, includeTimestamps) {
2
+ const roleLabel = message.role.charAt(0).toUpperCase() + message.role.slice(1);
3
+ const timestamp = includeTimestamps && message.timestamp
4
+ ? ` _(${new Date(message.timestamp).toLocaleString()})_`
5
+ : "";
6
+ return `### ${roleLabel}${timestamp}\n\n${message.content}\n`;
7
+ }
8
+ function formatMetadata(conversation) {
9
+ const { metadata } = conversation;
10
+ const lines = [
11
+ "---",
12
+ `id: ${conversation.id}`,
13
+ `title: "${metadata.title}"`,
14
+ `created_at: ${metadata.createdAt}`,
15
+ ];
16
+ if (metadata.updatedAt) {
17
+ lines.push(`updated_at: ${metadata.updatedAt}`);
18
+ }
19
+ if (metadata.sourceApp) {
20
+ lines.push(`source_app: ${metadata.sourceApp}`);
21
+ }
22
+ if (metadata.tags && metadata.tags.length > 0) {
23
+ lines.push(`tags: [${metadata.tags.map((t) => `"${t}"`).join(", ")}]`);
24
+ }
25
+ lines.push("---\n");
26
+ return lines.join("\n");
27
+ }
28
+ function parseMetadata(frontmatter) {
29
+ const lines = frontmatter.split("\n").filter((l) => l.trim());
30
+ const data = {};
31
+ for (const line of lines) {
32
+ const colonIndex = line.indexOf(":");
33
+ if (colonIndex === -1)
34
+ continue;
35
+ const key = line.slice(0, colonIndex).trim();
36
+ let value = line.slice(colonIndex + 1).trim();
37
+ if (value.startsWith('"') && value.endsWith('"')) {
38
+ value = value.slice(1, -1);
39
+ }
40
+ data[key] = value;
41
+ }
42
+ if (!data.id || !data.title || !data.created_at) {
43
+ return null;
44
+ }
45
+ let tags;
46
+ if (data.tags) {
47
+ const tagsMatch = data.tags.match(/\[(.+)\]/);
48
+ if (tagsMatch) {
49
+ tags = tagsMatch[1].split(",").map((t) => t.trim().replace(/^"|"$/g, ""));
50
+ }
51
+ }
52
+ return {
53
+ id: data.id,
54
+ metadata: {
55
+ title: data.title,
56
+ createdAt: data.created_at,
57
+ updatedAt: data.updated_at,
58
+ sourceApp: data.source_app,
59
+ tags,
60
+ },
61
+ };
62
+ }
63
+ function parseMessages(content) {
64
+ const messages = [];
65
+ const messageRegex = /### (User|Assistant|System)(?:\s+_\((.+?)\)_)?\s*\n\n([\s\S]*?)(?=\n### |$)/gi;
66
+ let match;
67
+ while ((match = messageRegex.exec(content)) !== null) {
68
+ const role = match[1].toLowerCase();
69
+ const timestampStr = match[2];
70
+ const messageContent = match[3].trim();
71
+ const message = {
72
+ role,
73
+ content: messageContent,
74
+ };
75
+ if (timestampStr) {
76
+ try {
77
+ const date = new Date(timestampStr);
78
+ if (!isNaN(date.getTime())) {
79
+ message.timestamp = date.toISOString();
80
+ }
81
+ }
82
+ catch {
83
+ // Ignore invalid timestamps
84
+ }
85
+ }
86
+ messages.push(message);
87
+ }
88
+ return messages;
89
+ }
90
+ export const markdownFormatter = {
91
+ extension: ".md",
92
+ format(conversation, options) {
93
+ const parts = [];
94
+ if (options.includeMetadata) {
95
+ parts.push(formatMetadata(conversation));
96
+ }
97
+ parts.push(`# ${conversation.metadata.title}\n`);
98
+ if (conversation.metadata.summary) {
99
+ parts.push(`> ${conversation.metadata.summary}\n`);
100
+ }
101
+ parts.push("## Conversation\n");
102
+ for (const message of conversation.messages) {
103
+ parts.push(formatMessage(message, options.includeTimestamps));
104
+ }
105
+ return parts.join("\n");
106
+ },
107
+ parse(content) {
108
+ let id = crypto.randomUUID();
109
+ let metadata = {
110
+ title: "Untitled Conversation",
111
+ createdAt: new Date().toISOString(),
112
+ };
113
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
114
+ if (frontmatterMatch) {
115
+ const parsed = parseMetadata(frontmatterMatch[1]);
116
+ if (parsed) {
117
+ id = parsed.id;
118
+ metadata = parsed.metadata;
119
+ }
120
+ content = content.slice(frontmatterMatch[0].length);
121
+ }
122
+ const titleMatch = content.match(/^#\s+(.+)$/m);
123
+ if (titleMatch && metadata.title === "Untitled Conversation") {
124
+ metadata.title = titleMatch[1].trim();
125
+ }
126
+ const summaryMatch = content.match(/^>\s+(.+)$/m);
127
+ if (summaryMatch) {
128
+ metadata.summary = summaryMatch[1].trim();
129
+ }
130
+ const messages = parseMessages(content);
131
+ return { id, metadata, messages };
132
+ },
133
+ };
134
+ //# sourceMappingURL=markdown.js.map