context-mode 0.9.16 → 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.js +34 -60
- package/package.json +7 -2
- package/server.bundle.mjs +42 -93
- 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.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";
|
|
@@ -628,75 +629,48 @@ server.registerTool("search", {
|
|
|
628
629
|
}
|
|
629
630
|
});
|
|
630
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
|
+
// ─────────────────────────────────────────────────────────
|
|
631
651
|
// Tool: fetch_and_index
|
|
632
652
|
// ─────────────────────────────────────────────────────────
|
|
633
|
-
|
|
634
|
-
const
|
|
635
|
-
|
|
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)};
|
|
636
660
|
|
|
637
661
|
async function main() {
|
|
638
662
|
const resp = await fetch(url);
|
|
639
663
|
if (!resp.ok) { console.error("HTTP " + resp.status); process.exit(1); }
|
|
664
|
+
const html = await resp.text();
|
|
640
665
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
html = html.replace(/<style[^>]*>[\\s\\S]*?<\\/style>/gi, "");
|
|
646
|
-
html = html.replace(/<nav[^>]*>[\\s\\S]*?<\\/nav>/gi, "");
|
|
647
|
-
html = html.replace(/<header[^>]*>[\\s\\S]*?<\\/header>/gi, "");
|
|
648
|
-
html = html.replace(/<footer[^>]*>[\\s\\S]*?<\\/footer>/gi, "");
|
|
649
|
-
|
|
650
|
-
// Convert headings to markdown
|
|
651
|
-
html = html.replace(/<h1[^>]*>(.*?)<\\/h1>/gi, "\\n# $1\\n");
|
|
652
|
-
html = html.replace(/<h2[^>]*>(.*?)<\\/h2>/gi, "\\n## $1\\n");
|
|
653
|
-
html = html.replace(/<h3[^>]*>(.*?)<\\/h3>/gi, "\\n### $1\\n");
|
|
654
|
-
html = html.replace(/<h4[^>]*>(.*?)<\\/h4>/gi, "\\n#### $1\\n");
|
|
655
|
-
|
|
656
|
-
// Convert code blocks
|
|
657
|
-
html = html.replace(/<pre[^>]*><code[^>]*class="[^"]*language-(\\w+)"[^>]*>([\\s\\S]*?)<\\/code><\\/pre>/gi,
|
|
658
|
-
(_, lang, code) => "\\n\\\`\\\`\\\`" + lang + "\\n" + decodeEntities(code) + "\\n\\\`\\\`\\\`\\n");
|
|
659
|
-
html = html.replace(/<pre[^>]*><code[^>]*>([\\s\\S]*?)<\\/code><\\/pre>/gi,
|
|
660
|
-
(_, code) => "\\n\\\`\\\`\\\`\\n" + decodeEntities(code) + "\\n\\\`\\\`\\\`\\n");
|
|
661
|
-
html = html.replace(/<code[^>]*>([^<]*)<\\/code>/gi, "\\\`$1\\\`");
|
|
662
|
-
|
|
663
|
-
// Convert links
|
|
664
|
-
html = html.replace(/<a[^>]*href="([^"]*)"[^>]*>(.*?)<\\/a>/gi, "[$2]($1)");
|
|
665
|
-
|
|
666
|
-
// Convert lists
|
|
667
|
-
html = html.replace(/<li[^>]*>(.*?)<\\/li>/gi, "- $1\\n");
|
|
668
|
-
|
|
669
|
-
// Convert paragraphs and line breaks
|
|
670
|
-
html = html.replace(/<p[^>]*>(.*?)<\\/p>/gi, "\\n$1\\n");
|
|
671
|
-
html = html.replace(/<br\\s*\\/?>/gi, "\\n");
|
|
672
|
-
html = html.replace(/<hr\\s*\\/?>/gi, "\\n---\\n");
|
|
673
|
-
|
|
674
|
-
// Strip remaining HTML tags
|
|
675
|
-
html = html.replace(/<[^>]+>/g, "");
|
|
676
|
-
|
|
677
|
-
// Decode HTML entities
|
|
678
|
-
html = decodeEntities(html);
|
|
679
|
-
|
|
680
|
-
// Clean up whitespace
|
|
681
|
-
html = html.replace(/\\n{3,}/g, "\\n\\n").trim();
|
|
682
|
-
|
|
683
|
-
console.log(html);
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
function decodeEntities(s) {
|
|
687
|
-
return s
|
|
688
|
-
.replace(/&/g, "&")
|
|
689
|
-
.replace(/</g, "<")
|
|
690
|
-
.replace(/>/g, ">")
|
|
691
|
-
.replace(/"/g, '"')
|
|
692
|
-
.replace(/'/g, "'")
|
|
693
|
-
.replace(/'/g, "'")
|
|
694
|
-
.replace(///g, "/")
|
|
695
|
-
.replace(/ /g, " ");
|
|
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));
|
|
696
670
|
}
|
|
697
|
-
|
|
698
671
|
main();
|
|
699
672
|
`;
|
|
673
|
+
}
|
|
700
674
|
server.registerTool("fetch_and_index", {
|
|
701
675
|
title: "Fetch & Index URL",
|
|
702
676
|
description: "Fetches URL content, converts HTML to markdown, indexes into searchable knowledge base, " +
|
|
@@ -712,7 +686,7 @@ server.registerTool("fetch_and_index", {
|
|
|
712
686
|
}, async ({ url, source }) => {
|
|
713
687
|
try {
|
|
714
688
|
// Execute fetch inside subprocess — raw HTML never enters context
|
|
715
|
-
const fetchCode =
|
|
689
|
+
const fetchCode = buildFetchCode(url);
|
|
716
690
|
const result = await executor.execute({
|
|
717
691
|
language: "javascript",
|
|
718
692
|
code: fetchCode,
|
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"
|