Compare commits

...

1 Commits

Author SHA1 Message Date
Simon Klee
75666b271b opencode: lazy-load top-level CLI commands
The CLI imports every top-level command before argument parsing has
decided which handler will run. This makes simple invocations pay for
the full command graph up front and slows down the default startup path.

Parse the root argv first and load only the command module that matches
the selected top-level command. Keep falling back to the default TUI
path for non-command positionals, and preserve root help, version and
completion handling
2026-04-12 11:25:35 +02:00

View File

@@ -1,40 +1,17 @@
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/log"
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 { NamedError } from "@opencode-ai/util/error"
import { FormatError } from "./cli/error"
import { ServeCommand } from "./cli/cmd/serve"
import { Filesystem } from "./util/filesystem"
import { DebugCommand } from "./cli/cmd/debug"
import { StatsCommand } from "./cli/cmd/stats"
import { McpCommand } from "./cli/cmd/mcp"
import { GithubCommand } from "./cli/cmd/github"
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"
import { PrCommand } from "./cli/cmd/pr"
import { SessionCommand } from "./cli/cmd/session"
import { DbCommand } from "./cli/cmd/db"
import path from "path"
import { Global } from "./global"
import { JsonMigration } from "./storage/json-migration"
import { Database } from "./storage/db"
import { errorMessage } from "./util/error"
import { PluginCommand } from "./cli/cmd/plug"
import { Heap } from "./cli/heap"
import { drizzle } from "drizzle-orm/bun-sqlite"
@@ -52,6 +29,156 @@ process.on("uncaughtException", (e) => {
const args = hideBin(process.argv)
type Mode =
| "all"
| "none"
| "tui"
| "attach"
| "run"
| "acp"
| "mcp"
| "generate"
| "debug"
| "console"
| "providers"
| "agent"
| "upgrade"
| "uninstall"
| "serve"
| "web"
| "models"
| "stats"
| "export"
| "import"
| "github"
| "pr"
| "session"
| "plugin"
| "db"
const map = new Map<string, Mode>([
["attach", "attach"],
["run", "run"],
["acp", "acp"],
["mcp", "mcp"],
["generate", "generate"],
["debug", "debug"],
["console", "console"],
["providers", "providers"],
["auth", "providers"],
["agent", "agent"],
["upgrade", "upgrade"],
["uninstall", "uninstall"],
["serve", "serve"],
["web", "web"],
["models", "models"],
["stats", "stats"],
["export", "export"],
["import", "import"],
["github", "github"],
["pr", "pr"],
["session", "session"],
["plugin", "plugin"],
["plug", "plugin"],
["db", "db"],
])
function flag(arg: string, name: string) {
return arg === `--${name}` || arg === `--no-${name}` || arg.startsWith(`--${name}=`)
}
function value(arg: string, name: string) {
return arg === `--${name}` || arg.startsWith(`--${name}=`)
}
// Match the root parser closely enough to decide which top-level module to load.
function pick(argv: string[]): Mode {
for (let i = 0; i < argv.length; i++) {
const arg = argv[i]
if (!arg) continue
if (arg === "--") return "tui"
if (arg === "completion") return "all"
if (arg === "--help" || arg === "-h") return "all"
if (arg === "--version" || arg === "-v") return "none"
if (flag(arg, "print-logs") || flag(arg, "pure")) continue
if (value(arg, "log-level")) {
if (arg === "--log-level") i += 1
continue
}
if (arg.startsWith("-") && !arg.startsWith("--")) {
if (arg.includes("h")) return "all"
if (arg.includes("v")) return "none"
return "tui"
}
if (arg.startsWith("-")) return "tui"
return map.get(arg) ?? "tui"
}
return "tui"
}
const mode = pick(args)
const all = mode === "all"
const none = mode === "none"
function load<T>(on: boolean, get: () => Promise<T>): Promise<T | undefined> {
if (!on) {
return Promise.resolve(undefined)
}
return get()
}
const [
TuiThreadCommand,
AttachCommand,
RunCommand,
AcpCommand,
McpCommand,
GenerateCommand,
DebugCommand,
ConsoleCommand,
ProvidersCommand,
AgentCommand,
UpgradeCommand,
UninstallCommand,
ServeCommand,
WebCommand,
ModelsCommand,
StatsCommand,
ExportCommand,
ImportCommand,
GithubCommand,
PrCommand,
SessionCommand,
PluginCommand,
DbCommand,
] = await Promise.all([
load(!none && (all || mode === "tui"), () => import("./cli/cmd/tui/thread").then((x) => x.TuiThreadCommand)),
load(!none && (all || mode === "attach"), () => import("./cli/cmd/tui/attach").then((x) => x.AttachCommand)),
load(!none && (all || mode === "run"), () => import("./cli/cmd/run").then((x) => x.RunCommand)),
load(!none && (all || mode === "acp"), () => import("./cli/cmd/acp").then((x) => x.AcpCommand)),
load(!none && (all || mode === "mcp"), () => import("./cli/cmd/mcp").then((x) => x.McpCommand)),
load(!none && (all || mode === "generate"), () => import("./cli/cmd/generate").then((x) => x.GenerateCommand)),
load(!none && (all || mode === "debug"), () => import("./cli/cmd/debug").then((x) => x.DebugCommand)),
load(!none && (all || mode === "console"), () => import("./cli/cmd/account").then((x) => x.ConsoleCommand)),
load(!none && (all || mode === "providers"), () => import("./cli/cmd/providers").then((x) => x.ProvidersCommand)),
load(!none && (all || mode === "agent"), () => import("./cli/cmd/agent").then((x) => x.AgentCommand)),
load(!none && (all || mode === "upgrade"), () => import("./cli/cmd/upgrade").then((x) => x.UpgradeCommand)),
load(!none && (all || mode === "uninstall"), () => import("./cli/cmd/uninstall").then((x) => x.UninstallCommand)),
load(!none && (all || mode === "serve"), () => import("./cli/cmd/serve").then((x) => x.ServeCommand)),
load(!none && (all || mode === "web"), () => import("./cli/cmd/web").then((x) => x.WebCommand)),
load(!none && (all || mode === "models"), () => import("./cli/cmd/models").then((x) => x.ModelsCommand)),
load(!none && (all || mode === "stats"), () => import("./cli/cmd/stats").then((x) => x.StatsCommand)),
load(!none && (all || mode === "export"), () => import("./cli/cmd/export").then((x) => x.ExportCommand)),
load(!none && (all || mode === "import"), () => import("./cli/cmd/import").then((x) => x.ImportCommand)),
load(!none && (all || mode === "github"), () => import("./cli/cmd/github").then((x) => x.GithubCommand)),
load(!none && (all || mode === "pr"), () => import("./cli/cmd/pr").then((x) => x.PrCommand)),
load(!none && (all || mode === "session"), () => import("./cli/cmd/session").then((x) => x.SessionCommand)),
load(!none && (all || mode === "plugin"), () => import("./cli/cmd/plug").then((x) => x.PluginCommand)),
load(!none && (all || mode === "db"), () => import("./cli/cmd/db").then((x) => x.DbCommand)),
])
function show(out: string) {
const text = out.trimStart()
if (!text.startsWith("opencode ")) {
@@ -148,29 +275,100 @@ const cli = yargs(args)
})
.usage("")
.completion("completion", "generate shell completion script")
.command(AcpCommand)
.command(McpCommand)
.command(TuiThreadCommand)
.command(AttachCommand)
.command(RunCommand)
.command(GenerateCommand)
.command(DebugCommand)
.command(ConsoleCommand)
.command(ProvidersCommand)
.command(AgentCommand)
.command(UpgradeCommand)
.command(UninstallCommand)
.command(ServeCommand)
.command(WebCommand)
.command(ModelsCommand)
.command(StatsCommand)
.command(ExportCommand)
.command(ImportCommand)
.command(GithubCommand)
.command(PrCommand)
.command(SessionCommand)
.command(PluginCommand)
.command(DbCommand)
if (TuiThreadCommand) {
cli.command(TuiThreadCommand)
}
if (AttachCommand) {
cli.command(AttachCommand)
}
if (AcpCommand) {
cli.command(AcpCommand)
}
if (McpCommand) {
cli.command(McpCommand)
}
if (RunCommand) {
cli.command(RunCommand)
}
if (GenerateCommand) {
cli.command(GenerateCommand)
}
if (DebugCommand) {
cli.command(DebugCommand)
}
if (ConsoleCommand) {
cli.command(ConsoleCommand)
}
if (ProvidersCommand) {
cli.command(ProvidersCommand)
}
if (AgentCommand) {
cli.command(AgentCommand)
}
if (UpgradeCommand) {
cli.command(UpgradeCommand)
}
if (UninstallCommand) {
cli.command(UninstallCommand)
}
if (ServeCommand) {
cli.command(ServeCommand)
}
if (WebCommand) {
cli.command(WebCommand)
}
if (ModelsCommand) {
cli.command(ModelsCommand)
}
if (StatsCommand) {
cli.command(StatsCommand)
}
if (ExportCommand) {
cli.command(ExportCommand)
}
if (ImportCommand) {
cli.command(ImportCommand)
}
if (GithubCommand) {
cli.command(GithubCommand)
}
if (PrCommand) {
cli.command(PrCommand)
}
if (SessionCommand) {
cli.command(SessionCommand)
}
if (PluginCommand) {
cli.command(PluginCommand)
}
if (DbCommand) {
cli.command(DbCommand)
}
cli
.fail((msg, err) => {
if (
msg?.startsWith("Unknown argument") ||