context-mode 0.6.1 → 0.7.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 +28 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +43 -18
- package/build/cli.d.ts +3 -1
- package/build/cli.js +476 -50
- package/build/server.js +93 -28
- package/build/store.d.ts +4 -0
- package/build/store.js +163 -1
- package/hooks/hooks.json +28 -1
- package/hooks/pretooluse.sh +82 -23
- package/package.json +4 -2
- package/server.bundle.mjs +76 -42
- package/skills/doctor/SKILL.md +32 -0
- package/skills/stats/SKILL.md +20 -0
- package/skills/upgrade/SKILL.md +31 -0
package/build/cli.js
CHANGED
|
@@ -5,11 +5,17 @@
|
|
|
5
5
|
* Usage:
|
|
6
6
|
* context-mode → Start MCP server (stdio)
|
|
7
7
|
* context-mode setup → Interactive setup (detect runtimes, install Bun)
|
|
8
|
-
* context-mode doctor → Diagnose runtime issues
|
|
8
|
+
* context-mode doctor → Diagnose runtime issues, hooks, FTS5, version
|
|
9
|
+
* context-mode upgrade → Fix hooks, permissions, and settings
|
|
10
|
+
* context-mode stats → (skill only — /context-mode:stats)
|
|
9
11
|
*/
|
|
10
12
|
import * as p from "@clack/prompts";
|
|
11
13
|
import color from "picocolors";
|
|
12
14
|
import { execSync } from "node:child_process";
|
|
15
|
+
import { readFileSync, writeFileSync, copyFileSync, chmodSync, accessSync, readdirSync, constants } from "node:fs";
|
|
16
|
+
import { resolve, dirname } from "node:path";
|
|
17
|
+
import { fileURLToPath } from "node:url";
|
|
18
|
+
import { homedir } from "node:os";
|
|
13
19
|
import { detectRuntimes, getRuntimeSummary, hasBunRuntime, getAvailableLanguages, } from "./runtime.js";
|
|
14
20
|
const args = process.argv.slice(2);
|
|
15
21
|
if (args[0] === "setup") {
|
|
@@ -18,10 +24,479 @@ if (args[0] === "setup") {
|
|
|
18
24
|
else if (args[0] === "doctor") {
|
|
19
25
|
doctor();
|
|
20
26
|
}
|
|
27
|
+
else if (args[0] === "upgrade") {
|
|
28
|
+
upgrade();
|
|
29
|
+
}
|
|
21
30
|
else {
|
|
22
31
|
// Default: start MCP server
|
|
23
32
|
import("./server.js");
|
|
24
33
|
}
|
|
34
|
+
/* -------------------------------------------------------
|
|
35
|
+
* Shared helpers
|
|
36
|
+
* ------------------------------------------------------- */
|
|
37
|
+
function getPluginRoot() {
|
|
38
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
39
|
+
const __dirname = dirname(__filename);
|
|
40
|
+
return resolve(__dirname, "..");
|
|
41
|
+
}
|
|
42
|
+
function getSettingsPath() {
|
|
43
|
+
return resolve(homedir(), ".claude", "settings.json");
|
|
44
|
+
}
|
|
45
|
+
function readSettings() {
|
|
46
|
+
try {
|
|
47
|
+
const raw = readFileSync(getSettingsPath(), "utf-8");
|
|
48
|
+
return JSON.parse(raw);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function getHookScriptPath() {
|
|
55
|
+
return resolve(getPluginRoot(), "hooks", "pretooluse.sh");
|
|
56
|
+
}
|
|
57
|
+
function getLocalVersion() {
|
|
58
|
+
try {
|
|
59
|
+
const pkg = JSON.parse(readFileSync(resolve(getPluginRoot(), "package.json"), "utf-8"));
|
|
60
|
+
return pkg.version ?? "unknown";
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return "unknown";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function fetchLatestVersion() {
|
|
67
|
+
try {
|
|
68
|
+
const resp = await fetch("https://registry.npmjs.org/context-mode/latest");
|
|
69
|
+
if (!resp.ok)
|
|
70
|
+
return "unknown";
|
|
71
|
+
const data = (await resp.json());
|
|
72
|
+
return data.version ?? "unknown";
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return "unknown";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function getMarketplaceVersion() {
|
|
79
|
+
// Detect from our own path: .../plugins/cache/<marketplace>/<plugin>/<version>/
|
|
80
|
+
const root = getPluginRoot();
|
|
81
|
+
const match = root.match(/plugins\/cache\/[^/]+\/[^/]+\/(\d+\.\d+\.\d+[^/]*)/);
|
|
82
|
+
if (match)
|
|
83
|
+
return match[1];
|
|
84
|
+
// Fallback: scan common plugin cache locations
|
|
85
|
+
const bases = [
|
|
86
|
+
resolve(homedir(), ".claude"),
|
|
87
|
+
resolve(homedir(), ".config", "claude"),
|
|
88
|
+
];
|
|
89
|
+
for (const base of bases) {
|
|
90
|
+
const cacheDir = resolve(base, "plugins", "cache", "claude-context-mode", "context-mode");
|
|
91
|
+
try {
|
|
92
|
+
const entries = readdirSync(cacheDir);
|
|
93
|
+
const versions = entries
|
|
94
|
+
.filter((e) => /^\d+\.\d+\.\d+/.test(e))
|
|
95
|
+
.sort((a, b) => {
|
|
96
|
+
const pa = a.split(".").map(Number);
|
|
97
|
+
const pb = b.split(".").map(Number);
|
|
98
|
+
for (let i = 0; i < 3; i++) {
|
|
99
|
+
if ((pa[i] ?? 0) !== (pb[i] ?? 0))
|
|
100
|
+
return (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
101
|
+
}
|
|
102
|
+
return 0;
|
|
103
|
+
});
|
|
104
|
+
if (versions.length > 0)
|
|
105
|
+
return versions[versions.length - 1];
|
|
106
|
+
}
|
|
107
|
+
catch { /* continue */ }
|
|
108
|
+
}
|
|
109
|
+
return "not installed";
|
|
110
|
+
}
|
|
111
|
+
function semverGt(a, b) {
|
|
112
|
+
const pa = a.split(".").map(Number);
|
|
113
|
+
const pb = b.split(".").map(Number);
|
|
114
|
+
for (let i = 0; i < 3; i++) {
|
|
115
|
+
if ((pa[i] ?? 0) > (pb[i] ?? 0))
|
|
116
|
+
return true;
|
|
117
|
+
if ((pa[i] ?? 0) < (pb[i] ?? 0))
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
/* -------------------------------------------------------
|
|
123
|
+
* Doctor
|
|
124
|
+
* ------------------------------------------------------- */
|
|
125
|
+
async function doctor() {
|
|
126
|
+
console.clear();
|
|
127
|
+
p.intro(color.bgMagenta(color.white(" context-mode doctor ")));
|
|
128
|
+
const s = p.spinner();
|
|
129
|
+
s.start("Running diagnostics");
|
|
130
|
+
const runtimes = detectRuntimes();
|
|
131
|
+
const available = getAvailableLanguages(runtimes);
|
|
132
|
+
s.stop("Diagnostics complete");
|
|
133
|
+
// Runtime check
|
|
134
|
+
p.note(getRuntimeSummary(runtimes), "Runtimes");
|
|
135
|
+
// Speed tier
|
|
136
|
+
if (hasBunRuntime()) {
|
|
137
|
+
p.log.success(color.green("Performance: FAST") +
|
|
138
|
+
" — Bun detected for JS/TS execution");
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
p.log.warn(color.yellow("Performance: NORMAL") +
|
|
142
|
+
" — Using Node.js (install Bun for 3-5x speed boost)");
|
|
143
|
+
}
|
|
144
|
+
// Language coverage
|
|
145
|
+
const total = 10;
|
|
146
|
+
const pct = ((available.length / total) * 100).toFixed(0);
|
|
147
|
+
p.log.info(`Language coverage: ${available.length}/${total} (${pct}%)` +
|
|
148
|
+
color.dim(` — ${available.join(", ")}`));
|
|
149
|
+
// Server test
|
|
150
|
+
p.log.step("Testing server initialization...");
|
|
151
|
+
try {
|
|
152
|
+
const { PolyglotExecutor } = await import("./executor.js");
|
|
153
|
+
const executor = new PolyglotExecutor({ runtimes });
|
|
154
|
+
const result = await executor.execute({
|
|
155
|
+
language: "javascript",
|
|
156
|
+
code: 'console.log("ok");',
|
|
157
|
+
timeout: 5000,
|
|
158
|
+
});
|
|
159
|
+
if (result.exitCode === 0 && result.stdout.trim() === "ok") {
|
|
160
|
+
p.log.success(color.green("Server test: PASS"));
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
p.log.error(color.red("Server test: FAIL") + ` — exit ${result.exitCode}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
168
|
+
p.log.error(color.red("Server test: FAIL") + ` — ${message}`);
|
|
169
|
+
}
|
|
170
|
+
// Hooks installed
|
|
171
|
+
p.log.step("Checking hooks configuration...");
|
|
172
|
+
const settings = readSettings();
|
|
173
|
+
const hookScriptPath = getHookScriptPath();
|
|
174
|
+
if (settings) {
|
|
175
|
+
const hooks = settings.hooks;
|
|
176
|
+
const preToolUse = hooks?.PreToolUse;
|
|
177
|
+
if (preToolUse && preToolUse.length > 0) {
|
|
178
|
+
const hasCorrectHook = preToolUse.some((entry) => entry.hooks?.some((h) => h.command?.includes("pretooluse.sh")));
|
|
179
|
+
if (hasCorrectHook) {
|
|
180
|
+
p.log.success(color.green("Hooks installed: PASS") + " — PreToolUse hook configured");
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
p.log.error(color.red("Hooks installed: FAIL") +
|
|
184
|
+
" — PreToolUse exists but does not point to pretooluse.sh" +
|
|
185
|
+
color.dim("\n Run: npx context-mode upgrade"));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
p.log.error(color.red("Hooks installed: FAIL") +
|
|
190
|
+
" — No PreToolUse hooks found" +
|
|
191
|
+
color.dim("\n Run: npx context-mode upgrade"));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
p.log.error(color.red("Hooks installed: FAIL") +
|
|
196
|
+
" — Could not read ~/.claude/settings.json" +
|
|
197
|
+
color.dim("\n Run: npx context-mode upgrade"));
|
|
198
|
+
}
|
|
199
|
+
// Hook script exists
|
|
200
|
+
p.log.step("Checking hook script...");
|
|
201
|
+
try {
|
|
202
|
+
accessSync(hookScriptPath, constants.R_OK);
|
|
203
|
+
p.log.success(color.green("Hook script exists: PASS") + color.dim(` — ${hookScriptPath}`));
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
p.log.error(color.red("Hook script exists: FAIL") +
|
|
207
|
+
color.dim(` — not found at ${hookScriptPath}`));
|
|
208
|
+
}
|
|
209
|
+
// Plugin enabled
|
|
210
|
+
p.log.step("Checking plugin registration...");
|
|
211
|
+
if (settings) {
|
|
212
|
+
const enabledPlugins = settings.enabledPlugins;
|
|
213
|
+
if (enabledPlugins) {
|
|
214
|
+
const pluginKey = Object.keys(enabledPlugins).find((k) => k.startsWith("context-mode"));
|
|
215
|
+
if (pluginKey && enabledPlugins[pluginKey]) {
|
|
216
|
+
p.log.success(color.green("Plugin enabled: PASS") + color.dim(` — ${pluginKey}`));
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
p.log.warn(color.yellow("Plugin enabled: WARN") +
|
|
220
|
+
" — context-mode not in enabledPlugins" +
|
|
221
|
+
color.dim(" (might be using standalone MCP mode)"));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
p.log.warn(color.yellow("Plugin enabled: WARN") +
|
|
226
|
+
" — no enabledPlugins section found" +
|
|
227
|
+
color.dim(" (might be using standalone MCP mode)"));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
p.log.warn(color.yellow("Plugin enabled: WARN") +
|
|
232
|
+
" — could not read settings.json");
|
|
233
|
+
}
|
|
234
|
+
// FTS5 / better-sqlite3
|
|
235
|
+
p.log.step("Checking FTS5 / better-sqlite3...");
|
|
236
|
+
try {
|
|
237
|
+
const Database = (await import("better-sqlite3")).default;
|
|
238
|
+
const db = new Database(":memory:");
|
|
239
|
+
db.exec("CREATE VIRTUAL TABLE fts_test USING fts5(content)");
|
|
240
|
+
db.exec("INSERT INTO fts_test(content) VALUES ('hello world')");
|
|
241
|
+
const row = db.prepare("SELECT * FROM fts_test WHERE fts_test MATCH 'hello'").get();
|
|
242
|
+
db.close();
|
|
243
|
+
if (row && row.content === "hello world") {
|
|
244
|
+
p.log.success(color.green("FTS5 / better-sqlite3: PASS") + " — native module works");
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
p.log.error(color.red("FTS5 / better-sqlite3: FAIL") + " — query returned unexpected result");
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
252
|
+
p.log.error(color.red("FTS5 / better-sqlite3: FAIL") +
|
|
253
|
+
` — ${message}` +
|
|
254
|
+
color.dim("\n Try: npm rebuild better-sqlite3"));
|
|
255
|
+
}
|
|
256
|
+
// Version check
|
|
257
|
+
p.log.step("Checking versions...");
|
|
258
|
+
const localVersion = getLocalVersion();
|
|
259
|
+
const latestVersion = await fetchLatestVersion();
|
|
260
|
+
const marketplaceVersion = getMarketplaceVersion();
|
|
261
|
+
// npm / MCP version
|
|
262
|
+
if (latestVersion === "unknown") {
|
|
263
|
+
p.log.warn(color.yellow("npm (MCP): WARN") +
|
|
264
|
+
` — local v${localVersion}, could not reach npm registry`);
|
|
265
|
+
}
|
|
266
|
+
else if (localVersion === latestVersion) {
|
|
267
|
+
p.log.success(color.green("npm (MCP): PASS") +
|
|
268
|
+
` — v${localVersion}`);
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
p.log.warn(color.yellow("npm (MCP): WARN") +
|
|
272
|
+
` — local v${localVersion}, latest v${latestVersion}` +
|
|
273
|
+
color.dim("\n Run: npm install -g context-mode@latest"));
|
|
274
|
+
}
|
|
275
|
+
// Marketplace version
|
|
276
|
+
if (marketplaceVersion === "not installed") {
|
|
277
|
+
p.log.info(color.dim("Marketplace: not installed") +
|
|
278
|
+
" — using standalone MCP mode");
|
|
279
|
+
}
|
|
280
|
+
else if (latestVersion !== "unknown" && marketplaceVersion === latestVersion) {
|
|
281
|
+
p.log.success(color.green("Marketplace: PASS") +
|
|
282
|
+
` — v${marketplaceVersion}`);
|
|
283
|
+
}
|
|
284
|
+
else if (latestVersion !== "unknown") {
|
|
285
|
+
p.log.warn(color.yellow("Marketplace: WARN") +
|
|
286
|
+
` — v${marketplaceVersion}, latest v${latestVersion}` +
|
|
287
|
+
color.dim("\n Update via Claude Code marketplace or reinstall plugin"));
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
p.log.info(`Marketplace: v${marketplaceVersion}` +
|
|
291
|
+
color.dim(" — could not verify against npm registry"));
|
|
292
|
+
}
|
|
293
|
+
// Summary
|
|
294
|
+
p.outro(available.length >= 4
|
|
295
|
+
? color.green("Diagnostics complete!")
|
|
296
|
+
: color.yellow("Some checks need attention — see above for details"));
|
|
297
|
+
}
|
|
298
|
+
/* -------------------------------------------------------
|
|
299
|
+
* Upgrade
|
|
300
|
+
* ------------------------------------------------------- */
|
|
301
|
+
async function upgrade() {
|
|
302
|
+
console.clear();
|
|
303
|
+
p.intro(color.bgCyan(color.black(" context-mode upgrade ")));
|
|
304
|
+
let pluginRoot = getPluginRoot();
|
|
305
|
+
const settingsPath = getSettingsPath();
|
|
306
|
+
const changes = [];
|
|
307
|
+
const s = p.spinner();
|
|
308
|
+
// Step 1: Pull latest from GitHub (same source as marketplace)
|
|
309
|
+
p.log.step("Pulling latest from GitHub...");
|
|
310
|
+
const localVersion = getLocalVersion();
|
|
311
|
+
const tmpDir = `/tmp/context-mode-upgrade-${Date.now()}`;
|
|
312
|
+
s.start("Cloning mksglu/claude-context-mode");
|
|
313
|
+
try {
|
|
314
|
+
execSync(`git clone --depth 1 https://github.com/mksglu/claude-context-mode.git "${tmpDir}"`, { stdio: "pipe", timeout: 30000 });
|
|
315
|
+
s.stop("Downloaded");
|
|
316
|
+
const srcDir = tmpDir;
|
|
317
|
+
// Read new version
|
|
318
|
+
const newPkg = JSON.parse(readFileSync(resolve(srcDir, "package.json"), "utf-8"));
|
|
319
|
+
const newVersion = newPkg.version ?? "unknown";
|
|
320
|
+
if (newVersion === localVersion) {
|
|
321
|
+
p.log.success(color.green("Already on latest") + ` — v${localVersion}`);
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
p.log.info(`Update available: ${color.yellow("v" + localVersion)} → ${color.green("v" + newVersion)}`);
|
|
325
|
+
}
|
|
326
|
+
// Step 2: Install dependencies + build
|
|
327
|
+
s.start("Installing dependencies & building");
|
|
328
|
+
execSync("npm install --no-audit --no-fund 2>/dev/null", {
|
|
329
|
+
cwd: srcDir,
|
|
330
|
+
stdio: "pipe",
|
|
331
|
+
timeout: 60000,
|
|
332
|
+
});
|
|
333
|
+
execSync("npm run build 2>/dev/null", {
|
|
334
|
+
cwd: srcDir,
|
|
335
|
+
stdio: "pipe",
|
|
336
|
+
timeout: 30000,
|
|
337
|
+
});
|
|
338
|
+
s.stop("Built successfully");
|
|
339
|
+
// Step 3: Copy to plugin root
|
|
340
|
+
s.start("Installing files");
|
|
341
|
+
const items = [
|
|
342
|
+
"build", "hooks", "skills", ".claude-plugin",
|
|
343
|
+
"start.sh", "server.bundle.mjs", "package.json", ".mcp.json",
|
|
344
|
+
];
|
|
345
|
+
for (const item of items) {
|
|
346
|
+
try {
|
|
347
|
+
execSync(`rm -rf "${pluginRoot}/${item}"`, { stdio: "pipe" });
|
|
348
|
+
execSync(`cp -r "${srcDir}/${item}" "${pluginRoot}/"`, { stdio: "pipe" });
|
|
349
|
+
}
|
|
350
|
+
catch { /* some files may not exist */ }
|
|
351
|
+
}
|
|
352
|
+
s.stop(color.green("Files installed"));
|
|
353
|
+
// Install production deps in plugin root
|
|
354
|
+
s.start("Installing production dependencies");
|
|
355
|
+
execSync("npm install --production --no-audit --no-fund 2>/dev/null", {
|
|
356
|
+
cwd: pluginRoot,
|
|
357
|
+
stdio: "pipe",
|
|
358
|
+
timeout: 60000,
|
|
359
|
+
});
|
|
360
|
+
s.stop("Dependencies ready");
|
|
361
|
+
// Step 2.5: Migrate versioned cache directory if version changed
|
|
362
|
+
const cacheMatch = pluginRoot.match(/^(.*\/plugins\/cache\/[^/]+\/[^/]+\/)(\d+\.\d+\.\d+[^/]*)$/);
|
|
363
|
+
if (cacheMatch && newVersion !== cacheMatch[2] && newVersion !== "unknown") {
|
|
364
|
+
const oldDirVersion = cacheMatch[2];
|
|
365
|
+
const newCacheDir = cacheMatch[1] + newVersion;
|
|
366
|
+
s.start(`Migrating cache: ${oldDirVersion} → ${newVersion}`);
|
|
367
|
+
try {
|
|
368
|
+
execSync(`rm -rf "${newCacheDir}"`, { stdio: "pipe" });
|
|
369
|
+
execSync(`mv "${pluginRoot}" "${newCacheDir}"`, { stdio: "pipe" });
|
|
370
|
+
pluginRoot = newCacheDir;
|
|
371
|
+
s.stop(color.green(`Cache directory: ${newVersion}`));
|
|
372
|
+
changes.push(`Migrated cache: ${oldDirVersion} → ${newVersion}`);
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
s.stop(color.yellow("Cache migration skipped — using existing directory"));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
// Update global npm package from same GitHub source
|
|
379
|
+
s.start("Updating npm global package");
|
|
380
|
+
try {
|
|
381
|
+
execSync(`npm install -g "${pluginRoot}" --no-audit --no-fund 2>/dev/null`, {
|
|
382
|
+
stdio: "pipe",
|
|
383
|
+
timeout: 30000,
|
|
384
|
+
});
|
|
385
|
+
s.stop(color.green("npm global updated"));
|
|
386
|
+
changes.push("Updated npm global package");
|
|
387
|
+
}
|
|
388
|
+
catch {
|
|
389
|
+
s.stop(color.yellow("npm global update skipped"));
|
|
390
|
+
p.log.info(color.dim(" Could not update global npm — may need sudo or standalone install"));
|
|
391
|
+
}
|
|
392
|
+
// Cleanup
|
|
393
|
+
execSync(`rm -rf "${tmpDir}"`, { stdio: "pipe" });
|
|
394
|
+
changes.push(newVersion !== localVersion
|
|
395
|
+
? `Updated v${localVersion} → v${newVersion}`
|
|
396
|
+
: `Reinstalled v${localVersion} from GitHub`);
|
|
397
|
+
p.log.success(color.green("Plugin reinstalled from GitHub!") +
|
|
398
|
+
color.dim(` — v${newVersion}`));
|
|
399
|
+
}
|
|
400
|
+
catch (err) {
|
|
401
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
402
|
+
s.stop(color.red("Update failed"));
|
|
403
|
+
p.log.error(color.red("GitHub pull failed") + ` — ${message}`);
|
|
404
|
+
p.log.info(color.dim("Continuing with hooks/settings fix..."));
|
|
405
|
+
// Cleanup on failure
|
|
406
|
+
try {
|
|
407
|
+
execSync(`rm -rf "${tmpDir}"`, { stdio: "pipe" });
|
|
408
|
+
}
|
|
409
|
+
catch { /* ignore */ }
|
|
410
|
+
}
|
|
411
|
+
// Step 3: Backup settings.json
|
|
412
|
+
p.log.step("Backing up settings.json...");
|
|
413
|
+
try {
|
|
414
|
+
accessSync(settingsPath, constants.R_OK);
|
|
415
|
+
const backupPath = settingsPath + ".bak";
|
|
416
|
+
copyFileSync(settingsPath, backupPath);
|
|
417
|
+
p.log.success(color.green("Backup created") + color.dim(" -> " + backupPath));
|
|
418
|
+
changes.push("Backed up settings.json");
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
p.log.warn(color.yellow("No existing settings.json to backup") +
|
|
422
|
+
" — a new one will be created");
|
|
423
|
+
}
|
|
424
|
+
// Step 4: Fix hooks
|
|
425
|
+
p.log.step("Configuring PreToolUse hooks...");
|
|
426
|
+
const hookScriptPath = resolve(pluginRoot, "hooks", "pretooluse.sh");
|
|
427
|
+
const settings = readSettings() ?? {};
|
|
428
|
+
const desiredHookEntry = {
|
|
429
|
+
matcher: "Bash|Read|Grep|Glob|WebFetch|WebSearch|Task",
|
|
430
|
+
hooks: [
|
|
431
|
+
{
|
|
432
|
+
type: "command",
|
|
433
|
+
command: "bash " + hookScriptPath,
|
|
434
|
+
},
|
|
435
|
+
],
|
|
436
|
+
};
|
|
437
|
+
const hooks = (settings.hooks ?? {});
|
|
438
|
+
const existingPreToolUse = hooks.PreToolUse;
|
|
439
|
+
if (existingPreToolUse && Array.isArray(existingPreToolUse)) {
|
|
440
|
+
const existingIdx = existingPreToolUse.findIndex((entry) => {
|
|
441
|
+
const entryHooks = entry.hooks;
|
|
442
|
+
return entryHooks?.some((h) => h.command?.includes("pretooluse.sh"));
|
|
443
|
+
});
|
|
444
|
+
if (existingIdx >= 0) {
|
|
445
|
+
existingPreToolUse[existingIdx] = desiredHookEntry;
|
|
446
|
+
p.log.info(color.dim("Updated existing PreToolUse hook entry"));
|
|
447
|
+
changes.push("Updated existing PreToolUse hook entry");
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
existingPreToolUse.push(desiredHookEntry);
|
|
451
|
+
p.log.info(color.dim("Added PreToolUse hook entry"));
|
|
452
|
+
changes.push("Added PreToolUse hook entry to existing hooks");
|
|
453
|
+
}
|
|
454
|
+
hooks.PreToolUse = existingPreToolUse;
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
hooks.PreToolUse = [desiredHookEntry];
|
|
458
|
+
p.log.info(color.dim("Created PreToolUse hooks section"));
|
|
459
|
+
changes.push("Created PreToolUse hooks section");
|
|
460
|
+
}
|
|
461
|
+
settings.hooks = hooks;
|
|
462
|
+
// Write updated settings
|
|
463
|
+
try {
|
|
464
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
465
|
+
p.log.success(color.green("Hooks configured") + color.dim(" -> " + settingsPath));
|
|
466
|
+
}
|
|
467
|
+
catch (err) {
|
|
468
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
469
|
+
p.log.error(color.red("Failed to write settings.json") + " — " + message);
|
|
470
|
+
p.outro(color.red("Upgrade failed."));
|
|
471
|
+
process.exit(1);
|
|
472
|
+
}
|
|
473
|
+
// Step 5: Set hook script permissions
|
|
474
|
+
p.log.step("Setting hook script permissions...");
|
|
475
|
+
try {
|
|
476
|
+
accessSync(hookScriptPath, constants.R_OK);
|
|
477
|
+
chmodSync(hookScriptPath, 0o755);
|
|
478
|
+
p.log.success(color.green("Permissions set") + color.dim(" — chmod +x " + hookScriptPath));
|
|
479
|
+
changes.push("Set pretooluse.sh as executable");
|
|
480
|
+
}
|
|
481
|
+
catch {
|
|
482
|
+
p.log.error(color.red("Hook script not found") +
|
|
483
|
+
color.dim(" — expected at " + hookScriptPath));
|
|
484
|
+
}
|
|
485
|
+
// Step 6: Report
|
|
486
|
+
if (changes.length > 0) {
|
|
487
|
+
p.note(changes.map((c) => color.green(" + ") + c).join("\n"), "Changes Applied");
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
p.log.info(color.dim("No changes were needed."));
|
|
491
|
+
}
|
|
492
|
+
// Step 7: Run doctor
|
|
493
|
+
p.log.step("Running doctor to verify...");
|
|
494
|
+
console.log();
|
|
495
|
+
await doctor();
|
|
496
|
+
}
|
|
497
|
+
/* -------------------------------------------------------
|
|
498
|
+
* Setup
|
|
499
|
+
* ------------------------------------------------------- */
|
|
25
500
|
async function setup() {
|
|
26
501
|
console.clear();
|
|
27
502
|
p.intro(color.bgCyan(color.black(" context-mode setup ")));
|
|
@@ -142,52 +617,3 @@ async function setup() {
|
|
|
142
617
|
" " +
|
|
143
618
|
color.dim(available.length + " languages ready."));
|
|
144
619
|
}
|
|
145
|
-
async function doctor() {
|
|
146
|
-
console.clear();
|
|
147
|
-
p.intro(color.bgMagenta(color.white(" context-mode doctor ")));
|
|
148
|
-
const s = p.spinner();
|
|
149
|
-
s.start("Running diagnostics");
|
|
150
|
-
const runtimes = detectRuntimes();
|
|
151
|
-
const available = getAvailableLanguages(runtimes);
|
|
152
|
-
s.stop("Diagnostics complete");
|
|
153
|
-
// Runtime check
|
|
154
|
-
p.note(getRuntimeSummary(runtimes), "Runtimes");
|
|
155
|
-
// Speed tier
|
|
156
|
-
if (hasBunRuntime()) {
|
|
157
|
-
p.log.success(color.green("Performance: FAST") +
|
|
158
|
-
" — Bun detected for JS/TS execution");
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
p.log.warn(color.yellow("Performance: NORMAL") +
|
|
162
|
-
" — Using Node.js (install Bun for 3-5x speed boost)");
|
|
163
|
-
}
|
|
164
|
-
// Language coverage
|
|
165
|
-
const total = 10;
|
|
166
|
-
const pct = ((available.length / total) * 100).toFixed(0);
|
|
167
|
-
p.log.info(`Language coverage: ${available.length}/${total} (${pct}%)` +
|
|
168
|
-
color.dim(` — ${available.join(", ")}`));
|
|
169
|
-
// Server test
|
|
170
|
-
p.log.step("Testing server initialization...");
|
|
171
|
-
try {
|
|
172
|
-
const { PolyglotExecutor } = await import("./executor.js");
|
|
173
|
-
const executor = new PolyglotExecutor({ runtimes });
|
|
174
|
-
const result = await executor.execute({
|
|
175
|
-
language: "javascript",
|
|
176
|
-
code: 'console.log("ok");',
|
|
177
|
-
timeout: 5000,
|
|
178
|
-
});
|
|
179
|
-
if (result.exitCode === 0 && result.stdout.trim() === "ok") {
|
|
180
|
-
p.log.success(color.green("Server test: PASS"));
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
183
|
-
p.log.error(color.red("Server test: FAIL") + ` — exit ${result.exitCode}`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
catch (err) {
|
|
187
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
188
|
-
p.log.error(color.red("Server test: FAIL") + ` — ${message}`);
|
|
189
|
-
}
|
|
190
|
-
p.outro(available.length >= 4
|
|
191
|
-
? color.green("Everything looks good!")
|
|
192
|
-
: color.yellow("Some runtimes missing — install them for full coverage"));
|
|
193
|
-
}
|