summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorSimon Klee <[email protected]>2026-04-05 15:02:29 +0200
committerSimon Klee <[email protected]>2026-04-09 19:42:25 +0200
commit537160dbc0cfa9e2eb1bc3ea5cf86743a15f8d90 (patch)
tree8e6e27a0ab0aec38ea70a7db967e29a8530c2e51
parentb0600664abacabc3b6bd41de88859248bc2a2594 (diff)
downloadopencode-537160dbc0cfa9e2eb1bc3ea5cf86743a15f8d90.tar.gz
opencode-537160dbc0cfa9e2eb1bc3ea5cf86743a15f8d90.zip
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
-rw-r--r--packages/opencode/src/index.ts290
1 files changed, 244 insertions, 46 deletions
diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts
index 753becc26..59608f757 100644
--- a/packages/opencode/src/index.ts
+++ b/packages/opencode/src/index.ts
@@ -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") ||