mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
feat(cli): ship code-mcp-server with npm install
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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');
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user