feat(cli): ship code-mcp-server with npm install

This commit is contained in:
James Peter
2025-09-22 15:02:41 +10:00
parent 5846693cdd
commit 33f498e19b
7 changed files with 78 additions and 35 deletions
+35 -13
View File
@@ -265,7 +265,7 @@ jobs:
RUST_BACKTRACE: "1"
run: |
cd codex-rs
cargo build --release --frozen --locked --timings --target ${{ matrix.target }} --bin code
cargo build --release --frozen --locked --timings --target ${{ matrix.target }} --bin code --bin code-mcp-server
- name: Post-build smoke (run binary) [Unix]
if: |
@@ -297,8 +297,10 @@ jobs:
mkdir -p artifacts
if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
cp codex-rs/target/${{ matrix.target }}/release/code.exe artifacts/${{ matrix.artifact }}
cp codex-rs/target/${{ matrix.target }}/release/code-mcp-server.exe artifacts/code-mcp-server-${{ matrix.target }}.exe
else
cp codex-rs/target/${{ matrix.target }}/release/code artifacts/${{ matrix.artifact }}
cp codex-rs/target/${{ matrix.target }}/release/code-mcp-server artifacts/code-mcp-server-${{ matrix.target }}
fi
- name: Compress artifacts (Windows)
@@ -421,11 +423,11 @@ jobs:
map_target() {
case "$1" in
aarch64-apple-darwin) echo "os=darwin cpu=arm64 pkg=@just-every/code-darwin-arm64" ;;
x86_64-apple-darwin) echo "os=darwin cpu=x64 pkg=@just-every/code-darwin-x64" ;;
x86_64-unknown-linux-musl) echo "os=linux cpu=x64 pkg=@just-every/code-linux-x64-musl" ;;
aarch64-unknown-linux-musl) echo "os=linux cpu=arm64 pkg=@just-every/code-linux-arm64-musl" ;;
x86_64-pc-windows-msvc.exe) echo "os=win32 cpu=x64 pkg=@just-every/code-win32-x64" ;;
aarch64-apple-darwin) echo "os=darwin cpu=arm64 pkg=@just-every/code-darwin-arm64 target=$1" ;;
x86_64-apple-darwin) echo "os=darwin cpu=x64 pkg=@just-every/code-darwin-x64 target=$1" ;;
x86_64-unknown-linux-musl) echo "os=linux cpu=x64 pkg=@just-every/code-linux-x64-musl target=$1" ;;
aarch64-unknown-linux-musl) echo "os=linux cpu=arm64 pkg=@just-every/code-linux-arm64-musl target=$1" ;;
x86_64-pc-windows-msvc.exe) echo "os=win32 cpu=x64 pkg=@just-every/code-win32-x64 target=$1" ;;
*) echo "" ;;
esac
}
@@ -457,6 +459,14 @@ jobs:
esac
}
targets=(
aarch64-apple-darwin
x86_64-apple-darwin
x86_64-unknown-linux-musl
aarch64-unknown-linux-musl
x86_64-pc-windows-msvc.exe
)
for f in release-assets/*; do
b="$(basename "$f")"
# Derive target triple from the filename while preserving optional .exe (Windows):
@@ -467,20 +477,32 @@ jobs:
*.zip) base="${b%.zip}" ;;
*) base="$b" ;;
esac
t="${base#code-}"
meta="$(map_target "$t")" || true
match=""
for candidate in "${targets[@]}"; do
if [[ "$base" == code-*"$candidate" ]]; then
match="$candidate"
break
fi
done
if [ -z "$match" ]; then
echo "Skipping unrecognized artifact: $b"
continue
fi
meta="$(map_target "$match")" || true
if [ -z "$meta" ]; then
echo "Skipping unrecognized artifact: $b"
continue
fi
eval "$meta" # sets os, cpu, pkg
eval "$meta" # sets os, cpu, pkg, target
workdir="$stage_root/${pkg##*/}"
rm -rf "$workdir" && mkdir -p "$workdir/bin"
mkdir -p "$workdir/bin"
rawbin="$(decompress_to "$f" "$workdir/bin")"
chmod +x "$rawbin" || true
# package.json
printf '{\n "name": "%s",\n "version": "%s",\n "license": "Apache-2.0",\n "description": "Platform binary for @just-every/code (%s)",\n "os": ["%s"],\n "cpu": ["%s"],\n "files": ["bin/"],\n "private": false\n}\n' \
"$pkg" "$NEW_VERSION" "$t" "$os" "$cpu" > "$workdir/package.json"
if [ ! -f "$workdir/package.json" ]; then
printf '{\n "name": "%s",\n "version": "%s",\n "license": "Apache-2.0",\n "description": "Platform binary for @just-every/code (%s)",\n "os": ["%s"],\n "cpu": ["%s"],\n "files": ["bin/"],\n "private": false\n}\n' \
"$pkg" "$NEW_VERSION" "$target" "$os" "$cpu" > "$workdir/package.json"
fi
done
ls -R "$stage_root"
+2 -1
View File
@@ -43,7 +43,8 @@ npx -y @just-every/code
```bash
npm install -g @just-every/code
code // or `coder` if you're using VS Code
code # or `coder` if you're using VS Code
code-mcp-server --stdio # launches the MCP server for IDE integrations
```
Note: If another tool already provides a `code` command (e.g. VS Code), our CLI is also installed as `coder`. Use `coder` to avoid conflicts.
@@ -0,0 +1,6 @@
#!/usr/bin/env node
process.env.CODE_NATIVE_BINARY = process.env.CODE_NATIVE_BINARY || 'code-mcp-server';
process.env.CODE_NATIVE_COMMAND = process.env.CODE_NATIVE_COMMAND || 'code-mcp-server';
await import('./coder.js');
+16 -12
View File
@@ -12,6 +12,8 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const { platform, arch } = process;
const binaryBaseName = process.env.CODE_NATIVE_BINARY || "code";
const commandLabel = process.env.CODE_NATIVE_COMMAND || (binaryBaseName === "code" ? "code" : binaryBaseName);
// Important: Never delegate to another system's `code` binary (e.g., VS Code).
// When users run via `npx @just-every/code`, we must always execute our
@@ -73,9 +75,11 @@ if (!targetTriple) {
throw new Error(`Unsupported platform: ${platform} (${arch})`);
}
// Prefer new 'code-*' binary names; fall back to legacy 'coder-*' if missing.
let binaryPath = path.join(__dirname, "..", "bin", `code-${targetTriple}`);
let legacyBinaryPath = path.join(__dirname, "..", "bin", `coder-${targetTriple}`);
// Prefer '<binaryBaseName>-*' binary names; fall back to legacy 'coder-*' if running the main CLI.
let binaryPath = path.join(__dirname, "..", "bin", `${binaryBaseName}-${targetTriple}`);
const legacyBinaryPath = binaryBaseName === "code"
? path.join(__dirname, "..", "bin", `coder-${targetTriple}`)
: null;
// --- Bootstrap helper (runs if the binary is missing, e.g. Bun blocked postinstall) ---
import { existsSync, chmodSync, statSync, openSync, readSync, closeSync, mkdirSync, copyFileSync, readFileSync, unlinkSync, createWriteStream } from "fs";
@@ -129,7 +133,7 @@ const getCachedBinaryPath = (version) => {
// targetTriple already includes the proper extension on Windows ("...msvc.exe").
// Do not append another suffix; just use the exact targetTriple-derived name.
const cacheDir = getCacheDir(version);
return path.join(cacheDir, `code-${targetTriple}`);
return path.join(cacheDir, `${binaryBaseName}-${targetTriple}`);
};
let lastBootstrapError = null;
@@ -201,7 +205,7 @@ const tryBootstrapBinary = async () => {
try {
const pkgJson = req.resolve(`${name}/package.json`);
const pkgDir = path.dirname(pkgJson);
const src = path.join(pkgDir, "bin", `code-${targetTriple}`);
const src = path.join(pkgDir, "bin", `${binaryBaseName}-${targetTriple}`);
if (existsSync(src)) {
// Always ensure cache has the binary; on Unix mirror into node_modules
copyFileSync(src, cachePath);
@@ -218,8 +222,8 @@ const tryBootstrapBinary = async () => {
// 4) Download from GitHub release
const isWin = platform === "win32";
const archiveName = isWin
? `code-${targetTriple}.zip`
: (() => { try { execSync("zstd --version", { stdio: "ignore", shell: true }); return `code-${targetTriple}.zst`; } catch { return `code-${targetTriple}.tar.gz`; } })();
? `${binaryBaseName}-${targetTriple}.zip`
: (() => { try { execSync("zstd --version", { stdio: "ignore", shell: true }); return `${binaryBaseName}-${targetTriple}.zst`; } catch { return `${binaryBaseName}-${targetTriple}.tar.gz`; } })();
const url = `https://github.com/just-every/code/releases/download/v${version}/${archiveName}`;
const tmp = path.join(binDir, `.${archiveName}.part`);
return httpsDownload(url, tmp)
@@ -270,11 +274,11 @@ const tryBootstrapBinary = async () => {
};
// If missing, attempt to bootstrap into place (helps when Bun blocks postinstall)
if (!existsSync(binaryPath) && !existsSync(legacyBinaryPath)) {
if (!existsSync(binaryPath) && !(legacyBinaryPath && existsSync(legacyBinaryPath))) {
const ok = await tryBootstrapBinary();
if (!ok) {
// retry legacy name in case archive provided coder-*
if (existsSync(legacyBinaryPath) && !existsSync(binaryPath)) {
if (legacyBinaryPath && existsSync(legacyBinaryPath) && !existsSync(binaryPath)) {
binaryPath = legacyBinaryPath;
}
}
@@ -288,7 +292,7 @@ try {
const v = existsSync(cached) ? validateBinary(cached) : { ok: false };
if (v.ok) {
binaryPath = cached;
} else if (!existsSync(binaryPath) && existsSync(legacyBinaryPath)) {
} else if (!existsSync(binaryPath) && legacyBinaryPath && existsSync(legacyBinaryPath)) {
binaryPath = legacyBinaryPath;
}
} catch {
@@ -349,7 +353,7 @@ if (!validation.ok) {
try {
const ua = process.env.npm_config_user_agent || "";
const isNpx = ua.includes("npx");
if (isNpx && process.stderr && process.stderr.isTTY) {
if (binaryBaseName === "code" && isNpx && process.stderr && process.stderr.isTTY) {
// Best-effort discovery of another 'code' on PATH for user clarity
let otherCode = "";
try {
@@ -381,7 +385,7 @@ const { spawn } = await import("child_process");
const child = spawn(binaryPath, process.argv.slice(2), {
stdio: "inherit",
env: { ...process.env, CODER_MANAGED_BY_NPM: "1", CODEX_MANAGED_BY_NPM: "1" },
env: { ...process.env, CODER_MANAGED_BY_NPM: "1", CODEX_MANAGED_BY_NPM: "1", CODE_NATIVE_COMMAND: commandLabel },
});
child.on("error", (err) => {
+9 -7
View File
@@ -48,10 +48,12 @@ function getCacheDir(version) {
return dir;
}
function getCachedBinaryPath(version, targetTriple, isWindows) {
const ext = isWindows ? '.exe' : '';
function getCachedBinaryPath(version, binaryName, isWindows) {
const cacheDir = getCacheDir(version);
return join(cacheDir, `code-${targetTriple}${ext}`);
if (isWindows && !binaryName.endsWith('.exe')) {
return join(cacheDir, `${binaryName}.exe`);
}
return join(cacheDir, binaryName);
}
function isWSL() {
@@ -313,14 +315,14 @@ async function main() {
const version = packageJson.version;
// Download only the primary binary; we'll create wrappers for legacy names.
const binaries = ['code'];
const binaries = ['code', 'code-mcp-server'];
console.log(`Installing @just-every/code v${version} for ${targetTriple}...`);
for (const binary of binaries) {
const binaryName = `${binary}-${targetTriple}${binaryExt}`;
const localPath = join(binDir, binaryName);
const cachePath = getCachedBinaryPath(version, targetTriple, isWindows);
const cachePath = getCachedBinaryPath(version, binaryName, isWindows);
// On Windows we avoid placing the executable inside node_modules to prevent
// EBUSY/EPERM during global upgrades when the binary is in use.
@@ -530,9 +532,9 @@ async function main() {
const mainBinary = `code-${targetTriple}${binaryExt}`;
const mainBinaryPath = join(binDir, mainBinary);
if (existsSync(mainBinaryPath) || existsSync(getCachedBinaryPath(version, targetTriple, platform() === 'win32'))) {
if (existsSync(mainBinaryPath) || existsSync(getCachedBinaryPath(version, mainBinary, platform() === 'win32'))) {
try {
const probePath = existsSync(mainBinaryPath) ? mainBinaryPath : getCachedBinaryPath(version, targetTriple, platform() === 'win32');
const probePath = existsSync(mainBinaryPath) ? mainBinaryPath : getCachedBinaryPath(version, mainBinary, platform() === 'win32');
const stats = statSync(probePath);
if (!stats.size) throw new Error('binary is empty (download likely failed)');
const valid = validateDownloadedBinary(probePath);
+3 -1
View File
@@ -4,7 +4,8 @@
"license": "Apache-2.0",
"description": "Lightweight coding agent that runs in your terminal - fork of OpenAI Codex",
"bin": {
"coder": "bin/coder.js"
"coder": "bin/coder.js",
"code-mcp-server": "bin/code-mcp-server.js"
},
"type": "module",
"engines": {
@@ -12,6 +13,7 @@
},
"files": [
"bin/coder.js",
"bin/code-mcp-server.js",
"bin/codex.js",
"postinstall.js",
"scripts/preinstall.js",
+7 -1
View File
@@ -22,7 +22,13 @@ Any existing MCP servers can remain in this table; the exiting overrides simply
## 2. Launch the MCP server under the new name
Build the workspace and run:
If you globally installed Code from npm (`npm install -g @just-every/code`), the postinstall step now places the prebuilt `code-mcp-server` alongside the other `code-*` binaries on your `PATH`. You can launch it directly:
```bash
code-mcp-server --stdio
```
Prefer building from source? The previous workflow still works:
```bash
cargo run -p code-mcp-server -- --stdio