Merge pull request #13472 from Kilo-Org/fix-kilo-startup-black-screen

fix(cli): restore terminal startup
This commit is contained in:
Marius
2026-08-26 15:48:56 +02:00
committed by GitHub
4 changed files with 105 additions and 4 deletions
@@ -0,0 +1,5 @@
---
"@kilocode/cli": patch
---
Prevent the CLI from opening to a blank terminal on startup.
+53 -2
View File
@@ -4,6 +4,56 @@ import { spawn } from "#pty"
const TIMEOUT = 15_000
async function render() {
const proc = spawn(process.execPath, ["--pure"], {
name: "xterm-256color",
cwd: process.cwd(),
env: {
...process.env,
TERM: "xterm-256color",
KILO_TERMINAL: "1",
KILO_NO_DAEMON: "1",
KILO_DISABLE_AUTOUPDATE: "1",
KILO_DISABLE_MODELS_FETCH: "1",
KILO_DISABLE_PROJECT_CONFIG: "1",
KILO_DISABLE_DEFAULT_PLUGINS: "1",
KILO_DISABLE_TERMINAL_TITLE: "0",
KILO_CONFIG_CONTENT: "{}",
KILO_AUTH_CONTENT: "{}",
} as Record<string, string>,
cols: 100,
rows: 40,
})
const state = { output: "", exited: false }
const ready = Promise.withResolvers<void>()
const data = proc.onData((chunk) => {
state.output = (state.output + chunk).slice(-20_000)
if (state.output.includes("Ask anything...")) ready.resolve()
})
const exit = proc.onExit((event) => {
state.exited = true
ready.reject(new Error(`TUI exited before rendering (code ${event.exitCode}): ${JSON.stringify(state.output)}`))
})
const timeout = AbortSignal.timeout(TIMEOUT)
try {
await Promise.race([
ready.promise,
new Promise<never>((_, reject) =>
timeout.addEventListener(
"abort",
() => reject(new Error(`TUI produced no rendered frame within ${TIMEOUT}ms: ${JSON.stringify(state.output)}`)),
{ once: true },
),
),
])
} finally {
data.dispose()
exit.dispose()
if (!state.exited) await KiloPtyTermination.terminate(proc)
}
}
export async function smoke() {
const proc = spawn(Shell.preferred(), [], {
name: "xterm-256color",
@@ -17,8 +67,7 @@ export async function smoke() {
const exited = Promise.withResolvers<number>()
const data = proc.onData((chunk) => {
state.output += chunk
const lines = state.output.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").split(/\r?\n/)
if (lines.some((line) => line.trim() === "KILO_PTY_READY")) output.resolve()
if (/(?:^|[\r\n])KILO_PTY_READY(?:\r?\n|$)/.test(state.output)) output.resolve()
})
const exit = proc.onExit((event) => {
state.exited = true
@@ -67,6 +116,8 @@ export async function smoke() {
} finally {
if (!stopped) active.kill()
}
await render()
}
export * as PtySmoke from "./smoke"
+7 -2
View File
@@ -24,6 +24,7 @@ import { stageBubblewrap } from "./kilocode/bubblewrap"
import { LanceDBRuntime } from "../src/kilocode/lancedb"
import { KiloSandboxWorker } from "./kilocode/kilo-sandbox-worker"
import { KiloSandboxNetwork } from "./kilocode/kilo-sandbox-network"
import { KiloCliSmoke } from "./kilocode/cli-smoke"
// kilocode_change end
const singleFlag = process.argv.includes("--single")
@@ -312,8 +313,9 @@ for (const item of targets) {
// kilocode_change end
format: "esm",
minify: true,
// Bun 1.4 fixes the cross-chunk re-export codegen bug from Bun 1.3.14.
splitting: true, // kilocode_change
// kilocode_change start - keep the compiled OpenTUI/Solid graph in one chunk to avoid blank startup frames.
splitting: false,
// kilocode_change end
compile: {
autoloadBunfig: false,
autoloadDotenv: false,
@@ -394,6 +396,9 @@ for (const item of targets) {
console.log("Models snapshot smoke test passed")
await KiloSandboxWorker.smoke(binaryPath)
console.log("Kilo sandbox mutation worker smoke test passed")
console.log(`Running smoke test: ${binaryPath} --pure __pty-smoke`)
await KiloCliSmoke.pty(binaryPath)
console.log("Packaged TUI smoke test passed")
// kilocode_change end
// kilocode_change start
} catch (e) {
@@ -0,0 +1,40 @@
import fs from "fs"
import os from "os"
import path from "path"
export namespace KiloCliSmoke {
export async function pty(binary: string) {
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "kilo-pty-"))
const env = { ...process.env }
delete env.KILO_MODELS_PATH
delete env.KILO_MODELS_URL
delete env.KILO_CONFIG
delete env.KILO_CONFIG_DIR
try {
const proc = Bun.spawn([binary, "--pure", "__pty-smoke"], {
env: {
...env,
HOME: root,
XDG_DATA_HOME: path.join(root, "data"),
XDG_CACHE_HOME: path.join(root, "cache"),
XDG_CONFIG_HOME: path.join(root, "config"),
XDG_STATE_HOME: path.join(root, "state"),
KILO_PTY_SMOKE: "1",
KILO_NO_DAEMON: "1",
KILO_DISABLE_MODELS_FETCH: "1",
KILO_DISABLE_PROJECT_CONFIG: "1",
KILO_CONFIG_CONTENT: "{}",
KILO_AUTH_CONTENT: "{}",
},
stdout: "inherit",
stderr: "inherit",
windowsHide: true,
})
const code = await proc.exited
if (code !== 0) throw new Error(`Compiled TUI smoke test exited with code ${code}`)
} finally {
await fs.promises.rm(root, { recursive: true, force: true })
}
}
}