Skip to content

Commit 537160d

Browse files
committed
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
1 parent b060066 commit 537160d

1 file changed

Lines changed: 244 additions & 46 deletions

File tree

packages/opencode/src/index.ts

Lines changed: 244 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,17 @@
11
import yargs from "yargs"
22
import { hideBin } from "yargs/helpers"
3-
import { RunCommand } from "./cli/cmd/run"
4-
import { GenerateCommand } from "./cli/cmd/generate"
53
import { Log } from "./util/log"
6-
import { ConsoleCommand } from "./cli/cmd/account"
7-
import { ProvidersCommand } from "./cli/cmd/providers"
8-
import { AgentCommand } from "./cli/cmd/agent"
9-
import { UpgradeCommand } from "./cli/cmd/upgrade"
10-
import { UninstallCommand } from "./cli/cmd/uninstall"
11-
import { ModelsCommand } from "./cli/cmd/models"
124
import { UI } from "./cli/ui"
135
import { Installation } from "./installation"
146
import { NamedError } from "@opencode-ai/util/error"
157
import { FormatError } from "./cli/error"
16-
import { ServeCommand } from "./cli/cmd/serve"
178
import { Filesystem } from "./util/filesystem"
18-
import { DebugCommand } from "./cli/cmd/debug"
19-
import { StatsCommand } from "./cli/cmd/stats"
20-
import { McpCommand } from "./cli/cmd/mcp"
21-
import { GithubCommand } from "./cli/cmd/github"
22-
import { ExportCommand } from "./cli/cmd/export"
23-
import { ImportCommand } from "./cli/cmd/import"
24-
import { AttachCommand } from "./cli/cmd/tui/attach"
25-
import { TuiThreadCommand } from "./cli/cmd/tui/thread"
26-
import { AcpCommand } from "./cli/cmd/acp"
279
import { EOL } from "os"
28-
import { WebCommand } from "./cli/cmd/web"
29-
import { PrCommand } from "./cli/cmd/pr"
30-
import { SessionCommand } from "./cli/cmd/session"
31-
import { DbCommand } from "./cli/cmd/db"
3210
import path from "path"
3311
import { Global } from "./global"
3412
import { JsonMigration } from "./storage/json-migration"
3513
import { Database } from "./storage/db"
3614
import { errorMessage } from "./util/error"
37-
import { PluginCommand } from "./cli/cmd/plug"
3815
import { Heap } from "./cli/heap"
3916
import { drizzle } from "drizzle-orm/bun-sqlite"
4017

@@ -52,6 +29,156 @@ process.on("uncaughtException", (e) => {
5229

5330
const args = hideBin(process.argv)
5431

32+
type Mode =
33+
| "all"
34+
| "none"
35+
| "tui"
36+
| "attach"
37+
| "run"
38+
| "acp"
39+
| "mcp"
40+
| "generate"
41+
| "debug"
42+
| "console"
43+
| "providers"
44+
| "agent"
45+
| "upgrade"
46+
| "uninstall"
47+
| "serve"
48+
| "web"
49+
| "models"
50+
| "stats"
51+
| "export"
52+
| "import"
53+
| "github"
54+
| "pr"
55+
| "session"
56+
| "plugin"
57+
| "db"
58+
59+
const map = new Map<string, Mode>([
60+
["attach", "attach"],
61+
["run", "run"],
62+
["acp", "acp"],
63+
["mcp", "mcp"],
64+
["generate", "generate"],
65+
["debug", "debug"],
66+
["console", "console"],
67+
["providers", "providers"],
68+
["auth", "providers"],
69+
["agent", "agent"],
70+
["upgrade", "upgrade"],
71+
["uninstall", "uninstall"],
72+
["serve", "serve"],
73+
["web", "web"],
74+
["models", "models"],
75+
["stats", "stats"],
76+
["export", "export"],
77+
["import", "import"],
78+
["github", "github"],
79+
["pr", "pr"],
80+
["session", "session"],
81+
["plugin", "plugin"],
82+
["plug", "plugin"],
83+
["db", "db"],
84+
])
85+
86+
function flag(arg: string, name: string) {
87+
return arg === `--${name}` || arg === `--no-${name}` || arg.startsWith(`--${name}=`)
88+
}
89+
90+
function value(arg: string, name: string) {
91+
return arg === `--${name}` || arg.startsWith(`--${name}=`)
92+
}
93+
94+
// Match the root parser closely enough to decide which top-level module to load.
95+
function pick(argv: string[]): Mode {
96+
for (let i = 0; i < argv.length; i++) {
97+
const arg = argv[i]
98+
if (!arg) continue
99+
if (arg === "--") return "tui"
100+
if (arg === "completion") return "all"
101+
if (arg === "--help" || arg === "-h") return "all"
102+
if (arg === "--version" || arg === "-v") return "none"
103+
if (flag(arg, "print-logs") || flag(arg, "pure")) continue
104+
if (value(arg, "log-level")) {
105+
if (arg === "--log-level") i += 1
106+
continue
107+
}
108+
if (arg.startsWith("-") && !arg.startsWith("--")) {
109+
if (arg.includes("h")) return "all"
110+
if (arg.includes("v")) return "none"
111+
return "tui"
112+
}
113+
if (arg.startsWith("-")) return "tui"
114+
return map.get(arg) ?? "tui"
115+
}
116+
117+
return "tui"
118+
}
119+
120+
const mode = pick(args)
121+
const all = mode === "all"
122+
const none = mode === "none"
123+
124+
function load<T>(on: boolean, get: () => Promise<T>): Promise<T | undefined> {
125+
if (!on) {
126+
return Promise.resolve(undefined)
127+
}
128+
129+
return get()
130+
}
131+
132+
const [
133+
TuiThreadCommand,
134+
AttachCommand,
135+
RunCommand,
136+
AcpCommand,
137+
McpCommand,
138+
GenerateCommand,
139+
DebugCommand,
140+
ConsoleCommand,
141+
ProvidersCommand,
142+
AgentCommand,
143+
UpgradeCommand,
144+
UninstallCommand,
145+
ServeCommand,
146+
WebCommand,
147+
ModelsCommand,
148+
StatsCommand,
149+
ExportCommand,
150+
ImportCommand,
151+
GithubCommand,
152+
PrCommand,
153+
SessionCommand,
154+
PluginCommand,
155+
DbCommand,
156+
] = await Promise.all([
157+
load(!none && (all || mode === "tui"), () => import("./cli/cmd/tui/thread").then((x) => x.TuiThreadCommand)),
158+
load(!none && (all || mode === "attach"), () => import("./cli/cmd/tui/attach").then((x) => x.AttachCommand)),
159+
load(!none && (all || mode === "run"), () => import("./cli/cmd/run").then((x) => x.RunCommand)),
160+
load(!none && (all || mode === "acp"), () => import("./cli/cmd/acp").then((x) => x.AcpCommand)),
161+
load(!none && (all || mode === "mcp"), () => import("./cli/cmd/mcp").then((x) => x.McpCommand)),
162+
load(!none && (all || mode === "generate"), () => import("./cli/cmd/generate").then((x) => x.GenerateCommand)),
163+
load(!none && (all || mode === "debug"), () => import("./cli/cmd/debug").then((x) => x.DebugCommand)),
164+
load(!none && (all || mode === "console"), () => import("./cli/cmd/account").then((x) => x.ConsoleCommand)),
165+
load(!none && (all || mode === "providers"), () => import("./cli/cmd/providers").then((x) => x.ProvidersCommand)),
166+
load(!none && (all || mode === "agent"), () => import("./cli/cmd/agent").then((x) => x.AgentCommand)),
167+
load(!none && (all || mode === "upgrade"), () => import("./cli/cmd/upgrade").then((x) => x.UpgradeCommand)),
168+
load(!none && (all || mode === "uninstall"), () => import("./cli/cmd/uninstall").then((x) => x.UninstallCommand)),
169+
load(!none && (all || mode === "serve"), () => import("./cli/cmd/serve").then((x) => x.ServeCommand)),
170+
load(!none && (all || mode === "web"), () => import("./cli/cmd/web").then((x) => x.WebCommand)),
171+
load(!none && (all || mode === "models"), () => import("./cli/cmd/models").then((x) => x.ModelsCommand)),
172+
load(!none && (all || mode === "stats"), () => import("./cli/cmd/stats").then((x) => x.StatsCommand)),
173+
load(!none && (all || mode === "export"), () => import("./cli/cmd/export").then((x) => x.ExportCommand)),
174+
load(!none && (all || mode === "import"), () => import("./cli/cmd/import").then((x) => x.ImportCommand)),
175+
load(!none && (all || mode === "github"), () => import("./cli/cmd/github").then((x) => x.GithubCommand)),
176+
load(!none && (all || mode === "pr"), () => import("./cli/cmd/pr").then((x) => x.PrCommand)),
177+
load(!none && (all || mode === "session"), () => import("./cli/cmd/session").then((x) => x.SessionCommand)),
178+
load(!none && (all || mode === "plugin"), () => import("./cli/cmd/plug").then((x) => x.PluginCommand)),
179+
load(!none && (all || mode === "db"), () => import("./cli/cmd/db").then((x) => x.DbCommand)),
180+
])
181+
55182
function show(out: string) {
56183
const text = out.trimStart()
57184
if (!text.startsWith("opencode ")) {
@@ -148,29 +275,100 @@ const cli = yargs(args)
148275
})
149276
.usage("")
150277
.completion("completion", "generate shell completion script")
151-
.command(AcpCommand)
152-
.command(McpCommand)
153-
.command(TuiThreadCommand)
154-
.command(AttachCommand)
155-
.command(RunCommand)
156-
.command(GenerateCommand)
157-
.command(DebugCommand)
158-
.command(ConsoleCommand)
159-
.command(ProvidersCommand)
160-
.command(AgentCommand)
161-
.command(UpgradeCommand)
162-
.command(UninstallCommand)
163-
.command(ServeCommand)
164-
.command(WebCommand)
165-
.command(ModelsCommand)
166-
.command(StatsCommand)
167-
.command(ExportCommand)
168-
.command(ImportCommand)
169-
.command(GithubCommand)
170-
.command(PrCommand)
171-
.command(SessionCommand)
172-
.command(PluginCommand)
173-
.command(DbCommand)
278+
279+
if (TuiThreadCommand) {
280+
cli.command(TuiThreadCommand)
281+
}
282+
283+
if (AttachCommand) {
284+
cli.command(AttachCommand)
285+
}
286+
287+
if (AcpCommand) {
288+
cli.command(AcpCommand)
289+
}
290+
291+
if (McpCommand) {
292+
cli.command(McpCommand)
293+
}
294+
295+
if (RunCommand) {
296+
cli.command(RunCommand)
297+
}
298+
299+
if (GenerateCommand) {
300+
cli.command(GenerateCommand)
301+
}
302+
303+
if (DebugCommand) {
304+
cli.command(DebugCommand)
305+
}
306+
307+
if (ConsoleCommand) {
308+
cli.command(ConsoleCommand)
309+
}
310+
311+
if (ProvidersCommand) {
312+
cli.command(ProvidersCommand)
313+
}
314+
315+
if (AgentCommand) {
316+
cli.command(AgentCommand)
317+
}
318+
319+
if (UpgradeCommand) {
320+
cli.command(UpgradeCommand)
321+
}
322+
323+
if (UninstallCommand) {
324+
cli.command(UninstallCommand)
325+
}
326+
327+
if (ServeCommand) {
328+
cli.command(ServeCommand)
329+
}
330+
331+
if (WebCommand) {
332+
cli.command(WebCommand)
333+
}
334+
335+
if (ModelsCommand) {
336+
cli.command(ModelsCommand)
337+
}
338+
339+
if (StatsCommand) {
340+
cli.command(StatsCommand)
341+
}
342+
343+
if (ExportCommand) {
344+
cli.command(ExportCommand)
345+
}
346+
347+
if (ImportCommand) {
348+
cli.command(ImportCommand)
349+
}
350+
351+
if (GithubCommand) {
352+
cli.command(GithubCommand)
353+
}
354+
355+
if (PrCommand) {
356+
cli.command(PrCommand)
357+
}
358+
359+
if (SessionCommand) {
360+
cli.command(SessionCommand)
361+
}
362+
363+
if (PluginCommand) {
364+
cli.command(PluginCommand)
365+
}
366+
367+
if (DbCommand) {
368+
cli.command(DbCommand)
369+
}
370+
371+
cli
174372
.fail((msg, err) => {
175373
if (
176374
msg?.startsWith("Unknown argument") ||

0 commit comments

Comments
 (0)