watch-cli.ts 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. #!/usr/bin/env bun
  2. /**
  3. * Watches packages/opencode/src/ for changes and rebuilds the CLI binary,
  4. * then copies it into packages/kilo-vscode/bin/kilo.
  5. *
  6. * Used during development so the VS Code extension always has an up-to-date
  7. * CLI backend without manual rebuild steps.
  8. */
  9. import { watch, chmodSync } from "node:fs"
  10. import { join, relative } from "node:path"
  11. import { $ } from "bun"
  12. const kiloVscodeDir = join(import.meta.dir, "..")
  13. const packagesDir = join(kiloVscodeDir, "..")
  14. const opencodeDir = join(packagesDir, "opencode")
  15. const opencodeSrcDir = join(opencodeDir, "src")
  16. const targetBinDir = join(kiloVscodeDir, "bin")
  17. const targetBinPath = join(targetBinDir, "kilo")
  18. let building = false
  19. let pending = false
  20. let installed = false
  21. function log(msg: string) {
  22. console.log(`[watch-cli] ${msg}`)
  23. }
  24. function sourceBinaryPath(): string {
  25. return join(opencodeDir, "dist", `@kilocode/cli-${process.platform}-${process.arch}`, "bin", "kilo")
  26. }
  27. async function rebuild() {
  28. if (building) {
  29. pending = true
  30. return
  31. }
  32. building = true
  33. pending = false
  34. try {
  35. log("Rebuilding CLI binary...")
  36. const start = performance.now()
  37. const args = installed ? ["run", "build", "--single", "--skip-install"] : ["run", "build", "--single"]
  38. const result = await $`bun ${args}`.cwd(opencodeDir).nothrow().quiet()
  39. if (result.exitCode !== 0) {
  40. log(`Build failed (exit ${result.exitCode}):\n${result.stderr.toString()}`)
  41. return
  42. }
  43. installed = true
  44. const source = sourceBinaryPath()
  45. if (!(await Bun.file(source).exists())) {
  46. log(`ERROR: Build completed but no binary found at ${relative(packagesDir, source)}`)
  47. return
  48. }
  49. await $`mkdir -p ${targetBinDir}`
  50. await $`cp ${source} ${targetBinPath}`
  51. chmodSync(targetBinPath, 0o755)
  52. const elapsed = ((performance.now() - start) / 1000).toFixed(1)
  53. log(`Binary updated (${elapsed}s): ${relative(packagesDir, source)} -> bin/kilo`)
  54. } catch (err) {
  55. log(`ERROR: ${err instanceof Error ? err.message : String(err)}`)
  56. } finally {
  57. building = false
  58. if (pending) rebuild()
  59. }
  60. }
  61. // Initial build
  62. await rebuild()
  63. // Watch for changes
  64. log(`Watching ${relative(kiloVscodeDir, opencodeSrcDir)}/ for changes...`)
  65. const debounce = 500
  66. let timer: ReturnType<typeof setTimeout> | null = null
  67. watch(opencodeSrcDir, { recursive: true }, (_event, filename) => {
  68. if (!filename) return
  69. // Skip non-source files and build-generated files
  70. if (filename.endsWith(".test.ts") || filename.endsWith(".test.tsx")) return
  71. if (filename.includes("models-snapshot")) return
  72. if (timer) clearTimeout(timer)
  73. timer = setTimeout(() => {
  74. log(`Change detected: ${filename}`)
  75. rebuild()
  76. }, debounce)
  77. })
  78. log("CLI watcher running. Press Ctrl+C to stop.")