chore(cli): bundle for node, prep for npm publish
Some checks failed
CI / Tests / 🧪 Test (push) Has been cancelled
Some checks failed
CI / Tests / 🧪 Test (push) Has been cancelled
Makes @claudemesh/cli installable globally via npm without requiring bun on user machines. (Bun stays the dev runtime; bundled output is node-compatible.) - bun build --target=node --outfile dist/index.js produces a 2.69MB standalone bundle with node-shebang banner - package.json: add description/keywords/author/license/homepage/ repository, set bin to ./dist/index.js, files=[dist, README, LICENSE], publishConfig.access=public, engines.node >=20 - prepublishOnly auto-runs the build - pin zod from catalog: to 4.1.13 (npm rejects catalog: refs) - swap Bun.spawnSync → node:child_process.spawnSync in install.ts (the only Bun-global usage in the package) - strip shebang from src/index.ts (banner supplies it post-bundle) install command now runs in two modes: - BUNDLED (npm i -g): detects dist/index.js path, writes MCP entry with command "claudemesh" (relies on the global bin shim on PATH) - SOURCE (bun src/index.ts, dev): preflights bun, writes MCP entry with command "bun <absolute-path> mcp" verified end-to-end: - node dist/index.js --help prints usage ✓ - node dist/index.js install writes correct ~/.claude.json ✓ - node dist/index.js mcp | tools/list returns all 5 tools ✓ - bun src/index.ts install (dev mode) still works ✓ NOT PUBLISHED YET — @claudemesh/cli is owned by an unrelated project on npm. Awaiting user decision on alternative name (claudemesh-cli, @alezmad/claudemesh-cli, or new org scope). Bundle is name-agnostic and will reuse regardless. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,26 +1,55 @@
|
|||||||
{
|
{
|
||||||
"name": "@claudemesh/cli",
|
"name": "@claudemesh/cli",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"description": "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
|
||||||
|
"keywords": [
|
||||||
|
"claude-code",
|
||||||
|
"mcp",
|
||||||
|
"model-context-protocol",
|
||||||
|
"claudemesh",
|
||||||
|
"peer-messaging",
|
||||||
|
"multi-agent"
|
||||||
|
],
|
||||||
|
"author": "Alejandro Gutiérrez",
|
||||||
|
"license": "MIT",
|
||||||
|
"homepage": "https://claudemesh.com",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/alezmad/claudemesh.git",
|
||||||
|
"directory": "apps/cli"
|
||||||
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
"claudemesh": "./src/index.ts"
|
"claudemesh": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist",
|
||||||
|
"README.md",
|
||||||
|
"LICENSE"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"build": "bun build src/index.ts --target=node --outfile dist/index.js --banner \"#!/usr/bin/env node\" && chmod +x dist/index.js",
|
||||||
"clean": "git clean -xdf .cache .turbo dist node_modules",
|
"clean": "git clean -xdf .cache .turbo dist node_modules",
|
||||||
"dev": "bun --hot src/index.ts",
|
"dev": "bun --hot src/index.ts",
|
||||||
"start": "bun src/index.ts",
|
"start": "bun src/index.ts",
|
||||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||||
"lint": "eslint",
|
"lint": "eslint",
|
||||||
|
"prepublishOnly": "bun run build",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"prettier": "@turbostarter/prettier-config",
|
"prettier": "@turbostarter/prettier-config",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "1.27.1",
|
"@modelcontextprotocol/sdk": "1.27.1",
|
||||||
"libsodium-wrappers": "0.7.15",
|
"libsodium-wrappers": "0.7.15",
|
||||||
"ws": "8.20.0",
|
"ws": "8.20.0",
|
||||||
"zod": "catalog:"
|
"zod": "4.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@turbostarter/eslint-config": "workspace:*",
|
"@turbostarter/eslint-config": "workspace:*",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
import { homedir, platform } from "node:os";
|
import { homedir, platform } from "node:os";
|
||||||
import { dirname, join, resolve } from "node:path";
|
import { dirname, join, resolve } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { spawnSync } from "node:child_process";
|
||||||
|
|
||||||
const MCP_NAME = "claudemesh";
|
const MCP_NAME = "claudemesh";
|
||||||
const CLAUDE_CONFIG = join(homedir(), ".claude.json");
|
const CLAUDE_CONFIG = join(homedir(), ".claude.json");
|
||||||
@@ -64,22 +65,44 @@ function writeClaudeConfig(obj: Record<string, unknown>): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Check `bun` is on PATH — OS-agnostic. */
|
/** Check `bun` is on PATH — OS-agnostic, node:child_process. */
|
||||||
function bunAvailable(): boolean {
|
function bunAvailable(): boolean {
|
||||||
const which =
|
const res =
|
||||||
platform() === "win32"
|
platform() === "win32"
|
||||||
? Bun.spawnSync(["where", "bun"])
|
? spawnSync("where", ["bun"])
|
||||||
: Bun.spawnSync(["sh", "-c", "command -v bun"]);
|
: spawnSync("sh", ["-c", "command -v bun"]);
|
||||||
return which.exitCode === 0;
|
return res.status === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Absolute path to this CLI's entry file. */
|
/** Absolute path to this CLI's entry file. */
|
||||||
function resolveEntry(): string {
|
function resolveEntry(): string {
|
||||||
const here = fileURLToPath(import.meta.url);
|
const here = fileURLToPath(import.meta.url);
|
||||||
|
// When bundled (dist/index.js), this file IS the entry → return self.
|
||||||
|
// When running from source (src/index.ts via bun), walk up to the
|
||||||
|
// dir + resolve index.ts.
|
||||||
|
if (here.endsWith("/dist/index.js") || here.endsWith("\\dist\\index.js")) {
|
||||||
|
return here;
|
||||||
|
}
|
||||||
return resolve(dirname(here), "..", "index.ts");
|
return resolve(dirname(here), "..", "index.ts");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the MCP server entry for Claude Code's config.
|
||||||
|
*
|
||||||
|
* Two modes:
|
||||||
|
* - Installed globally (npm i -g @claudemesh/cli): use `claudemesh`
|
||||||
|
* as the command, relies on it being on PATH.
|
||||||
|
* - Local dev (bun apps/cli/src/index.ts): use `bun <absolute-path>`.
|
||||||
|
*/
|
||||||
function buildMcpEntry(entryPath: string): McpEntry {
|
function buildMcpEntry(entryPath: string): McpEntry {
|
||||||
|
const isBundled = entryPath.endsWith("/dist/index.js") ||
|
||||||
|
entryPath.endsWith("\\dist\\index.js");
|
||||||
|
if (isBundled) {
|
||||||
|
return {
|
||||||
|
command: "claudemesh",
|
||||||
|
args: ["mcp"],
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
command: "bun",
|
command: "bun",
|
||||||
args: [entryPath, "mcp"],
|
args: [entryPath, "mcp"],
|
||||||
@@ -97,14 +120,18 @@ export function runInstall(): void {
|
|||||||
console.log("claudemesh install");
|
console.log("claudemesh install");
|
||||||
console.log("------------------");
|
console.log("------------------");
|
||||||
|
|
||||||
if (!bunAvailable()) {
|
const entry = resolveEntry();
|
||||||
|
const isBundled = entry.endsWith("/dist/index.js") ||
|
||||||
|
entry.endsWith("\\dist\\index.js");
|
||||||
|
|
||||||
|
// Dev mode (running from src/) requires bun on PATH; bundled mode
|
||||||
|
// (npm install -g) just uses node + the claudemesh bin shim.
|
||||||
|
if (!isBundled && !bunAvailable()) {
|
||||||
console.error(
|
console.error(
|
||||||
"✗ `bun` is not on PATH. Install Bun first: https://bun.com",
|
"✗ `bun` is not on PATH. Install Bun first: https://bun.com",
|
||||||
);
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const entry = resolveEntry();
|
|
||||||
if (!existsSync(entry)) {
|
if (!existsSync(entry)) {
|
||||||
console.error(`✗ MCP entry not found at ${entry}`);
|
console.error(`✗ MCP entry not found at ${entry}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
@@ -142,7 +169,9 @@ export function runInstall(): void {
|
|||||||
|
|
||||||
console.log(`✓ MCP server "${MCP_NAME}" ${action}`);
|
console.log(`✓ MCP server "${MCP_NAME}" ${action}`);
|
||||||
console.log(` config: ${CLAUDE_CONFIG}`);
|
console.log(` config: ${CLAUDE_CONFIG}`);
|
||||||
console.log(` command: bun ${entry} mcp`);
|
console.log(
|
||||||
|
` command: ${desired.command}${desired.args?.length ? " " + desired.args.join(" ") : ""}`,
|
||||||
|
);
|
||||||
console.log("");
|
console.log("");
|
||||||
console.log("Restart Claude Code to load the MCP server.");
|
console.log("Restart Claude Code to load the MCP server.");
|
||||||
console.log("Then join a mesh:");
|
console.log("Then join a mesh:");
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env bun
|
|
||||||
/**
|
/**
|
||||||
* @claudemesh/cli entry point.
|
* @claudemesh/cli entry point.
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user