feat(vscode): auto-rebuild CLI binary on opencode source changes

- Add watch-cli.ts script that watches packages/opencode/src/ for changes,
  triggers a rebuild (bun run build --single --skip-install), and copies
  the new binary to packages/kilo-vscode/bin/kilo
- Add watch:cli npm script in kilo-vscode package.json
- Add 'VSCode - CLI Watch' task in .vscode/tasks.json
- Include CLI watch in the composite 'VSCode - Watch' task so it runs
  automatically when launching the extension via F5
This commit is contained in:
Mark IJbema
2026-02-17 10:53:31 +01:00
parent 985d6d4367
commit 7feae1ef06
3 changed files with 115 additions and 1 deletions
+20 -1
View File
@@ -5,13 +5,16 @@
"tasks": [
{
"label": "VSCode - Watch",
"dependsOn": ["VSCode - TSC", "VSCode - ESBuild"],
"dependsOn": ["VSCode - TSC", "VSCode - ESBuild", "VSCode - CLI Watch"],
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
},
"runOptions": {
"runOn": "folderOpen"
}
},
{
@@ -46,6 +49,22 @@
"cwd": "${workspaceFolder}/packages/kilo-vscode"
}
},
{
"label": "VSCode - CLI Watch",
"type": "shell",
"command": "bun",
"args": ["run", "watch:cli"],
"group": "build",
"isBackground": true,
"presentation": {
"group": "watch",
"reveal": "never"
},
"options": {
"cwd": "${workspaceFolder}/packages/kilo-vscode"
},
"problemMatcher": []
},
{
"label": "VSCode - Tests",
"type": "shell",
+1
View File
@@ -259,6 +259,7 @@
"watch": "bun run --parallel watch:esbuild watch:tsc",
"watch:esbuild": "bun run prepare:cli-binary && node esbuild.js --watch",
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
"watch:cli": "bun script/watch-cli.ts",
"package": "bun run prepare:cli-binary && bun run check-types && bun run lint && node esbuild.js --production",
"compile-tests": "tsc -p . --outDir out",
"watch-tests": "tsc -p . -w --outDir out",
+94
View File
@@ -0,0 +1,94 @@
#!/usr/bin/env bun
/**
* Watches packages/opencode/src/ for changes and rebuilds the CLI binary,
* then copies it into packages/kilo-vscode/bin/kilo.
*
* Used during development so the VS Code extension always has an up-to-date
* CLI backend without manual rebuild steps.
*/
import { watch, chmodSync } from "node:fs"
import { join, relative } from "node:path"
import { $ } from "bun"
const kiloVscodeDir = join(import.meta.dir, "..")
const packagesDir = join(kiloVscodeDir, "..")
const opencodeDir = join(packagesDir, "opencode")
const opencodeSrcDir = join(opencodeDir, "src")
const targetBinDir = join(kiloVscodeDir, "bin")
const targetBinPath = join(targetBinDir, "kilo")
let building = false
let pending = false
let installed = false
function log(msg: string) {
console.log(`[watch-cli] ${msg}`)
}
function sourceBinaryPath(): string {
return join(opencodeDir, "dist", `@kilocode/cli-${process.platform}-${process.arch}`, "bin", "kilo")
}
async function rebuild() {
if (building) {
pending = true
return
}
building = true
pending = false
try {
log("Rebuilding CLI binary...")
const start = performance.now()
const args = installed ? ["run", "build", "--single", "--skip-install"] : ["run", "build", "--single"]
const result = await $`bun ${args}`.cwd(opencodeDir).nothrow().quiet()
if (result.exitCode !== 0) {
log(`Build failed (exit ${result.exitCode}):\n${result.stderr.toString()}`)
return
}
installed = true
const source = sourceBinaryPath()
if (!await Bun.file(source).exists()) {
log(`ERROR: Build completed but no binary found at ${relative(packagesDir, source)}`)
return
}
await $`mkdir -p ${targetBinDir}`
await $`cp ${source} ${targetBinPath}`
chmodSync(targetBinPath, 0o755)
const elapsed = ((performance.now() - start) / 1000).toFixed(1)
log(`Binary updated (${elapsed}s): ${relative(packagesDir, source)} -> bin/kilo`)
} catch (err) {
log(`ERROR: ${err instanceof Error ? err.message : String(err)}`)
} finally {
building = false
if (pending) rebuild()
}
}
// Initial build
await rebuild()
// Watch for changes
log(`Watching ${relative(kiloVscodeDir, opencodeSrcDir)}/ for changes...`)
const debounce = 500
let timer: ReturnType<typeof setTimeout> | null = null
watch(opencodeSrcDir, { recursive: true }, (_event, filename) => {
if (!filename) return
// Skip non-source files and build-generated files
if (filename.endsWith(".test.ts") || filename.endsWith(".test.tsx")) return
if (filename.includes("models-snapshot")) return
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
log(`Change detected: ${filename}`)
rebuild()
}, debounce)
})
log("CLI watcher running. Press Ctrl+C to stop.")