import type { Command } from "commander"; import { listChannelPlugins } from "../channels/plugins/index.js"; import { channelsAddCommand, channelsCapabilitiesCommand, channelsListCommand, channelsLogsCommand, channelsRemoveCommand, channelsResolveCommand, channelsStatusCommand, } from "../commands/channels.js"; import { danger } from "../globals.js"; import { defaultRuntime } from "../runtime.js"; import { formatDocsLink } from "../terminal/links.js"; import { theme } from "../terminal/theme.js"; import { runChannelLogin, runChannelLogout } from "./channel-auth.js"; import { runCommandWithRuntime } from "./cli-utils.js"; import { hasExplicitOptions } from "./command-options.js"; const optionNamesAdd = [ "channel", "account", "name", "token", "tokenFile", "botToken", "appToken", "signalNumber", "cliPath", "dbPath", "service", "region", "authDir", "httpUrl", "httpHost", "httpPort", "webhookPath", "useEnv", "homeserver", "userId", "accessToken", "password", "deviceName", "initialSyncLimit", ] as const; const optionNamesRemove = ["channel", "account", "delete"] as const; function runChannelsCommand(action: () => Promise) { return runCommandWithRuntime(defaultRuntime, action); } function runChannelsCommandWithDanger(action: () => Promise, label: string) { return runCommandWithRuntime(defaultRuntime, action, (err) => { defaultRuntime.error(danger(`${label}: ${String(err)}`)); defaultRuntime.exit(1); }); } export function registerChannelsCli(program: Command) { const channelNames = listChannelPlugins() .map((plugin) => plugin.id) .join("|"); const channels = program .command("channels") .description("Manage chat channel accounts") .addHelpText( "after", () => `\n${theme.muted("Docs:")} ${formatDocsLink( "/cli/channels", "docs.clawd.bot/cli/channels", )}\n`, ); channels .command("list") .description("List configured channels + auth profiles") .option("--no-usage", "Skip model provider usage/quota snapshots") .option("--json", "Output JSON", false) .action(async (opts) => { await runChannelsCommand(async () => { await channelsListCommand(opts, defaultRuntime); }); }); channels .command("status") .description("Show gateway channel status (use status --deep for local)") .option("--probe", "Probe channel credentials", false) .option("--timeout ", "Timeout in ms", "10000") .option("--json", "Output JSON", false) .action(async (opts) => { await runChannelsCommand(async () => { await channelsStatusCommand(opts, defaultRuntime); }); }); channels .command("capabilities") .description("Show provider capabilities (intents/scopes + supported features)") .option("--channel ", `Channel (${channelNames}|all)`) .option("--account ", "Account id (only with --channel)") .option("--target ", "Channel target for permission audit (Discord channel:)") .option("--timeout ", "Timeout in ms", "10000") .option("--json", "Output JSON", false) .action(async (opts) => { await runChannelsCommand(async () => { await channelsCapabilitiesCommand(opts, defaultRuntime); }); }); channels .command("resolve") .description("Resolve channel/user names to IDs") .argument("", "Entries to resolve (names or ids)") .option("--channel ", `Channel (${channelNames})`) .option("--account ", "Account id (accountId)") .option("--kind ", "Target kind (auto|user|group)", "auto") .option("--json", "Output JSON", false) .action(async (entries, opts) => { await runChannelsCommand(async () => { await channelsResolveCommand( { channel: opts.channel as string | undefined, account: opts.account as string | undefined, kind: opts.kind as "auto" | "user" | "group", json: Boolean(opts.json), entries: Array.isArray(entries) ? entries : [String(entries)], }, defaultRuntime, ); }); }); channels .command("logs") .description("Show recent channel logs from the gateway log file") .option("--channel ", `Channel (${channelNames}|all)`, "all") .option("--lines ", "Number of lines (default: 200)", "200") .option("--json", "Output JSON", false) .action(async (opts) => { await runChannelsCommand(async () => { await channelsLogsCommand(opts, defaultRuntime); }); }); channels .command("add") .description("Add or update a channel account") .option("--channel ", `Channel (${channelNames})`) .option("--account ", "Account id (default when omitted)") .option("--name ", "Display name for this account") .option("--token ", "Bot token (Telegram/Discord)") .option("--token-file ", "Bot token file (Telegram)") .option("--bot-token ", "Slack bot token (xoxb-...)") .option("--app-token ", "Slack app token (xapp-...)") .option("--signal-number ", "Signal account number (E.164)") .option("--cli-path ", "CLI path (signal-cli or imsg)") .option("--db-path ", "iMessage database path") .option("--service ", "iMessage service (imessage|sms|auto)") .option("--region ", "iMessage region (for SMS)") .option("--auth-dir ", "WhatsApp auth directory override") .option("--http-url ", "Signal HTTP daemon base URL") .option("--http-host ", "Signal HTTP host") .option("--http-port ", "Signal HTTP port") .option("--webhook-path ", "BlueBubbles webhook path") .option("--homeserver ", "Matrix homeserver URL") .option("--user-id ", "Matrix user ID") .option("--access-token ", "Matrix access token") .option("--password ", "Matrix password") .option("--device-name ", "Matrix device name") .option("--initial-sync-limit ", "Matrix initial sync limit") .option("--use-env", "Use env token (default account only)", false) .action(async (opts, command) => { await runChannelsCommand(async () => { const hasFlags = hasExplicitOptions(command, optionNamesAdd); await channelsAddCommand(opts, defaultRuntime, { hasFlags }); }); }); channels .command("remove") .description("Disable or delete a channel account") .option("--channel ", `Channel (${channelNames})`) .option("--account ", "Account id (default when omitted)") .option("--delete", "Delete config entries (no prompt)", false) .action(async (opts, command) => { await runChannelsCommand(async () => { const hasFlags = hasExplicitOptions(command, optionNamesRemove); await channelsRemoveCommand(opts, defaultRuntime, { hasFlags }); }); }); channels .command("login") .description("Link a channel account (if supported)") .option("--channel ", "Channel alias (default: whatsapp)") .option("--account ", "Account id (accountId)") .option("--verbose", "Verbose connection logs", false) .action(async (opts) => { await runChannelsCommandWithDanger(async () => { await runChannelLogin( { channel: opts.channel as string | undefined, account: opts.account as string | undefined, verbose: Boolean(opts.verbose), }, defaultRuntime, ); }, "Channel login failed"); }); channels .command("logout") .description("Log out of a channel session (if supported)") .option("--channel ", "Channel alias (default: whatsapp)") .option("--account ", "Account id (accountId)") .action(async (opts) => { await runChannelsCommandWithDanger(async () => { await runChannelLogout( { channel: opts.channel as string | undefined, account: opts.account as string | undefined, }, defaultRuntime, ); }, "Channel logout failed"); }); }