context-mode 0.9.22 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/hooks/hooks.json +46 -4
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +4 -4
- package/README.md +370 -185
- package/build/adapters/claude-code/config.d.ts +8 -0
- package/build/adapters/claude-code/config.js +8 -0
- package/build/adapters/claude-code/hooks.d.ts +53 -0
- package/build/adapters/claude-code/hooks.js +88 -0
- package/build/adapters/claude-code/index.d.ts +50 -0
- package/build/adapters/claude-code/index.js +523 -0
- package/build/adapters/codex/config.d.ts +8 -0
- package/build/adapters/codex/config.js +8 -0
- package/build/adapters/codex/hooks.d.ts +21 -0
- package/build/adapters/codex/hooks.js +27 -0
- package/build/adapters/codex/index.d.ts +44 -0
- package/build/adapters/codex/index.js +223 -0
- package/build/adapters/detect.d.ts +26 -0
- package/build/adapters/detect.js +131 -0
- package/build/adapters/gemini-cli/config.d.ts +8 -0
- package/build/adapters/gemini-cli/config.js +8 -0
- package/build/adapters/gemini-cli/hooks.d.ts +44 -0
- package/build/adapters/gemini-cli/hooks.js +64 -0
- package/build/adapters/gemini-cli/index.d.ts +57 -0
- package/build/adapters/gemini-cli/index.js +468 -0
- package/build/adapters/opencode/config.d.ts +8 -0
- package/build/adapters/opencode/config.js +8 -0
- package/build/adapters/opencode/hooks.d.ts +38 -0
- package/build/adapters/opencode/hooks.js +50 -0
- package/build/adapters/opencode/index.d.ts +52 -0
- package/build/adapters/opencode/index.js +386 -0
- package/build/adapters/types.d.ts +218 -0
- package/build/adapters/types.js +13 -0
- package/build/adapters/vscode-copilot/config.d.ts +8 -0
- package/build/adapters/vscode-copilot/config.js +8 -0
- package/build/adapters/vscode-copilot/hooks.d.ts +49 -0
- package/build/adapters/vscode-copilot/hooks.js +76 -0
- package/build/adapters/vscode-copilot/index.d.ts +58 -0
- package/build/adapters/vscode-copilot/index.js +512 -0
- package/build/cli.d.ts +7 -5
- package/build/cli.js +127 -421
- package/build/db-base.d.ts +84 -0
- package/build/db-base.js +128 -0
- package/build/executor.d.ts +6 -7
- package/build/executor.js +111 -51
- package/build/opencode-plugin.d.ts +37 -0
- package/build/opencode-plugin.js +118 -0
- package/build/runtime.js +1 -1
- package/build/server.js +436 -117
- package/build/session/db.d.ts +110 -0
- package/build/session/db.js +285 -0
- package/build/session/extract.d.ts +51 -0
- package/build/session/extract.js +407 -0
- package/build/session/snapshot.d.ts +74 -0
- package/build/session/snapshot.js +344 -0
- package/build/store.d.ts +4 -22
- package/build/store.js +67 -55
- package/build/truncate.d.ts +59 -0
- package/build/truncate.js +157 -0
- package/build/types.d.ts +101 -0
- package/build/types.js +20 -0
- package/configs/claude-code/CLAUDE.md +62 -0
- package/configs/codex/AGENTS.md +58 -0
- package/configs/codex/config.toml +5 -0
- package/configs/gemini-cli/GEMINI.md +58 -0
- package/configs/gemini-cli/mcp.json +7 -0
- package/configs/gemini-cli/settings.json +49 -0
- package/configs/opencode/AGENTS.md +58 -0
- package/configs/opencode/opencode.json +10 -0
- package/configs/vscode-copilot/copilot-instructions.md +58 -0
- package/configs/vscode-copilot/hooks.json +16 -0
- package/configs/vscode-copilot/mcp.json +8 -0
- package/hooks/core/formatters.mjs +86 -0
- package/hooks/core/routing.mjs +262 -0
- package/hooks/core/stdin.mjs +19 -0
- package/hooks/formatters/claude-code.mjs +57 -0
- package/hooks/formatters/gemini-cli.mjs +55 -0
- package/hooks/formatters/vscode-copilot.mjs +55 -0
- package/hooks/gemini-cli/aftertool.mjs +58 -0
- package/hooks/gemini-cli/beforetool.mjs +25 -0
- package/hooks/gemini-cli/precompress.mjs +51 -0
- package/hooks/gemini-cli/sessionstart.mjs +117 -0
- package/hooks/hooks.json +46 -4
- package/hooks/posttooluse.mjs +53 -0
- package/hooks/precompact.mjs +55 -0
- package/hooks/pretooluse.mjs +23 -266
- package/hooks/routing-block.mjs +19 -6
- package/hooks/session-directive.mjs +395 -0
- package/hooks/session-helpers.mjs +112 -0
- package/hooks/sessionstart.mjs +123 -16
- package/hooks/userpromptsubmit.mjs +58 -0
- package/hooks/vscode-copilot/posttooluse.mjs +58 -0
- package/hooks/vscode-copilot/precompact.mjs +51 -0
- package/hooks/vscode-copilot/pretooluse.mjs +25 -0
- package/hooks/vscode-copilot/sessionstart.mjs +115 -0
- package/package.json +20 -17
- package/server.bundle.mjs +157 -109
- package/skills/context-mode/SKILL.md +49 -49
- package/skills/ctx-stats/SKILL.md +1 -1
- package/start.mjs +47 -0
- package/hooks/pretooluse.sh +0 -147
package/build/cli.js
CHANGED
|
@@ -3,30 +3,70 @@
|
|
|
3
3
|
* context-mode CLI
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
* context-mode
|
|
7
|
-
* context-mode
|
|
8
|
-
* context-mode
|
|
9
|
-
* context-mode
|
|
10
|
-
*
|
|
6
|
+
* context-mode → Start MCP server (stdio)
|
|
7
|
+
* context-mode doctor → Diagnose runtime issues, hooks, FTS5, version
|
|
8
|
+
* context-mode upgrade → Fix hooks, permissions, and settings
|
|
9
|
+
* context-mode hook <platform> <event> → Dispatch a hook script (used by platform hook configs)
|
|
10
|
+
*
|
|
11
|
+
* Platform auto-detection: CLI detects which platform is running
|
|
12
|
+
* (Claude Code, Gemini CLI, OpenCode, etc.) and uses the appropriate adapter.
|
|
11
13
|
*/
|
|
12
14
|
import * as p from "@clack/prompts";
|
|
13
15
|
import color from "picocolors";
|
|
14
16
|
import { execSync } from "node:child_process";
|
|
15
|
-
import { readFileSync,
|
|
16
|
-
import { resolve, dirname } from "node:path";
|
|
17
|
+
import { readFileSync, cpSync, accessSync, readdirSync, rmSync, constants } from "node:fs";
|
|
18
|
+
import { resolve, dirname, join } from "node:path";
|
|
19
|
+
import { tmpdir } from "node:os";
|
|
17
20
|
import { fileURLToPath } from "node:url";
|
|
18
|
-
import { homedir } from "node:os";
|
|
19
21
|
import { detectRuntimes, getRuntimeSummary, hasBunRuntime, getAvailableLanguages, } from "./runtime.js";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
// ── Adapter imports ──────────────────────────────────────
|
|
23
|
+
import { detectPlatform, getAdapter } from "./adapters/detect.js";
|
|
24
|
+
/* -------------------------------------------------------
|
|
25
|
+
* Hook dispatcher — `context-mode hook <platform> <event>`
|
|
26
|
+
* ------------------------------------------------------- */
|
|
27
|
+
const HOOK_MAP = {
|
|
28
|
+
"claude-code": {
|
|
29
|
+
pretooluse: "hooks/pretooluse.mjs",
|
|
30
|
+
posttooluse: "hooks/posttooluse.mjs",
|
|
31
|
+
precompact: "hooks/precompact.mjs",
|
|
32
|
+
sessionstart: "hooks/sessionstart.mjs",
|
|
33
|
+
userpromptsubmit: "hooks/userpromptsubmit.mjs",
|
|
34
|
+
},
|
|
35
|
+
"gemini-cli": {
|
|
36
|
+
beforetool: "hooks/gemini-cli/beforetool.mjs",
|
|
37
|
+
aftertool: "hooks/gemini-cli/aftertool.mjs",
|
|
38
|
+
precompress: "hooks/gemini-cli/precompress.mjs",
|
|
39
|
+
sessionstart: "hooks/gemini-cli/sessionstart.mjs",
|
|
40
|
+
},
|
|
41
|
+
"vscode-copilot": {
|
|
42
|
+
pretooluse: "hooks/vscode-copilot/pretooluse.mjs",
|
|
43
|
+
posttooluse: "hooks/vscode-copilot/posttooluse.mjs",
|
|
44
|
+
precompact: "hooks/vscode-copilot/precompact.mjs",
|
|
45
|
+
sessionstart: "hooks/vscode-copilot/sessionstart.mjs",
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
async function hookDispatch(platform, event) {
|
|
49
|
+
const scriptPath = HOOK_MAP[platform]?.[event];
|
|
50
|
+
if (!scriptPath) {
|
|
51
|
+
console.error(`Unknown hook: ${platform}/${event}`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
const pluginRoot = getPluginRoot();
|
|
55
|
+
await import(join(pluginRoot, scriptPath));
|
|
23
56
|
}
|
|
24
|
-
|
|
57
|
+
/* -------------------------------------------------------
|
|
58
|
+
* Entry point
|
|
59
|
+
* ------------------------------------------------------- */
|
|
60
|
+
const args = process.argv.slice(2);
|
|
61
|
+
if (args[0] === "doctor") {
|
|
25
62
|
doctor().then((code) => process.exit(code));
|
|
26
63
|
}
|
|
27
64
|
else if (args[0] === "upgrade") {
|
|
28
65
|
upgrade();
|
|
29
66
|
}
|
|
67
|
+
else if (args[0] === "hook") {
|
|
68
|
+
hookDispatch(args[1], args[2]);
|
|
69
|
+
}
|
|
30
70
|
else {
|
|
31
71
|
// Default: start MCP server
|
|
32
72
|
import("./server.js");
|
|
@@ -43,21 +83,6 @@ function getPluginRoot() {
|
|
|
43
83
|
const __dirname = dirname(__filename);
|
|
44
84
|
return resolve(__dirname, "..");
|
|
45
85
|
}
|
|
46
|
-
function getSettingsPath() {
|
|
47
|
-
return resolve(homedir(), ".claude", "settings.json");
|
|
48
|
-
}
|
|
49
|
-
function readSettings() {
|
|
50
|
-
try {
|
|
51
|
-
const raw = readFileSync(getSettingsPath(), "utf-8");
|
|
52
|
-
return JSON.parse(raw);
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
function getHookScriptPath() {
|
|
59
|
-
return resolve(getPluginRoot(), "hooks", "pretooluse.mjs");
|
|
60
|
-
}
|
|
61
86
|
function getLocalVersion() {
|
|
62
87
|
try {
|
|
63
88
|
const pkg = JSON.parse(readFileSync(resolve(getPluginRoot(), "package.json"), "utf-8"));
|
|
@@ -79,71 +104,18 @@ async function fetchLatestVersion() {
|
|
|
79
104
|
return "unknown";
|
|
80
105
|
}
|
|
81
106
|
}
|
|
82
|
-
function getMarketplaceVersion() {
|
|
83
|
-
// Primary: read from installed_plugins.json (source of truth for Claude Code)
|
|
84
|
-
try {
|
|
85
|
-
const ipPath = resolve(homedir(), ".claude", "plugins", "installed_plugins.json");
|
|
86
|
-
const ipRaw = JSON.parse(readFileSync(ipPath, "utf-8"));
|
|
87
|
-
const plugins = ipRaw.plugins ?? {};
|
|
88
|
-
for (const [key, entries] of Object.entries(plugins)) {
|
|
89
|
-
if (!key.toLowerCase().includes("context-mode"))
|
|
90
|
-
continue;
|
|
91
|
-
const arr = entries;
|
|
92
|
-
if (arr.length > 0 && typeof arr[0].version === "string") {
|
|
93
|
-
return arr[0].version;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
catch { /* fallback below */ }
|
|
98
|
-
// Fallback: read from own package.json
|
|
99
|
-
const localVer = getLocalVersion();
|
|
100
|
-
if (localVer !== "unknown")
|
|
101
|
-
return localVer;
|
|
102
|
-
// Last resort: scan common plugin cache locations
|
|
103
|
-
const bases = [
|
|
104
|
-
resolve(homedir(), ".claude"),
|
|
105
|
-
resolve(homedir(), ".config", "claude"),
|
|
106
|
-
];
|
|
107
|
-
for (const base of bases) {
|
|
108
|
-
const cacheDir = resolve(base, "plugins", "cache", "claude-context-mode", "context-mode");
|
|
109
|
-
try {
|
|
110
|
-
const entries = readdirSync(cacheDir);
|
|
111
|
-
const versions = entries
|
|
112
|
-
.filter((e) => /^\d+\.\d+\.\d+/.test(e))
|
|
113
|
-
.sort((a, b) => {
|
|
114
|
-
const pa = a.split(".").map(Number);
|
|
115
|
-
const pb = b.split(".").map(Number);
|
|
116
|
-
for (let i = 0; i < 3; i++) {
|
|
117
|
-
if ((pa[i] ?? 0) !== (pb[i] ?? 0))
|
|
118
|
-
return (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
119
|
-
}
|
|
120
|
-
return 0;
|
|
121
|
-
});
|
|
122
|
-
if (versions.length > 0)
|
|
123
|
-
return versions[versions.length - 1];
|
|
124
|
-
}
|
|
125
|
-
catch { /* continue */ }
|
|
126
|
-
}
|
|
127
|
-
return "not installed";
|
|
128
|
-
}
|
|
129
|
-
function semverGt(a, b) {
|
|
130
|
-
const pa = a.split(".").map(Number);
|
|
131
|
-
const pb = b.split(".").map(Number);
|
|
132
|
-
for (let i = 0; i < 3; i++) {
|
|
133
|
-
if ((pa[i] ?? 0) > (pb[i] ?? 0))
|
|
134
|
-
return true;
|
|
135
|
-
if ((pa[i] ?? 0) < (pb[i] ?? 0))
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
107
|
/* -------------------------------------------------------
|
|
141
|
-
* Doctor
|
|
108
|
+
* Doctor — adapter-aware diagnostics
|
|
142
109
|
* ------------------------------------------------------- */
|
|
143
110
|
async function doctor() {
|
|
144
111
|
if (process.stdout.isTTY)
|
|
145
112
|
console.clear();
|
|
113
|
+
// Detect platform
|
|
114
|
+
const detection = detectPlatform();
|
|
115
|
+
const adapter = await getAdapter(detection.platform);
|
|
146
116
|
p.intro(color.bgMagenta(color.white(" context-mode doctor ")));
|
|
117
|
+
p.log.info(`Platform: ${color.cyan(adapter.name)}` +
|
|
118
|
+
color.dim(` (${detection.confidence} confidence — ${detection.reason})`));
|
|
147
119
|
let criticalFails = 0;
|
|
148
120
|
const s = p.spinner();
|
|
149
121
|
s.start("Running diagnostics");
|
|
@@ -156,7 +128,7 @@ async function doctor() {
|
|
|
156
128
|
catch {
|
|
157
129
|
s.stop("Diagnostics partial");
|
|
158
130
|
p.log.warn(color.yellow("Could not detect runtimes") + color.dim(" — module may be missing, restart session after upgrade"));
|
|
159
|
-
p.outro(color.yellow("Doctor could not fully run — try again after restarting
|
|
131
|
+
p.outro(color.yellow("Doctor could not fully run — try again after restarting"));
|
|
160
132
|
return 1;
|
|
161
133
|
}
|
|
162
134
|
s.stop("Diagnostics complete");
|
|
@@ -212,55 +184,23 @@ async function doctor() {
|
|
|
212
184
|
p.log.error(color.red("Server test: FAIL") + ` — ${message}`);
|
|
213
185
|
}
|
|
214
186
|
}
|
|
215
|
-
// Hooks
|
|
216
|
-
p.log.step(
|
|
217
|
-
const
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (preToolUse && preToolUse.length > 0) {
|
|
223
|
-
const hasCorrectHook = preToolUse.some((entry) => entry.hooks?.some((h) => h.command?.includes("pretooluse.mjs")));
|
|
224
|
-
if (hasCorrectHook) {
|
|
225
|
-
p.log.success(color.green("Hooks installed: PASS") + " — PreToolUse hook configured");
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
p.log.error(color.red("Hooks installed: FAIL") +
|
|
229
|
-
" — PreToolUse exists but does not point to pretooluse.mjs" +
|
|
230
|
-
color.dim("\n Run: npx context-mode upgrade"));
|
|
231
|
-
}
|
|
187
|
+
// Hooks — adapter-aware validation
|
|
188
|
+
p.log.step(`Checking ${adapter.name} hooks configuration...`);
|
|
189
|
+
const pluginRoot = getPluginRoot();
|
|
190
|
+
const hookResults = adapter.validateHooks(pluginRoot);
|
|
191
|
+
for (const result of hookResults) {
|
|
192
|
+
if (result.status === "pass") {
|
|
193
|
+
p.log.success(color.green(`${result.check}: PASS`) + ` — ${result.message}`);
|
|
232
194
|
}
|
|
233
195
|
else {
|
|
234
|
-
p.log.error(color.red(
|
|
235
|
-
|
|
236
|
-
color.dim(
|
|
237
|
-
}
|
|
238
|
-
// Check SessionStart hook
|
|
239
|
-
const sessionStart = hooks?.SessionStart;
|
|
240
|
-
if (sessionStart && sessionStart.length > 0) {
|
|
241
|
-
const hasSessionHook = sessionStart.some((entry) => entry.hooks?.some((h) => h.command?.includes("sessionstart.mjs")));
|
|
242
|
-
if (hasSessionHook) {
|
|
243
|
-
p.log.success(color.green("SessionStart hook: PASS") + " — SessionStart hook configured");
|
|
244
|
-
}
|
|
245
|
-
else {
|
|
246
|
-
p.log.error(color.red("SessionStart hook: FAIL") +
|
|
247
|
-
" — SessionStart exists but does not point to sessionstart.mjs" +
|
|
248
|
-
color.dim("\n Run: npx context-mode upgrade"));
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
p.log.error(color.red("SessionStart hook: FAIL") +
|
|
253
|
-
" — No SessionStart hooks found" +
|
|
254
|
-
color.dim("\n Run: npx context-mode upgrade"));
|
|
196
|
+
p.log.error(color.red(`${result.check}: FAIL`) +
|
|
197
|
+
` — ${result.message}` +
|
|
198
|
+
(result.fix ? color.dim(`\n Run: ${result.fix}`) : ""));
|
|
255
199
|
}
|
|
256
200
|
}
|
|
257
|
-
else {
|
|
258
|
-
p.log.error(color.red("Hooks installed: FAIL") +
|
|
259
|
-
" — Could not read ~/.claude/settings.json" +
|
|
260
|
-
color.dim("\n Run: npx context-mode upgrade"));
|
|
261
|
-
}
|
|
262
201
|
// Hook script exists
|
|
263
202
|
p.log.step("Checking hook script...");
|
|
203
|
+
const hookScriptPath = resolve(pluginRoot, "hooks", "pretooluse.mjs");
|
|
264
204
|
try {
|
|
265
205
|
accessSync(hookScriptPath, constants.R_OK);
|
|
266
206
|
p.log.success(color.green("Hook script exists: PASS") + color.dim(` — ${hookScriptPath}`));
|
|
@@ -269,30 +209,15 @@ async function doctor() {
|
|
|
269
209
|
p.log.error(color.red("Hook script exists: FAIL") +
|
|
270
210
|
color.dim(` — not found at ${hookScriptPath}`));
|
|
271
211
|
}
|
|
272
|
-
// Plugin
|
|
273
|
-
p.log.step(
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const pluginKey = Object.keys(enabledPlugins).find((k) => k.startsWith("context-mode"));
|
|
278
|
-
if (pluginKey && enabledPlugins[pluginKey]) {
|
|
279
|
-
p.log.success(color.green("Plugin enabled: PASS") + color.dim(` — ${pluginKey}`));
|
|
280
|
-
}
|
|
281
|
-
else {
|
|
282
|
-
p.log.warn(color.yellow("Plugin enabled: WARN") +
|
|
283
|
-
" — context-mode not in enabledPlugins" +
|
|
284
|
-
color.dim(" (might be using standalone MCP mode)"));
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
else {
|
|
288
|
-
p.log.warn(color.yellow("Plugin enabled: WARN") +
|
|
289
|
-
" — no enabledPlugins section found" +
|
|
290
|
-
color.dim(" (might be using standalone MCP mode)"));
|
|
291
|
-
}
|
|
212
|
+
// Plugin registration — adapter-aware
|
|
213
|
+
p.log.step(`Checking ${adapter.name} plugin registration...`);
|
|
214
|
+
const pluginCheck = adapter.checkPluginRegistration();
|
|
215
|
+
if (pluginCheck.status === "pass") {
|
|
216
|
+
p.log.success(color.green("Plugin enabled: PASS") + color.dim(` — ${pluginCheck.message}`));
|
|
292
217
|
}
|
|
293
218
|
else {
|
|
294
219
|
p.log.warn(color.yellow("Plugin enabled: WARN") +
|
|
295
|
-
|
|
220
|
+
` — ${pluginCheck.message}`);
|
|
296
221
|
}
|
|
297
222
|
// FTS5 / better-sqlite3
|
|
298
223
|
p.log.step("Checking FTS5 / better-sqlite3...");
|
|
@@ -323,12 +248,11 @@ async function doctor() {
|
|
|
323
248
|
color.dim("\n Try: npm rebuild better-sqlite3"));
|
|
324
249
|
}
|
|
325
250
|
}
|
|
326
|
-
// Version check
|
|
251
|
+
// Version check — adapter-aware
|
|
327
252
|
p.log.step("Checking versions...");
|
|
328
253
|
const localVersion = getLocalVersion();
|
|
329
254
|
const latestVersion = await fetchLatestVersion();
|
|
330
|
-
const
|
|
331
|
-
// npm / MCP version
|
|
255
|
+
const installedVersion = adapter.getInstalledVersion();
|
|
332
256
|
if (latestVersion === "unknown") {
|
|
333
257
|
p.log.warn(color.yellow("npm (MCP): WARN") +
|
|
334
258
|
` — local v${localVersion}, could not reach npm registry`);
|
|
@@ -342,22 +266,21 @@ async function doctor() {
|
|
|
342
266
|
` — local v${localVersion}, latest v${latestVersion}` +
|
|
343
267
|
color.dim("\n Run: /context-mode:ctx-upgrade"));
|
|
344
268
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
p.log.info(color.dim("Marketplace: not installed") +
|
|
269
|
+
if (installedVersion === "not installed") {
|
|
270
|
+
p.log.info(color.dim(`${adapter.name}: not installed`) +
|
|
348
271
|
" — using standalone MCP mode");
|
|
349
272
|
}
|
|
350
|
-
else if (latestVersion !== "unknown" &&
|
|
351
|
-
p.log.success(color.green(
|
|
352
|
-
` — v${
|
|
273
|
+
else if (latestVersion !== "unknown" && installedVersion === latestVersion) {
|
|
274
|
+
p.log.success(color.green(`${adapter.name}: PASS`) +
|
|
275
|
+
` — v${installedVersion}`);
|
|
353
276
|
}
|
|
354
277
|
else if (latestVersion !== "unknown") {
|
|
355
|
-
p.log.warn(color.yellow(
|
|
356
|
-
` — v${
|
|
278
|
+
p.log.warn(color.yellow(`${adapter.name}: WARN`) +
|
|
279
|
+
` — v${installedVersion}, latest v${latestVersion}` +
|
|
357
280
|
color.dim("\n Run: /context-mode:ctx-upgrade"));
|
|
358
281
|
}
|
|
359
282
|
else {
|
|
360
|
-
p.log.info(
|
|
283
|
+
p.log.info(`${adapter.name}: v${installedVersion}` +
|
|
361
284
|
color.dim(" — could not verify against npm registry"));
|
|
362
285
|
}
|
|
363
286
|
// Summary
|
|
@@ -371,26 +294,29 @@ async function doctor() {
|
|
|
371
294
|
return 0;
|
|
372
295
|
}
|
|
373
296
|
/* -------------------------------------------------------
|
|
374
|
-
* Upgrade
|
|
297
|
+
* Upgrade — adapter-aware hook configuration
|
|
375
298
|
* ------------------------------------------------------- */
|
|
376
299
|
async function upgrade() {
|
|
377
300
|
if (process.stdout.isTTY)
|
|
378
301
|
console.clear();
|
|
302
|
+
// Detect platform
|
|
303
|
+
const detection = detectPlatform();
|
|
304
|
+
const adapter = await getAdapter(detection.platform);
|
|
379
305
|
p.intro(color.bgCyan(color.black(" context-mode upgrade ")));
|
|
306
|
+
p.log.info(`Platform: ${color.cyan(adapter.name)}` +
|
|
307
|
+
color.dim(` (${detection.confidence} confidence)`));
|
|
380
308
|
let pluginRoot = getPluginRoot();
|
|
381
|
-
const settingsPath = getSettingsPath();
|
|
382
309
|
const changes = [];
|
|
383
310
|
const s = p.spinner();
|
|
384
|
-
// Step 1: Pull latest from GitHub
|
|
311
|
+
// Step 1: Pull latest from GitHub
|
|
385
312
|
p.log.step("Pulling latest from GitHub...");
|
|
386
313
|
const localVersion = getLocalVersion();
|
|
387
|
-
const tmpDir =
|
|
388
|
-
s.start("Cloning mksglu/
|
|
314
|
+
const tmpDir = join(tmpdir(), `context-mode-upgrade-${Date.now()}`);
|
|
315
|
+
s.start("Cloning mksglu/context-mode");
|
|
389
316
|
try {
|
|
390
|
-
execSync(`git clone --depth 1 https://github.com/mksglu/
|
|
317
|
+
execSync(`git clone --depth 1 https://github.com/mksglu/context-mode.git "${tmpDir}"`, { stdio: "pipe", timeout: 30000 });
|
|
391
318
|
s.stop("Downloaded");
|
|
392
319
|
const srcDir = tmpDir;
|
|
393
|
-
// Read new version
|
|
394
320
|
const newPkg = JSON.parse(readFileSync(resolve(srcDir, "package.json"), "utf-8"));
|
|
395
321
|
const newVersion = newPkg.version ?? "unknown";
|
|
396
322
|
if (newVersion === localVersion) {
|
|
@@ -412,9 +338,8 @@ async function upgrade() {
|
|
|
412
338
|
timeout: 30000,
|
|
413
339
|
});
|
|
414
340
|
s.stop("Built successfully");
|
|
415
|
-
// Step 3: Update in-place
|
|
341
|
+
// Step 3: Update in-place
|
|
416
342
|
s.start("Updating files in-place");
|
|
417
|
-
// Clean stale version dirs from previous upgrade attempts
|
|
418
343
|
const cacheParentMatch = pluginRoot.match(/^(.*[\\/]plugins[\\/]cache[\\/][^\\/]+[\\/][^\\/]+[\\/])/);
|
|
419
344
|
if (cacheParentMatch) {
|
|
420
345
|
const cacheParent = cacheParentMatch[1];
|
|
@@ -433,7 +358,6 @@ async function upgrade() {
|
|
|
433
358
|
}
|
|
434
359
|
catch { /* parent may not exist */ }
|
|
435
360
|
}
|
|
436
|
-
// Copy new files over old ones — same path, no registry update needed
|
|
437
361
|
const items = [
|
|
438
362
|
"build", "src", "hooks", "skills", ".claude-plugin",
|
|
439
363
|
"start.mjs", "server.bundle.mjs", "package.json", ".mcp.json",
|
|
@@ -446,24 +370,10 @@ async function upgrade() {
|
|
|
446
370
|
catch { /* some files may not exist in source */ }
|
|
447
371
|
}
|
|
448
372
|
s.stop(color.green(`Updated in-place to v${newVersion}`));
|
|
449
|
-
// Fix registry
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
for (const [key, entries] of Object.entries(ipRaw.plugins || {})) {
|
|
454
|
-
if (!key.toLowerCase().includes("context-mode"))
|
|
455
|
-
continue;
|
|
456
|
-
for (const entry of entries) {
|
|
457
|
-
entry.installPath = pluginRoot;
|
|
458
|
-
entry.version = newVersion;
|
|
459
|
-
entry.lastUpdated = new Date().toISOString();
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
writeFileSync(ipPath, JSON.stringify(ipRaw, null, 2) + "\n", "utf-8");
|
|
463
|
-
p.log.info(color.dim(" Registry synced to " + pluginRoot));
|
|
464
|
-
}
|
|
465
|
-
catch { /* best effort */ }
|
|
466
|
-
// Install production deps (rebuild native modules if needed)
|
|
373
|
+
// Fix registry — adapter-aware
|
|
374
|
+
adapter.updatePluginRegistry(pluginRoot, newVersion);
|
|
375
|
+
p.log.info(color.dim(" Registry synced to " + pluginRoot));
|
|
376
|
+
// Install production deps
|
|
467
377
|
s.start("Installing production dependencies");
|
|
468
378
|
execSync("npm install --production --no-audit --no-fund", {
|
|
469
379
|
cwd: pluginRoot,
|
|
@@ -471,10 +381,10 @@ async function upgrade() {
|
|
|
471
381
|
timeout: 60000,
|
|
472
382
|
});
|
|
473
383
|
s.stop("Dependencies ready");
|
|
474
|
-
// Update global npm
|
|
384
|
+
// Update global npm
|
|
475
385
|
s.start("Updating npm global package");
|
|
476
386
|
try {
|
|
477
|
-
execSync(`npm install -g "${pluginRoot}" --no-audit --no-fund
|
|
387
|
+
execSync(`npm install -g "${pluginRoot}" --no-audit --no-fund`, {
|
|
478
388
|
stdio: "pipe",
|
|
479
389
|
timeout: 30000,
|
|
480
390
|
});
|
|
@@ -498,120 +408,40 @@ async function upgrade() {
|
|
|
498
408
|
s.stop(color.red("Update failed"));
|
|
499
409
|
p.log.error(color.red("GitHub pull failed") + ` — ${message}`);
|
|
500
410
|
p.log.info(color.dim("Continuing with hooks/settings fix..."));
|
|
501
|
-
// Cleanup on failure
|
|
502
411
|
try {
|
|
503
412
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
504
413
|
}
|
|
505
414
|
catch { /* ignore */ }
|
|
506
415
|
}
|
|
507
|
-
// Step 3: Backup settings
|
|
508
|
-
p.log.step(
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const backupPath = settingsPath + ".bak";
|
|
512
|
-
copyFileSync(settingsPath, backupPath);
|
|
416
|
+
// Step 3: Backup settings — adapter-aware
|
|
417
|
+
p.log.step(`Backing up ${adapter.name} settings...`);
|
|
418
|
+
const backupPath = adapter.backupSettings();
|
|
419
|
+
if (backupPath) {
|
|
513
420
|
p.log.success(color.green("Backup created") + color.dim(" -> " + backupPath));
|
|
514
|
-
changes.push("Backed up settings
|
|
515
|
-
}
|
|
516
|
-
catch {
|
|
517
|
-
p.log.warn(color.yellow("No existing settings.json to backup") +
|
|
518
|
-
" — a new one will be created");
|
|
519
|
-
}
|
|
520
|
-
// Step 4: Fix hooks
|
|
521
|
-
p.log.step("Configuring PreToolUse hooks...");
|
|
522
|
-
const hookScriptPath = toUnixPath(resolve(pluginRoot, "hooks", "pretooluse.mjs"));
|
|
523
|
-
const settings = readSettings() ?? {};
|
|
524
|
-
const desiredHookEntry = {
|
|
525
|
-
matcher: "Bash|Read|Grep|WebFetch|Task|mcp__plugin_context-mode_context-mode__execute|mcp__plugin_context-mode_context-mode__execute_file|mcp__plugin_context-mode_context-mode__batch_execute",
|
|
526
|
-
hooks: [
|
|
527
|
-
{
|
|
528
|
-
type: "command",
|
|
529
|
-
command: "node " + hookScriptPath,
|
|
530
|
-
},
|
|
531
|
-
],
|
|
532
|
-
};
|
|
533
|
-
const hooks = (settings.hooks ?? {});
|
|
534
|
-
const existingPreToolUse = hooks.PreToolUse;
|
|
535
|
-
if (existingPreToolUse && Array.isArray(existingPreToolUse)) {
|
|
536
|
-
const existingIdx = existingPreToolUse.findIndex((entry) => {
|
|
537
|
-
const entryHooks = entry.hooks;
|
|
538
|
-
return entryHooks?.some((h) => h.command?.includes("pretooluse.mjs"));
|
|
539
|
-
});
|
|
540
|
-
if (existingIdx >= 0) {
|
|
541
|
-
existingPreToolUse[existingIdx] = desiredHookEntry;
|
|
542
|
-
p.log.info(color.dim("Updated existing PreToolUse hook entry"));
|
|
543
|
-
changes.push("Updated existing PreToolUse hook entry");
|
|
544
|
-
}
|
|
545
|
-
else {
|
|
546
|
-
existingPreToolUse.push(desiredHookEntry);
|
|
547
|
-
p.log.info(color.dim("Added PreToolUse hook entry"));
|
|
548
|
-
changes.push("Added PreToolUse hook entry to existing hooks");
|
|
549
|
-
}
|
|
550
|
-
hooks.PreToolUse = existingPreToolUse;
|
|
421
|
+
changes.push("Backed up settings");
|
|
551
422
|
}
|
|
552
423
|
else {
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
changes.push("Created PreToolUse hooks section");
|
|
556
|
-
}
|
|
557
|
-
// --- SessionStart hook ---
|
|
558
|
-
p.log.step("Configuring SessionStart hook...");
|
|
559
|
-
const sessionHookScriptPath = resolve(pluginRoot, "hooks", "sessionstart.mjs");
|
|
560
|
-
const desiredSessionHookEntry = {
|
|
561
|
-
matcher: "",
|
|
562
|
-
hooks: [
|
|
563
|
-
{
|
|
564
|
-
type: "command",
|
|
565
|
-
command: "node " + sessionHookScriptPath,
|
|
566
|
-
},
|
|
567
|
-
],
|
|
568
|
-
};
|
|
569
|
-
const existingSessionStart = hooks.SessionStart;
|
|
570
|
-
if (existingSessionStart && Array.isArray(existingSessionStart)) {
|
|
571
|
-
const existingSessionIdx = existingSessionStart.findIndex((entry) => {
|
|
572
|
-
const entryHooks = entry.hooks;
|
|
573
|
-
return entryHooks?.some((h) => h.command?.includes("sessionstart.mjs"));
|
|
574
|
-
});
|
|
575
|
-
if (existingSessionIdx >= 0) {
|
|
576
|
-
existingSessionStart[existingSessionIdx] = desiredSessionHookEntry;
|
|
577
|
-
p.log.info(color.dim("Updated existing SessionStart hook entry"));
|
|
578
|
-
changes.push("Updated existing SessionStart hook entry");
|
|
579
|
-
}
|
|
580
|
-
else {
|
|
581
|
-
existingSessionStart.push(desiredSessionHookEntry);
|
|
582
|
-
p.log.info(color.dim("Added SessionStart hook entry"));
|
|
583
|
-
changes.push("Added SessionStart hook entry to existing hooks");
|
|
584
|
-
}
|
|
585
|
-
hooks.SessionStart = existingSessionStart;
|
|
586
|
-
}
|
|
587
|
-
else {
|
|
588
|
-
hooks.SessionStart = [desiredSessionHookEntry];
|
|
589
|
-
p.log.info(color.dim("Created SessionStart hooks section"));
|
|
590
|
-
changes.push("Created SessionStart hooks section");
|
|
424
|
+
p.log.warn(color.yellow("No existing settings to backup") +
|
|
425
|
+
" — a new one will be created");
|
|
591
426
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
p.log.
|
|
427
|
+
// Step 4: Configure hooks — adapter-aware
|
|
428
|
+
p.log.step(`Configuring ${adapter.name} hooks...`);
|
|
429
|
+
const hookChanges = adapter.configureAllHooks(pluginRoot);
|
|
430
|
+
for (const change of hookChanges) {
|
|
431
|
+
p.log.info(color.dim(` ${change}`));
|
|
432
|
+
changes.push(change);
|
|
597
433
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
p.log.error(color.red("Failed to write settings.json") + " — " + message);
|
|
601
|
-
p.outro(color.red("Upgrade failed."));
|
|
602
|
-
process.exit(1);
|
|
603
|
-
}
|
|
604
|
-
// Step 5: Set hook script permissions
|
|
434
|
+
p.log.success(color.green("Hooks configured") + color.dim(` — ${adapter.name}`));
|
|
435
|
+
// Step 5: Set hook script permissions — adapter-aware
|
|
605
436
|
p.log.step("Setting hook script permissions...");
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
changes.push("Set pretooluse.mjs as executable");
|
|
437
|
+
const permSet = adapter.setHookPermissions(pluginRoot);
|
|
438
|
+
if (permSet.length > 0) {
|
|
439
|
+
p.log.success(color.green("Permissions set") + color.dim(` — ${permSet.length} hook script(s)`));
|
|
440
|
+
changes.push(`Set ${permSet.length} hook scripts as executable`);
|
|
611
441
|
}
|
|
612
|
-
|
|
613
|
-
p.log.error(color.red("
|
|
614
|
-
color.dim(" — expected
|
|
442
|
+
else {
|
|
443
|
+
p.log.error(color.red("No hook scripts found") +
|
|
444
|
+
color.dim(" — expected in " + resolve(pluginRoot, "hooks")));
|
|
615
445
|
}
|
|
616
446
|
// Step 6: Report
|
|
617
447
|
if (changes.length > 0) {
|
|
@@ -620,7 +450,7 @@ async function upgrade() {
|
|
|
620
450
|
else {
|
|
621
451
|
p.log.info(color.dim("No changes were needed."));
|
|
622
452
|
}
|
|
623
|
-
// Step 7: Run doctor
|
|
453
|
+
// Step 7: Run doctor
|
|
624
454
|
p.log.step("Running doctor to verify...");
|
|
625
455
|
console.log();
|
|
626
456
|
try {
|
|
@@ -632,130 +462,6 @@ async function upgrade() {
|
|
|
632
462
|
}
|
|
633
463
|
catch {
|
|
634
464
|
p.log.warn(color.yellow("Doctor had warnings") +
|
|
635
|
-
color.dim(
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
/* -------------------------------------------------------
|
|
639
|
-
* Setup
|
|
640
|
-
* ------------------------------------------------------- */
|
|
641
|
-
async function setup() {
|
|
642
|
-
if (process.stdout.isTTY)
|
|
643
|
-
console.clear();
|
|
644
|
-
p.intro(color.bgCyan(color.black(" context-mode setup ")));
|
|
645
|
-
const s = p.spinner();
|
|
646
|
-
// Step 1: Detect runtimes
|
|
647
|
-
s.start("Detecting installed runtimes");
|
|
648
|
-
const runtimes = detectRuntimes();
|
|
649
|
-
const available = getAvailableLanguages(runtimes);
|
|
650
|
-
s.stop("Detected " + available.length + " languages");
|
|
651
|
-
// Show what's available
|
|
652
|
-
p.note(getRuntimeSummary(runtimes), "Detected Runtimes");
|
|
653
|
-
// Step 2: Check Bun
|
|
654
|
-
if (!hasBunRuntime()) {
|
|
655
|
-
p.log.warn(color.yellow("Bun is not installed.") +
|
|
656
|
-
" JS/TS will run with Node.js (3-5x slower).");
|
|
657
|
-
const installBun = await p.confirm({
|
|
658
|
-
message: "Would you like to install Bun for faster execution?",
|
|
659
|
-
initialValue: true,
|
|
660
|
-
});
|
|
661
|
-
if (p.isCancel(installBun)) {
|
|
662
|
-
p.cancel("Setup cancelled.");
|
|
663
|
-
process.exit(0);
|
|
664
|
-
}
|
|
665
|
-
if (installBun) {
|
|
666
|
-
s.start("Installing Bun");
|
|
667
|
-
try {
|
|
668
|
-
execSync("curl -fsSL https://bun.sh/install | bash", {
|
|
669
|
-
stdio: "pipe",
|
|
670
|
-
timeout: 60000,
|
|
671
|
-
});
|
|
672
|
-
s.stop(color.green("Bun installed successfully!"));
|
|
673
|
-
// Re-detect runtimes
|
|
674
|
-
const newRuntimes = detectRuntimes();
|
|
675
|
-
if (hasBunRuntime()) {
|
|
676
|
-
p.log.success("JavaScript and TypeScript will now use Bun " +
|
|
677
|
-
color.dim("(3-5x faster)"));
|
|
678
|
-
}
|
|
679
|
-
p.note(getRuntimeSummary(newRuntimes), "Updated Runtimes");
|
|
680
|
-
}
|
|
681
|
-
catch (err) {
|
|
682
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
683
|
-
s.stop(color.red("Failed to install Bun"));
|
|
684
|
-
p.log.error("Installation failed: " +
|
|
685
|
-
message +
|
|
686
|
-
"\nYou can install manually: curl -fsSL https://bun.sh/install | bash");
|
|
687
|
-
p.log.info(color.dim("Continuing with Node.js — everything will still work."));
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
else {
|
|
691
|
-
p.log.info(color.dim("No problem! Using Node.js. You can install Bun later: curl -fsSL https://bun.sh/install | bash"));
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
else {
|
|
695
|
-
p.log.success(color.green("Bun detected!") +
|
|
696
|
-
" JS/TS will run at maximum speed.");
|
|
697
|
-
}
|
|
698
|
-
// Step 3: Check optional runtimes
|
|
699
|
-
const missing = [];
|
|
700
|
-
if (!runtimes.python)
|
|
701
|
-
missing.push("Python (python3)");
|
|
702
|
-
if (!runtimes.ruby)
|
|
703
|
-
missing.push("Ruby (ruby)");
|
|
704
|
-
if (!runtimes.go)
|
|
705
|
-
missing.push("Go (go)");
|
|
706
|
-
if (!runtimes.php)
|
|
707
|
-
missing.push("PHP (php)");
|
|
708
|
-
if (!runtimes.r)
|
|
709
|
-
missing.push("R (Rscript)");
|
|
710
|
-
if (missing.length > 0) {
|
|
711
|
-
p.log.info(color.dim("Optional runtimes not found: " + missing.join(", ")));
|
|
712
|
-
p.log.info(color.dim("Install them to enable additional language support in context-mode."));
|
|
713
|
-
}
|
|
714
|
-
// Step 4: Installation instructions
|
|
715
|
-
const installMethod = await p.select({
|
|
716
|
-
message: "How would you like to configure context-mode?",
|
|
717
|
-
options: [
|
|
718
|
-
{
|
|
719
|
-
value: "claude-code",
|
|
720
|
-
label: "Claude Code (recommended)",
|
|
721
|
-
hint: "claude mcp add",
|
|
722
|
-
},
|
|
723
|
-
{
|
|
724
|
-
value: "manual",
|
|
725
|
-
label: "Show manual configuration",
|
|
726
|
-
hint: ".mcp.json",
|
|
727
|
-
},
|
|
728
|
-
{ value: "skip", label: "Skip — I'll configure later" },
|
|
729
|
-
],
|
|
730
|
-
});
|
|
731
|
-
if (p.isCancel(installMethod)) {
|
|
732
|
-
p.cancel("Setup cancelled.");
|
|
733
|
-
process.exit(0);
|
|
734
|
-
}
|
|
735
|
-
const serverPath = new URL("./server.js", import.meta.url).pathname;
|
|
736
|
-
if (installMethod === "claude-code") {
|
|
737
|
-
s.start("Adding to Claude Code");
|
|
738
|
-
try {
|
|
739
|
-
execSync(`claude mcp add context-mode -- node ${serverPath}`, { stdio: "pipe", timeout: 10000 });
|
|
740
|
-
s.stop(color.green("Added to Claude Code!"));
|
|
741
|
-
}
|
|
742
|
-
catch {
|
|
743
|
-
s.stop(color.yellow("Could not add automatically"));
|
|
744
|
-
p.log.info("Run manually:\n" +
|
|
745
|
-
color.cyan(` claude mcp add context-mode -- node ${serverPath}`));
|
|
746
|
-
}
|
|
465
|
+
color.dim(` — restart your ${adapter.name} session to pick up the new version`));
|
|
747
466
|
}
|
|
748
|
-
else if (installMethod === "manual") {
|
|
749
|
-
p.note(JSON.stringify({
|
|
750
|
-
mcpServers: {
|
|
751
|
-
"context-mode": {
|
|
752
|
-
command: "node",
|
|
753
|
-
args: [serverPath],
|
|
754
|
-
},
|
|
755
|
-
},
|
|
756
|
-
}, null, 2), "Add to your .mcp.json or Claude Code settings");
|
|
757
|
-
}
|
|
758
|
-
p.outro(color.green("Setup complete!") +
|
|
759
|
-
" " +
|
|
760
|
-
color.dim(available.length + " languages ready."));
|
|
761
467
|
}
|