From d3cf62a1e58787532d5455db0def4608134700fd Mon Sep 17 00:00:00 2001 From: Drax Date: Wed, 28 Jan 2026 23:36:31 +0100 Subject: [PATCH 1/2] feat(cli): add --json flag to channels login for programmatic QR access Adds JSON output mode for that returns QR code as base64 PNG data URL instead of terminal graphics. Useful for web dashboards and hosting platforms that need to display the QR in a browser. - Add --json and --timeout flags to channels login command - Use gateway.loginWithQrStart/Wait methods for JSON mode - Output two JSON objects: QR data, then connection result - Document new mode in docs/cli/channels.md --- docs/cli/channels.md | 21 +++++++++++++++++ src/cli/channel-auth.ts | 50 +++++++++++++++++++++++++++++++++++++++++ src/cli/channels-cli.ts | 4 ++++ 3 files changed, 75 insertions(+) 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, ); From 9ed0f1be56edb117575ad266fcce8e354432648c Mon Sep 17 00:00:00 2001 From: Drax Date: Thu, 29 Jan 2026 00:01:23 +0100 Subject: [PATCH 2/2] test: update channels login test for new json and timeout options --- src/cli/program.smoke.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/program.smoke.test.ts b/src/cli/program.smoke.test.ts index f6b155554..3f3465c5e 100644 --- a/src/cli/program.smoke.test.ts +++ b/src/cli/program.smoke.test.ts @@ -208,7 +208,7 @@ describe("cli program (smoke)", () => { from: "user", }); expect(runChannelLogin).toHaveBeenCalledWith( - { channel: undefined, account: "work", verbose: false }, + { channel: undefined, account: "work", verbose: false, json: false, timeoutMs: 60000 }, runtime, ); });