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
This commit is contained in:
Drax 2026-01-28 23:36:31 +01:00
parent a7534dc223
commit d3cf62a1e5
3 changed files with 75 additions and 0 deletions

View File

@ -40,6 +40,27 @@ moltbot channels login --channel whatsapp
moltbot channels logout --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 `<img>` tag.
## Troubleshooting ## Troubleshooting
- Run `moltbot status --deep` for a broad probe. - Run `moltbot status --deep` for a broad probe.

View File

@ -9,6 +9,8 @@ type ChannelAuthOptions = {
channel?: string; channel?: string;
account?: string; account?: string;
verbose?: boolean; verbose?: boolean;
json?: boolean;
timeoutMs?: number;
}; };
export async function runChannelLogin( export async function runChannelLogin(
@ -21,6 +23,54 @@ export async function runChannelLogin(
throw new Error(`Unsupported channel: ${channelInput}`); throw new Error(`Unsupported channel: ${channelInput}`);
} }
const plugin = getChannelPlugin(channelId); 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) { if (!plugin?.auth?.login) {
throw new Error(`Channel ${channelId} does not support login`); throw new Error(`Channel ${channelId} does not support login`);
} }

View File

@ -215,6 +215,8 @@ export function registerChannelsCli(program: Command) {
.option("--channel <channel>", "Channel alias (default: whatsapp)") .option("--channel <channel>", "Channel alias (default: whatsapp)")
.option("--account <id>", "Account id (accountId)") .option("--account <id>", "Account id (accountId)")
.option("--verbose", "Verbose connection logs", false) .option("--verbose", "Verbose connection logs", false)
.option("--json", "Output QR data as JSON (for programmatic use)", false)
.option("--timeout <ms>", "Timeout for QR generation in ms", "60000")
.action(async (opts) => { .action(async (opts) => {
await runChannelsCommandWithDanger(async () => { await runChannelsCommandWithDanger(async () => {
await runChannelLogin( await runChannelLogin(
@ -222,6 +224,8 @@ export function registerChannelsCli(program: Command) {
channel: opts.channel as string | undefined, channel: opts.channel as string | undefined,
account: opts.account as string | undefined, account: opts.account as string | undefined,
verbose: Boolean(opts.verbose), verbose: Boolean(opts.verbose),
json: Boolean(opts.json),
timeoutMs: parseInt(String(opts.timeout), 10) || 60000,
}, },
defaultRuntime, defaultRuntime,
); );