context-mode 1.0.38 → 1.0.40
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 +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/build/adapters/detect.d.ts +1 -1
- package/build/adapters/detect.js +3 -3
- package/build/adapters/openclaw/index.d.ts +1 -1
- package/build/adapters/openclaw/index.js +5 -5
- package/build/adapters/opencode/index.js +6 -6
- package/build/openclaw-plugin.js +1 -1
- package/build/opencode-plugin.d.ts +34 -10
- package/build/opencode-plugin.js +13 -11
- package/build/server.js +107 -34
- package/cli.bundle.mjs +1 -1
- package/hooks/session-loaders.mjs +25 -9
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.mjs +78 -22
- package/skills/ctx-doctor/SKILL.md +4 -14
- package/skills/ctx-upgrade/SKILL.md +10 -11
- package/start.mjs +63 -38
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Claude Code plugins by Mert Koseoğlu",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.40"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
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": "1.0.
|
|
16
|
+
"version": "1.0.40",
|
|
17
17
|
"author": {
|
|
18
18
|
"name": "Mert Koseoğlu"
|
|
19
19
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.40",
|
|
4
4
|
"description": "MCP server that saves 98% of your context window with session continuity. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and automatic state restore across compactions.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Mert Koseoğlu",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "Context Mode",
|
|
4
4
|
"kind": "tool",
|
|
5
5
|
"description": "OpenClaw plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
|
|
6
|
-
"version": "1.0.
|
|
6
|
+
"version": "1.0.40",
|
|
7
7
|
"sandbox": {
|
|
8
8
|
"mode": "permissive",
|
|
9
9
|
"filesystem_access": "full",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.40",
|
|
4
4
|
"description": "OpenClaw 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",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - Claude Code: CLAUDE_PROJECT_DIR, CLAUDE_SESSION_ID | ~/.claude/
|
|
11
11
|
* - Gemini CLI: GEMINI_PROJECT_DIR (hooks), GEMINI_CLI (MCP) | ~/.gemini/
|
|
12
12
|
* - OpenCode: OPENCODE, OPENCODE_PID | ~/.config/opencode/
|
|
13
|
-
* - OpenClaw: OPENCLAW_HOME,
|
|
13
|
+
* - OpenClaw: OPENCLAW_HOME, OPENCLAW_CLI | ~/.openclaw/
|
|
14
14
|
* - Codex CLI: CODEX_CI, CODEX_THREAD_ID | ~/.codex/
|
|
15
15
|
* - Cursor: CURSOR_TRACE_ID (MCP), CURSOR_CLI (terminal) | ~/.cursor/
|
|
16
16
|
* - VS Code Copilot: VSCODE_PID, VSCODE_CWD | ~/.vscode/
|
package/build/adapters/detect.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - Claude Code: CLAUDE_PROJECT_DIR, CLAUDE_SESSION_ID | ~/.claude/
|
|
11
11
|
* - Gemini CLI: GEMINI_PROJECT_DIR (hooks), GEMINI_CLI (MCP) | ~/.gemini/
|
|
12
12
|
* - OpenCode: OPENCODE, OPENCODE_PID | ~/.config/opencode/
|
|
13
|
-
* - OpenClaw: OPENCLAW_HOME,
|
|
13
|
+
* - OpenClaw: OPENCLAW_HOME, OPENCLAW_CLI | ~/.openclaw/
|
|
14
14
|
* - Codex CLI: CODEX_CI, CODEX_THREAD_ID | ~/.codex/
|
|
15
15
|
* - Cursor: CURSOR_TRACE_ID (MCP), CURSOR_CLI (terminal) | ~/.cursor/
|
|
16
16
|
* - VS Code Copilot: VSCODE_PID, VSCODE_CWD | ~/.vscode/
|
|
@@ -67,11 +67,11 @@ export function detectPlatform(clientInfo) {
|
|
|
67
67
|
reason: "GEMINI_PROJECT_DIR or GEMINI_CLI env var set",
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
|
-
if (process.env.OPENCLAW_HOME || process.env.
|
|
70
|
+
if (process.env.OPENCLAW_HOME || process.env.OPENCLAW_CLI) {
|
|
71
71
|
return {
|
|
72
72
|
platform: "openclaw",
|
|
73
73
|
confidence: "high",
|
|
74
|
-
reason: "OPENCLAW_HOME or
|
|
74
|
+
reason: "OPENCLAW_HOME or OPENCLAW_CLI env var set",
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
77
|
if (process.env.OPENCODE || process.env.OPENCODE_PID) {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* - Arg modification: mutate event.params in tool_call:before
|
|
12
12
|
* - Blocking: return { block: true, blockReason } from tool_call:before
|
|
13
13
|
* - Session ID: event context (no specific env var)
|
|
14
|
-
* - Project dir: process.
|
|
14
|
+
* - Project dir: process.cwd()
|
|
15
15
|
* - Config: openclaw.json plugins.entries, ~/.openclaw/extensions/
|
|
16
16
|
* - Session dir: ~/.openclaw/context-mode/sessions/
|
|
17
17
|
*/
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* - Arg modification: mutate event.params in tool_call:before
|
|
12
12
|
* - Blocking: return { block: true, blockReason } from tool_call:before
|
|
13
13
|
* - Session ID: event context (no specific env var)
|
|
14
|
-
* - Project dir: process.
|
|
14
|
+
* - Project dir: process.cwd()
|
|
15
15
|
* - Config: openclaw.json plugins.entries, ~/.openclaw/extensions/
|
|
16
16
|
* - Session dir: ~/.openclaw/context-mode/sessions/
|
|
17
17
|
*/
|
|
@@ -45,7 +45,7 @@ export class OpenClawAdapter {
|
|
|
45
45
|
toolName: input.toolName ?? input.tool_name ?? "",
|
|
46
46
|
toolInput: input.params ?? input.tool_input ?? {},
|
|
47
47
|
sessionId: this.extractSessionId(input),
|
|
48
|
-
projectDir: process.
|
|
48
|
+
projectDir: process.cwd(),
|
|
49
49
|
raw,
|
|
50
50
|
};
|
|
51
51
|
}
|
|
@@ -57,7 +57,7 @@ export class OpenClawAdapter {
|
|
|
57
57
|
toolOutput: input.output ?? input.tool_output,
|
|
58
58
|
isError: input.isError ?? input.is_error,
|
|
59
59
|
sessionId: this.extractSessionId(input),
|
|
60
|
-
projectDir: process.
|
|
60
|
+
projectDir: process.cwd(),
|
|
61
61
|
raw,
|
|
62
62
|
};
|
|
63
63
|
}
|
|
@@ -65,7 +65,7 @@ export class OpenClawAdapter {
|
|
|
65
65
|
const input = raw;
|
|
66
66
|
return {
|
|
67
67
|
sessionId: this.extractSessionId(input),
|
|
68
|
-
projectDir: process.
|
|
68
|
+
projectDir: process.cwd(),
|
|
69
69
|
raw,
|
|
70
70
|
};
|
|
71
71
|
}
|
|
@@ -89,7 +89,7 @@ export class OpenClawAdapter {
|
|
|
89
89
|
return {
|
|
90
90
|
sessionId: this.extractSessionId(input),
|
|
91
91
|
source,
|
|
92
|
-
projectDir: process.
|
|
92
|
+
projectDir: process.cwd(),
|
|
93
93
|
raw,
|
|
94
94
|
};
|
|
95
95
|
}
|
|
@@ -42,8 +42,8 @@ export class OpenCodeAdapter {
|
|
|
42
42
|
parsePreToolUseInput(raw) {
|
|
43
43
|
const input = raw;
|
|
44
44
|
return {
|
|
45
|
-
toolName: input.
|
|
46
|
-
toolInput: input.
|
|
45
|
+
toolName: input.tool ?? "",
|
|
46
|
+
toolInput: input.args ?? {},
|
|
47
47
|
sessionId: this.extractSessionId(input),
|
|
48
48
|
projectDir: process.env.OPENCODE_PROJECT_DIR || process.cwd(),
|
|
49
49
|
raw,
|
|
@@ -52,10 +52,10 @@ export class OpenCodeAdapter {
|
|
|
52
52
|
parsePostToolUseInput(raw) {
|
|
53
53
|
const input = raw;
|
|
54
54
|
return {
|
|
55
|
-
toolName: input.
|
|
56
|
-
toolInput: input.
|
|
57
|
-
toolOutput: input.
|
|
58
|
-
isError:
|
|
55
|
+
toolName: input.tool ?? "",
|
|
56
|
+
toolInput: input.args ?? {},
|
|
57
|
+
toolOutput: input.output,
|
|
58
|
+
isError: undefined, // OpenCode doesn't provide isError
|
|
59
59
|
sessionId: this.extractSessionId(input),
|
|
60
60
|
projectDir: process.env.OPENCODE_PROJECT_DIR || process.cwd(),
|
|
61
61
|
raw,
|
package/build/openclaw-plugin.js
CHANGED
|
@@ -98,7 +98,7 @@ export default {
|
|
|
98
98
|
register(api) {
|
|
99
99
|
// Resolve build dir from compiled JS location
|
|
100
100
|
const buildDir = dirname(fileURLToPath(import.meta.url));
|
|
101
|
-
const projectDir = process.
|
|
101
|
+
const projectDir = process.cwd();
|
|
102
102
|
const pluginRoot = resolve(buildDir, "..");
|
|
103
103
|
// Structured logger — wraps api.logger, falls back to no-op.
|
|
104
104
|
// info/error always emit; debug only when api.logger.debug is present
|
|
@@ -17,21 +17,45 @@
|
|
|
17
17
|
interface PluginContext {
|
|
18
18
|
directory: string;
|
|
19
19
|
}
|
|
20
|
-
/**
|
|
21
|
-
interface
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
/** OpenCode tool.execute.before — first parameter */
|
|
21
|
+
interface BeforeHookInput {
|
|
22
|
+
tool: string;
|
|
23
|
+
sessionID: string;
|
|
24
|
+
callID: string;
|
|
25
|
+
}
|
|
26
|
+
/** OpenCode tool.execute.before — second parameter */
|
|
27
|
+
interface BeforeHookOutput {
|
|
28
|
+
args: any;
|
|
29
|
+
}
|
|
30
|
+
/** OpenCode tool.execute.after — first parameter */
|
|
31
|
+
interface AfterHookInput {
|
|
32
|
+
tool: string;
|
|
33
|
+
sessionID: string;
|
|
34
|
+
callID: string;
|
|
35
|
+
args: any;
|
|
36
|
+
}
|
|
37
|
+
/** OpenCode tool.execute.after — second parameter */
|
|
38
|
+
interface AfterHookOutput {
|
|
39
|
+
title: string;
|
|
40
|
+
output: string;
|
|
41
|
+
metadata: any;
|
|
42
|
+
}
|
|
43
|
+
/** OpenCode experimental.session.compacting — first parameter */
|
|
44
|
+
interface CompactingHookInput {
|
|
45
|
+
sessionID: string;
|
|
46
|
+
}
|
|
47
|
+
/** OpenCode experimental.session.compacting — second parameter */
|
|
48
|
+
interface CompactingHookOutput {
|
|
49
|
+
context: string[];
|
|
50
|
+
prompt?: string;
|
|
27
51
|
}
|
|
28
52
|
/**
|
|
29
53
|
* OpenCode plugin factory. Called once when OpenCode loads the plugin.
|
|
30
54
|
* Returns an object mapping hook event names to async handler functions.
|
|
31
55
|
*/
|
|
32
56
|
export declare const ContextModePlugin: (ctx: PluginContext) => Promise<{
|
|
33
|
-
"tool.execute.before": (input:
|
|
34
|
-
"tool.execute.after": (input:
|
|
35
|
-
"experimental.session.compacting": () => Promise<string>;
|
|
57
|
+
"tool.execute.before": (input: BeforeHookInput, output: BeforeHookOutput) => Promise<void>;
|
|
58
|
+
"tool.execute.after": (input: AfterHookInput, output: AfterHookOutput) => Promise<void>;
|
|
59
|
+
"experimental.session.compacting": (input: CompactingHookInput, output: CompactingHookOutput) => Promise<string>;
|
|
36
60
|
}>;
|
|
37
61
|
export {};
|
package/build/opencode-plugin.js
CHANGED
|
@@ -63,9 +63,9 @@ export const ContextModePlugin = async (ctx) => {
|
|
|
63
63
|
db.cleanupOldSessions(0);
|
|
64
64
|
return {
|
|
65
65
|
// ── PreToolUse: Routing enforcement ─────────────────
|
|
66
|
-
"tool.execute.before": async (input) => {
|
|
67
|
-
const toolName = input.
|
|
68
|
-
const toolInput =
|
|
66
|
+
"tool.execute.before": async (input, output) => {
|
|
67
|
+
const toolName = input.tool ?? "";
|
|
68
|
+
const toolInput = output.args ?? {};
|
|
69
69
|
let decision;
|
|
70
70
|
try {
|
|
71
71
|
decision = routing.routePreToolUse(toolName, toolInput, projectDir);
|
|
@@ -80,19 +80,19 @@ export const ContextModePlugin = async (ctx) => {
|
|
|
80
80
|
throw new Error(decision.reason ?? "Blocked by context-mode");
|
|
81
81
|
}
|
|
82
82
|
if (decision.action === "modify" && decision.updatedInput) {
|
|
83
|
-
// Mutate args
|
|
84
|
-
Object.assign(
|
|
83
|
+
// Mutate output.args — OpenCode reads the mutated output object
|
|
84
|
+
Object.assign(output.args, decision.updatedInput);
|
|
85
85
|
}
|
|
86
86
|
// "context" action → no-op (OpenCode doesn't support context injection)
|
|
87
87
|
},
|
|
88
88
|
// ── PostToolUse: Session event capture ──────────────
|
|
89
|
-
"tool.execute.after": async (input) => {
|
|
89
|
+
"tool.execute.after": async (input, output) => {
|
|
90
90
|
try {
|
|
91
91
|
const hookInput = {
|
|
92
|
-
tool_name: input.
|
|
93
|
-
tool_input: input.
|
|
94
|
-
tool_response:
|
|
95
|
-
tool_output:
|
|
92
|
+
tool_name: input.tool ?? "",
|
|
93
|
+
tool_input: input.args ?? {},
|
|
94
|
+
tool_response: output.output,
|
|
95
|
+
tool_output: undefined, // OpenCode doesn't provide isError
|
|
96
96
|
};
|
|
97
97
|
const events = extractEvents(hookInput);
|
|
98
98
|
for (const event of events) {
|
|
@@ -105,7 +105,7 @@ export const ContextModePlugin = async (ctx) => {
|
|
|
105
105
|
}
|
|
106
106
|
},
|
|
107
107
|
// ── PreCompact: Snapshot generation ─────────────────
|
|
108
|
-
"experimental.session.compacting": async () => {
|
|
108
|
+
"experimental.session.compacting": async (input, output) => {
|
|
109
109
|
try {
|
|
110
110
|
const events = db.getEvents(sessionId);
|
|
111
111
|
if (events.length === 0)
|
|
@@ -116,6 +116,8 @@ export const ContextModePlugin = async (ctx) => {
|
|
|
116
116
|
});
|
|
117
117
|
db.upsertResume(sessionId, snapshot, events.length);
|
|
118
118
|
db.incrementCompactCount(sessionId);
|
|
119
|
+
// Mutate output.context to inject the snapshot
|
|
120
|
+
output.context.push(snapshot);
|
|
119
121
|
return snapshot;
|
|
120
122
|
}
|
|
121
123
|
catch {
|
package/build/server.js
CHANGED
|
@@ -80,7 +80,7 @@ function maybeIndexSessionEvents(store) {
|
|
|
80
80
|
function getStorePath() {
|
|
81
81
|
const projectDir = process.env.CLAUDE_PROJECT_DIR
|
|
82
82
|
|| process.env.GEMINI_PROJECT_DIR
|
|
83
|
-
|| process.env.
|
|
83
|
+
|| process.env.OPENCLAW_HOME
|
|
84
84
|
|| process.cwd();
|
|
85
85
|
const normalized = projectDir.replace(/\\/g, "/");
|
|
86
86
|
const hash = createHash("sha256").update(normalized).digest("hex").slice(0, 16);
|
|
@@ -1504,43 +1504,70 @@ server.registerTool("ctx_stats", {
|
|
|
1504
1504
|
content: [{ type: "text", text }],
|
|
1505
1505
|
});
|
|
1506
1506
|
});
|
|
1507
|
-
// ── ctx-doctor: diagnostics
|
|
1507
|
+
// ── ctx-doctor: diagnostics (server-side) ─────────────────────────────────
|
|
1508
1508
|
server.registerTool("ctx_doctor", {
|
|
1509
1509
|
title: "Run Diagnostics",
|
|
1510
|
-
description: "Diagnose context-mode installation.
|
|
1511
|
-
"
|
|
1512
|
-
"run_in_terminal, etc.) and display the output as a markdown checklist.",
|
|
1510
|
+
description: "Diagnose context-mode installation. Runs all checks server-side and " +
|
|
1511
|
+
"returns results as a markdown checklist. No CLI execution needed.",
|
|
1513
1512
|
inputSchema: z.object({}),
|
|
1514
1513
|
}, async () => {
|
|
1514
|
+
const lines = ["## context-mode doctor", ""];
|
|
1515
1515
|
const pluginRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
1516
|
-
|
|
1517
|
-
const
|
|
1518
|
-
const
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
""
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
"
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1516
|
+
// Runtimes
|
|
1517
|
+
const total = 11;
|
|
1518
|
+
const pct = ((available.length / total) * 100).toFixed(0);
|
|
1519
|
+
lines.push(`- [x] Runtimes: ${available.length}/${total} (${pct}%) — ${available.join(", ")}`);
|
|
1520
|
+
// Performance
|
|
1521
|
+
if (hasBunRuntime()) {
|
|
1522
|
+
lines.push("- [x] Performance: FAST (Bun)");
|
|
1523
|
+
}
|
|
1524
|
+
else {
|
|
1525
|
+
lines.push("- [-] Performance: NORMAL — install Bun for 3-5x speed boost");
|
|
1526
|
+
}
|
|
1527
|
+
// Server test
|
|
1528
|
+
try {
|
|
1529
|
+
const testExecutor = new PolyglotExecutor({ runtimes });
|
|
1530
|
+
const result = await testExecutor.execute({ language: "javascript", code: 'console.log("ok");', timeout: 5000 });
|
|
1531
|
+
if (result.exitCode === 0 && result.stdout.trim() === "ok") {
|
|
1532
|
+
lines.push("- [x] Server test: PASS");
|
|
1533
|
+
}
|
|
1534
|
+
else {
|
|
1535
|
+
lines.push(`- [ ] Server test: FAIL — exit ${result.exitCode}`);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
catch (err) {
|
|
1539
|
+
lines.push(`- [ ] Server test: FAIL — ${err instanceof Error ? err.message : err}`);
|
|
1540
|
+
}
|
|
1541
|
+
// FTS5 / SQLite
|
|
1542
|
+
try {
|
|
1543
|
+
const Database = loadDatabase();
|
|
1544
|
+
const db = new Database(":memory:");
|
|
1545
|
+
db.exec("CREATE VIRTUAL TABLE fts_test USING fts5(content)");
|
|
1546
|
+
db.exec("INSERT INTO fts_test(content) VALUES ('hello world')");
|
|
1547
|
+
const row = db.prepare("SELECT * FROM fts_test WHERE fts_test MATCH 'hello'").get();
|
|
1548
|
+
db.close();
|
|
1549
|
+
if (row && row.content === "hello world") {
|
|
1550
|
+
lines.push("- [x] FTS5 / SQLite: PASS — native module works");
|
|
1551
|
+
}
|
|
1552
|
+
else {
|
|
1553
|
+
lines.push("- [ ] FTS5 / SQLite: FAIL — unexpected result");
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
catch (err) {
|
|
1557
|
+
lines.push(`- [ ] FTS5 / SQLite: FAIL — ${err instanceof Error ? err.message : err}`);
|
|
1558
|
+
}
|
|
1559
|
+
// Hook script
|
|
1560
|
+
const hookPath = resolve(pluginRoot, "hooks", "pretooluse.mjs");
|
|
1561
|
+
if (existsSync(hookPath)) {
|
|
1562
|
+
lines.push(`- [x] Hook script: PASS — ${hookPath}`);
|
|
1563
|
+
}
|
|
1564
|
+
else {
|
|
1565
|
+
lines.push(`- [ ] Hook script: FAIL — not found at ${hookPath}`);
|
|
1566
|
+
}
|
|
1567
|
+
// Version
|
|
1568
|
+
lines.push(`- [x] Version: v${VERSION}`);
|
|
1542
1569
|
return trackResponse("ctx_doctor", {
|
|
1543
|
-
content: [{ type: "text", text }],
|
|
1570
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
1544
1571
|
});
|
|
1545
1572
|
});
|
|
1546
1573
|
// ── ctx-upgrade: upgrade meta-tool ─────────────────────────────────────────
|
|
@@ -1555,8 +1582,54 @@ server.registerTool("ctx_upgrade", {
|
|
|
1555
1582
|
const pluginRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
1556
1583
|
const bundlePath = resolve(pluginRoot, "cli.bundle.mjs");
|
|
1557
1584
|
const fallbackPath = resolve(pluginRoot, "build", "cli.js");
|
|
1558
|
-
|
|
1559
|
-
|
|
1585
|
+
let cmd;
|
|
1586
|
+
if (existsSync(bundlePath)) {
|
|
1587
|
+
cmd = `node "${bundlePath}" upgrade`;
|
|
1588
|
+
}
|
|
1589
|
+
else if (existsSync(fallbackPath)) {
|
|
1590
|
+
cmd = `node "${fallbackPath}" upgrade`;
|
|
1591
|
+
}
|
|
1592
|
+
else {
|
|
1593
|
+
// Inline fallback: neither CLI file exists (e.g. marketplace installs).
|
|
1594
|
+
// Generate a self-contained node -e script that performs the upgrade.
|
|
1595
|
+
const repoUrl = "https://github.com/mksglu/context-mode.git";
|
|
1596
|
+
const copyDirs = ["build", "hooks", "skills", "scripts", ".claude-plugin"];
|
|
1597
|
+
const copyFiles = ["start.mjs", "server.bundle.mjs", "cli.bundle.mjs", "package.json"];
|
|
1598
|
+
// Write inline script to a temp .mjs file — avoids quote-escaping issues
|
|
1599
|
+
// across cmd.exe, PowerShell, and bash (node -e '...' breaks on Windows).
|
|
1600
|
+
const scriptLines = [
|
|
1601
|
+
`import{execSync}from"node:child_process";`,
|
|
1602
|
+
`import{cpSync,rmSync,existsSync,mkdtempSync}from"node:fs";`,
|
|
1603
|
+
`import{join}from"node:path";`,
|
|
1604
|
+
`import{tmpdir}from"node:os";`,
|
|
1605
|
+
`const P=${JSON.stringify(pluginRoot)};`,
|
|
1606
|
+
`const T=mkdtempSync(join(tmpdir(),"ctx-upgrade-"));`,
|
|
1607
|
+
`try{`,
|
|
1608
|
+
`console.log("- [x] Starting inline upgrade (no CLI found)");`,
|
|
1609
|
+
`execSync("git clone --depth 1 ${repoUrl} \\""+T+"\\"",{stdio:"inherit"});`,
|
|
1610
|
+
`console.log("- [x] Cloned latest source");`,
|
|
1611
|
+
`execSync("npm install",{cwd:T,stdio:"inherit"});`,
|
|
1612
|
+
`execSync("npm run build",{cwd:T,stdio:"inherit"});`,
|
|
1613
|
+
`console.log("- [x] Built from source");`,
|
|
1614
|
+
...copyDirs.map((d) => `if(existsSync(join(T,${JSON.stringify(d)})))cpSync(join(T,${JSON.stringify(d)}),join(P,${JSON.stringify(d)}),{recursive:true,force:true});`),
|
|
1615
|
+
...copyFiles.map((f) => `if(existsSync(join(T,${JSON.stringify(f)})))cpSync(join(T,${JSON.stringify(f)}),join(P,${JSON.stringify(f)}),{force:true});`),
|
|
1616
|
+
`console.log("- [x] Copied build artifacts");`,
|
|
1617
|
+
`execSync("npm install --production",{cwd:P,stdio:"inherit"});`,
|
|
1618
|
+
`console.log("- [x] Installed production dependencies");`,
|
|
1619
|
+
`console.log("## context-mode upgrade complete");`,
|
|
1620
|
+
`}catch(e){`,
|
|
1621
|
+
`console.error("- [ ] Upgrade failed:",e.message);`,
|
|
1622
|
+
`process.exit(1);`,
|
|
1623
|
+
`}finally{`,
|
|
1624
|
+
`try{rmSync(T,{recursive:true,force:true})}catch{}`,
|
|
1625
|
+
`}`,
|
|
1626
|
+
].join("\n");
|
|
1627
|
+
// Server writes the temp script file — avoids shell quoting issues entirely
|
|
1628
|
+
const tmpScript = resolve(pluginRoot, ".ctx-upgrade-inline.mjs");
|
|
1629
|
+
const { writeFileSync: writeTmp } = await import("node:fs");
|
|
1630
|
+
writeTmp(tmpScript, scriptLines);
|
|
1631
|
+
cmd = `node "${tmpScript}"`;
|
|
1632
|
+
}
|
|
1560
1633
|
const text = [
|
|
1561
1634
|
"## ctx-upgrade",
|
|
1562
1635
|
"",
|
package/cli.bundle.mjs
CHANGED
|
@@ -524,7 +524,7 @@ ${be("gray",d+Rh.repeat(c+2)+A0)}
|
|
|
524
524
|
Run: ${h.fix}`):""));I.step("Checking hook script...");let l=dt(c,"hooks","pretooluse.mjs");try{o0(l,s0.R_OK),I.success(T.default.green("Hook script exists: PASS")+T.default.dim(` \u2014 ${l}`))}catch{I.error(T.default.red("Hook script exists: FAIL")+T.default.dim(` \u2014 not found at ${l}`))}I.step(`Checking ${e.name} plugin registration...`);let d=e.checkPluginRegistration();d.status==="pass"?I.success(T.default.green("Plugin enabled: PASS")+T.default.dim(` \u2014 ${d.message}`)):I.warn(T.default.yellow("Plugin enabled: WARN")+` \u2014 ${d.message}`),I.step("Checking FTS5 / SQLite...");try{let h=(await Promise.resolve().then(()=>(ii(),Ek))).loadDatabase(),g=new h(":memory:");g.exec("CREATE VIRTUAL TABLE fts_test USING fts5(content)"),g.exec("INSERT INTO fts_test(content) VALUES ('hello world')");let _=g.prepare("SELECT * FROM fts_test WHERE fts_test MATCH 'hello'").get();g.close(),_&&_.content==="hello world"?I.success(T.default.green("FTS5 / SQLite: PASS")+" \u2014 native module works"):(r++,I.error(T.default.red("FTS5 / SQLite: FAIL")+" \u2014 query returned unexpected result"))}catch(h){let g=h instanceof Error?h.message:String(h);g.includes("Cannot find module")||g.includes("MODULE_NOT_FOUND")?I.warn(T.default.yellow("FTS5 / better-sqlite3: SKIP")+T.default.dim(" \u2014 module not available (restart session after upgrade)")):(r++,I.error(T.default.red("FTS5 / better-sqlite3: FAIL")+` \u2014 ${g}`+T.default.dim(`
|
|
525
525
|
Try: npm rebuild better-sqlite3`)))}I.step("Checking versions...");let m=a0(),f=await r2(),p=e.getInstalledVersion();return f==="unknown"?I.warn(T.default.yellow("npm (MCP): WARN")+` \u2014 local v${m}, could not reach npm registry`):m===f?I.success(T.default.green("npm (MCP): PASS")+` \u2014 v${m}`):I.warn(T.default.yellow("npm (MCP): WARN")+` \u2014 local v${m}, latest v${f}`+T.default.dim(`
|
|
526
526
|
Run: /context-mode:ctx-upgrade`)),p==="not installed"?I.info(T.default.dim(`${e.name}: not installed`)+" \u2014 using standalone MCP mode"):f!=="unknown"&&p===f?I.success(T.default.green(`${e.name}: PASS`)+` \u2014 v${p}`):f!=="unknown"?I.warn(T.default.yellow(`${e.name}: WARN`)+` \u2014 v${p}, latest v${f}`+T.default.dim(`
|
|
527
|
-
Run: /context-mode:ctx-upgrade`)):I.info(`${e.name}: v${p}`+T.default.dim(" \u2014 could not verify against npm registry")),r>0?(gi(T.default.red(`Diagnostics failed \u2014 ${r} critical issue(s) found`)),1):(gi(s.length>=4?T.default.green("Diagnostics complete!"):T.default.yellow("Some checks need attention \u2014 see above for details")),0)}async function o2(){process.stdout.isTTY&&console.clear();let t=Ko(),e=await Ni(t.platform);ou(T.default.bgCyan(T.default.black(" context-mode upgrade "))),I.info(`Platform: ${T.default.cyan(e.name)}`+T.default.dim(` (${t.confidence} confidence)`));let r=Bc(),n=[],o=iu();I.step("Pulling latest from GitHub...");let s=a0(),i=i0(JA(),`context-mode-upgrade-${Date.now()}`);o.start("Cloning mksglu/context-mode");try{Vr(`git clone --depth 1 https://github.com/mksglu/context-mode.git "${i}"`,{stdio:"pipe",timeout:3e4}),o.stop("Downloaded");let l=i,m=JSON.parse(n0(dt(l,"package.json"),"utf-8")).version??"unknown";m===s?I.success(T.default.green("Already on latest")+` \u2014 v${s}`):I.info(`Update available: ${T.default.yellow("v"+s)} \u2192 ${T.default.green("v"+m)}`),o.start("Installing dependencies & building"),Vr("npm install --no-audit --no-fund",{cwd:l,stdio:"pipe",timeout:6e4}),Vr("npm run build",{cwd:l,stdio:"pipe",timeout:3e4}),o.stop("Built successfully"),o.start("Updating files in-place");let f=r.match(/^(.*[\\/]plugins[\\/]cache[\\/][^\\/]+[\\/][^\\/]+[\\/])/);if(f){let g=f[1],_=r.replace(g,"").replace(/[\\/]/g,"");try{let y=VA(g).filter(S=>S!==_);for(let S of y)try{Vc(dt(g,S),{recursive:!0,force:!0})}catch{}y.length>0&&I.info(T.default.dim(` Cleaned ${y.length} stale cache dir(s)`))}catch{}}let p=["build","src","hooks","skills",".claude-plugin","start.mjs","
|
|
527
|
+
Run: /context-mode:ctx-upgrade`)):I.info(`${e.name}: v${p}`+T.default.dim(" \u2014 could not verify against npm registry")),r>0?(gi(T.default.red(`Diagnostics failed \u2014 ${r} critical issue(s) found`)),1):(gi(s.length>=4?T.default.green("Diagnostics complete!"):T.default.yellow("Some checks need attention \u2014 see above for details")),0)}async function o2(){process.stdout.isTTY&&console.clear();let t=Ko(),e=await Ni(t.platform);ou(T.default.bgCyan(T.default.black(" context-mode upgrade "))),I.info(`Platform: ${T.default.cyan(e.name)}`+T.default.dim(` (${t.confidence} confidence)`));let r=Bc(),n=[],o=iu();I.step("Pulling latest from GitHub...");let s=a0(),i=i0(JA(),`context-mode-upgrade-${Date.now()}`);o.start("Cloning mksglu/context-mode");try{Vr(`git clone --depth 1 https://github.com/mksglu/context-mode.git "${i}"`,{stdio:"pipe",timeout:3e4}),o.stop("Downloaded");let l=i,m=JSON.parse(n0(dt(l,"package.json"),"utf-8")).version??"unknown";m===s?I.success(T.default.green("Already on latest")+` \u2014 v${s}`):I.info(`Update available: ${T.default.yellow("v"+s)} \u2192 ${T.default.green("v"+m)}`),o.start("Installing dependencies & building"),Vr("npm install --no-audit --no-fund",{cwd:l,stdio:"pipe",timeout:6e4}),Vr("npm run build",{cwd:l,stdio:"pipe",timeout:3e4}),o.stop("Built successfully"),o.start("Updating files in-place");let f=r.match(/^(.*[\\/]plugins[\\/]cache[\\/][^\\/]+[\\/][^\\/]+[\\/])/);if(f){let g=f[1],_=r.replace(g,"").replace(/[\\/]/g,"");try{let y=VA(g).filter(S=>S!==_);for(let S of y)try{Vc(dt(g,S),{recursive:!0,force:!0})}catch{}y.length>0&&I.info(T.default.dim(` Cleaned ${y.length} stale cache dir(s)`))}catch{}}let p=["build","src","hooks","skills","scripts",".claude-plugin","start.mjs","server.bundle.mjs","cli.bundle.mjs","package.json"];for(let g of p)try{Vc(dt(r,g),{recursive:!0,force:!0}),FA(dt(l,g),dt(r,g),{recursive:!0})}catch{}let h={mcpServers:{"context-mode":{command:"node",args:[dt(r,"start.mjs")]}}};HA(dt(r,".mcp.json"),JSON.stringify(h,null,2)+`
|
|
528
528
|
`),o.stop(T.default.green(`Updated in-place to v${m}`)),e.updatePluginRegistry(r,m),I.info(T.default.dim(" Registry synced to "+r)),o.start("Installing production dependencies"),Vr("npm install --production --no-audit --no-fund",{cwd:r,stdio:"pipe",timeout:6e4}),o.stop("Dependencies ready"),o.start("Rebuilding native addons");try{Vr("npm rebuild better-sqlite3",{cwd:r,stdio:"pipe",timeout:6e4}),o.stop(T.default.green("Native addons rebuilt")),n.push("Rebuilt better-sqlite3 for current Node.js")}catch(g){let _=g instanceof Error?g.message:String(g);o.stop(T.default.yellow("Native addon rebuild warning")),I.warn(T.default.yellow("better-sqlite3 rebuild issue")+` \u2014 ${_}`+T.default.dim(`
|
|
529
529
|
Try manually: cd "${r}" && npm rebuild better-sqlite3`))}o.start("Updating npm global package");try{Vr(`npm install -g "${r}" --no-audit --no-fund`,{stdio:"pipe",timeout:3e4}),o.stop(T.default.green("npm global updated")),n.push("Updated npm global package")}catch{o.stop(T.default.yellow("npm global update skipped")),I.info(T.default.dim(" Could not update global npm \u2014 may need sudo or standalone install"))}Vc(i,{recursive:!0,force:!0}),n.push(m!==s?`Updated v${s} \u2192 v${m}`:`Reinstalled v${s} from GitHub`),I.success(T.default.green("Plugin reinstalled from GitHub!")+T.default.dim(` \u2014 v${m}`))}catch(l){let d=l instanceof Error?l.message:String(l);o.stop(T.default.red("Update failed")),I.error(T.default.red("GitHub pull failed")+` \u2014 ${d}`),I.info(T.default.dim("Continuing with hooks/settings fix..."));try{Vc(i,{recursive:!0,force:!0})}catch{}}I.step(`Backing up ${e.name} settings...`);let a=e.backupSettings();a?(I.success(T.default.green("Backup created")+T.default.dim(" -> "+a)),n.push("Backed up settings")):I.warn(T.default.yellow("No existing settings to backup")+" \u2014 a new one will be created"),I.step(`Configuring ${e.name} hooks...`);let c=e.configureAllHooks(r);for(let l of c)I.info(T.default.dim(` ${l}`)),n.push(l);I.success(T.default.green("Hooks configured")+T.default.dim(` \u2014 ${e.name}`)),I.step("Setting hook script permissions...");let u=e.setHookPermissions(r);if(process.platform!=="win32")for(let l of["build/cli.js","cli.bundle.mjs"]){let d=dt(r,l);try{o0(d,s0.F_OK),Vr(`chmod +x "${d}"`,{stdio:"ignore"}),u.push(d)}catch{}}u.length>0?(I.success(T.default.green("Permissions set")+T.default.dim(` \u2014 ${u.length} hook script(s)`)),n.push(`Set ${u.length} hook scripts as executable`)):I.error(T.default.red("No hook scripts found")+T.default.dim(" \u2014 expected in "+dt(r,"hooks"))),n.length>0?su(n.map(l=>T.default.green(" + ")+l).join(`
|
|
530
530
|
`),"Changes Applied"):I.info(T.default.dim("No changes were needed.")),I.step("Running doctor to verify..."),console.log();try{let l=dt(r,"cli.bundle.mjs"),d=dt(r,"build","cli.js"),m=qA(l)?l:d;Vr(`node "${m}" doctor`,{stdio:"inherit",timeout:3e4,cwd:r})}catch{I.warn(T.default.yellow("Doctor had warnings")+T.default.dim(` \u2014 restart your ${e.name} session to pick up the new version`))}}export{mH as toUnixPath};
|
|
@@ -1,28 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Session module loaders — bundle-
|
|
2
|
+
* Session module loaders — bundle-first with build/ fallback.
|
|
3
3
|
*
|
|
4
4
|
* All session modules are loaded from esbuild bundles (hooks/session-*.bundle.mjs).
|
|
5
5
|
* Bundles are built by CI (bundle.yml) and shipped with every release.
|
|
6
|
-
*
|
|
6
|
+
* Fallback: if bundles are missing (marketplace installs), try build/session/*.js.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { join
|
|
9
|
+
import { join } from "node:path";
|
|
10
10
|
import { pathToFileURL } from "node:url";
|
|
11
|
+
import { existsSync } from "node:fs";
|
|
11
12
|
|
|
12
13
|
export function createSessionLoaders(hookDir) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// Auto-detect bundle directory: bundles live in hooks/ root, not platform subdirs.
|
|
15
|
+
// If hookDir itself has bundles, use it; otherwise go up one level.
|
|
16
|
+
const bundleDir = existsSync(join(hookDir, "session-db.bundle.mjs"))
|
|
17
|
+
? hookDir
|
|
18
|
+
: join(hookDir, "..");
|
|
19
|
+
|
|
20
|
+
// Fallback: if bundles missing, try build/session/*.js (marketplace installs)
|
|
21
|
+
const pluginRoot = join(bundleDir, "..");
|
|
22
|
+
const buildSession = join(pluginRoot, "build", "session");
|
|
23
|
+
|
|
24
|
+
async function loadModule(bundleName, buildName) {
|
|
25
|
+
const bundlePath = join(bundleDir, bundleName);
|
|
26
|
+
if (existsSync(bundlePath)) {
|
|
27
|
+
return await import(pathToFileURL(bundlePath).href);
|
|
28
|
+
}
|
|
29
|
+
const buildPath = join(buildSession, buildName);
|
|
30
|
+
return await import(pathToFileURL(buildPath).href);
|
|
31
|
+
}
|
|
16
32
|
|
|
17
33
|
return {
|
|
18
34
|
async loadSessionDB() {
|
|
19
|
-
return await
|
|
35
|
+
return await loadModule("session-db.bundle.mjs", "db.js");
|
|
20
36
|
},
|
|
21
37
|
async loadExtract() {
|
|
22
|
-
return await
|
|
38
|
+
return await loadModule("session-extract.bundle.mjs", "extract.js");
|
|
23
39
|
},
|
|
24
40
|
async loadSnapshot() {
|
|
25
|
-
return await
|
|
41
|
+
return await loadModule("session-snapshot.bundle.mjs", "snapshot.js");
|
|
26
42
|
},
|
|
27
43
|
};
|
|
28
44
|
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "Context Mode",
|
|
4
4
|
"kind": "tool",
|
|
5
5
|
"description": "OpenClaw plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
|
|
6
|
-
"version": "1.0.
|
|
6
|
+
"version": "1.0.40",
|
|
7
7
|
"sandbox": {
|
|
8
8
|
"mode": "permissive",
|
|
9
9
|
"filesystem_access": "full",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.40",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP plugin that saves 98% of your context window. Works with Claude Code, Gemini CLI, VS Code Copilot, OpenCode, and Codex CLI. Sandboxed code execution, FTS5 knowledge base, and intent-driven search.",
|
|
6
6
|
"author": "Mert Koseoğlu",
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Creates a directory junction so npm's %~dp0\node_modules\... resolves.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
11
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { execSync } from "node:child_process";
|
|
13
13
|
import { dirname, resolve, join } from "node:path";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
@@ -16,6 +16,14 @@ import { fileURLToPath } from "node:url";
|
|
|
16
16
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
17
|
const pkgRoot = resolve(__dirname, "..");
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Validate that a path is safe to interpolate into a cmd.exe command.
|
|
21
|
+
* Rejects characters that could enable command injection via cmd.exe.
|
|
22
|
+
*/
|
|
23
|
+
function isSafeWindowsPath(p) {
|
|
24
|
+
return !/[&|<>"^%\r\n]/.test(p);
|
|
25
|
+
}
|
|
26
|
+
|
|
19
27
|
// ── 1. OpenClaw detection ────────────────────────────────────────────
|
|
20
28
|
if (process.env.OPENCLAW_STATE_DIR) {
|
|
21
29
|
console.log("\n OpenClaw detected. Run: npm run install:openclaw\n");
|
|
@@ -32,32 +40,80 @@ if (process.env.OPENCLAW_STATE_DIR) {
|
|
|
32
40
|
|
|
33
41
|
if (process.platform === "win32" && process.env.npm_config_global === "true") {
|
|
34
42
|
try {
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
//
|
|
43
|
+
// npm prefix is where both the .cmd shims and node_modules live
|
|
44
|
+
// Use npm_config_prefix env (set during install) or fall back to `npm config get prefix`
|
|
45
|
+
// Note: `npm bin -g` was removed in npm v9+, so we use prefix instead
|
|
46
|
+
const prefix = (
|
|
47
|
+
process.env.npm_config_prefix ||
|
|
48
|
+
execSync("npm config get prefix", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim()
|
|
49
|
+
);
|
|
50
|
+
|
|
38
51
|
const actualPkgDir = pkgRoot;
|
|
39
|
-
// Where the .cmd shim expects us to be
|
|
40
|
-
const expectedPkgDir = join(binDir, "node_modules", "context-mode");
|
|
41
52
|
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
// npm's .cmd shim uses %~dp0\node_modules\<pkg>\... to find the entry point.
|
|
54
|
+
// On nvm4w, stale shims at C:\nvm4w\nodejs\ may exist alongside correct ones
|
|
55
|
+
// at the npm prefix. We create junctions at ALL known shim locations.
|
|
56
|
+
const shimDirs = new Set([prefix]);
|
|
57
|
+
|
|
58
|
+
// Detect stale shim locations via `where` command
|
|
59
|
+
try {
|
|
60
|
+
const whereOutput = execSync("where context-mode.cmd", {
|
|
61
|
+
encoding: "utf-8",
|
|
62
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
63
|
+
}).trim();
|
|
64
|
+
for (const line of whereOutput.split(/\r?\n/)) {
|
|
65
|
+
if (line.endsWith("context-mode.cmd")) {
|
|
66
|
+
shimDirs.add(dirname(line));
|
|
67
|
+
}
|
|
51
68
|
}
|
|
69
|
+
} catch { /* where may fail if not installed yet */ }
|
|
52
70
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
71
|
+
for (const shimDir of shimDirs) {
|
|
72
|
+
const expectedPkgDir = join(shimDir, "node_modules", "context-mode");
|
|
73
|
+
|
|
74
|
+
if (
|
|
75
|
+
resolve(expectedPkgDir).toLowerCase() !== resolve(actualPkgDir).toLowerCase() &&
|
|
76
|
+
!existsSync(expectedPkgDir)
|
|
77
|
+
) {
|
|
78
|
+
const expectedNodeModules = join(shimDir, "node_modules");
|
|
79
|
+
if (!existsSync(expectedNodeModules)) {
|
|
80
|
+
mkdirSync(expectedNodeModules, { recursive: true });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Create directory junction (no admin privileges needed on Windows 10+)
|
|
84
|
+
// Validate paths to prevent cmd.exe injection via shell metacharacters
|
|
85
|
+
if (!isSafeWindowsPath(expectedPkgDir) || !isSafeWindowsPath(actualPkgDir)) {
|
|
86
|
+
console.warn(` context-mode: skipping junction — path contains unsafe characters`);
|
|
87
|
+
} else {
|
|
88
|
+
execSync(`mklink /J "${expectedPkgDir}" "${actualPkgDir}"`, {
|
|
89
|
+
shell: "cmd.exe",
|
|
90
|
+
stdio: "pipe",
|
|
91
|
+
});
|
|
92
|
+
console.log(`\n context-mode: created junction for nvm4w compatibility`);
|
|
93
|
+
console.log(` ${expectedPkgDir} → ${actualPkgDir}\n`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
60
96
|
}
|
|
97
|
+
|
|
98
|
+
// Also fix stale shims that reference old bin entry (build/cli.js → cli.bundle.mjs)
|
|
99
|
+
try {
|
|
100
|
+
const whereOutput = execSync("where context-mode.cmd", {
|
|
101
|
+
encoding: "utf-8",
|
|
102
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
103
|
+
}).trim();
|
|
104
|
+
for (const line of whereOutput.split(/\r?\n/)) {
|
|
105
|
+
if (line.endsWith("context-mode.cmd")) {
|
|
106
|
+
const content = readFileSync(line, "utf-8");
|
|
107
|
+
if (content.includes("build\\cli.js") || content.includes("build/cli.js")) {
|
|
108
|
+
// Rewrite stale shim to use cli.bundle.mjs
|
|
109
|
+
const fixed = content
|
|
110
|
+
.replace(/build[\\\/]cli\.js/g, "cli.bundle.mjs");
|
|
111
|
+
writeFileSync(line, fixed);
|
|
112
|
+
console.log(` context-mode: fixed stale shim at ${line}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} catch { /* best effort */ }
|
|
61
117
|
} catch {
|
|
62
118
|
// Best effort — don't block install. User can use npx as fallback.
|
|
63
119
|
}
|
|
@@ -13,20 +13,10 @@ Run diagnostics and display results directly in the conversation.
|
|
|
13
13
|
|
|
14
14
|
## Instructions
|
|
15
15
|
|
|
16
|
-
1.
|
|
17
|
-
2.
|
|
16
|
+
1. Call the `ctx_doctor` MCP tool directly. It runs all checks server-side and returns a markdown checklist.
|
|
17
|
+
2. Display the results verbatim — they are already formatted as a checklist with `[x]` PASS, `[ ]` FAIL, `[-]` WARN.
|
|
18
|
+
3. **Fallback** (only if MCP tool call fails): Derive the **plugin root** from this skill's base directory (go up 2 levels — remove `/skills/ctx-doctor`), then run with Bash:
|
|
18
19
|
```
|
|
19
20
|
CLI="<PLUGIN_ROOT>/cli.bundle.mjs"; [ ! -f "$CLI" ] && CLI="<PLUGIN_ROOT>/build/cli.js"; node "$CLI" doctor
|
|
20
21
|
```
|
|
21
|
-
|
|
22
|
-
```
|
|
23
|
-
## context-mode doctor
|
|
24
|
-
- [x] Runtimes: 6/10 (javascript, typescript, python, shell, ruby, perl)
|
|
25
|
-
- [x] Performance: FAST (Bun)
|
|
26
|
-
- [x] Server test: PASS
|
|
27
|
-
- [x] Hooks: PASS
|
|
28
|
-
- [x] FTS5: PASS
|
|
29
|
-
- [x] npm: v0.7.1
|
|
30
|
-
- [x] Marketplace: v0.7.1
|
|
31
|
-
```
|
|
32
|
-
Use `[x]` for PASS, `[ ]` for FAIL, `[-]` for WARN.
|
|
22
|
+
Re-display results as a markdown checklist.
|
|
@@ -13,20 +13,19 @@ Pull latest from GitHub and reinstall the plugin.
|
|
|
13
13
|
|
|
14
14
|
## Instructions
|
|
15
15
|
|
|
16
|
-
1.
|
|
17
|
-
2. Run
|
|
18
|
-
|
|
19
|
-
CLI="<PLUGIN_ROOT>/cli.bundle.mjs"; [ ! -f "$CLI" ] && CLI="<PLUGIN_ROOT>/build/cli.js"; node "$CLI" upgrade
|
|
20
|
-
```
|
|
21
|
-
3. **IMPORTANT**: After the Bash tool completes, re-display the key results as markdown text directly in the conversation so the user sees them without expanding the tool output. Format as:
|
|
16
|
+
1. Call the `ctx_upgrade` MCP tool directly. It returns a shell command to execute.
|
|
17
|
+
2. Run the returned command using your shell execution tool (Bash, shell_execute, etc.).
|
|
18
|
+
3. Display results as a markdown checklist:
|
|
22
19
|
```
|
|
23
20
|
## context-mode upgrade
|
|
24
21
|
- [x] Pulled latest from GitHub
|
|
25
|
-
- [x] Built and installed
|
|
26
|
-
- [x] npm global updated
|
|
22
|
+
- [x] Built and installed v1.0.39
|
|
27
23
|
- [x] Hooks configured
|
|
28
|
-
- [x] Permissions set
|
|
29
24
|
- [x] Doctor: all checks PASS
|
|
30
25
|
```
|
|
31
|
-
Use `[x]` for success, `[ ]` for failure. Show
|
|
32
|
-
|
|
26
|
+
Use `[x]` for success, `[ ]` for failure. Show actual version numbers.
|
|
27
|
+
4. Tell the user to **restart their session** to pick up the new version.
|
|
28
|
+
5. **Fallback** (only if MCP tool call fails): Derive the **plugin root** from this skill's base directory (go up 2 levels — remove `/skills/ctx-upgrade`), then run with Bash:
|
|
29
|
+
```
|
|
30
|
+
CLI="<PLUGIN_ROOT>/cli.bundle.mjs"; [ ! -f "$CLI" ] && CLI="<PLUGIN_ROOT>/build/cli.js"; node "$CLI" upgrade
|
|
31
|
+
```
|
package/start.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execSync } from "node:child_process";
|
|
3
|
-
import { existsSync, copyFileSync, readFileSync, writeFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import { existsSync, copyFileSync, chmodSync, readFileSync, writeFileSync, readdirSync } from "node:fs";
|
|
4
4
|
import { dirname, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { homedir } from "node:os";
|
|
@@ -56,47 +56,65 @@ if (!process.env.CLAUDE_PROJECT_DIR) {
|
|
|
56
56
|
process.env.CLAUDE_PROJECT_DIR = originalCwd;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
// Auto-write routing instructions file for
|
|
59
|
+
// Auto-write routing instructions file for NON-hook-capable platforms only.
|
|
60
|
+
// Hook-capable platforms (Claude Code, Gemini CLI, VS Code Copilot, OpenCode, OpenClaw)
|
|
61
|
+
// inject routing via SessionStart hook — writing to disk dirties the git tree (#158).
|
|
62
|
+
// server.ts also guards this with adapter.capabilities.sessionStart.
|
|
60
63
|
try {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
{ env: ["CLAUDE_PROJECT_DIR", "CLAUDE_SESSION_ID"], dir: "claude-code", file: "CLAUDE.md", target: "CLAUDE.md" },
|
|
72
|
-
{ env: ["GEMINI_PROJECT_DIR", "GEMINI_SESSION_ID"], dir: "gemini-cli", file: "GEMINI.md", target: "GEMINI.md" },
|
|
73
|
-
{ env: ["VSCODE_PID", "VSCODE_CWD"], dir: "vscode-copilot", file: "copilot-instructions.md", target: ".github/copilot-instructions.md" },
|
|
74
|
-
{ env: ["OPENCODE_PROJECT_DIR", "OPENCODE_SESSION_ID"], dir: "opencode", file: "AGENTS.md", target: "AGENTS.md" },
|
|
75
|
-
{ env: ["OPENCLAW_HOME", "OPENCLAW_PROJECT_DIR"], dir: "openclaw", file: "AGENTS.md", target: "AGENTS.md" },
|
|
76
|
-
{ env: ["CODEX_HOME"], dir: "codex", file: "AGENTS.md", target: "AGENTS.md" },
|
|
64
|
+
// Hook-capable platforms set these env vars — skip file write for them.
|
|
65
|
+
// Uses verified env vars from src/adapters/detect.ts (not invented session IDs).
|
|
66
|
+
const hookCapableSessionVars = [
|
|
67
|
+
"CLAUDE_SESSION_ID", // Claude Code
|
|
68
|
+
"GEMINI_PROJECT_DIR", // Gemini CLI (GEMINI_CLI also valid)
|
|
69
|
+
"OPENCODE", // OpenCode (OPENCODE_PID also valid)
|
|
70
|
+
"OPENCLAW_HOME", // OpenClaw (user-set home dir)
|
|
71
|
+
"OPENCLAW_CLI", // OpenClaw (set at runtime by openclaw CLI)
|
|
72
|
+
// VS Code Copilot: VSCODE_PID is too broad (set for ALL VS Code extensions).
|
|
73
|
+
// Accept occasional harmless write rather than false-positive suppression.
|
|
77
74
|
];
|
|
75
|
+
const hasHookSupport = hookCapableSessionVars.some((e) => process.env[e]);
|
|
76
|
+
|
|
77
|
+
if (!hasHookSupport) {
|
|
78
|
+
const projectDir =
|
|
79
|
+
process.env.CLAUDE_PROJECT_DIR ||
|
|
80
|
+
process.env.GEMINI_PROJECT_DIR ||
|
|
81
|
+
process.env.VSCODE_CWD ||
|
|
82
|
+
process.cwd();
|
|
83
|
+
|
|
84
|
+
const configsDir = resolve(__dirname, "configs");
|
|
85
|
+
|
|
86
|
+
// Detect platform and determine instruction file
|
|
87
|
+
const platformConfigs = [
|
|
88
|
+
{ env: ["CLAUDE_PROJECT_DIR"], dir: "claude-code", file: "CLAUDE.md", target: "CLAUDE.md" },
|
|
89
|
+
{ env: ["GEMINI_PROJECT_DIR"], dir: "gemini-cli", file: "GEMINI.md", target: "GEMINI.md" },
|
|
90
|
+
{ env: ["VSCODE_CWD"], dir: "vscode-copilot", file: "copilot-instructions.md", target: ".github/copilot-instructions.md" },
|
|
91
|
+
{ env: ["OPENCODE_PROJECT_DIR"], dir: "opencode", file: "AGENTS.md", target: "AGENTS.md" },
|
|
92
|
+
{ env: ["OPENCLAW_HOME"], dir: "openclaw", file: "AGENTS.md", target: "AGENTS.md" },
|
|
93
|
+
{ env: ["CODEX_HOME"], dir: "codex", file: "AGENTS.md", target: "AGENTS.md" },
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
const detected = platformConfigs.find((p) => p.env.some((e) => process.env[e]));
|
|
97
|
+
if (detected) {
|
|
98
|
+
const targetPath = resolve(projectDir, detected.target);
|
|
99
|
+
const sourcePath = resolve(configsDir, detected.dir, detected.file);
|
|
100
|
+
|
|
101
|
+
// Ensure parent dir exists (for .github/copilot-instructions.md)
|
|
102
|
+
const targetDir = resolve(targetPath, "..");
|
|
103
|
+
if (!existsSync(targetDir)) {
|
|
104
|
+
const { mkdirSync } = await import("node:fs");
|
|
105
|
+
mkdirSync(targetDir, { recursive: true });
|
|
106
|
+
}
|
|
78
107
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
mkdirSync(targetDir, { recursive: true });
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (existsSync(sourcePath)) {
|
|
92
|
-
const content = readFileSync(sourcePath, "utf-8");
|
|
93
|
-
if (existsSync(targetPath)) {
|
|
94
|
-
const existing = readFileSync(targetPath, "utf-8");
|
|
95
|
-
if (!existing.includes("context-mode")) {
|
|
96
|
-
writeFileSync(targetPath, existing.trimEnd() + "\n\n" + content, "utf-8");
|
|
108
|
+
if (existsSync(sourcePath)) {
|
|
109
|
+
const content = readFileSync(sourcePath, "utf-8");
|
|
110
|
+
if (existsSync(targetPath)) {
|
|
111
|
+
const existing = readFileSync(targetPath, "utf-8");
|
|
112
|
+
if (!existing.includes("context-mode")) {
|
|
113
|
+
writeFileSync(targetPath, existing.trimEnd() + "\n\n" + content, "utf-8");
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
writeFileSync(targetPath, content, "utf-8");
|
|
97
117
|
}
|
|
98
|
-
} else {
|
|
99
|
-
writeFileSync(targetPath, content, "utf-8");
|
|
100
118
|
}
|
|
101
119
|
}
|
|
102
120
|
}
|
|
@@ -172,6 +190,13 @@ for (const pkg of ["better-sqlite3", "turndown", "turndown-plugin-gfm", "@mixmar
|
|
|
172
190
|
// Each ABI needs its own compiled binary — cache them side-by-side.
|
|
173
191
|
ensureNativeCompat(__dirname);
|
|
174
192
|
|
|
193
|
+
// Self-heal: create CLI shim if cli.bundle.mjs is missing (marketplace installs)
|
|
194
|
+
if (!existsSync(resolve(__dirname, "cli.bundle.mjs")) && existsSync(resolve(__dirname, "build", "cli.js"))) {
|
|
195
|
+
const shimPath = resolve(__dirname, "cli.bundle.mjs");
|
|
196
|
+
writeFileSync(shimPath, '#!/usr/bin/env node\nawait import("./build/cli.js");\n');
|
|
197
|
+
if (process.platform !== "win32") chmodSync(shimPath, 0o755);
|
|
198
|
+
}
|
|
199
|
+
|
|
175
200
|
// Bundle exists (CI-built) — start instantly
|
|
176
201
|
if (existsSync(resolve(__dirname, "server.bundle.mjs"))) {
|
|
177
202
|
await import("./server.bundle.mjs");
|