diff --git a/src/cli/channels-cli.ts b/src/cli/channels-cli.ts index d4ad1fef45b..45c8debd20a 100644 --- a/src/cli/channels-cli.ts +++ b/src/cli/channels-cli.ts @@ -11,44 +11,6 @@ import { formatHelpExamples } from "./help-format.js"; type ChannelsCommandsModule = typeof import("../commands/channels.js"); -const optionNamesAdd = [ - "channel", - "account", - "name", - "token", - "privateKey", - "tokenFile", - "botToken", - "appToken", - "signalNumber", - "cliPath", - "dbPath", - "service", - "region", - "authDir", - "httpUrl", - "httpHost", - "httpPort", - "webhookPath", - "webhookUrl", - "audienceType", - "audience", - "useEnv", - "homeserver", - "userId", - "accessToken", - "password", - "deviceName", - "initialSyncLimit", - "ship", - "url", - "relayUrls", - "code", - "groupChannels", - "dmAllowlist", - "autoDiscoverChannels", -] as const; - const optionNamesRemove = ["channel", "account", "delete"] as const; let channelsCommandsPromise: Promise | undefined; @@ -69,6 +31,10 @@ function runChannelsCommandWithDanger(action: () => Promise, label: string }); } +function getOptionNames(command: Command): string[] { + return command.options.map((option) => option.attributeName()); +} + export function registerChannelsCli(program: Command) { const channelNames = formatCliChannelOptions(); const channels = program @@ -210,7 +176,7 @@ export function registerChannelsCli(program: Command) { .action(async (opts, command) => { await runChannelsCommand(async () => { const { channelsAddCommand } = await loadChannelsCommands(); - const hasFlags = hasExplicitOptions(command, optionNamesAdd); + const hasFlags = hasExplicitOptions(command, getOptionNames(command)); await channelsAddCommand(opts, defaultRuntime, { hasFlags }); }); }); diff --git a/src/commands/channels/add.ts b/src/commands/channels/add.ts index 347acb8908b..76c1adaaf2b 100644 --- a/src/commands/channels/add.ts +++ b/src/commands/channels/add.ts @@ -34,10 +34,9 @@ function loadOnboardChannels(): Promise { export type ChannelsAddOptions = { channel?: string; account?: string; - initialSyncLimit?: number | string; - groupChannels?: string; - dmAllowlist?: string; -} & Omit; +} & Record; + +const CHANNEL_ADD_CONTROL_OPTION_KEYS = new Set(["channel", "account"]); async function resolveCatalogChannelEntry(raw: string, cfg: OpenClawConfig | null) { const trimmed = normalizeOptionalLowercaseString(raw); @@ -56,6 +55,38 @@ async function resolveCatalogChannelEntry(raw: string, cfg: OpenClawConfig | nul }); } +function parseOptionalInt(value: unknown): number | undefined { + if (typeof value === "number") { + return value; + } + if (typeof value === "string" && value.trim()) { + return Number.parseInt(value, 10); + } + return undefined; +} + +function parseOptionalDelimitedInput(value: unknown): string[] | undefined { + if (Array.isArray(value)) { + return value.filter((entry): entry is string => typeof entry === "string"); + } + return parseOptionalDelimitedEntries(typeof value === "string" ? value : undefined); +} + +function buildChannelSetupInput(opts: ChannelsAddOptions): ChannelSetupInput { + const input: Record = {}; + for (const [key, value] of Object.entries(opts)) { + if (CHANNEL_ADD_CONTROL_OPTION_KEYS.has(key) || value === undefined) { + continue; + } + input[key] = value; + } + + input.initialSyncLimit = parseOptionalInt(opts.initialSyncLimit); + input.groupChannels = parseOptionalDelimitedInput(opts.groupChannels); + input.dmAllowlist = parseOptionalDelimitedInput(opts.dmAllowlist); + return input as ChannelSetupInput; +} + export async function channelsAddCommand( opts: ChannelsAddOptions, runtime: RuntimeEnv = defaultRuntime, @@ -282,51 +313,7 @@ export async function channelsAddCommand( runtime.exit(1); return; } - const useEnv = opts.useEnv === true; - const initialSyncLimit = - typeof opts.initialSyncLimit === "number" - ? opts.initialSyncLimit - : typeof opts.initialSyncLimit === "string" && opts.initialSyncLimit.trim() - ? Number.parseInt(opts.initialSyncLimit, 10) - : undefined; - const groupChannels = parseOptionalDelimitedEntries(opts.groupChannels); - const dmAllowlist = parseOptionalDelimitedEntries(opts.dmAllowlist); - - const input: ChannelSetupInput = { - name: opts.name, - token: opts.token, - privateKey: opts.privateKey, - tokenFile: opts.tokenFile, - botToken: opts.botToken, - appToken: opts.appToken, - signalNumber: opts.signalNumber, - cliPath: opts.cliPath, - dbPath: opts.dbPath, - service: opts.service, - region: opts.region, - authDir: opts.authDir, - httpUrl: opts.httpUrl, - httpHost: opts.httpHost, - httpPort: opts.httpPort, - webhookPath: opts.webhookPath, - webhookUrl: opts.webhookUrl, - audienceType: opts.audienceType, - audience: opts.audience, - homeserver: opts.homeserver, - userId: opts.userId, - accessToken: opts.accessToken, - password: opts.password, - deviceName: opts.deviceName, - initialSyncLimit, - useEnv, - ship: opts.ship, - url: opts.url, - relayUrls: opts.relayUrls, - code: opts.code, - groupChannels, - dmAllowlist, - autoDiscoverChannels: opts.autoDiscoverChannels, - }; + const input = buildChannelSetupInput(opts); const accountId = plugin.setup.resolveAccountId?.({ cfg: nextConfig,