| import type { Command } from "commander"; |
| import type { OpenClawConfig } from "../../config/config.js"; |
| import { isTruthyEnvValue } from "../../infra/env.js"; |
| import { buildParseArgv, getPrimaryCommand, hasHelpOrVersion } from "../argv.js"; |
| import { resolveActionArgs } from "./helpers.js"; |
|
|
| type SubCliRegistrar = (program: Command) => Promise<void> | void; |
|
|
| type SubCliEntry = { |
| name: string; |
| description: string; |
| register: SubCliRegistrar; |
| }; |
|
|
| const shouldRegisterPrimaryOnly = (argv: string[]) => { |
| if (isTruthyEnvValue(process.env.OPENCLAW_DISABLE_LAZY_SUBCOMMANDS)) { |
| return false; |
| } |
| if (hasHelpOrVersion(argv)) { |
| return false; |
| } |
| return true; |
| }; |
|
|
| const shouldEagerRegisterSubcommands = (_argv: string[]) => { |
| return isTruthyEnvValue(process.env.OPENCLAW_DISABLE_LAZY_SUBCOMMANDS); |
| }; |
|
|
| const loadConfig = async (): Promise<OpenClawConfig> => { |
| const mod = await import("../../config/config.js"); |
| return mod.loadConfig(); |
| }; |
|
|
| const entries: SubCliEntry[] = [ |
| { |
| name: "acp", |
| description: "Agent Control Protocol tools", |
| register: async (program) => { |
| const mod = await import("../acp-cli.js"); |
| mod.registerAcpCli(program); |
| }, |
| }, |
| { |
| name: "gateway", |
| description: "Gateway control", |
| register: async (program) => { |
| const mod = await import("../gateway-cli.js"); |
| mod.registerGatewayCli(program); |
| }, |
| }, |
| { |
| name: "daemon", |
| description: "Gateway service (legacy alias)", |
| register: async (program) => { |
| const mod = await import("../daemon-cli.js"); |
| mod.registerDaemonCli(program); |
| }, |
| }, |
| { |
| name: "logs", |
| description: "Gateway logs", |
| register: async (program) => { |
| const mod = await import("../logs-cli.js"); |
| mod.registerLogsCli(program); |
| }, |
| }, |
| { |
| name: "system", |
| description: "System events, heartbeat, and presence", |
| register: async (program) => { |
| const mod = await import("../system-cli.js"); |
| mod.registerSystemCli(program); |
| }, |
| }, |
| { |
| name: "models", |
| description: "Model configuration", |
| register: async (program) => { |
| const mod = await import("../models-cli.js"); |
| mod.registerModelsCli(program); |
| }, |
| }, |
| { |
| name: "approvals", |
| description: "Exec approvals", |
| register: async (program) => { |
| const mod = await import("../exec-approvals-cli.js"); |
| mod.registerExecApprovalsCli(program); |
| }, |
| }, |
| { |
| name: "nodes", |
| description: "Node commands", |
| register: async (program) => { |
| const mod = await import("../nodes-cli.js"); |
| mod.registerNodesCli(program); |
| }, |
| }, |
| { |
| name: "devices", |
| description: "Device pairing + token management", |
| register: async (program) => { |
| const mod = await import("../devices-cli.js"); |
| mod.registerDevicesCli(program); |
| }, |
| }, |
| { |
| name: "node", |
| description: "Node control", |
| register: async (program) => { |
| const mod = await import("../node-cli.js"); |
| mod.registerNodeCli(program); |
| }, |
| }, |
| { |
| name: "sandbox", |
| description: "Sandbox tools", |
| register: async (program) => { |
| const mod = await import("../sandbox-cli.js"); |
| mod.registerSandboxCli(program); |
| }, |
| }, |
| { |
| name: "tui", |
| description: "Terminal UI", |
| register: async (program) => { |
| const mod = await import("../tui-cli.js"); |
| mod.registerTuiCli(program); |
| }, |
| }, |
| { |
| name: "cron", |
| description: "Cron scheduler", |
| register: async (program) => { |
| const mod = await import("../cron-cli.js"); |
| mod.registerCronCli(program); |
| }, |
| }, |
| { |
| name: "dns", |
| description: "DNS helpers", |
| register: async (program) => { |
| const mod = await import("../dns-cli.js"); |
| mod.registerDnsCli(program); |
| }, |
| }, |
| { |
| name: "docs", |
| description: "Docs helpers", |
| register: async (program) => { |
| const mod = await import("../docs-cli.js"); |
| mod.registerDocsCli(program); |
| }, |
| }, |
| { |
| name: "hooks", |
| description: "Hooks tooling", |
| register: async (program) => { |
| const mod = await import("../hooks-cli.js"); |
| mod.registerHooksCli(program); |
| }, |
| }, |
| { |
| name: "webhooks", |
| description: "Webhook helpers", |
| register: async (program) => { |
| const mod = await import("../webhooks-cli.js"); |
| mod.registerWebhooksCli(program); |
| }, |
| }, |
| { |
| name: "pairing", |
| description: "Pairing helpers", |
| register: async (program) => { |
| |
| |
| |
| const { registerPluginCliCommands } = await import("../../plugins/cli.js"); |
| registerPluginCliCommands(program, await loadConfig()); |
| const mod = await import("../pairing-cli.js"); |
| mod.registerPairingCli(program); |
| }, |
| }, |
| { |
| name: "plugins", |
| description: "Plugin management", |
| register: async (program) => { |
| const mod = await import("../plugins-cli.js"); |
| mod.registerPluginsCli(program); |
| const { registerPluginCliCommands } = await import("../../plugins/cli.js"); |
| registerPluginCliCommands(program, await loadConfig()); |
| }, |
| }, |
| { |
| name: "channels", |
| description: "Channel management", |
| register: async (program) => { |
| const mod = await import("../channels-cli.js"); |
| mod.registerChannelsCli(program); |
| }, |
| }, |
| { |
| name: "directory", |
| description: "Directory commands", |
| register: async (program) => { |
| const mod = await import("../directory-cli.js"); |
| mod.registerDirectoryCli(program); |
| }, |
| }, |
| { |
| name: "security", |
| description: "Security helpers", |
| register: async (program) => { |
| const mod = await import("../security-cli.js"); |
| mod.registerSecurityCli(program); |
| }, |
| }, |
| { |
| name: "skills", |
| description: "Skills management", |
| register: async (program) => { |
| const mod = await import("../skills-cli.js"); |
| mod.registerSkillsCli(program); |
| }, |
| }, |
| { |
| name: "update", |
| description: "CLI update helpers", |
| register: async (program) => { |
| const mod = await import("../update-cli.js"); |
| mod.registerUpdateCli(program); |
| }, |
| }, |
| { |
| name: "completion", |
| description: "Generate shell completion script", |
| register: async (program) => { |
| const mod = await import("../completion-cli.js"); |
| mod.registerCompletionCli(program); |
| }, |
| }, |
| ]; |
|
|
| export function getSubCliEntries(): SubCliEntry[] { |
| return entries; |
| } |
|
|
| function removeCommand(program: Command, command: Command) { |
| const commands = program.commands as Command[]; |
| const index = commands.indexOf(command); |
| if (index >= 0) { |
| commands.splice(index, 1); |
| } |
| } |
|
|
| export async function registerSubCliByName(program: Command, name: string): Promise<boolean> { |
| const entry = entries.find((candidate) => candidate.name === name); |
| if (!entry) { |
| return false; |
| } |
| const existing = program.commands.find((cmd) => cmd.name() === entry.name); |
| if (existing) { |
| removeCommand(program, existing); |
| } |
| await entry.register(program); |
| return true; |
| } |
|
|
| function registerLazyCommand(program: Command, entry: SubCliEntry) { |
| const placeholder = program.command(entry.name).description(entry.description); |
| placeholder.allowUnknownOption(true); |
| placeholder.allowExcessArguments(true); |
| placeholder.action(async (...actionArgs) => { |
| removeCommand(program, placeholder); |
| await entry.register(program); |
| const actionCommand = actionArgs.at(-1) as Command | undefined; |
| const root = actionCommand?.parent ?? program; |
| const rawArgs = (root as Command & { rawArgs?: string[] }).rawArgs; |
| const actionArgsList = resolveActionArgs(actionCommand); |
| const fallbackArgv = actionCommand?.name() |
| ? [actionCommand.name(), ...actionArgsList] |
| : actionArgsList; |
| const parseArgv = buildParseArgv({ |
| programName: program.name(), |
| rawArgs, |
| fallbackArgv, |
| }); |
| await program.parseAsync(parseArgv); |
| }); |
| } |
|
|
| export function registerSubCliCommands(program: Command, argv: string[] = process.argv) { |
| if (shouldEagerRegisterSubcommands(argv)) { |
| for (const entry of entries) { |
| void entry.register(program); |
| } |
| return; |
| } |
| const primary = getPrimaryCommand(argv); |
| if (primary && shouldRegisterPrimaryOnly(argv)) { |
| const entry = entries.find((candidate) => candidate.name === primary); |
| if (entry) { |
| registerLazyCommand(program, entry); |
| return; |
| } |
| } |
| for (const candidate of entries) { |
| registerLazyCommand(program, candidate); |
| } |
| } |
|
|