diff --git a/src/cli/program/command-registry.ts b/src/cli/program/command-registry.ts index 257cf73d070..3c20ef3eea3 100644 --- a/src/cli/program/command-registry.ts +++ b/src/cli/program/command-registry.ts @@ -1,14 +1,17 @@ import type { Command } from "commander"; -import { getPrimaryCommand } from "../argv.js"; +import { resolveCliArgvInvocation } from "../argv-invocation.js"; import { shouldRegisterPrimaryCommandOnly } from "../command-registration-policy.js"; -import { removeCommandByName } from "./command-tree.js"; import type { ProgramContext } from "./context.js"; import { type CoreCliCommandDescriptor, getCoreCliCommandDescriptors, getCoreCliCommandsWithSubcommands, } from "./core-command-descriptors.js"; -import { registerLazyCommand } from "./register-lazy-command.js"; +import { + registerCommandGroupByName, + registerCommandGroups, + type CommandGroupEntry, +} from "./register-command-groups.js"; import { registerSubCliCommands } from "./register.subclis.js"; export { getCoreCliCommandDescriptors, getCoreCliCommandsWithSubcommands }; @@ -26,7 +29,7 @@ export type CommandRegistration = { type CoreCliEntry = { commands: CoreCliCommandDescriptor[]; - register: (params: CommandRegisterParams) => Promise | void; + registerWithParams: (params: CommandRegisterParams) => Promise | void; }; // Note for humans and agents: @@ -41,7 +44,7 @@ const coreEntries: CoreCliEntry[] = [ hasSubcommands: false, }, ], - register: async ({ program }) => { + registerWithParams: async ({ program }) => { const mod = await import("./register.setup.js"); mod.registerSetupCommand(program); }, @@ -54,7 +57,7 @@ const coreEntries: CoreCliEntry[] = [ hasSubcommands: false, }, ], - register: async ({ program }) => { + registerWithParams: async ({ program }) => { const mod = await import("./register.onboard.js"); mod.registerOnboardCommand(program); }, @@ -68,7 +71,7 @@ const coreEntries: CoreCliEntry[] = [ hasSubcommands: false, }, ], - register: async ({ program }) => { + registerWithParams: async ({ program }) => { const mod = await import("./register.configure.js"); mod.registerConfigureCommand(program); }, @@ -82,7 +85,7 @@ const coreEntries: CoreCliEntry[] = [ hasSubcommands: true, }, ], - register: async ({ program }) => { + registerWithParams: async ({ program }) => { const mod = await import("../config-cli.js"); mod.registerConfigCli(program); }, @@ -95,7 +98,7 @@ const coreEntries: CoreCliEntry[] = [ hasSubcommands: true, }, ], - register: async ({ program }) => { + registerWithParams: async ({ program }) => { const mod = await import("./register.backup.js"); mod.registerBackupCommand(program); }, @@ -123,7 +126,7 @@ const coreEntries: CoreCliEntry[] = [ hasSubcommands: false, }, ], - register: async ({ program }) => { + registerWithParams: async ({ program }) => { const mod = await import("./register.maintenance.js"); mod.registerMaintenanceCommands(program); }, @@ -136,7 +139,7 @@ const coreEntries: CoreCliEntry[] = [ hasSubcommands: true, }, ], - register: async ({ program, ctx }) => { + registerWithParams: async ({ program, ctx }) => { const mod = await import("./register.message.js"); mod.registerMessageCommands(program, ctx); }, @@ -149,7 +152,7 @@ const coreEntries: CoreCliEntry[] = [ hasSubcommands: true, }, ], - register: async ({ program }) => { + registerWithParams: async ({ program }) => { const mod = await import("../mcp-cli.js"); mod.registerMcpCli(program); }, @@ -167,7 +170,7 @@ const coreEntries: CoreCliEntry[] = [ hasSubcommands: true, }, ], - register: async ({ program, ctx }) => { + registerWithParams: async ({ program, ctx }) => { const mod = await import("./register.agent.js"); mod.registerAgentCommands(program, { agentChannelOptions: ctx.agentChannelOptions, @@ -197,80 +200,42 @@ const coreEntries: CoreCliEntry[] = [ hasSubcommands: true, }, ], - register: async ({ program }) => { + registerWithParams: async ({ program }) => { const mod = await import("./register.status-health-sessions.js"); mod.registerStatusHealthSessionsCommands(program); }, }, ]; +function resolveCoreCommandGroups(ctx: ProgramContext, argv: string[]): CommandGroupEntry[] { + return coreEntries.map((entry) => ({ + placeholders: entry.commands, + register: async (program) => { + await entry.registerWithParams({ program, ctx, argv }); + }, + })); +} + export function getCoreCliCommandNames(): string[] { return getCoreCliCommandDescriptors().map((command) => command.name); } -function removeEntryCommands(program: Command, entry: CoreCliEntry) { - // Some registrars install multiple top-level commands (e.g. status/health/sessions). - // Remove placeholders/old registrations for all names in the entry before re-registering. - for (const cmd of entry.commands) { - removeCommandByName(program, cmd.name); - } -} - -function registerLazyCoreCommand( - program: Command, - ctx: ProgramContext, - entry: CoreCliEntry, - command: CoreCliCommandDescriptor, -) { - registerLazyCommand({ - program, - name: command.name, - description: command.description, - removeNames: entry.commands.map((cmd) => cmd.name), - register: async () => { - await entry.register({ program, ctx, argv: process.argv }); - }, - }); -} - export async function registerCoreCliByName( program: Command, ctx: ProgramContext, name: string, argv: string[] = process.argv, ): Promise { - const entry = coreEntries.find((candidate) => - candidate.commands.some((cmd) => cmd.name === name), - ); - if (!entry) { - return false; - } - - removeEntryCommands(program, entry); - await entry.register({ program, ctx, argv }); - return true; + return registerCommandGroupByName(program, resolveCoreCommandGroups(ctx, argv), name); } export function registerCoreCliCommands(program: Command, ctx: ProgramContext, argv: string[]) { - const primary = getPrimaryCommand(argv); - if (primary && shouldRegisterPrimaryCommandOnly(argv)) { - const entry = coreEntries.find((candidate) => - candidate.commands.some((cmd) => cmd.name === primary), - ); - if (entry) { - const cmd = entry.commands.find((c) => c.name === primary); - if (cmd) { - registerLazyCoreCommand(program, ctx, entry, cmd); - } - return; - } - } - - for (const entry of coreEntries) { - for (const cmd of entry.commands) { - registerLazyCoreCommand(program, ctx, entry, cmd); - } - } + const { primary } = resolveCliArgvInvocation(argv); + registerCommandGroups(program, resolveCoreCommandGroups(ctx, argv), { + eager: false, + primary, + registerPrimaryOnly: Boolean(primary && shouldRegisterPrimaryCommandOnly(argv)), + }); } export function registerProgramCommands( diff --git a/src/cli/program/register-command-groups.ts b/src/cli/program/register-command-groups.ts new file mode 100644 index 00000000000..e1e401f17b1 --- /dev/null +++ b/src/cli/program/register-command-groups.ts @@ -0,0 +1,88 @@ +import type { Command } from "commander"; +import { removeCommandByName } from "./command-tree.js"; +import { registerLazyCommand } from "./register-lazy-command.js"; + +export type CommandGroupPlaceholder = { + name: string; + description: string; +}; + +export type CommandGroupEntry = { + placeholders: readonly CommandGroupPlaceholder[]; + register: (program: Command) => Promise | void; +}; + +function findCommandGroupEntry( + entries: readonly CommandGroupEntry[], + name: string, +): CommandGroupEntry | undefined { + return entries.find((entry) => + entry.placeholders.some((placeholder) => placeholder.name === name), + ); +} + +export async function registerCommandGroupByName( + program: Command, + entries: readonly CommandGroupEntry[], + name: string, +): Promise { + const entry = findCommandGroupEntry(entries, name); + if (!entry) { + return false; + } + for (const placeholder of entry.placeholders) { + removeCommandByName(program, placeholder.name); + } + await entry.register(program); + return true; +} + +function registerLazyCommandGroup( + program: Command, + entry: CommandGroupEntry, + placeholder: CommandGroupPlaceholder, +) { + registerLazyCommand({ + program, + name: placeholder.name, + description: placeholder.description, + removeNames: entry.placeholders.map((candidate) => candidate.name), + register: async () => { + await entry.register(program); + }, + }); +} + +export function registerCommandGroups( + program: Command, + entries: readonly CommandGroupEntry[], + params: { + eager: boolean; + primary: string | null; + registerPrimaryOnly: boolean; + }, +) { + if (params.eager) { + for (const entry of entries) { + void entry.register(program); + } + return; + } + + if (params.primary && params.registerPrimaryOnly) { + const entry = findCommandGroupEntry(entries, params.primary); + if (entry) { + const placeholder = entry.placeholders.find((candidate) => candidate.name === params.primary); + if (placeholder) { + registerLazyCommandGroup(program, entry, placeholder); + } + return; + } + } + + for (const entry of entries) { + for (const placeholder of entry.placeholders) { + registerLazyCommandGroup(program, entry, placeholder); + } + } +} diff --git a/src/cli/program/register.subclis.ts b/src/cli/program/register.subclis.ts index 8036e9d2c70..b55c73b15f9 100644 --- a/src/cli/program/register.subclis.ts +++ b/src/cli/program/register.subclis.ts @@ -1,12 +1,15 @@ import type { Command } from "commander"; import type { OpenClawConfig } from "../../config/config.js"; -import { getPrimaryCommand } from "../argv.js"; +import { resolveCliArgvInvocation } from "../argv-invocation.js"; import { shouldEagerRegisterSubcommands, shouldRegisterPrimarySubcommandOnly, } from "../command-registration-policy.js"; -import { removeCommandByName } from "./command-tree.js"; -import { registerLazyCommand as registerLazyCommandPlaceholder } from "./register-lazy-command.js"; +import { + registerCommandGroupByName, + registerCommandGroups, + type CommandGroupEntry, +} from "./register-command-groups.js"; import { getSubCliCommandsWithSubcommands, getSubCliEntries as getSubCliEntryDescriptors, @@ -311,47 +314,26 @@ const entries: SubCliEntry[] = [ }, ]; +function resolveSubCliCommandGroups(): CommandGroupEntry[] { + return entries.map((entry) => ({ + placeholders: [entry], + register: entry.register, + })); +} + export function getSubCliEntries(): ReadonlyArray { return getSubCliEntryDescriptors(); } export async function registerSubCliByName(program: Command, name: string): Promise { - const entry = entries.find((candidate) => candidate.name === name); - if (!entry) { - return false; - } - removeCommandByName(program, entry.name); - await entry.register(program); - return true; -} - -function registerLazyCommand(program: Command, entry: SubCliEntry) { - registerLazyCommandPlaceholder({ - program, - name: entry.name, - description: entry.description, - register: async () => { - await entry.register(program); - }, - }); + return registerCommandGroupByName(program, resolveSubCliCommandGroups(), name); } export function registerSubCliCommands(program: Command, argv: string[] = process.argv) { - if (shouldEagerRegisterSubcommands()) { - for (const entry of entries) { - void entry.register(program); - } - return; - } - const primary = getPrimaryCommand(argv); - if (primary && shouldRegisterPrimarySubcommandOnly(argv)) { - const entry = entries.find((candidate) => candidate.name === primary); - if (entry) { - registerLazyCommand(program, entry); - return; - } - } - for (const candidate of entries) { - registerLazyCommand(program, candidate); - } + const { primary } = resolveCliArgvInvocation(argv); + registerCommandGroups(program, resolveSubCliCommandGroups(), { + eager: shouldEagerRegisterSubcommands(), + primary, + registerPrimaryOnly: Boolean(primary && shouldRegisterPrimarySubcommandOnly(argv)), + }); }