From 80a16339e1fc3a3eb33941b39946714e414b533a Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 22 Apr 2026 19:12:28 +0100 Subject: [PATCH] refactor: declare channel add flags in manifests --- extensions/bluebubbles/package.json | 8 +- extensions/googlechat/package.json | 20 +++- extensions/imessage/package.json | 16 +++- extensions/matrix/package.json | 22 +++++ extensions/nostr/package.json | 12 ++- extensions/signal/package.json | 16 +++- extensions/tlon/package.json | 28 +++++- extensions/whatsapp/package.json | 8 +- src/cli/channels-cli.ts | 93 ++++++++++--------- .../bundled-package-channel-metadata.ts | 2 +- src/plugins/manifest.ts | 7 ++ 11 files changed, 179 insertions(+), 53 deletions(-) diff --git a/extensions/bluebubbles/package.json b/extensions/bluebubbles/package.json index da46c46a98b..5652caa88ac 100644 --- a/extensions/bluebubbles/package.json +++ b/extensions/bluebubbles/package.json @@ -35,7 +35,13 @@ "imessage" ], "systemImage": "bubble.left.and.text.bubble.right", - "order": 75 + "order": 75, + "cliAddOptions": [ + { + "flags": "--webhook-path ", + "description": "BlueBubbles webhook path" + } + ] }, "install": { "npmSpec": "@openclaw/bluebubbles", diff --git a/extensions/googlechat/package.json b/extensions/googlechat/package.json index 213528d2641..2ee3fe67542 100644 --- a/extensions/googlechat/package.json +++ b/extensions/googlechat/package.json @@ -49,7 +49,25 @@ "groupModel": "route", "groupAllowFromFallbackToAllowFrom": false, "warnOnEmptyGroupSenderAllowlist": false - } + }, + "cliAddOptions": [ + { + "flags": "--webhook-path ", + "description": "Google Chat webhook path" + }, + { + "flags": "--webhook-url ", + "description": "Google Chat webhook URL" + }, + { + "flags": "--audience-type ", + "description": "Google Chat audience type (app-url|project-number)" + }, + { + "flags": "--audience ", + "description": "Google Chat audience value (app URL or project number)" + } + ] }, "install": { "npmSpec": "@openclaw/googlechat", diff --git a/extensions/imessage/package.json b/extensions/imessage/package.json index 874b9812664..58a266d8d95 100644 --- a/extensions/imessage/package.json +++ b/extensions/imessage/package.json @@ -23,7 +23,21 @@ "aliases": [ "imsg" ], - "systemImage": "message.fill" + "systemImage": "message.fill", + "cliAddOptions": [ + { + "flags": "--db-path ", + "description": "iMessage database path" + }, + { + "flags": "--service ", + "description": "iMessage service (imessage|sms|auto)" + }, + { + "flags": "--region ", + "description": "iMessage region (for SMS)" + } + ] } } } diff --git a/extensions/matrix/package.json b/extensions/matrix/package.json index 55ef273fecb..03ea15dd363 100644 --- a/extensions/matrix/package.json +++ b/extensions/matrix/package.json @@ -48,6 +48,28 @@ "groupAllowFromFallbackToAllowFrom": false, "warnOnEmptyGroupSenderAllowlist": true }, + "cliAddOptions": [ + { + "flags": "--homeserver ", + "description": "Matrix homeserver URL" + }, + { + "flags": "--user-id ", + "description": "Matrix user ID" + }, + { + "flags": "--access-token ", + "description": "Matrix access token" + }, + { + "flags": "--device-name ", + "description": "Matrix device name" + }, + { + "flags": "--initial-sync-limit ", + "description": "Matrix initial sync limit" + } + ], "persistedAuthState": { "specifier": "./auth-presence", "exportName": "hasAnyMatrixAuth" diff --git a/extensions/nostr/package.json b/extensions/nostr/package.json index b104a90d5f0..0a2585c3efa 100644 --- a/extensions/nostr/package.json +++ b/extensions/nostr/package.json @@ -32,7 +32,17 @@ "docsLabel": "nostr", "blurb": "Decentralized protocol; encrypted DMs via NIP-04.", "order": 55, - "quickstartAllowFrom": true + "quickstartAllowFrom": true, + "cliAddOptions": [ + { + "flags": "--private-key ", + "description": "Nostr private key (nsec... or hex)" + }, + { + "flags": "--relay-urls ", + "description": "Nostr relay URLs (comma-separated)" + } + ] }, "install": { "npmSpec": "@openclaw/nostr", diff --git a/extensions/signal/package.json b/extensions/signal/package.json index 3d3cce0f756..51481b99847 100644 --- a/extensions/signal/package.json +++ b/extensions/signal/package.json @@ -21,7 +21,21 @@ "docsLabel": "signal", "blurb": "signal-cli linked device; more setup (David Reagans: \"Hop on Discord.\").", "systemImage": "antenna.radiowaves.left.and.right", - "markdownCapable": true + "markdownCapable": true, + "cliAddOptions": [ + { + "flags": "--signal-number ", + "description": "Signal account number (E.164)" + }, + { + "flags": "--http-host ", + "description": "Signal HTTP daemon host" + }, + { + "flags": "--http-port ", + "description": "Signal HTTP daemon port" + } + ] } } } diff --git a/extensions/tlon/package.json b/extensions/tlon/package.json index cad08ba5d62..4834c91da7d 100644 --- a/extensions/tlon/package.json +++ b/extensions/tlon/package.json @@ -34,7 +34,33 @@ "docsLabel": "tlon", "blurb": "decentralized messaging on Urbit; install the plugin to enable.", "order": 90, - "quickstartAllowFrom": true + "quickstartAllowFrom": true, + "cliAddOptions": [ + { + "flags": "--ship ", + "description": "Tlon ship name (~sampel-palnet)" + }, + { + "flags": "--code ", + "description": "Tlon login code" + }, + { + "flags": "--group-channels ", + "description": "Tlon group channels (comma-separated)" + }, + { + "flags": "--dm-allowlist ", + "description": "Tlon DM allowlist (comma-separated ships)" + }, + { + "flags": "--auto-discover-channels", + "description": "Tlon auto-discover group channels" + }, + { + "flags": "--no-auto-discover-channels", + "description": "Disable Tlon auto-discovery" + } + ] }, "install": { "npmSpec": "@openclaw/tlon", diff --git a/extensions/whatsapp/package.json b/extensions/whatsapp/package.json index 58007e83391..e63f340c700 100644 --- a/extensions/whatsapp/package.json +++ b/extensions/whatsapp/package.json @@ -43,7 +43,13 @@ "persistedAuthState": { "specifier": "./auth-presence", "exportName": "hasAnyWhatsAppAuth" - } + }, + "cliAddOptions": [ + { + "flags": "--auth-dir ", + "description": "WhatsApp auth directory override" + } + ] }, "install": { "npmSpec": "@openclaw/whatsapp", diff --git a/src/cli/channels-cli.ts b/src/cli/channels-cli.ts index 45c8debd20a..890fac2e7a5 100644 --- a/src/cli/channels-cli.ts +++ b/src/cli/channels-cli.ts @@ -1,5 +1,6 @@ import type { Command } from "commander"; import { danger } from "../globals.js"; +import { listBundledPackageChannelMetadata } from "../plugins/bundled-package-channel-metadata.js"; import { defaultRuntime } from "../runtime.js"; import { formatDocsLink } from "../terminal/links.js"; import { theme } from "../terminal/theme.js"; @@ -35,6 +36,31 @@ function getOptionNames(command: Command): string[] { return command.options.map((option) => option.attributeName()); } +function addChannelSetupOptions(command: Command): Command { + const seenFlags = new Set(command.options.map((option) => option.flags)); + const channels = listBundledPackageChannelMetadata().toSorted((left, right) => { + const leftOrder = left.order ?? Number.MAX_SAFE_INTEGER; + const rightOrder = right.order ?? Number.MAX_SAFE_INTEGER; + return leftOrder === rightOrder + ? (left.id ?? "").localeCompare(right.id ?? "") + : leftOrder - rightOrder; + }); + for (const channel of channels) { + for (const option of channel.cliAddOptions ?? []) { + if (seenFlags.has(option.flags)) { + continue; + } + seenFlags.add(option.flags); + if (option.defaultValue !== undefined) { + command.option(option.flags, option.description, option.defaultValue); + } else { + command.option(option.flags, option.description); + } + } + } + return command; +} + export function registerChannelsCli(program: Command) { const channelNames = formatCliChannelOptions(); const channels = program @@ -134,52 +160,29 @@ export function registerChannelsCli(program: Command) { }); }); - 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("--private-key ", "Nostr private key (nsec... or hex)") - .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 ", "Webhook path (Google Chat/BlueBubbles)") - .option("--webhook-url ", "Google Chat webhook URL") - .option("--audience-type ", "Google Chat audience type (app-url|project-number)") - .option("--audience ", "Google Chat audience value (app URL or project number)") - .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("--ship ", "Tlon ship name (~sampel-palnet)") - .option("--url ", "Tlon ship URL") - .option("--relay-urls ", "Nostr relay URLs (comma-separated)") - .option("--code ", "Tlon login code") - .option("--group-channels ", "Tlon group channels (comma-separated)") - .option("--dm-allowlist ", "Tlon DM allowlist (comma-separated ships)") - .option("--auto-discover-channels", "Tlon auto-discover group channels") - .option("--no-auto-discover-channels", "Disable Tlon auto-discovery") - .option("--use-env", "Use env token (default account only)", false) - .action(async (opts, command) => { - await runChannelsCommand(async () => { - const { channelsAddCommand } = await loadChannelsCommands(); - const hasFlags = hasExplicitOptions(command, getOptionNames(command)); - await channelsAddCommand(opts, defaultRuntime, { hasFlags }); - }); + addChannelSetupOptions( + 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 ", "Channel token or credential payload") + .option("--token-file ", "Read channel token or credential payload from file") + .option("--bot-token ", "Bot token") + .option("--app-token ", "App token") + .option("--password ", "Channel password or login secret") + .option("--cli-path ", "Channel CLI path") + .option("--url ", "Channel setup URL") + .option("--http-url ", "Channel HTTP service URL") + .option("--use-env", "Use env-backed credentials when supported", false), + ).action(async (opts, command) => { + await runChannelsCommand(async () => { + const { channelsAddCommand } = await loadChannelsCommands(); + const hasFlags = hasExplicitOptions(command, getOptionNames(command)); + await channelsAddCommand(opts, defaultRuntime, { hasFlags }); }); + }); channels .command("remove") diff --git a/src/plugins/bundled-package-channel-metadata.ts b/src/plugins/bundled-package-channel-metadata.ts index 214fbb8ccc8..7d8694e282e 100644 --- a/src/plugins/bundled-package-channel-metadata.ts +++ b/src/plugins/bundled-package-channel-metadata.ts @@ -28,7 +28,7 @@ function readPackageManifest(pluginDir: string): PackageManifest | undefined { } } -function listBundledPackageChannelMetadata(): readonly PluginPackageChannel[] { +export function listBundledPackageChannelMetadata(): readonly PluginPackageChannel[] { if (bundledPackageChannelMetadataCache) { return bundledPackageChannelMetadataCache; } diff --git a/src/plugins/manifest.ts b/src/plugins/manifest.ts index 1845b8f0bf3..4d477420b75 100644 --- a/src/plugins/manifest.ts +++ b/src/plugins/manifest.ts @@ -960,6 +960,7 @@ export type PluginPackageChannel = { exportName?: string; }; doctorCapabilities?: PluginPackageChannelDoctorCapabilities; + cliAddOptions?: readonly PluginPackageChannelCliOption[]; }; export type PluginPackageChannelDoctorCapabilities = { @@ -969,6 +970,12 @@ export type PluginPackageChannelDoctorCapabilities = { warnOnEmptyGroupSenderAllowlist?: boolean; }; +export type PluginPackageChannelCliOption = { + flags: string; + description: string; + defaultValue?: boolean | string; +}; + export type PluginPackageInstall = { npmSpec?: string; localPath?: string;