251ed67347
Migrate all deep import paths across source and test files to reference barrel index modules instead of direct file paths. This covers config, provider, storage, util, tool, lsp, project, and installation modules. Also moves provider promise helpers out of the namespace block, switches Clipboard to namespace import style, fixes a duplicate Filesystem import, removes the deleted paste-summary test, and corrects snapshot cache typing and indentation.
317 lines
10 KiB
TypeScript
317 lines
10 KiB
TypeScript
import yargs from "yargs"
|
|
import { hideBin } from "yargs/helpers"
|
|
import { RunCommand } from "./cli/cmd/run"
|
|
import { GenerateCommand } from "./cli/cmd/generate"
|
|
import { Log } from "./util"
|
|
// kilocode_change start
|
|
// import { LoginCommand, LogoutCommand, SwitchCommand, OrgsCommand } from "./cli/cmd/account"
|
|
// import { ConsoleCommand } from "./cli/cmd/account"
|
|
// kilocode_change end
|
|
import { ConsoleCommand } from "./cli/cmd/account"
|
|
import { ProvidersCommand } from "./cli/cmd/providers"
|
|
import { AgentCommand } from "./cli/cmd/agent"
|
|
import { UpgradeCommand } from "./cli/cmd/upgrade"
|
|
import { UninstallCommand } from "./cli/cmd/uninstall"
|
|
import { ModelsCommand } from "./cli/cmd/models"
|
|
import { UI } from "./cli/ui"
|
|
import { Installation } from "./installation"
|
|
import { InstallationVersion } from "./installation/version"
|
|
import { NamedError } from "@opencode-ai/shared/util/error"
|
|
import { FormatError } from "./cli/error"
|
|
import { ServeCommand } from "./cli/cmd/serve"
|
|
import { Filesystem } from "./util"
|
|
import { ConfigCommand as ConfigCLICommand } from "./cli/cmd/config" // kilocode_change
|
|
import { DebugCommand } from "./cli/cmd/debug"
|
|
import { StatsCommand } from "./cli/cmd/stats"
|
|
import { McpCommand } from "./cli/cmd/mcp"
|
|
// import { GithubCommand } from "./cli/cmd/github" // kilocode_change
|
|
import { ExportCommand } from "./cli/cmd/export"
|
|
import { ImportCommand } from "./cli/cmd/import"
|
|
import { AttachCommand } from "./cli/cmd/tui/attach"
|
|
import { TuiThreadCommand } from "./cli/cmd/tui/thread"
|
|
import { AcpCommand } from "./cli/cmd/acp"
|
|
import { EOL } from "os"
|
|
// import { WebCommand } from "./cli/cmd/web" // kilocode_change (Disabled unsupported opencode web UI)
|
|
import { PrCommand } from "./cli/cmd/pr"
|
|
import { SessionCommand } from "./cli/cmd/session"
|
|
import { RemoteCommand } from "./cli/cmd/remote" // kilocode_change
|
|
// kilocode_change start - Import telemetry, instance disposal, and legacy migration
|
|
import { Telemetry } from "@kilocode/kilo-telemetry"
|
|
import { Instance } from "./project/instance" // kilocode_change
|
|
import { migrateLegacyKiloAuth, ENV_FEATURE, ENV_VERSION } from "@kilocode/kilo-gateway"
|
|
|
|
// kilocode_change - set feature for tracking. 'serve' is spawned by other services
|
|
// (extension, cloud) which set their own KILOCODE_FEATURE env var. Direct CLI use
|
|
// (any command other than 'serve') is tagged as 'cli'. If 'serve' is spawned without
|
|
// the env var, it gets 'unknown' so the misconfiguration is visible in data.
|
|
if (!process.env[ENV_FEATURE]) {
|
|
const isServe = process.argv.includes("serve")
|
|
process.env[ENV_FEATURE] = isServe ? "unknown" : "cli"
|
|
}
|
|
|
|
// kilocode_change - set version so kilo-gateway can include it in the editor name header
|
|
if (!process.env[ENV_VERSION]) {
|
|
process.env[ENV_VERSION] = InstallationVersion
|
|
}
|
|
import { Config } from "./config"
|
|
import { Auth } from "./auth"
|
|
// kilocode_change end
|
|
import { DbCommand } from "./cli/cmd/db"
|
|
import path from "path"
|
|
import { Global } from "./global"
|
|
import { createHelpCommand } from "./kilocode/help-command" // kilocode_change
|
|
import { JsonMigration } from "./storage"
|
|
import { Database } from "./storage"
|
|
import { errorMessage } from "./util/error"
|
|
import { PluginCommand } from "./cli/cmd/plug"
|
|
import { Heap } from "./cli/heap"
|
|
import { drizzle } from "drizzle-orm/bun-sqlite"
|
|
|
|
process.on("unhandledRejection", (e) => {
|
|
Log.Default.error("rejection", {
|
|
e: errorMessage(e),
|
|
})
|
|
})
|
|
|
|
process.on("uncaughtException", (e) => {
|
|
Log.Default.error("exception", {
|
|
e: errorMessage(e),
|
|
})
|
|
})
|
|
|
|
const args = hideBin(process.argv)
|
|
|
|
function show(out: string) {
|
|
const text = out.trimStart()
|
|
if (!text.startsWith("opencode ")) {
|
|
process.stderr.write(UI.logo() + EOL + EOL)
|
|
process.stderr.write(text)
|
|
return
|
|
}
|
|
process.stderr.write(out)
|
|
}
|
|
|
|
let cli = yargs(args) // kilocode_change
|
|
.parserConfiguration({ "populate--": true })
|
|
.scriptName("kilo") // kilocode_change
|
|
.wrap(100)
|
|
.help("help", "show help")
|
|
.alias("help", "h")
|
|
.version("version", "show version number", InstallationVersion)
|
|
.alias("version", "v")
|
|
.option("print-logs", {
|
|
describe: "print logs to stderr",
|
|
type: "boolean",
|
|
})
|
|
.option("log-level", {
|
|
describe: "log level",
|
|
type: "string",
|
|
choices: ["DEBUG", "INFO", "WARN", "ERROR"],
|
|
})
|
|
.option("pure", {
|
|
describe: "run without external plugins",
|
|
type: "boolean",
|
|
})
|
|
.middleware(async (opts) => {
|
|
if (opts.pure) {
|
|
process.env.KILO_PURE = "1"
|
|
}
|
|
|
|
await Log.init({
|
|
print: process.argv.includes("--print-logs"),
|
|
dev: Installation.isLocal(),
|
|
level: (() => {
|
|
if (opts.logLevel) return opts.logLevel as Log.Level
|
|
if (Installation.isLocal()) return "DEBUG"
|
|
return "INFO"
|
|
})(),
|
|
})
|
|
|
|
Heap.start()
|
|
|
|
process.env.AGENT = "1"
|
|
process.env.OPENCODE = "1"
|
|
process.env.KILO_PID = String(process.pid)
|
|
|
|
Log.Default.info("opencode", {
|
|
version: InstallationVersion,
|
|
args: process.argv.slice(2),
|
|
})
|
|
|
|
// kilocode_change start - Initialize telemetry
|
|
const globalCfg = await Config.getGlobal()
|
|
await Telemetry.init({
|
|
dataPath: Global.Path.data,
|
|
version: InstallationVersion,
|
|
enabled: globalCfg.experimental?.openTelemetry !== false,
|
|
})
|
|
|
|
// Migrate legacy Kilo CLI auth if needed
|
|
await migrateLegacyKiloAuth(
|
|
async () => (await Auth.get("kilo")) !== undefined,
|
|
async (auth) => Auth.set("kilo", auth),
|
|
)
|
|
|
|
const kiloAuth = await Auth.get("kilo")
|
|
if (kiloAuth) {
|
|
const token = kiloAuth.type === "oauth" ? kiloAuth.access : kiloAuth.key
|
|
const accountId = kiloAuth.type === "oauth" ? kiloAuth.accountId : undefined
|
|
await Telemetry.updateIdentity(token, accountId)
|
|
}
|
|
|
|
Telemetry.trackCliStart()
|
|
// kilocode_change end
|
|
|
|
const marker = path.join(Global.Path.data, "kilo.db")
|
|
if (!(await Filesystem.exists(marker))) {
|
|
const tty = process.stderr.isTTY
|
|
process.stderr.write("Performing one time database migration, may take a few minutes..." + EOL)
|
|
const width = 36
|
|
const orange = "\x1b[38;5;214m"
|
|
const muted = "\x1b[0;2m"
|
|
const reset = "\x1b[0m"
|
|
let last = -1
|
|
if (tty) process.stderr.write("\x1b[?25l")
|
|
try {
|
|
await JsonMigration.run(drizzle({ client: Database.Client().$client }), {
|
|
progress: (event) => {
|
|
const percent = Math.floor((event.current / event.total) * 100)
|
|
if (percent === last && event.current !== event.total) return
|
|
last = percent
|
|
if (tty) {
|
|
const fill = Math.round((percent / 100) * width)
|
|
const bar = `${"■".repeat(fill)}${"・".repeat(width - fill)}`
|
|
process.stderr.write(
|
|
`\r${orange}${bar} ${percent.toString().padStart(3)}%${reset} ${muted}${event.label.padEnd(12)} ${event.current}/${event.total}${reset}`,
|
|
)
|
|
if (event.current === event.total) process.stderr.write("\n")
|
|
} else {
|
|
process.stderr.write(`sqlite-migration:${percent}${EOL}`)
|
|
}
|
|
},
|
|
})
|
|
} finally {
|
|
if (tty) process.stderr.write("\x1b[?25h")
|
|
else {
|
|
process.stderr.write(`sqlite-migration:done${EOL}`)
|
|
}
|
|
}
|
|
process.stderr.write("Database migration complete." + EOL)
|
|
}
|
|
})
|
|
.usage("")
|
|
.completion("completion", "generate shell completion script")
|
|
.command(AcpCommand)
|
|
.command(McpCommand)
|
|
.command(TuiThreadCommand)
|
|
.command(AttachCommand)
|
|
.command(RunCommand)
|
|
.command(GenerateCommand)
|
|
.command(DebugCommand)
|
|
// kilocode_change start
|
|
// .command(LoginCommand)
|
|
// .command(LogoutCommand)
|
|
// .command(SwitchCommand)
|
|
// .command(OrgsCommand)
|
|
// .command(ConsoleCommand)
|
|
// kilocode_change end
|
|
.command(ProvidersCommand)
|
|
.command(AgentCommand)
|
|
.command(UpgradeCommand)
|
|
.command(UninstallCommand)
|
|
.command(ServeCommand)
|
|
// .command(WebCommand) // kilocode_change (Disabled unsupported opencode web UI)
|
|
.command(ModelsCommand)
|
|
.command(StatsCommand)
|
|
.command(ExportCommand)
|
|
.command(ImportCommand)
|
|
// .command(GithubCommand) // kilocode_change (Disabled until backend is ready)
|
|
.command(PrCommand)
|
|
.command(SessionCommand)
|
|
.command(RemoteCommand) // kilocode_change
|
|
.command(ConfigCLICommand) // kilocode_change
|
|
.command(PluginCommand)
|
|
.command(DbCommand)
|
|
|
|
// kilocode_change start - registered after initial chain to avoid self-referential type error
|
|
cli = cli.command(createHelpCommand(() => cli))
|
|
|
|
cli = cli
|
|
// kilocode_change end
|
|
.fail((msg, err) => {
|
|
if (
|
|
msg?.startsWith("Unknown argument") ||
|
|
msg?.startsWith("Not enough non-option arguments") ||
|
|
msg?.startsWith("Invalid values:")
|
|
) {
|
|
if (err) throw err
|
|
cli.showHelp(show)
|
|
}
|
|
if (err) throw err
|
|
process.exit(1)
|
|
})
|
|
.strict()
|
|
|
|
try {
|
|
if (args.includes("-h") || args.includes("--help")) {
|
|
await cli.parse(args, (err: Error | undefined, _argv: unknown, out: string) => {
|
|
if (err) throw err
|
|
if (!out) return
|
|
show(out)
|
|
})
|
|
} else {
|
|
await cli.parse()
|
|
}
|
|
} catch (e) {
|
|
let data: Record<string, any> = {}
|
|
if (e instanceof NamedError) {
|
|
const obj = e.toObject()
|
|
Object.assign(data, {
|
|
...obj.data,
|
|
})
|
|
}
|
|
|
|
if (e instanceof Error) {
|
|
Object.assign(data, {
|
|
name: e.name,
|
|
message: e.message,
|
|
cause: e.cause?.toString(),
|
|
stack: e.stack,
|
|
})
|
|
}
|
|
|
|
if (e instanceof ResolveMessage) {
|
|
Object.assign(data, {
|
|
name: e.name,
|
|
message: e.message,
|
|
code: e.code,
|
|
specifier: e.specifier,
|
|
referrer: e.referrer,
|
|
position: e.position,
|
|
importKind: e.importKind,
|
|
})
|
|
}
|
|
Log.Default.error("fatal", data)
|
|
const formatted = FormatError(e)
|
|
if (formatted) UI.error(formatted)
|
|
if (formatted === undefined) {
|
|
UI.error("Unexpected error, check log file at " + Log.file() + " for more details" + EOL)
|
|
process.stderr.write(errorMessage(e) + EOL)
|
|
}
|
|
process.exitCode = 1
|
|
} finally {
|
|
// kilocode_change start - Track CLI exit and shutdown telemetry
|
|
const exitCode = typeof process.exitCode === "number" ? process.exitCode : undefined
|
|
Telemetry.trackCliExit(exitCode)
|
|
await Telemetry.shutdown()
|
|
// kilocode_change end
|
|
|
|
await Instance.disposeAll() // kilocode_change - safety net disposal (no-op if already disposed)
|
|
|
|
// Some subprocesses don't react properly to SIGTERM and similar signals.
|
|
// Most notably, some docker-container-based MCP servers don't handle such signals unless
|
|
// run using `docker run --init`.
|
|
// Explicitly exit to avoid any hanging subprocesses.
|
|
process.exit()
|
|
}
|