fix(onboard): explain DM pairing defaults

This commit is contained in:
Peter Steinberger
2026-01-06 17:58:06 +01:00
parent 4bb53e19f9
commit b081f45b17

View File

@@ -1,6 +1,7 @@
import fs from "node:fs/promises"; import fs from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import type { ClawdbotConfig } from "../config/config.js"; import type { ClawdbotConfig } from "../config/config.js";
import type { DmPolicy } from "../config/types.js";
import { loginWeb } from "../provider-web.js"; import { loginWeb } from "../provider-web.js";
import type { RuntimeEnv } from "../runtime.js"; import type { RuntimeEnv } from "../runtime.js";
import { normalizeE164 } from "../utils.js"; import { normalizeE164 } from "../utils.js";
@@ -27,6 +28,10 @@ async function detectWhatsAppLinked(): Promise<boolean> {
async function noteProviderPrimer(prompter: WizardPrompter): Promise<void> { async function noteProviderPrimer(prompter: WizardPrompter): Promise<void> {
await prompter.note( await prompter.note(
[ [
"DM security: default is pairing; unknown DMs get a pairing code.",
"Approve with: clawdbot pairing approve --provider <provider> <code>",
'Public DMs require dmPolicy="open" + allowFrom=["*"].',
"",
"WhatsApp: links via WhatsApp Web (scan QR), stores creds for future sends.", "WhatsApp: links via WhatsApp Web (scan QR), stores creds for future sends.",
"WhatsApp: dedicated second number recommended; primary number OK (self-chat).", "WhatsApp: dedicated second number recommended; primary number OK (self-chat).",
"Telegram: Bot API (token from @BotFather), replies via your bot.", "Telegram: Bot API (token from @BotFather), replies via your bot.",
@@ -153,6 +158,16 @@ async function noteSlackTokenHelp(
); );
} }
function setWhatsAppDmPolicy(cfg: ClawdbotConfig, dmPolicy?: DmPolicy) {
return {
...cfg,
whatsapp: {
...cfg.whatsapp,
dmPolicy,
},
};
}
function setWhatsAppAllowFrom(cfg: ClawdbotConfig, allowFrom?: string[]) { function setWhatsAppAllowFrom(cfg: ClawdbotConfig, allowFrom?: string[]) {
return { return {
...cfg, ...cfg,
@@ -168,41 +183,60 @@ async function promptWhatsAppAllowFrom(
_runtime: RuntimeEnv, _runtime: RuntimeEnv,
prompter: WizardPrompter, prompter: WizardPrompter,
): Promise<ClawdbotConfig> { ): Promise<ClawdbotConfig> {
const existingPolicy = cfg.whatsapp?.dmPolicy ?? "pairing";
const existingAllowFrom = cfg.whatsapp?.allowFrom ?? []; const existingAllowFrom = cfg.whatsapp?.allowFrom ?? [];
const existingLabel = const existingLabel =
existingAllowFrom.length > 0 ? existingAllowFrom.join(", ") : "unset"; existingAllowFrom.length > 0 ? existingAllowFrom.join(", ") : "unset";
await prompter.note( await prompter.note(
[ [
"WhatsApp direct chats are gated by `whatsapp.allowFrom`.", "WhatsApp direct chats are gated by `whatsapp.dmPolicy` + `whatsapp.allowFrom`.",
'Default (unset) = self-chat only; use "*" to allow anyone.', "- pairing (default): unknown senders get a pairing code; owner approves",
`Current: ${existingLabel}`, "- allowlist: unknown senders are blocked",
'- open: public inbound DMs (requires allowFrom to include "*")',
"- disabled: ignore WhatsApp DMs",
"",
`Current: dmPolicy=${existingPolicy}, allowFrom=${existingLabel}`,
].join("\n"), ].join("\n"),
"WhatsApp allowlist", "WhatsApp DM access",
); );
const policy = (await prompter.select({
message: "WhatsApp DM policy",
options: [
{ value: "pairing", label: "Pairing (recommended)" },
{ value: "allowlist", label: "Allowlist only (block unknown senders)" },
{ value: "open", label: "Open (public inbound DMs)" },
{ value: "disabled", label: "Disabled (ignore WhatsApp DMs)" },
],
})) as DmPolicy;
const next = setWhatsAppDmPolicy(cfg, policy);
if (policy === "open") return setWhatsAppAllowFrom(next, ["*"]);
if (policy === "disabled") return next;
const options = const options =
existingAllowFrom.length > 0 existingAllowFrom.length > 0
? ([ ? ([
{ value: "keep", label: "Keep current" }, { value: "keep", label: "Keep current allowFrom" },
{ value: "self", label: "Self-chat only (unset)" }, {
{ value: "list", label: "Specific numbers (recommended)" }, value: "unset",
{ value: "any", label: "Anyone (*)" }, label: "Unset allowFrom (use pairing approvals only)",
},
{ value: "list", label: "Set allowFrom to specific numbers" },
] as const) ] as const)
: ([ : ([
{ value: "self", label: "Self-chat only (default)" }, { value: "unset", label: "Unset allowFrom (default)" },
{ value: "list", label: "Specific numbers (recommended)" }, { value: "list", label: "Set allowFrom to specific numbers" },
{ value: "any", label: "Anyone (*)" },
] as const); ] as const);
const mode = (await prompter.select({ const mode = (await prompter.select({
message: "Who can trigger the bot via WhatsApp?", message: "WhatsApp allowFrom (optional pre-allowlist)",
options: options.map((opt) => ({ value: opt.value, label: opt.label })), options: options.map((opt) => ({ value: opt.value, label: opt.label })),
})) as (typeof options)[number]["value"]; })) as (typeof options)[number]["value"];
if (mode === "keep") return cfg; if (mode === "keep") return next;
if (mode === "self") return setWhatsAppAllowFrom(cfg, undefined); if (mode === "unset") return setWhatsAppAllowFrom(next, undefined);
if (mode === "any") return setWhatsAppAllowFrom(cfg, ["*"]);
const allowRaw = await prompter.text({ const allowRaw = await prompter.text({
message: "Allowed sender numbers (comma-separated, E.164)", message: "Allowed sender numbers (comma-separated, E.164)",
@@ -232,7 +266,7 @@ async function promptWhatsAppAllowFrom(
part === "*" ? "*" : normalizeE164(part), part === "*" ? "*" : normalizeE164(part),
); );
const unique = [...new Set(normalized.filter(Boolean))]; const unique = [...new Set(normalized.filter(Boolean))];
return setWhatsAppAllowFrom(cfg, unique); return setWhatsAppAllowFrom(next, unique);
} }
export async function setupProviders( export async function setupProviders(