feat(cli): move provider login/logout

This commit is contained in:
Peter Steinberger
2026-01-08 07:40:08 +01:00
parent 903f5af59c
commit e83c6ac088
19 changed files with 153 additions and 68 deletions

View File

@@ -16,7 +16,7 @@
- Commands: gate all slash commands to authorized senders; add `/compact` to manually compact session context. - Commands: gate all slash commands to authorized senders; add `/compact` to manually compact session context.
- Groups: `whatsapp.groups`, `telegram.groups`, and `imessage.groups` now act as allowlists when set. Add `"*"` to keep allow-all behavior. - Groups: `whatsapp.groups`, `telegram.groups`, and `imessage.groups` now act as allowlists when set. Add `"*"` to keep allow-all behavior.
- Auto-reply: removed `autoReply` from Discord/Slack/Telegram channel configs; use `requireMention` instead (Telegram topics now support `requireMention` overrides). - Auto-reply: removed `autoReply` from Discord/Slack/Telegram channel configs; use `requireMention` instead (Telegram topics now support `requireMention` overrides).
- CLI: remove `update`, `gateway-daemon`, `gateway {install|uninstall|start|stop|restart|daemon status|wake|send|agent}`, and `telegram` commands; use `daemon` for service control, `send`/`agent`/`wake` for RPC, and `nodes canvas` for canvas ops. - CLI: remove `update`, `gateway-daemon`, `gateway {install|uninstall|start|stop|restart|daemon status|wake|send|agent}`, and `telegram` commands; move `login/logout` to `providers login/logout` (top-level aliases hidden); use `daemon` for service control, `send`/`agent`/`wake` for RPC, and `nodes canvas` for canvas ops.
### Fixes ### Fixes
- Auto-reply: keep typing indicators alive during tool execution without changing typing-mode semantics. Thanks @thesash for PR #452. - Auto-reply: keep typing indicators alive during tool execution without changing typing-mode semantics. Thanks @thesash for PR #452.

View File

@@ -44,13 +44,13 @@ clawdbot [--dev] [--profile <name>] <command>
onboard onboard
configure (alias: config) configure (alias: config)
doctor doctor
login
logout
providers providers
list list
status status
add add
remove remove
login
logout
skills skills
list list
info info
@@ -138,8 +138,6 @@ clawdbot [--dev] [--profile <name>] <command>
pairing pairing
list list
approve approve
telegram
pairing list|approve
docs docs
dns dns
setup setup
@@ -198,22 +196,7 @@ Options:
- `--non-interactive`: skip prompts; apply safe migrations only. - `--non-interactive`: skip prompts; apply safe migrations only.
- `--deep`: scan system services for extra gateway installs. - `--deep`: scan system services for extra gateway installs.
## Auth + provider helpers ## Provider helpers
### `login`
Link a WhatsApp Web account via QR.
Options:
- `--verbose`
- `--provider <provider>` (default `whatsapp`)
- `--account <id>`
### `logout`
Clear cached WhatsApp Web credentials.
Options:
- `--provider <provider>`
- `--account <id>`
### `providers` ### `providers`
Manage chat provider accounts (WhatsApp/Telegram/Discord/Slack/Signal/iMessage). Manage chat provider accounts (WhatsApp/Telegram/Discord/Slack/Signal/iMessage).
@@ -223,12 +206,23 @@ Subcommands:
- `providers status`: check gateway reachability and provider health (`--probe` to verify credentials; use `status --deep` for local-only probes). - `providers status`: check gateway reachability and provider health (`--probe` to verify credentials; use `status --deep` for local-only probes).
- `providers add`: wizard-style setup when no flags are passed; flags switch to non-interactive mode. - `providers add`: wizard-style setup when no flags are passed; flags switch to non-interactive mode.
- `providers remove`: disable by default; pass `--delete` to remove config entries without prompts. - `providers remove`: disable by default; pass `--delete` to remove config entries without prompts.
- `providers login`: interactive provider login (WhatsApp Web only).
- `providers logout`: log out of a provider session (WhatsApp Web only).
Common options: Common options:
- `--provider <name>`: `whatsapp|telegram|discord|slack|signal|imessage` - `--provider <name>`: `whatsapp|telegram|discord|slack|signal|imessage`
- `--account <id>`: provider account id (default `default`) - `--account <id>`: provider account id (default `default`)
- `--name <label>`: display name for the account - `--name <label>`: display name for the account
`providers login` options:
- `--provider <provider>` (default `whatsapp`; supports `whatsapp`/`web`)
- `--account <id>`
- `--verbose`
`providers logout` options:
- `--provider <provider>` (default `whatsapp`; supports `whatsapp`/`web`)
- `--account <id>`
`providers list` options: `providers list` options:
- `--no-usage`: skip provider usage/quota snapshots (OAuth/API-backed only). - `--no-usage`: skip provider usage/quota snapshots (OAuth/API-backed only).
- `--json`: output JSON (includes usage unless `--no-usage` is set). - `--json`: output JSON (includes usage unless `--no-usage` is set).

View File

@@ -17,10 +17,10 @@ Short guide to verify the WhatsApp Web / Baileys stack without guessing.
## Deep diagnostics ## Deep diagnostics
- Creds on disk: `ls -l ~/.clawdbot/credentials/whatsapp/<accountId>/creds.json` (mtime should be recent). - Creds on disk: `ls -l ~/.clawdbot/credentials/whatsapp/<accountId>/creds.json` (mtime should be recent).
- Session store: `ls -l ~/.clawdbot/agents/<agentId>/sessions/sessions.json` (path can be overridden in config). Count and recent recipients are surfaced via `status`. - Session store: `ls -l ~/.clawdbot/agents/<agentId>/sessions/sessions.json` (path can be overridden in config). Count and recent recipients are surfaced via `status`.
- Relink flow: `clawdbot logout && clawdbot login --verbose` when status codes 409515 or `loggedOut` appear in logs. (Note: the QR login flow auto-restarts once for status 515 after pairing.) - Relink flow: `clawdbot providers logout && clawdbot providers login --verbose` when status codes 409515 or `loggedOut` appear in logs. (Note: the QR login flow auto-restarts once for status 515 after pairing.)
## When something fails ## When something fails
- `logged out` or status 409515 → relink with `clawdbot logout` then `clawdbot login`. - `logged out` or status 409515 → relink with `clawdbot providers logout` then `clawdbot providers login`.
- Gateway unreachable → start it: `clawdbot gateway --port 18789` (use `--force` if the port is busy). - Gateway unreachable → start it: `clawdbot gateway --port 18789` (use `--force` if the port is busy).
- No inbound messages → confirm linked phone is online and the sender is allowed (`whatsapp.allowFrom`); for group chats, ensure allowlist + mention rules match (`whatsapp.groups`, `routing.groupChat.mentionPatterns`). - No inbound messages → confirm linked phone is online and the sender is allowed (`whatsapp.allowFrom`); for group chats, ensure allowlist + mention rules match (`whatsapp.groups`, `routing.groupChat.mentionPatterns`).

View File

@@ -138,9 +138,9 @@ clawdbot gateway --verbose
If youre logged out / unlinked: If youre logged out / unlinked:
```bash ```bash
clawdbot logout clawdbot providers logout
trash ~/.clawdbot/credentials # if logout can't cleanly remove everything trash ~/.clawdbot/credentials # if logout can't cleanly remove everything
clawdbot login --verbose # re-scan QR clawdbot providers login --verbose # re-scan QR
``` ```
### Media Send Failing ### Media Send Failing
@@ -219,7 +219,7 @@ Get verbose logging:
# #
# Then run verbose commands to mirror debug output to stdout: # Then run verbose commands to mirror debug output to stdout:
clawdbot gateway --verbose clawdbot gateway --verbose
clawdbot login --verbose clawdbot providers login --verbose
``` ```
## Log Locations ## Log Locations
@@ -250,7 +250,7 @@ Nuclear option:
```bash ```bash
trash ~/.clawdbot trash ~/.clawdbot
clawdbot login # re-pair WhatsApp clawdbot providers login # re-pair WhatsApp
clawdbot gateway # start the Gateway again clawdbot gateway # start the Gateway again
``` ```

View File

@@ -104,7 +104,7 @@ pnpm build
pnpm link --global pnpm link --global
# Pair WhatsApp Web (shows QR) # Pair WhatsApp Web (shows QR)
clawdbot login clawdbot providers login
# Run the Gateway (leave running) # Run the Gateway (leave running)
clawdbot gateway --port 18789 clawdbot gateway --port 18789

View File

@@ -38,7 +38,7 @@ This flow lets the macOS app act as a full remote control for a Clawdbot gateway
- Nodes advertise their permission state via `node.list` / `node.describe` so agents know whats available. - Nodes advertise their permission state via `node.list` / `node.describe` so agents know whats available.
## WhatsApp login flow (remote) ## WhatsApp login flow (remote)
- Run `clawdbot login --verbose` **on the remote host**. Scan the QR with WhatsApp on your phone. - Run `clawdbot providers login --verbose` **on the remote host**. Scan the QR with WhatsApp on your phone.
- Re-run login on that host if auth expires. Health check will surface link problems. - Re-run login on that host if auth expires. Health check will surface link problems.
## Troubleshooting ## Troubleshooting

View File

@@ -43,13 +43,13 @@ WhatsApp requires a real mobile number for verification. VoIP and virtual number
- Result: unreliable delivery and frequent blocks, so support was removed. - Result: unreliable delivery and frequent blocks, so support was removed.
## Login + credentials ## Login + credentials
- Login command: `clawdbot login` (QR via Linked Devices). - Login command: `clawdbot providers login` (QR via Linked Devices).
- Multi-account login: `clawdbot login --account <id>` (`<id>` = `accountId`). - Multi-account login: `clawdbot providers login --account <id>` (`<id>` = `accountId`).
- Default account (when `--account` is omitted): `default` if present, otherwise the first configured account id (sorted). - Default account (when `--account` is omitted): `default` if present, otherwise the first configured account id (sorted).
- Credentials stored in `~/.clawdbot/credentials/whatsapp/<accountId>/creds.json`. - Credentials stored in `~/.clawdbot/credentials/whatsapp/<accountId>/creds.json`.
- Backup copy at `creds.json.bak` (restored on corruption). - Backup copy at `creds.json.bak` (restored on corruption).
- Legacy compatibility: older installs stored Baileys files directly in `~/.clawdbot/credentials/`. - Legacy compatibility: older installs stored Baileys files directly in `~/.clawdbot/credentials/`.
- Logout: `clawdbot logout` (or `--account <id>`) deletes WhatsApp auth state (but keeps shared `oauth.json`). - Logout: `clawdbot providers logout` (or `--account <id>`) deletes WhatsApp auth state (but keeps shared `oauth.json`).
- Logged-out socket => error instructs re-link. - Logged-out socket => error instructs re-link.
## Inbound flow (DM + group) ## Inbound flow (DM + group)

View File

@@ -60,7 +60,7 @@ If you link your personal WhatsApp to CLAWDBOT, every message to you becomes “
1) Pair WhatsApp Web (shows QR; scan with the assistant phone): 1) Pair WhatsApp Web (shows QR; scan with the assistant phone):
```bash ```bash
clawdbot login clawdbot providers login
``` ```
2) Start the Gateway (leave it running): 2) Start the Gateway (leave it running):

View File

@@ -430,7 +430,7 @@ You can add options like `debounce:2s cap:25 drop:summarize` for followup modes.
Run the login command again and scan the QR code: Run the login command again and scan the QR code:
```bash ```bash
clawdbot login clawdbot providers login
``` ```
### Build errors on `main` — whats the standard fix path? ### Build errors on `main` — whats the standard fix path?

View File

@@ -99,7 +99,7 @@ https://github.com/oven-sh/bun/issues/5951
### WhatsApp (QR login) ### WhatsApp (QR login)
```bash ```bash
pnpm clawdbot login pnpm clawdbot providers login
``` ```
Scan via WhatsApp → Settings → Linked Devices. Scan via WhatsApp → Settings → Linked Devices.

View File

@@ -46,7 +46,7 @@ pnpm clawdbot setup
4) Link surfaces (example: WhatsApp): 4) Link surfaces (example: WhatsApp):
```bash ```bash
clawdbot login clawdbot providers login
``` ```
5) Sanity check: 5) Sanity check:
@@ -56,7 +56,7 @@ clawdbot health
``` ```
If onboarding is still WIP/broken on your build: If onboarding is still WIP/broken on your build:
- Run `clawdbot setup`, then `clawdbot login`, then start the Gateway manually (`clawdbot gateway`). - Run `clawdbot setup`, then `clawdbot providers login`, then start the Gateway manually (`clawdbot gateway`).
## Bleeding edge workflow (Gateway in a terminal) ## Bleeding edge workflow (Gateway in a terminal)

View File

@@ -23,11 +23,10 @@ import {
} from "../config/config.js"; } from "../config/config.js";
import { danger, setVerbose } from "../globals.js"; import { danger, setVerbose } from "../globals.js";
import { autoMigrateLegacyState } from "../infra/state-migrations.js"; import { autoMigrateLegacyState } from "../infra/state-migrations.js";
import { loginWeb, logoutWeb } from "../provider-web.js"; import { runProviderLogin, runProviderLogout } from "./provider-auth.js";
import { defaultRuntime } from "../runtime.js"; import { defaultRuntime } from "../runtime.js";
import { isRich, theme } from "../terminal/theme.js"; import { isRich, theme } from "../terminal/theme.js";
import { VERSION } from "../version.js"; import { VERSION } from "../version.js";
import { resolveWhatsAppAccount } from "../web/accounts.js";
import { emitCliBanner, formatCliBannerLine } from "./banner.js"; import { emitCliBanner, formatCliBannerLine } from "./banner.js";
import { registerBrowserCli } from "./browser-cli.js"; import { registerBrowserCli } from "./browser-cli.js";
import { hasExplicitOptions } from "./command-options.js"; import { hasExplicitOptions } from "./command-options.js";
@@ -138,7 +137,7 @@ export function buildProgram() {
}); });
const examples = [ const examples = [
[ [
"clawdbot login --verbose", "clawdbot providers login --verbose",
"Link personal WhatsApp Web and show QR + connection logs.", "Link personal WhatsApp Web and show QR + connection logs.",
], ],
[ [
@@ -342,23 +341,22 @@ export function buildProgram() {
} }
}); });
// Deprecated hidden aliases: use `clawdbot providers login/logout`. Remove in a future major.
program program
program .command("login", { hidden: true })
.command("login")
.description("Link your personal WhatsApp via QR (web provider)") .description("Link your personal WhatsApp via QR (web provider)")
.option("--verbose", "Verbose connection logs", false) .option("--verbose", "Verbose connection logs", false)
.option("--provider <provider>", "Provider alias (default: whatsapp)") .option("--provider <provider>", "Provider alias (default: whatsapp)")
.option("--account <id>", "WhatsApp account id (accountId)") .option("--account <id>", "WhatsApp account id (accountId)")
.action(async (opts) => { .action(async (opts) => {
setVerbose(Boolean(opts.verbose));
try { try {
const provider = opts.provider ?? "whatsapp"; await runProviderLogin(
await loginWeb( {
Boolean(opts.verbose), provider: opts.provider as string | undefined,
provider, account: opts.account as string | undefined,
undefined, verbose: Boolean(opts.verbose),
},
defaultRuntime, defaultRuntime,
opts.account as string | undefined,
); );
} catch (err) { } catch (err) {
defaultRuntime.error(danger(`Web login failed: ${String(err)}`)); defaultRuntime.error(danger(`Web login failed: ${String(err)}`));
@@ -367,23 +365,19 @@ export function buildProgram() {
}); });
program program
.command("logout") .command("logout", { hidden: true })
.description("Clear cached WhatsApp Web credentials") .description("Log out of WhatsApp Web (keeps config)")
.option("--provider <provider>", "Provider alias (default: whatsapp)") .option("--provider <provider>", "Provider alias (default: whatsapp)")
.option("--account <id>", "WhatsApp account id (accountId)") .option("--account <id>", "WhatsApp account id (accountId)")
.action(async (opts) => { .action(async (opts) => {
try { try {
void opts.provider; // placeholder for future multi-provider; currently web only. await runProviderLogout(
const cfg = loadConfig(); {
const account = resolveWhatsAppAccount({ provider: opts.provider as string | undefined,
cfg, account: opts.account as string | undefined,
accountId: opts.account as string | undefined, },
}); defaultRuntime,
await logoutWeb({ );
runtime: defaultRuntime,
authDir: account.authDir,
isLegacyAuthDir: account.isLegacyAuthDir,
});
} catch (err) { } catch (err) {
defaultRuntime.error(danger(`Logout failed: ${String(err)}`)); defaultRuntime.error(danger(`Logout failed: ${String(err)}`));
defaultRuntime.exit(1); defaultRuntime.exit(1);

51
src/cli/provider-auth.ts Normal file
View File

@@ -0,0 +1,51 @@
import { loadConfig } from "../config/config.js";
import { setVerbose } from "../globals.js";
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
import { loginWeb, logoutWeb } from "../provider-web.js";
import { resolveWhatsAppAccount } from "../web/accounts.js";
type ProviderAuthOptions = {
provider?: string;
account?: string;
verbose?: boolean;
};
function normalizeProvider(raw?: string): "whatsapp" | "web" {
const value = String(raw ?? "whatsapp").trim().toLowerCase();
if (value === "whatsapp" || value === "web") return value;
throw new Error(`Unsupported provider: ${value}`);
}
export async function runProviderLogin(
opts: ProviderAuthOptions,
runtime: RuntimeEnv = defaultRuntime,
) {
const provider = normalizeProvider(opts.provider);
// Auth-only flow: do not mutate provider config here.
setVerbose(Boolean(opts.verbose));
await loginWeb(
Boolean(opts.verbose),
provider,
undefined,
runtime,
opts.account,
);
}
export async function runProviderLogout(
opts: ProviderAuthOptions,
runtime: RuntimeEnv = defaultRuntime,
) {
const provider = normalizeProvider(opts.provider);
// Auth-only flow: resolve account + clear session state only.
const cfg = loadConfig();
const account = resolveWhatsAppAccount({
cfg,
accountId: opts.account,
});
await logoutWeb({
runtime,
authDir: account.authDir,
isLegacyAuthDir: account.isLegacyAuthDir,
});
}

View File

@@ -7,7 +7,9 @@ import {
providersStatusCommand, providersStatusCommand,
} from "../commands/providers.js"; } from "../commands/providers.js";
import { listChatProviders } from "../providers/registry.js"; import { listChatProviders } from "../providers/registry.js";
import { danger } from "../globals.js";
import { defaultRuntime } from "../runtime.js"; import { defaultRuntime } from "../runtime.js";
import { runProviderLogin, runProviderLogout } from "./provider-auth.js";
import { hasExplicitOptions } from "./command-options.js"; import { hasExplicitOptions } from "./command-options.js";
const optionNamesAdd = [ const optionNamesAdd = [
@@ -116,4 +118,46 @@ export function registerProvidersCli(program: Command) {
defaultRuntime.exit(1); defaultRuntime.exit(1);
} }
}); });
providers
.command("login")
.description("Link a provider account (WhatsApp Web only)")
.option("--provider <provider>", "Provider alias (default: whatsapp)")
.option("--account <id>", "WhatsApp account id (accountId)")
.option("--verbose", "Verbose connection logs", false)
.action(async (opts) => {
try {
await runProviderLogin(
{
provider: opts.provider as string | undefined,
account: opts.account as string | undefined,
verbose: Boolean(opts.verbose),
},
defaultRuntime,
);
} catch (err) {
defaultRuntime.error(danger(`Provider login failed: ${String(err)}`));
defaultRuntime.exit(1);
}
});
providers
.command("logout")
.description("Log out of a provider session (WhatsApp Web only)")
.option("--provider <provider>", "Provider alias (default: whatsapp)")
.option("--account <id>", "WhatsApp account id (accountId)")
.action(async (opts) => {
try {
await runProviderLogout(
{
provider: opts.provider as string | undefined,
account: opts.account as string | undefined,
},
defaultRuntime,
);
} catch (err) {
defaultRuntime.error(danger(`Provider logout failed: ${String(err)}`));
defaultRuntime.exit(1);
}
});
} }

View File

@@ -143,7 +143,7 @@ export async function healthCommand(
runtime.log( runtime.log(
summary.web.linked summary.web.linked
? `Web: linked (auth age ${summary.web.authAgeMs ? `${Math.round(summary.web.authAgeMs / 60000)}m` : "unknown"})` ? `Web: linked (auth age ${summary.web.authAgeMs ? `${Math.round(summary.web.authAgeMs / 60000)}m` : "unknown"})`
: "Web: not linked (run clawdbot login)", : "Web: not linked (run clawdbot providers login)",
); );
if (summary.web.linked) { if (summary.web.linked) {
const cfg = loadConfig(); const cfg = loadConfig();

View File

@@ -856,7 +856,7 @@ export async function setupProviders(
} }
} else if (!whatsappLinked) { } else if (!whatsappLinked) {
await prompter.note( await prompter.note(
"Run `clawdbot login` later to link WhatsApp.", "Run `clawdbot providers login` later to link WhatsApp.",
"WhatsApp", "WhatsApp",
); );
} }

View File

@@ -1611,7 +1611,7 @@ export async function monitorWebProvider(
if (loggedOut) { if (loggedOut) {
runtime.error( runtime.error(
"WhatsApp session logged out. Run `clawdbot login --provider web` to relink.", "WhatsApp session logged out. Run `clawdbot providers login --provider web` to relink.",
); );
await closeListener(); await closeListener();
break; break;

View File

@@ -70,7 +70,7 @@ export async function loginWeb(
}); });
console.error( console.error(
danger( danger(
"WhatsApp reported the session is logged out. Cleared cached web session; please rerun clawdbot login and scan the QR again.", "WhatsApp reported the session is logged out. Cleared cached web session; please rerun clawdbot providers login and scan the QR again.",
), ),
); );
throw new Error("Session logged out; cache cleared. Re-run login."); throw new Error("Session logged out; cache cleared. Re-run login.");

View File

@@ -169,7 +169,9 @@ export async function createWaSocket(
const status = getStatusCode(lastDisconnect?.error); const status = getStatusCode(lastDisconnect?.error);
if (status === DisconnectReason.loggedOut) { if (status === DisconnectReason.loggedOut) {
console.error( console.error(
danger("WhatsApp session logged out. Run: clawdbot login"), danger(
"WhatsApp session logged out. Run: clawdbot providers login",
),
); );
} }
} }
@@ -454,7 +456,7 @@ export async function pickProvider(
const hasWeb = await webAuthExists(authDir); const hasWeb = await webAuthExists(authDir);
if (!hasWeb) { if (!hasWeb) {
throw new Error( throw new Error(
"No WhatsApp Web session found. Run `clawdbot login --verbose` to link.", "No WhatsApp Web session found. Run `clawdbot providers login --verbose` to link.",
); );
} }
return choice; return choice;