import { existsSync, mkdirSync, readdirSync, statSync, copyFileSync, chmodSync } from "node:fs" import path from "node:path" import { fileURLToPath } from "node:url" import { execSync } from "node:child_process" /** * Ensures the VS Code extension has a CLI binary at `packages/kilo-vscode/bin/kilo`. * * Strategy: * 1) If `bin/kilo` already exists -> ok. * 2) Else try to locate a prebuilt binary produced by `packages/opencode` build. * 3) Else try to build it via `bun run build --single` in `packages/opencode`. * 4) Copy the resulting binary into `packages/kilo-vscode/bin/kilo` and chmod +x. * * This script is intended to be run from `packages/kilo-vscode` as part of build/package. */ const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) const kiloVscodeDir = path.resolve(__dirname, "..") const repoRootDir = path.resolve(kiloVscodeDir, "..") const opencodeDir = path.resolve(repoRootDir, "opencode") const targetBinDir = path.resolve(kiloVscodeDir, "bin") const targetBinPath = path.resolve(targetBinDir, "kilo") function log(msg) { // Keep logs machine-grep friendly in CI console.log(`[prepare-cli-binary] ${msg}`) } function findKiloBinaryInOpencodeDist() { const distDir = path.resolve(opencodeDir, "dist") if (!existsSync(distDir)) return null // Expected: packages/opencode/dist/@kilocode/cli-/bin/kilo // But keep it flexible: find any dist/**/bin/kilo /** @type {string[]} */ const queue = [distDir] while (queue.length) { const dir = queue.pop() if (!dir) continue let entries try { entries = readdirSync(dir, { withFileTypes: true }) } catch { continue } for (const e of entries) { const p = path.join(dir, e.name) if (e.isDirectory()) { queue.push(p) continue } if (e.isFile() && e.name === "kilo" && path.basename(path.dirname(p)) === "bin") { return p } } } return null } function ensureBuiltBinary() { const found = findKiloBinaryInOpencodeDist() if (found) return found log( `No prebuilt binary found under ${path.relative(kiloVscodeDir, path.resolve(opencodeDir, "dist"))} - attempting build via bun.`, ) try { execSync("bun --version", { stdio: "ignore" }) } catch { throw new Error( `Bun is required to build the CLI binary, but was not found on PATH. ` + `Install bun, or build the CLI separately in ${opencodeDir} and re-run.`, ) } // Ensure dependencies are installed before building. log("Installing dependencies in opencode package...") execSync("bun install --frozen-lockfile", { cwd: opencodeDir, stdio: "inherit", env: process.env, }) // Build using the opencode package script. execSync("bun run build --single", { cwd: opencodeDir, stdio: "inherit", env: process.env, }) const built = findKiloBinaryInOpencodeDist() if (!built) { throw new Error( `CLI build completed but no binary was found in ${path.resolve(opencodeDir, "dist")} (expected dist/**/bin/kilo).`, ) } return built } function main() { if (existsSync(targetBinPath)) { const st = statSync(targetBinPath) log( `CLI binary already present at ${path.relative(kiloVscodeDir, targetBinPath)} (${Math.round(st.size / 1024 / 1024)}MB).`, ) return } if (!existsSync(opencodeDir)) { throw new Error(`Expected opencode package at ${opencodeDir}, but it does not exist.`) } const sourceBinPath = ensureBuiltBinary() mkdirSync(targetBinDir, { recursive: true }) copyFileSync(sourceBinPath, targetBinPath) chmodSync(targetBinPath, 0o755) log( `Copied CLI binary from ${path.relative(repoRootDir, sourceBinPath)} -> ${path.relative(kiloVscodeDir, targetBinPath)}`, ) } try { main() } catch (err) { console.error(`[prepare-cli-binary] ERROR: ${err instanceof Error ? err.message : String(err)}`) process.exit(1) }