Files
Kilo-Org_kilocode/packages/opencode/src/index.ts
T
Imanol Maiztegui 251ed67347 refactor(opencode): update internal import paths to use barrel re-exports
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.
2026-04-22 07:24:09 +02:00

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()
}