| import type { Command } from "commander"; |
| import { loadConfig, readConfigFileSnapshot, type OpenClawConfig } from "../../config/config.js"; |
| import { isTruthyEnvValue } from "../../infra/env.js"; |
| import { getPrimaryCommand, hasHelpOrVersion } from "../argv.js"; |
| import { reparseProgramFromActionArgs } from "./action-reparse.js"; |
| import { removeCommand, removeCommandByName } from "./command-tree.js"; |
|
|
| type SubCliRegistrar = (program: Command) => Promise<void> | void; |
|
|
| type SubCliEntry = { |
| name: string; |
| description: string; |
| hasSubcommands: boolean; |
| 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); |
| }; |
|
|
| export const loadValidatedConfigForPluginRegistration = |
| async (): Promise<OpenClawConfig | null> => { |
| const snapshot = await readConfigFileSnapshot(); |
| if (!snapshot.valid) { |
| return null; |
| } |
| return loadConfig(); |
| }; |
|
|
| |
| |
| |
| const entries: SubCliEntry[] = [ |
| { |
| name: "acp", |
| description: "Agent Control Protocol tools", |
| hasSubcommands: true, |
| register: async (program) => { |
| const mod = await import("../acp-cli.js"); |
| mod.registerAcpCli(program); |
| }, |
| }, |
| { |
| name: "gateway", |
| description: "Run, inspect, and query the WebSocket Gateway", |
| hasSubcommands: true, |
| register: async (program) => { |
| const mod = await import("../gateway-cli.js"); |
| mod.registerGatewayCli(program); |
| }, |
| }, |
| { |
| name: "daemon", |
| description: "Gateway service (legacy alias)", |
| hasSubcommands: true, |
| register: async (program) => { |
| const mod = await import("../daemon-cli.js"); |
| mod.registerDaemonCli(program); |
| }, |
| }, |
| { |
| name: "logs", |
| description: "Tail gateway file logs via RPC", |
| hasSubcommands: false, |
| register: async (program) => { |
| const mod = await import("../logs-cli.js"); |
| mod.registerLogsCli(program); |
| }, |
| }, |
| { |
| name: "system", |
| description: "System events, heartbeat, and presence", |
| hasSubcommands: true, |
| register: async (program) => { |
| const mod = await import("../system-cli.js"); |
| mod.registerSystemCli(program); |
| }, |
| }, |
| { |
| name: "models", |
| description: "Discover, scan, and configure models", |
| hasSubcommands: true, |
| register: async (program) => { |
| const mod = await import("../models-cli.js"); |
| mod.registerModelsCli(program); |
| }, |
| }, |
| { |
| name: "approvals", |
| description: "Manage exec approvals (gateway or node host)", |
| hasSubcommands: true, |
| register: async (program) => { |
| const mod = await import("../exec-approvals-cli.js"); |
| mod.registerExecApprovalsCli(program); |
| }, |
| }, |
| { |
| name: "nodes", |
| description: "Manage gateway-owned node pairing and node commands", |
| hasSubcommands: true, |
| register: async (program) => { |
| const mod = await import("../nodes-cli.js"); |
| mod.registerNodesCli(program); |
| }, |
| }, |
| { |
| name: "devices", |
| description: "Device pairing + token management", |
| hasSubcommands: true, |
| register: async (program) => { |
| const mod = await import("../devices-cli.js"); |
| mod.registerDevicesCli(program); |
| }, |
| }, |
| { |
| name: "node", |
| description: "Run and manage the headless node host service", |
| hasSubcommands: true, |
| register: async (program) => { |
| const mod = await import("../node-cli.js"); |
| mod.registerNodeCli(program); |
| }, |
| }, |
| { |
| name: "sandbox", |
| description: "Manage sandbox containers for agent isolation", |
| hasSubcommands: true, |
| register: async (program) => { |
| const mod = await import("../sandbox-cli.js"); |
| mod.registerSandboxCli(program); |
| }, |
| }, |
| { |
| name: "tui", |
| description: "Open a terminal UI connected to the Gateway", |
| hasSubcommands: false, |
| register: async (program) => { |
| const mod = await import("../tui-cli.js"); |
| mod.registerTuiCli(program); |
| }, |
| }, |
| { |
| name: "cron", |
| description: "Manage cron jobs via the Gateway scheduler", |
| hasSubcommands: true, |
| register: async (program) => { |
| const mod = await import("../cron-cli.js"); |
| mod.registerCronCli(program); |
| }, |
| }, |
| { |
| name: "dns", |
| description: "DNS helpers for wide-area discovery (Tailscale + CoreDNS)", |
| hasSubcommands: true, |
| register: async (program) => { |
| const mod = await import("../dns-cli.js"); |
| mod.registerDnsCli(program); |
| }, |
| }, |
| { |
| name: "docs", |
| description: "Search the live docs", |
| hasSubcommands: false, |
| register: async (program) => { |
| const mod = await import("../docs-cli.js"); |
| mod.registerDocsCli(program); |
| }, |
| }, |
| { |
| name: "hooks", |
| description: "Manage internal agent hooks", |
| hasSubcommands: true, |
| register: async (program) => { |
| const mod = await import("../hooks-cli.js"); |
| mod.registerHooksCli(program); |
| }, |
| }, |
| { |
| name: "webhooks", |
| description: "Webhook helpers and integrations", |
| hasSubcommands: true, |
| register: async (program) => { |
| const mod = await import("../webhooks-cli.js"); |
| mod.registerWebhooksCli(program); |
| }, |
| }, |
| { |
| name: "qr", |
| description: "Generate iOS pairing QR/setup code", |
| hasSubcommands: false, |
| register: async (program) => { |
| const mod = await import("../qr-cli.js"); |
| mod.registerQrCli(program); |
| }, |
| }, |
| { |
| name: "clawbot", |
| description: "Legacy clawbot command aliases", |
| hasSubcommands: true, |
| register: async (program) => { |
| const mod = await import("../clawbot-cli.js"); |
| mod.registerClawbotCli(program); |
| }, |
| }, |
| { |
| name: "pairing", |
| description: "Secure DM pairing (approve inbound requests)", |
| hasSubcommands: true, |
| register: async (program) => { |
| |
| |
| |
| const { registerPluginCliCommands } = await import("../../plugins/cli.js"); |
| const config = await loadValidatedConfigForPluginRegistration(); |
| if (config) { |
| registerPluginCliCommands(program, config); |
| } |
| const mod = await import("../pairing-cli.js"); |
| mod.registerPairingCli(program); |
| }, |
| }, |
| { |
| name: "plugins", |
| description: "Manage OpenSkyNet plugins and extensions", |
| hasSubcommands: true, |
| register: async (program) => { |
| const mod = await import("../plugins-cli.js"); |
| mod.registerPluginsCli(program); |
| const { registerPluginCliCommands } = await import("../../plugins/cli.js"); |
| const config = await loadValidatedConfigForPluginRegistration(); |
| if (config) { |
| registerPluginCliCommands(program, config); |
| } |
| }, |
| }, |
| { |
| name: "channels", |
| description: "Manage connected chat channels (Telegram, Discord, etc.)", |
| hasSubcommands: true, |
| register: async (program) => { |
| const mod = await import("../channels-cli.js"); |
| mod.registerChannelsCli(program); |
| }, |
| }, |
| { |
| name: "directory", |
| description: "Lookup contact and group IDs (self, peers, groups) for supported chat channels", |
| hasSubcommands: true, |
| register: async (program) => { |
| const mod = await import("../directory-cli.js"); |
| mod.registerDirectoryCli(program); |
| }, |
| }, |
| { |
| name: "security", |
| description: "Security tools and local config audits", |
| hasSubcommands: true, |
| register: async (program) => { |
| const mod = await import("../security-cli.js"); |
| mod.registerSecurityCli(program); |
| }, |
| }, |
| { |
| name: "secrets", |
| description: "Secrets runtime reload controls", |
| hasSubcommands: true, |
| register: async (program) => { |
| const mod = await import("../secrets-cli.js"); |
| mod.registerSecretsCli(program); |
| }, |
| }, |
| { |
| name: "skills", |
| description: "List and inspect available skills", |
| hasSubcommands: true, |
| register: async (program) => { |
| const mod = await import("../skills-cli.js"); |
| mod.registerSkillsCli(program); |
| }, |
| }, |
| { |
| name: "update", |
| description: "Update OpenSkyNet and inspect update channel status", |
| hasSubcommands: true, |
| register: async (program) => { |
| const mod = await import("../update-cli.js"); |
| mod.registerUpdateCli(program); |
| }, |
| }, |
| { |
| name: "completion", |
| description: "Generate shell completion script", |
| hasSubcommands: false, |
| register: async (program) => { |
| const mod = await import("../completion-cli.js"); |
| mod.registerCompletionCli(program); |
| }, |
| }, |
| ]; |
|
|
| export function getSubCliEntries(): SubCliEntry[] { |
| return entries; |
| } |
|
|
| export function getSubCliCommandsWithSubcommands(): string[] { |
| return entries.filter((entry) => entry.hasSubcommands).map((entry) => entry.name); |
| } |
|
|
| export async function registerSubCliByName(program: Command, name: string): Promise<boolean> { |
| 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) { |
| 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); |
| await reparseProgramFromActionArgs(program, actionArgs); |
| }); |
| } |
|
|
| 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); |
| } |
| } |
|
|