feat: cross-platform start.mjs + Windows-safe CLI + expanded CI

- Add start.mjs: cross-platform Node.js entry point replacing start.sh
- Update plugin.json, .mcp.json: command "node" + start.mjs
- CLI: replace rm -rf, cp -r, 2>/dev/null with Node.js fs equivalents
- CLI: add cpSync, rmSync to imports for cross-platform file ops
- CI: add search-wiring, search-fallback, subagent-budget tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Mert Koseoglu
2026-03-01 16:04:28 +03:00
co-authored by Claude Opus 4.6
parent 2be94b0309
commit 728d8417eb
6 changed files with 67 additions and 16 deletions
+2 -2
View File
@@ -21,8 +21,8 @@
],
"mcpServers": {
"context-mode": {
"command": "sh",
"args": ["${CLAUDE_PLUGIN_ROOT}/start.sh"]
"command": "node",
"args": ["${CLAUDE_PLUGIN_ROOT}/start.mjs"]
}
},
"skills": "./skills/"
+9
View File
@@ -60,5 +60,14 @@ jobs:
- name: Stream cap tests
run: npx tsx tests/stream-cap.test.ts
- name: Search wiring tests
run: npx tsx tests/search-wiring.test.ts
- name: Search fallback tests
run: npx tsx tests/search-fallback-integration.test.ts
- name: Subagent budget tests
run: npx tsx tests/subagent-budget.test.ts
- name: Doctor (verify server starts)
run: npx tsx src/cli.ts doctor
+2 -2
View File
@@ -1,8 +1,8 @@
{
"mcpServers": {
"context-mode": {
"command": "sh",
"args": ["${CLAUDE_PLUGIN_ROOT}/start.sh"]
"command": "node",
"args": ["${CLAUDE_PLUGIN_ROOT}/start.mjs"]
}
}
}
+1 -1
View File
@@ -32,7 +32,7 @@
"skills",
".claude-plugin",
".mcp.json",
"start.sh",
"start.mjs",
"README.md",
"LICENSE"
],
+12 -11
View File
@@ -13,7 +13,7 @@
import * as p from "@clack/prompts";
import color from "picocolors";
import { execSync } from "node:child_process";
import { readFileSync, writeFileSync, copyFileSync, chmodSync, accessSync, readdirSync, constants } from "node:fs";
import { readFileSync, writeFileSync, copyFileSync, cpSync, chmodSync, accessSync, readdirSync, rmSync, constants } from "node:fs";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { homedir } from "node:os";
@@ -416,12 +416,12 @@ async function upgrade() {
// Step 2: Install dependencies + build
s.start("Installing dependencies & building");
execSync("npm install --no-audit --no-fund 2>/dev/null", {
execSync("npm install --no-audit --no-fund", {
cwd: srcDir,
stdio: "pipe",
timeout: 60000,
});
execSync("npm run build 2>/dev/null", {
execSync("npm run build", {
cwd: srcDir,
stdio: "pipe",
timeout: 30000,
@@ -432,19 +432,19 @@ async function upgrade() {
s.start("Installing files");
const items = [
"build", "hooks", "skills", ".claude-plugin",
"start.sh", "server.bundle.mjs", "package.json", ".mcp.json",
"start.mjs", "server.bundle.mjs", "package.json", ".mcp.json",
];
for (const item of items) {
try {
execSync(`rm -rf "${pluginRoot}/${item}"`, { stdio: "pipe" });
execSync(`cp -r "${srcDir}/${item}" "${pluginRoot}/"`, { stdio: "pipe" });
rmSync(resolve(pluginRoot, item), { recursive: true, force: true });
cpSync(resolve(srcDir, item), resolve(pluginRoot, item), { recursive: true });
} catch { /* some files may not exist */ }
}
s.stop(color.green("Files installed"));
// Install production deps in plugin root
s.start("Installing production dependencies");
execSync("npm install --production --no-audit --no-fund 2>/dev/null", {
execSync("npm install --production --no-audit --no-fund", {
cwd: pluginRoot,
stdio: "pipe",
timeout: 60000,
@@ -460,8 +460,9 @@ async function upgrade() {
const newCacheDir = cacheMatch[1] + newVersion;
s.start(`Migrating cache: ${oldDirVersion} → ${newVersion}`);
try {
execSync(`rm -rf "${newCacheDir}"`, { stdio: "pipe" });
execSync(`mv "${pluginRoot}" "${newCacheDir}"`, { stdio: "pipe" });
rmSync(newCacheDir, { recursive: true, force: true });
cpSync(pluginRoot, newCacheDir, { recursive: true });
rmSync(pluginRoot, { recursive: true, force: true });
pluginRoot = newCacheDir;
s.stop(color.green(`Cache directory: ${newVersion}`));
changes.push(`Migrated cache: ${oldDirVersion} → ${newVersion}`);
@@ -485,7 +486,7 @@ async function upgrade() {
}
// Cleanup
execSync(`rm -rf "${tmpDir}"`, { stdio: "pipe" });
rmSync(tmpDir, { recursive: true, force: true });
changes.push(
newVersion !== localVersion
@@ -502,7 +503,7 @@ async function upgrade() {
p.log.error(color.red("GitHub pull failed") + ` — ${message}`);
p.log.info(color.dim("Continuing with hooks/settings fix..."));
// Cleanup on failure
try { execSync(`rm -rf "${tmpDir}"`, { stdio: "pipe" }); } catch { /* ignore */ }
try { rmSync(tmpDir, { recursive: true, force: true }); } catch { /* ignore */ }
}
// Step 3: Backup settings.json
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env node
import { execSync } from "node:child_process";
import { existsSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
process.chdir(__dirname);
if (!process.env.CLAUDE_PROJECT_DIR) {
process.env.CLAUDE_PROJECT_DIR = process.cwd();
}
// Ensure native module is available
if (!existsSync(resolve(__dirname, "node_modules", "better-sqlite3"))) {
try {
execSync("npm install better-sqlite3 --no-package-lock --no-save --silent", {
cwd: __dirname,
stdio: "pipe",
timeout: 60000,
});
} catch { /* best effort */ }
}
// Bundle exists (CI-built) — start instantly
if (existsSync(resolve(__dirname, "server.bundle.mjs"))) {
await import("./server.bundle.mjs");
} else {
// Dev or npm install — full build
if (!existsSync(resolve(__dirname, "node_modules"))) {
try {
execSync("npm install --silent", { cwd: __dirname, stdio: "pipe", timeout: 60000 });
} catch { /* best effort */ }
}
if (!existsSync(resolve(__dirname, "build", "server.js"))) {
try {
execSync("npx tsc --silent", { cwd: __dirname, stdio: "pipe", timeout: 30000 });
} catch { /* best effort */ }
}
await import("./build/server.js");
}