diff --git a/npm/README.md b/npm/README.md index ef8af7a..f8fabde 100644 --- a/npm/README.md +++ b/npm/README.md @@ -18,10 +18,9 @@ npx clawdchan@latest setup Installation pulls the prebuilt binary matching your platform from GitHub Releases and drops it into `~/.clawdchan/bin/` — a stable location that survives `npm uninstall`, matches the shell installer, and keeps the -launchd/systemd service pointing at a path that doesn't move across -npm upgrades. Supported: macOS (x64, arm64) and Linux (x64, arm64). -Windows users: grab the zip from the -[releases page](https://github.com/agents-first/clawdchan/releases). +launchd/systemd/Scheduled Task service pointing at a path that doesn't move +across npm upgrades. Supported: macOS (x64, arm64), Linux (x64, arm64), and +Windows (x64). Environment variables: diff --git a/npm/bin/_run.js b/npm/bin/_run.js index b4d0892..ca3373a 100644 --- a/npm/bin/_run.js +++ b/npm/bin/_run.js @@ -10,10 +10,11 @@ const VENDOR = path.join(__dirname, "..", "vendor"); module.exports = function run(name) { const override = process.env.CLAWDCHAN_INSTALL_DIR; + const exe = process.platform === "win32" ? `${name}.exe` : name; const candidates = [ - override && path.join(expandHome(override), name), - path.join(STABLE, name), - path.join(VENDOR, name), + override && path.join(expandHome(override), exe), + path.join(STABLE, exe), + path.join(VENDOR, exe), ].filter(Boolean); const bin = candidates.find((p) => fs.existsSync(p)); diff --git a/npm/package.json b/npm/package.json index 5c39eb5..645572e 100644 --- a/npm/package.json +++ b/npm/package.json @@ -17,7 +17,8 @@ ], "os": [ "darwin", - "linux" + "linux", + "win32" ], "cpu": [ "x64", diff --git a/npm/scripts/install.js b/npm/scripts/install.js index 01ceda4..ec86fb8 100644 --- a/npm/scripts/install.js +++ b/npm/scripts/install.js @@ -35,9 +35,10 @@ const REPO = "agents-first/clawdchan"; const PKG = require("../package.json"); const VERSION = process.env.CLAWDCHAN_VERSION || `v${PKG.version}`; const VENDOR = path.join(__dirname, "..", "vendor"); -const BINS = ["clawdchan", "clawdchan-mcp", "clawdchan-relay"]; +const BIN_EXT = process.platform === "win32" ? ".exe" : ""; +const BINS = ["clawdchan", "clawdchan-mcp", "clawdchan-relay"].map((b) => `${b}${BIN_EXT}`); -const OS_MAP = { darwin: "macOS", linux: "Linux" }; +const OS_MAP = { darwin: "macOS", linux: "Linux", win32: "Windows" }; const ARCH_MAP = { x64: "x86_64", arm64: "arm64" }; const osName = OS_MAP[process.platform]; @@ -48,6 +49,11 @@ if (!osName || !archName) { process.exit(0); } +if (process.platform === "win32" && process.arch !== "x64") { + log(`unsupported platform ${process.platform}/${process.arch} — install from source: https://github.com/${REPO}`); + process.exit(0); +} + main().catch((err) => { log(`install failed: ${err.message}`); log(`fetch manually from https://github.com/${REPO}/releases and place binaries in ${VENDOR}`); @@ -57,7 +63,8 @@ main().catch((err) => { async function main() { const tag = await resolveTag(VERSION); const version = tag.replace(/^v/, ""); - const archive = `clawdchan_${version}_${osName}_${archName}.tar.gz`; + const archiveExt = process.platform === "win32" ? "zip" : "tar.gz"; + const archive = `clawdchan_${version}_${osName}_${archName}.${archiveExt}`; const base = `https://github.com/${REPO}/releases/download/${tag}`; log(`downloading clawdchan ${tag} (${osName}_${archName})`); @@ -78,14 +85,14 @@ async function main() { const installDir = chooseInstallDir(); fs.mkdirSync(installDir, { recursive: true }); - execFileSync("tar", ["-xzf", archivePath, "-C", tmp], { stdio: "ignore" }); + extractArchive(archivePath, tmp); for (const b of BINS) { const src = path.join(tmp, b); if (!fs.existsSync(src)) throw new Error(`missing ${b} in archive`); const dst = path.join(installDir, b); fs.renameSync(src, dst); - fs.chmodSync(dst, 0o755); + if (process.platform !== "win32") fs.chmodSync(dst, 0o755); } log(`installed binaries to ${installDir}`); @@ -97,6 +104,26 @@ async function main() { } } +function extractArchive(archivePath, dest) { + if (process.platform === "win32") { + execFileSync( + "powershell", + [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "& { param($zip, $dest) Expand-Archive -LiteralPath $zip -DestinationPath $dest -Force }", + archivePath, + dest, + ], + { stdio: "ignore" } + ); + return; + } + execFileSync("tar", ["-xzf", archivePath, "-C", dest], { stdio: "ignore" }); +} + function chooseInstallDir() { const override = process.env.CLAWDCHAN_INSTALL_DIR; if (override) return expandHome(override);