286 lines
7.8 KiB
TypeScript
286 lines
7.8 KiB
TypeScript
import type { Command } from "commander";
|
|
import type { ClawdbotConfig } 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.CLAWDBOT_DISABLE_LAZY_SUBCOMMANDS)) return false;
|
|
if (hasHelpOrVersion(argv)) return false;
|
|
return true;
|
|
};
|
|
|
|
const shouldEagerRegisterSubcommands = (_argv: string[]) => {
|
|
return isTruthyEnvValue(process.env.CLAWDBOT_DISABLE_LAZY_SUBCOMMANDS);
|
|
};
|
|
|
|
const loadConfig = async (): Promise<ClawdbotConfig> => {
|
|
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: "daemon",
|
|
description: "Manage the gateway daemon",
|
|
register: async (program) => {
|
|
const mod = await import("../daemon-cli.js");
|
|
mod.registerDaemonCli(program);
|
|
},
|
|
},
|
|
{
|
|
name: "gateway",
|
|
description: "Gateway control",
|
|
register: async (program) => {
|
|
const mod = await import("../gateway-cli.js");
|
|
mod.registerGatewayCli(program);
|
|
},
|
|
},
|
|
{
|
|
name: "service",
|
|
description: "Service helpers",
|
|
register: async (program) => {
|
|
const mod = await import("../service-cli.js");
|
|
mod.registerServiceCli(program);
|
|
},
|
|
},
|
|
{
|
|
name: "logs",
|
|
description: "Gateway logs",
|
|
register: async (program) => {
|
|
const mod = await import("../logs-cli.js");
|
|
mod.registerLogsCli(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 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);
|
|
},
|
|
},
|
|
];
|
|
|
|
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);
|
|
}
|
|
}
|