From 2f49d8858c6c4eb897419cf894ce9ad7f149ab9c Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 14 Feb 2026 00:12:15 +0000 Subject: [PATCH] perf(cli): slim route-first bootstrap with lazy route handlers --- src/cli/program/command-registry.ts | 108 +------------------------ src/cli/program/routes.test.ts | 26 +++++++ src/cli/program/routes.ts | 117 ++++++++++++++++++++++++++++ src/cli/route.ts | 2 +- 4 files changed, 145 insertions(+), 108 deletions(-) create mode 100644 src/cli/program/routes.test.ts create mode 100644 src/cli/program/routes.ts diff --git a/src/cli/program/command-registry.ts b/src/cli/program/command-registry.ts index 7d7de6bfb..1e03dd702 100644 --- a/src/cli/program/command-registry.ts +++ b/src/cli/program/command-registry.ts @@ -1,14 +1,8 @@ import type { Command } from "commander"; import type { ProgramContext } from "./context.js"; -import { agentsListCommand } from "../../commands/agents.js"; -import { healthCommand } from "../../commands/health.js"; -import { sessionsCommand } from "../../commands/sessions.js"; -import { statusCommand } from "../../commands/status.js"; -import { defaultRuntime } from "../../runtime.js"; -import { getFlagValue, getPositiveIntFlagValue, getVerboseFlag, hasFlag } from "../argv.js"; import { registerBrowserCli } from "../browser-cli.js"; import { registerConfigCli } from "../config-cli.js"; -import { registerMemoryCli, runMemoryStatus } from "../memory-cli.js"; +import { registerMemoryCli } from "../memory-cli.js"; import { registerAgentCommands } from "./register.agent.js"; import { registerConfigureCommand } from "./register.configure.js"; import { registerMaintenanceCommands } from "./register.maintenance.js"; @@ -24,92 +18,9 @@ type CommandRegisterParams = { argv: string[]; }; -type RouteSpec = { - match: (path: string[]) => boolean; - loadPlugins?: boolean; - run: (argv: string[]) => Promise; -}; - export type CommandRegistration = { id: string; register: (params: CommandRegisterParams) => void; - routes?: RouteSpec[]; -}; - -const routeHealth: RouteSpec = { - match: (path) => path[0] === "health", - loadPlugins: true, - run: async (argv) => { - const json = hasFlag(argv, "--json"); - const verbose = getVerboseFlag(argv, { includeDebug: true }); - const timeoutMs = getPositiveIntFlagValue(argv, "--timeout"); - if (timeoutMs === null) { - return false; - } - await healthCommand({ json, timeoutMs, verbose }, defaultRuntime); - return true; - }, -}; - -const routeStatus: RouteSpec = { - match: (path) => path[0] === "status", - loadPlugins: true, - run: async (argv) => { - const json = hasFlag(argv, "--json"); - const deep = hasFlag(argv, "--deep"); - const all = hasFlag(argv, "--all"); - const usage = hasFlag(argv, "--usage"); - const verbose = getVerboseFlag(argv, { includeDebug: true }); - const timeoutMs = getPositiveIntFlagValue(argv, "--timeout"); - if (timeoutMs === null) { - return false; - } - await statusCommand({ json, deep, all, usage, timeoutMs, verbose }, defaultRuntime); - return true; - }, -}; - -const routeSessions: RouteSpec = { - match: (path) => path[0] === "sessions", - run: async (argv) => { - const json = hasFlag(argv, "--json"); - const store = getFlagValue(argv, "--store"); - if (store === null) { - return false; - } - const active = getFlagValue(argv, "--active"); - if (active === null) { - return false; - } - await sessionsCommand({ json, store, active }, defaultRuntime); - return true; - }, -}; - -const routeAgentsList: RouteSpec = { - match: (path) => path[0] === "agents" && path[1] === "list", - run: async (argv) => { - const json = hasFlag(argv, "--json"); - const bindings = hasFlag(argv, "--bindings"); - await agentsListCommand({ json, bindings }, defaultRuntime); - return true; - }, -}; - -const routeMemoryStatus: RouteSpec = { - match: (path) => path[0] === "memory" && path[1] === "status", - run: async (argv) => { - const agent = getFlagValue(argv, "--agent"); - if (agent === null) { - return false; - } - const json = hasFlag(argv, "--json"); - const deep = hasFlag(argv, "--deep"); - const index = hasFlag(argv, "--index"); - const verbose = hasFlag(argv, "--verbose"); - await runMemoryStatus({ agent, json, deep, index, verbose }); - return true; - }, }; export const commandRegistry: CommandRegistration[] = [ @@ -140,13 +51,11 @@ export const commandRegistry: CommandRegistration[] = [ { id: "memory", register: ({ program }) => registerMemoryCli(program), - routes: [routeMemoryStatus], }, { id: "agent", register: ({ program, ctx }) => registerAgentCommands(program, { agentChannelOptions: ctx.agentChannelOptions }), - routes: [routeAgentsList], }, { id: "subclis", @@ -155,7 +64,6 @@ export const commandRegistry: CommandRegistration[] = [ { id: "status-health-sessions", register: ({ program }) => registerStatusHealthSessionsCommands(program), - routes: [routeHealth, routeStatus, routeSessions], }, { id: "browser", @@ -172,17 +80,3 @@ export function registerProgramCommands( entry.register({ program, ctx, argv }); } } - -export function findRoutedCommand(path: string[]): RouteSpec | null { - for (const entry of commandRegistry) { - if (!entry.routes) { - continue; - } - for (const route of entry.routes) { - if (route.match(path)) { - return route; - } - } - } - return null; -} diff --git a/src/cli/program/routes.test.ts b/src/cli/program/routes.test.ts new file mode 100644 index 000000000..13809d1e7 --- /dev/null +++ b/src/cli/program/routes.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from "vitest"; +import { findRoutedCommand } from "./routes.js"; + +describe("program routes", () => { + it("matches status route and preserves plugin loading", () => { + const route = findRoutedCommand(["status"]); + expect(route).not.toBeNull(); + expect(route?.loadPlugins).toBe(true); + }); + + it("returns false when status timeout flag value is missing", async () => { + const route = findRoutedCommand(["status"]); + expect(route).not.toBeNull(); + await expect(route?.run(["node", "openclaw", "status", "--timeout"])).resolves.toBe(false); + }); + + it("returns false for sessions route when --store value is missing", async () => { + const route = findRoutedCommand(["sessions"]); + expect(route).not.toBeNull(); + await expect(route?.run(["node", "openclaw", "sessions", "--store"])).resolves.toBe(false); + }); + + it("does not match unknown routes", () => { + expect(findRoutedCommand(["definitely-not-real"])).toBeNull(); + }); +}); diff --git a/src/cli/program/routes.ts b/src/cli/program/routes.ts new file mode 100644 index 000000000..2efd9ef6f --- /dev/null +++ b/src/cli/program/routes.ts @@ -0,0 +1,117 @@ +import { getFlagValue, getPositiveIntFlagValue, getVerboseFlag, hasFlag } from "../argv.js"; + +export type RouteSpec = { + match: (path: string[]) => boolean; + loadPlugins?: boolean; + run: (argv: string[]) => Promise; +}; + +const routeHealth: RouteSpec = { + match: (path) => path[0] === "health", + loadPlugins: true, + run: async (argv) => { + const json = hasFlag(argv, "--json"); + const verbose = getVerboseFlag(argv, { includeDebug: true }); + const timeoutMs = getPositiveIntFlagValue(argv, "--timeout"); + if (timeoutMs === null) { + return false; + } + const [{ healthCommand }, { defaultRuntime }] = await Promise.all([ + import("../../commands/health.js"), + import("../../runtime.js"), + ]); + await healthCommand({ json, timeoutMs, verbose }, defaultRuntime); + return true; + }, +}; + +const routeStatus: RouteSpec = { + match: (path) => path[0] === "status", + loadPlugins: true, + run: async (argv) => { + const json = hasFlag(argv, "--json"); + const deep = hasFlag(argv, "--deep"); + const all = hasFlag(argv, "--all"); + const usage = hasFlag(argv, "--usage"); + const verbose = getVerboseFlag(argv, { includeDebug: true }); + const timeoutMs = getPositiveIntFlagValue(argv, "--timeout"); + if (timeoutMs === null) { + return false; + } + const [{ statusCommand }, { defaultRuntime }] = await Promise.all([ + import("../../commands/status.js"), + import("../../runtime.js"), + ]); + await statusCommand({ json, deep, all, usage, timeoutMs, verbose }, defaultRuntime); + return true; + }, +}; + +const routeSessions: RouteSpec = { + match: (path) => path[0] === "sessions", + run: async (argv) => { + const json = hasFlag(argv, "--json"); + const store = getFlagValue(argv, "--store"); + if (store === null) { + return false; + } + const active = getFlagValue(argv, "--active"); + if (active === null) { + return false; + } + const [{ sessionsCommand }, { defaultRuntime }] = await Promise.all([ + import("../../commands/sessions.js"), + import("../../runtime.js"), + ]); + await sessionsCommand({ json, store, active }, defaultRuntime); + return true; + }, +}; + +const routeAgentsList: RouteSpec = { + match: (path) => path[0] === "agents" && path[1] === "list", + run: async (argv) => { + const json = hasFlag(argv, "--json"); + const bindings = hasFlag(argv, "--bindings"); + const [{ agentsListCommand }, { defaultRuntime }] = await Promise.all([ + import("../../commands/agents.js"), + import("../../runtime.js"), + ]); + await agentsListCommand({ json, bindings }, defaultRuntime); + return true; + }, +}; + +const routeMemoryStatus: RouteSpec = { + match: (path) => path[0] === "memory" && path[1] === "status", + run: async (argv) => { + const agent = getFlagValue(argv, "--agent"); + if (agent === null) { + return false; + } + const json = hasFlag(argv, "--json"); + const deep = hasFlag(argv, "--deep"); + const index = hasFlag(argv, "--index"); + const verbose = hasFlag(argv, "--verbose"); + const { runMemoryStatus } = await import("../memory-cli.js"); + await runMemoryStatus({ agent, json, deep, index, verbose }); + return true; + }, +}; + +const routes: RouteSpec[] = [ + routeHealth, + routeStatus, + routeSessions, + routeAgentsList, + routeMemoryStatus, +]; + +export function findRoutedCommand(path: string[]): RouteSpec | null { + for (const route of routes) { + if (route.match(path)) { + return route; + } + } + return null; +} diff --git a/src/cli/route.ts b/src/cli/route.ts index 7a1ddf15a..38093e936 100644 --- a/src/cli/route.ts +++ b/src/cli/route.ts @@ -4,8 +4,8 @@ import { VERSION } from "../version.js"; import { getCommandPath, hasHelpOrVersion } from "./argv.js"; import { emitCliBanner } from "./banner.js"; import { ensurePluginRegistryLoaded } from "./plugin-registry.js"; -import { findRoutedCommand } from "./program/command-registry.js"; import { ensureConfigReady } from "./program/config-guard.js"; +import { findRoutedCommand } from "./program/routes.js"; async function prepareRoutedCommand(params: { argv: string[];