diff --git a/docs/cli/channels.md b/docs/cli/channels.md index f08c553fe..5b3463579 100644 --- a/docs/cli/channels.md +++ b/docs/cli/channels.md @@ -40,6 +40,27 @@ moltbot channels login --channel whatsapp moltbot channels logout --channel whatsapp ``` +### JSON mode (programmatic) + +For programmatic use (e.g., web dashboards, hosting platforms), use `--json` to get QR data as JSON instead of terminal output: + +```bash +moltbot channels login --channel whatsapp --json --timeout 60000 +``` + +This outputs two JSON objects: +1. Initial response with QR data: +```json +{"status":"pending","qrDataUrl":"data:image/png;base64,...","message":"Scan this QR...","accountId":"default","channel":"whatsapp"} +``` + +2. Final response after scan (or timeout): +```json +{"status":"connected","connected":true,"message":"✅ Linked!","accountId":"default","channel":"whatsapp"} +``` + +The `qrDataUrl` is a base64-encoded PNG that can be displayed directly in an `` tag. + ## Troubleshooting - Run `moltbot status --deep` for a broad probe. diff --git a/src/cli/channel-auth.ts b/src/cli/channel-auth.ts index f7c9d85ea..210719a72 100644 --- a/src/cli/channel-auth.ts +++ b/src/cli/channel-auth.ts @@ -9,6 +9,8 @@ type ChannelAuthOptions = { channel?: string; account?: string; verbose?: boolean; + json?: boolean; + timeoutMs?: number; }; export async function runChannelLogin( @@ -21,6 +23,54 @@ export async function runChannelLogin( throw new Error(`Unsupported channel: ${channelInput}`); } const plugin = getChannelPlugin(channelId); + + // JSON mode: use gateway QR login methods for programmatic access + if (opts.json) { + if (!plugin?.gateway?.loginWithQrStart || !plugin?.gateway?.loginWithQrWait) { + throw new Error(`Channel ${channelId} does not support JSON login mode`); + } + setVerbose(Boolean(opts.verbose)); + const cfg = loadConfig(); + const accountId = opts.account?.trim() || resolveChannelDefaultAccountId({ plugin, cfg }); + const timeoutMs = opts.timeoutMs ?? 60000; + + // Start QR login and get QR data + const startResult = await plugin.gateway.loginWithQrStart({ + accountId, + force: false, + timeoutMs, + verbose: Boolean(opts.verbose), + }); + + // Output QR data as JSON + const output = { + status: "pending", + qrDataUrl: startResult.qrDataUrl ?? null, + message: startResult.message, + accountId, + channel: channelId, + }; + runtime.log(JSON.stringify(output)); + + // Wait for connection + const waitResult = await plugin.gateway.loginWithQrWait({ + accountId, + timeoutMs, + }); + + // Output final result + const finalOutput = { + status: waitResult.connected ? "connected" : "failed", + connected: waitResult.connected, + message: waitResult.message, + accountId, + channel: channelId, + }; + runtime.log(JSON.stringify(finalOutput)); + return; + } + + // Standard interactive mode if (!plugin?.auth?.login) { throw new Error(`Channel ${channelId} does not support login`); } diff --git a/src/cli/channels-cli.ts b/src/cli/channels-cli.ts index 6d90ae935..0f32b1690 100644 --- a/src/cli/channels-cli.ts +++ b/src/cli/channels-cli.ts @@ -215,6 +215,8 @@ export function registerChannelsCli(program: Command) { .option("--channel ", "Channel alias (default: whatsapp)") .option("--account ", "Account id (accountId)") .option("--verbose", "Verbose connection logs", false) + .option("--json", "Output QR data as JSON (for programmatic use)", false) + .option("--timeout ", "Timeout for QR generation in ms", "60000") .action(async (opts) => { await runChannelsCommandWithDanger(async () => { await runChannelLogin( @@ -222,6 +224,8 @@ export function registerChannelsCli(program: Command) { channel: opts.channel as string | undefined, account: opts.account as string | undefined, verbose: Boolean(opts.verbose), + json: Boolean(opts.json), + timeoutMs: parseInt(String(opts.timeout), 10) || 60000, }, defaultRuntime, );