Ver Fonte

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
Simon Klee há 1 semana atrás
pai
commit
537160dbc0
1 ficheiros alterados com 244 adições e 46 exclusões
  1. 244 46
      packages/opencode/src/index.ts

+ 244 - 46
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") ||