context-mode 0.9.15 → 0.9.17
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +3 -1
- package/build/executor.js +15 -2
- package/build/runtime.d.ts +1 -1
- package/build/runtime.js +21 -3
- package/build/server.d.ts +7 -1
- package/build/server.js +100 -75
- package/build/store.d.ts +1 -0
- package/build/store.js +6 -2
- package/package.json +7 -2
- package/server.bundle.mjs +47 -96
- package/start.mjs +11 -9
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"name": "context-mode",
|
|
14
14
|
"source": "./",
|
|
15
15
|
"description": "Claude Code MCP plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
|
|
16
|
-
"version": "0.9.
|
|
16
|
+
"version": "0.9.17",
|
|
17
17
|
"author": {
|
|
18
18
|
"name": "Mert Koseoğlu"
|
|
19
19
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.17",
|
|
4
4
|
"description": "Claude Code MCP plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Mert Koseoğlu",
|
package/README.md
CHANGED
|
@@ -190,7 +190,9 @@ with code examples. Then run /context-mode:stats.
|
|
|
190
190
|
- **Claude Code** with MCP support
|
|
191
191
|
- Optional: Bun (auto-detected, 3-5x faster JS/TS)
|
|
192
192
|
|
|
193
|
-
##
|
|
193
|
+
## Contributing
|
|
194
|
+
|
|
195
|
+
We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for the full local development workflow, TDD guidelines, and how to test your changes in a live Claude Code session.
|
|
194
196
|
|
|
195
197
|
```bash
|
|
196
198
|
git clone https://github.com/mksglu/claude-context-mode.git
|
package/build/executor.js
CHANGED
|
@@ -36,7 +36,14 @@ export class PolyglotExecutor {
|
|
|
36
36
|
const tmpDir = mkdtempSync(join(tmpdir(), "ctx-mode-"));
|
|
37
37
|
try {
|
|
38
38
|
const filePath = this.#writeScript(tmpDir, code, language);
|
|
39
|
-
|
|
39
|
+
let cmd;
|
|
40
|
+
try {
|
|
41
|
+
cmd = buildCommand(this.#runtimes, language, filePath);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
45
|
+
return { exitCode: 1, stdout: "", stderr: msg, timedOut: false };
|
|
46
|
+
}
|
|
40
47
|
// Rust: compile then run
|
|
41
48
|
if (cmd[0] === "__rust_compile_run__") {
|
|
42
49
|
return await this.#compileAndRun(filePath, tmpDir, timeout);
|
|
@@ -44,7 +51,13 @@ export class PolyglotExecutor {
|
|
|
44
51
|
return await this.#spawn(cmd, tmpDir, timeout);
|
|
45
52
|
}
|
|
46
53
|
finally {
|
|
47
|
-
|
|
54
|
+
try {
|
|
55
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// On Windows, EBUSY/EPERM is common due to delayed handle release
|
|
59
|
+
// after child process exit. Silently ignore — OS cleans temp dirs.
|
|
60
|
+
}
|
|
48
61
|
}
|
|
49
62
|
}
|
|
50
63
|
async executeFile(opts) {
|
package/build/runtime.d.ts
CHANGED
package/build/runtime.js
CHANGED
|
@@ -40,7 +40,15 @@ export function detectRuntimes() {
|
|
|
40
40
|
: commandExists("python")
|
|
41
41
|
? "python"
|
|
42
42
|
: null,
|
|
43
|
-
shell: commandExists("bash")
|
|
43
|
+
shell: commandExists("bash")
|
|
44
|
+
? "bash"
|
|
45
|
+
: commandExists("sh")
|
|
46
|
+
? "sh"
|
|
47
|
+
: commandExists("powershell")
|
|
48
|
+
? "powershell"
|
|
49
|
+
: commandExists("cmd.exe")
|
|
50
|
+
? "cmd.exe"
|
|
51
|
+
: null,
|
|
44
52
|
ruby: commandExists("ruby") ? "ruby" : null,
|
|
45
53
|
go: commandExists("go") ? "go" : null,
|
|
46
54
|
rust: commandExists("rustc") ? "rustc" : null,
|
|
@@ -73,7 +81,12 @@ export function getRuntimeSummary(runtimes) {
|
|
|
73
81
|
else {
|
|
74
82
|
lines.push(` Python: not available`);
|
|
75
83
|
}
|
|
76
|
-
|
|
84
|
+
if (runtimes.shell) {
|
|
85
|
+
lines.push(` Shell: ${runtimes.shell} (${getVersion(runtimes.shell)})`);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
lines.push(` Shell: not available`);
|
|
89
|
+
}
|
|
77
90
|
// Optional runtimes — only show if available
|
|
78
91
|
if (runtimes.ruby)
|
|
79
92
|
lines.push(` Ruby: ${runtimes.ruby} (${getVersion(runtimes.ruby)})`);
|
|
@@ -96,7 +109,9 @@ export function getRuntimeSummary(runtimes) {
|
|
|
96
109
|
return lines.join("\n");
|
|
97
110
|
}
|
|
98
111
|
export function getAvailableLanguages(runtimes) {
|
|
99
|
-
const langs = ["javascript"
|
|
112
|
+
const langs = ["javascript"];
|
|
113
|
+
if (runtimes.shell)
|
|
114
|
+
langs.push("shell");
|
|
100
115
|
if (runtimes.typescript)
|
|
101
116
|
langs.push("typescript");
|
|
102
117
|
if (runtimes.python)
|
|
@@ -138,6 +153,9 @@ export function buildCommand(runtimes, language, filePath) {
|
|
|
138
153
|
}
|
|
139
154
|
return [runtimes.python, filePath];
|
|
140
155
|
case "shell":
|
|
156
|
+
if (!runtimes.shell) {
|
|
157
|
+
throw new Error("No shell runtime available. Install bash, sh, powershell, or cmd.");
|
|
158
|
+
}
|
|
141
159
|
return [runtimes.shell, filePath];
|
|
142
160
|
case "ruby":
|
|
143
161
|
if (!runtimes.ruby) {
|
package/build/server.d.ts
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Parse FTS5 highlight markers to find match positions in the
|
|
4
|
+
* original (marker-free) text. Returns character offsets into the
|
|
5
|
+
* stripped content where each matched token begins.
|
|
6
|
+
*/
|
|
7
|
+
export declare function positionsFromHighlight(highlighted: string): number[];
|
|
8
|
+
export declare function extractSnippet(content: string, query: string, maxLen?: number, highlighted?: string): string;
|
package/build/server.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
4
5
|
import { z } from "zod";
|
|
5
6
|
import { PolyglotExecutor } from "./executor.js";
|
|
6
7
|
import { ContentStore, cleanupStaleDBs } from "./store.js";
|
|
@@ -51,25 +52,76 @@ const bunNote = hasBunRuntime()
|
|
|
51
52
|
// ─────────────────────────────────────────────────────────
|
|
52
53
|
// Helper: smart snippet extraction — returns windows around
|
|
53
54
|
// matching query terms instead of dumb truncation
|
|
55
|
+
//
|
|
56
|
+
// When `highlighted` is provided (from FTS5 `highlight()` with
|
|
57
|
+
// STX/ETX markers), match positions are derived from the markers.
|
|
58
|
+
// This is the authoritative source — FTS5 uses the exact same
|
|
59
|
+
// tokenizer that produced the BM25 match, so stemmed variants
|
|
60
|
+
// like "configuration" matching query "configure" are found
|
|
61
|
+
// correctly. Falls back to indexOf on raw terms when highlighted
|
|
62
|
+
// is absent (non-FTS codepath).
|
|
54
63
|
// ─────────────────────────────────────────────────────────
|
|
55
|
-
|
|
64
|
+
const STX = "\x02";
|
|
65
|
+
const ETX = "\x03";
|
|
66
|
+
/**
|
|
67
|
+
* Parse FTS5 highlight markers to find match positions in the
|
|
68
|
+
* original (marker-free) text. Returns character offsets into the
|
|
69
|
+
* stripped content where each matched token begins.
|
|
70
|
+
*/
|
|
71
|
+
export function positionsFromHighlight(highlighted) {
|
|
72
|
+
const positions = [];
|
|
73
|
+
let cleanOffset = 0;
|
|
74
|
+
let i = 0;
|
|
75
|
+
while (i < highlighted.length) {
|
|
76
|
+
if (highlighted[i] === STX) {
|
|
77
|
+
// Record position of this match in the clean text
|
|
78
|
+
positions.push(cleanOffset);
|
|
79
|
+
i++; // skip STX
|
|
80
|
+
// Advance through matched text until ETX
|
|
81
|
+
while (i < highlighted.length && highlighted[i] !== ETX) {
|
|
82
|
+
cleanOffset++;
|
|
83
|
+
i++;
|
|
84
|
+
}
|
|
85
|
+
if (i < highlighted.length)
|
|
86
|
+
i++; // skip ETX
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
cleanOffset++;
|
|
90
|
+
i++;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return positions;
|
|
94
|
+
}
|
|
95
|
+
/** Strip STX/ETX markers to recover original content. */
|
|
96
|
+
function stripMarkers(highlighted) {
|
|
97
|
+
return highlighted.replaceAll(STX, "").replaceAll(ETX, "");
|
|
98
|
+
}
|
|
99
|
+
export function extractSnippet(content, query, maxLen = 1500, highlighted) {
|
|
56
100
|
if (content.length <= maxLen)
|
|
57
101
|
return content;
|
|
58
|
-
|
|
59
|
-
.toLowerCase()
|
|
60
|
-
.split(/\s+/)
|
|
61
|
-
.filter((t) => t.length > 2);
|
|
62
|
-
const lower = content.toLowerCase();
|
|
63
|
-
// Find all positions where query terms appear
|
|
102
|
+
// Derive match positions from FTS5 highlight markers when available
|
|
64
103
|
const positions = [];
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
positions.push(idx);
|
|
69
|
-
idx = lower.indexOf(term, idx + 1);
|
|
104
|
+
if (highlighted) {
|
|
105
|
+
for (const pos of positionsFromHighlight(highlighted)) {
|
|
106
|
+
positions.push(pos);
|
|
70
107
|
}
|
|
71
108
|
}
|
|
72
|
-
//
|
|
109
|
+
// Fallback: indexOf on raw query terms (non-FTS codepath)
|
|
110
|
+
if (positions.length === 0) {
|
|
111
|
+
const terms = query
|
|
112
|
+
.toLowerCase()
|
|
113
|
+
.split(/\s+/)
|
|
114
|
+
.filter((t) => t.length > 2);
|
|
115
|
+
const lower = content.toLowerCase();
|
|
116
|
+
for (const term of terms) {
|
|
117
|
+
let idx = lower.indexOf(term);
|
|
118
|
+
while (idx !== -1) {
|
|
119
|
+
positions.push(idx);
|
|
120
|
+
idx = lower.indexOf(term, idx + 1);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// No matches at all — return prefix
|
|
73
125
|
if (positions.length === 0) {
|
|
74
126
|
return content.slice(0, maxLen) + "\n…";
|
|
75
127
|
}
|
|
@@ -541,7 +593,7 @@ server.registerTool("search", {
|
|
|
541
593
|
.map((r, i) => {
|
|
542
594
|
const header = `--- [${r.source}] ---`;
|
|
543
595
|
const heading = `### ${r.title}`;
|
|
544
|
-
const snippet = extractSnippet(r.content, q);
|
|
596
|
+
const snippet = extractSnippet(r.content, q, 1500, r.highlighted);
|
|
545
597
|
return `${header}\n${heading}\n\n${snippet}`;
|
|
546
598
|
})
|
|
547
599
|
.join("\n\n");
|
|
@@ -577,75 +629,48 @@ server.registerTool("search", {
|
|
|
577
629
|
}
|
|
578
630
|
});
|
|
579
631
|
// ─────────────────────────────────────────────────────────
|
|
632
|
+
// Turndown path resolution (external dep, like better-sqlite3)
|
|
633
|
+
// ─────────────────────────────────────────────────────────
|
|
634
|
+
let _turndownPath = null;
|
|
635
|
+
let _gfmPluginPath = null;
|
|
636
|
+
function resolveTurndownPath() {
|
|
637
|
+
if (!_turndownPath) {
|
|
638
|
+
const require = createRequire(import.meta.url);
|
|
639
|
+
_turndownPath = require.resolve("turndown");
|
|
640
|
+
}
|
|
641
|
+
return _turndownPath;
|
|
642
|
+
}
|
|
643
|
+
function resolveGfmPluginPath() {
|
|
644
|
+
if (!_gfmPluginPath) {
|
|
645
|
+
const require = createRequire(import.meta.url);
|
|
646
|
+
_gfmPluginPath = require.resolve("turndown-plugin-gfm");
|
|
647
|
+
}
|
|
648
|
+
return _gfmPluginPath;
|
|
649
|
+
}
|
|
650
|
+
// ─────────────────────────────────────────────────────────
|
|
580
651
|
// Tool: fetch_and_index
|
|
581
652
|
// ─────────────────────────────────────────────────────────
|
|
582
|
-
|
|
583
|
-
const
|
|
584
|
-
|
|
653
|
+
function buildFetchCode(url) {
|
|
654
|
+
const turndownPath = JSON.stringify(resolveTurndownPath());
|
|
655
|
+
const gfmPath = JSON.stringify(resolveGfmPluginPath());
|
|
656
|
+
return `
|
|
657
|
+
const TurndownService = require(${turndownPath});
|
|
658
|
+
const { gfm } = require(${gfmPath});
|
|
659
|
+
const url = ${JSON.stringify(url)};
|
|
585
660
|
|
|
586
661
|
async function main() {
|
|
587
662
|
const resp = await fetch(url);
|
|
588
663
|
if (!resp.ok) { console.error("HTTP " + resp.status); process.exit(1); }
|
|
664
|
+
const html = await resp.text();
|
|
589
665
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
html = html.replace(/<style[^>]*>[\\s\\S]*?<\\/style>/gi, "");
|
|
595
|
-
html = html.replace(/<nav[^>]*>[\\s\\S]*?<\\/nav>/gi, "");
|
|
596
|
-
html = html.replace(/<header[^>]*>[\\s\\S]*?<\\/header>/gi, "");
|
|
597
|
-
html = html.replace(/<footer[^>]*>[\\s\\S]*?<\\/footer>/gi, "");
|
|
598
|
-
|
|
599
|
-
// Convert headings to markdown
|
|
600
|
-
html = html.replace(/<h1[^>]*>(.*?)<\\/h1>/gi, "\\n# $1\\n");
|
|
601
|
-
html = html.replace(/<h2[^>]*>(.*?)<\\/h2>/gi, "\\n## $1\\n");
|
|
602
|
-
html = html.replace(/<h3[^>]*>(.*?)<\\/h3>/gi, "\\n### $1\\n");
|
|
603
|
-
html = html.replace(/<h4[^>]*>(.*?)<\\/h4>/gi, "\\n#### $1\\n");
|
|
604
|
-
|
|
605
|
-
// Convert code blocks
|
|
606
|
-
html = html.replace(/<pre[^>]*><code[^>]*class="[^"]*language-(\\w+)"[^>]*>([\\s\\S]*?)<\\/code><\\/pre>/gi,
|
|
607
|
-
(_, lang, code) => "\\n\\\`\\\`\\\`" + lang + "\\n" + decodeEntities(code) + "\\n\\\`\\\`\\\`\\n");
|
|
608
|
-
html = html.replace(/<pre[^>]*><code[^>]*>([\\s\\S]*?)<\\/code><\\/pre>/gi,
|
|
609
|
-
(_, code) => "\\n\\\`\\\`\\\`\\n" + decodeEntities(code) + "\\n\\\`\\\`\\\`\\n");
|
|
610
|
-
html = html.replace(/<code[^>]*>([^<]*)<\\/code>/gi, "\\\`$1\\\`");
|
|
611
|
-
|
|
612
|
-
// Convert links
|
|
613
|
-
html = html.replace(/<a[^>]*href="([^"]*)"[^>]*>(.*?)<\\/a>/gi, "[$2]($1)");
|
|
614
|
-
|
|
615
|
-
// Convert lists
|
|
616
|
-
html = html.replace(/<li[^>]*>(.*?)<\\/li>/gi, "- $1\\n");
|
|
617
|
-
|
|
618
|
-
// Convert paragraphs and line breaks
|
|
619
|
-
html = html.replace(/<p[^>]*>(.*?)<\\/p>/gi, "\\n$1\\n");
|
|
620
|
-
html = html.replace(/<br\\s*\\/?>/gi, "\\n");
|
|
621
|
-
html = html.replace(/<hr\\s*\\/?>/gi, "\\n---\\n");
|
|
622
|
-
|
|
623
|
-
// Strip remaining HTML tags
|
|
624
|
-
html = html.replace(/<[^>]+>/g, "");
|
|
625
|
-
|
|
626
|
-
// Decode HTML entities
|
|
627
|
-
html = decodeEntities(html);
|
|
628
|
-
|
|
629
|
-
// Clean up whitespace
|
|
630
|
-
html = html.replace(/\\n{3,}/g, "\\n\\n").trim();
|
|
631
|
-
|
|
632
|
-
console.log(html);
|
|
666
|
+
const td = new TurndownService({ headingStyle: 'atx', codeBlockStyle: 'fenced' });
|
|
667
|
+
td.use(gfm);
|
|
668
|
+
td.remove(['script', 'style', 'nav', 'header', 'footer', 'noscript']);
|
|
669
|
+
console.log(td.turndown(html));
|
|
633
670
|
}
|
|
634
|
-
|
|
635
|
-
function decodeEntities(s) {
|
|
636
|
-
return s
|
|
637
|
-
.replace(/&/g, "&")
|
|
638
|
-
.replace(/</g, "<")
|
|
639
|
-
.replace(/>/g, ">")
|
|
640
|
-
.replace(/"/g, '"')
|
|
641
|
-
.replace(/'/g, "'")
|
|
642
|
-
.replace(/'/g, "'")
|
|
643
|
-
.replace(///g, "/")
|
|
644
|
-
.replace(/ /g, " ");
|
|
645
|
-
}
|
|
646
|
-
|
|
647
671
|
main();
|
|
648
672
|
`;
|
|
673
|
+
}
|
|
649
674
|
server.registerTool("fetch_and_index", {
|
|
650
675
|
title: "Fetch & Index URL",
|
|
651
676
|
description: "Fetches URL content, converts HTML to markdown, indexes into searchable knowledge base, " +
|
|
@@ -661,7 +686,7 @@ server.registerTool("fetch_and_index", {
|
|
|
661
686
|
}, async ({ url, source }) => {
|
|
662
687
|
try {
|
|
663
688
|
// Execute fetch inside subprocess — raw HTML never enters context
|
|
664
|
-
const fetchCode =
|
|
689
|
+
const fetchCode = buildFetchCode(url);
|
|
665
690
|
const result = await executor.execute({
|
|
666
691
|
language: "javascript",
|
|
667
692
|
code: fetchCode,
|
|
@@ -822,7 +847,7 @@ server.registerTool("batch_execute", {
|
|
|
822
847
|
queryResults.push("");
|
|
823
848
|
if (results.length > 0) {
|
|
824
849
|
for (const r of results) {
|
|
825
|
-
const snippet = extractSnippet(r.content, query);
|
|
850
|
+
const snippet = extractSnippet(r.content, query, 1500, r.highlighted);
|
|
826
851
|
queryResults.push(`### ${r.title}`);
|
|
827
852
|
queryResults.push(snippet);
|
|
828
853
|
queryResults.push("");
|
package/build/store.d.ts
CHANGED
package/build/store.js
CHANGED
|
@@ -271,7 +271,8 @@ export class ContentStore {
|
|
|
271
271
|
chunks.content,
|
|
272
272
|
chunks.content_type,
|
|
273
273
|
sources.label,
|
|
274
|
-
bm25(chunks, 2.0, 1.0) AS rank
|
|
274
|
+
bm25(chunks, 2.0, 1.0) AS rank,
|
|
275
|
+
highlight(chunks, 1, char(2), char(3)) AS highlighted
|
|
275
276
|
FROM chunks
|
|
276
277
|
JOIN sources ON sources.id = chunks.source_id
|
|
277
278
|
WHERE chunks MATCH ? ${sourceFilter}
|
|
@@ -288,6 +289,7 @@ export class ContentStore {
|
|
|
288
289
|
source: r.label,
|
|
289
290
|
rank: r.rank,
|
|
290
291
|
contentType: r.content_type,
|
|
292
|
+
highlighted: r.highlighted,
|
|
291
293
|
}));
|
|
292
294
|
}
|
|
293
295
|
// ── Trigram Search (Layer 2) ──
|
|
@@ -302,7 +304,8 @@ export class ContentStore {
|
|
|
302
304
|
chunks_trigram.content,
|
|
303
305
|
chunks_trigram.content_type,
|
|
304
306
|
sources.label,
|
|
305
|
-
bm25(chunks_trigram, 2.0, 1.0) AS rank
|
|
307
|
+
bm25(chunks_trigram, 2.0, 1.0) AS rank,
|
|
308
|
+
highlight(chunks_trigram, 1, char(2), char(3)) AS highlighted
|
|
306
309
|
FROM chunks_trigram
|
|
307
310
|
JOIN sources ON sources.id = chunks_trigram.source_id
|
|
308
311
|
WHERE chunks_trigram MATCH ? ${sourceFilter}
|
|
@@ -319,6 +322,7 @@ export class ContentStore {
|
|
|
319
322
|
source: r.label,
|
|
320
323
|
rank: r.rank,
|
|
321
324
|
contentType: r.content_type,
|
|
325
|
+
highlighted: r.highlighted,
|
|
322
326
|
}));
|
|
323
327
|
}
|
|
324
328
|
// ── Fuzzy Correction (Layer 3) ──
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.17",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Claude Code MCP plugin that saves 98% of your context window. Sandboxed code execution, FTS5 knowledge base, and intent-driven search.",
|
|
6
6
|
"author": "Mert Koseoğlu",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
],
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsc",
|
|
41
|
-
"bundle": "esbuild src/server.ts --bundle --platform=node --target=node18 --format=esm --outfile=server.bundle.mjs --external:better-sqlite3 --minify",
|
|
41
|
+
"bundle": "esbuild src/server.ts --bundle --platform=node --target=node18 --format=esm --outfile=server.bundle.mjs --external:better-sqlite3 --external:turndown --external:turndown-plugin-gfm --external:@mixmark-io/domino --minify",
|
|
42
42
|
"prepublishOnly": "npm run build",
|
|
43
43
|
"dev": "npx tsx src/server.ts",
|
|
44
44
|
"setup": "npx tsx src/cli.ts setup",
|
|
@@ -56,18 +56,23 @@
|
|
|
56
56
|
"test:stream-cap": "npx tsx tests/stream-cap.test.ts",
|
|
57
57
|
"test:search-wiring": "npx tsx tests/search-wiring.test.ts",
|
|
58
58
|
"test:search-fallback": "npx tsx tests/search-fallback-integration.test.ts",
|
|
59
|
+
"test:turndown": "npx tsx tests/turndown.test.ts",
|
|
59
60
|
"test:all": "for f in tests/*.test.ts; do npx tsx \"$f\" || exit 1; done"
|
|
60
61
|
},
|
|
61
62
|
"dependencies": {
|
|
62
63
|
"@clack/prompts": "^1.0.1",
|
|
64
|
+
"@mixmark-io/domino": "^2.2.0",
|
|
63
65
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
64
66
|
"better-sqlite3": "^12.6.2",
|
|
65
67
|
"picocolors": "^1.1.1",
|
|
68
|
+
"turndown": "^7.2.0",
|
|
69
|
+
"turndown-plugin-gfm": "^1.0.2",
|
|
66
70
|
"zod": "^3.25.0"
|
|
67
71
|
},
|
|
68
72
|
"devDependencies": {
|
|
69
73
|
"@types/better-sqlite3": "^7.6.13",
|
|
70
74
|
"@types/node": "^22.19.11",
|
|
75
|
+
"@types/turndown": "^5.0.5",
|
|
71
76
|
"esbuild": "^0.27.3",
|
|
72
77
|
"tsx": "^4.21.0",
|
|
73
78
|
"typescript": "^5.7.0"
|