diff --git a/src/cli/pairing-cli.ts b/src/cli/pairing-cli.ts index 645b1493e..044cf12ca 100644 --- a/src/cli/pairing-cli.ts +++ b/src/cli/pairing-cli.ts @@ -8,6 +8,7 @@ import { listProviderPairingRequests, type PairingProvider, } from "../pairing/pairing-store.js"; +import { PROVIDER_ID_LABELS } from "../pairing/pairing-labels.js"; import { sendMessageSignal } from "../signal/send.js"; import { sendMessageSlack } from "../slack/send.js"; import { sendMessageTelegram } from "../telegram/send.js"; @@ -22,15 +23,6 @@ const PROVIDERS: PairingProvider[] = [ "whatsapp", ]; -const PROVIDER_ID_LABELS: Record = { - telegram: "telegramUserId", - discord: "discordUserId", - slack: "slackUserId", - signal: "signalNumber", - imessage: "imessageSenderId", - whatsapp: "whatsappSenderId", -}; - function parseProvider(raw: unknown): PairingProvider { const value = ( typeof raw === "string" diff --git a/src/discord/monitor.ts b/src/discord/monitor.ts index dccf36de1..9f04d719e 100644 --- a/src/discord/monitor.ts +++ b/src/discord/monitor.ts @@ -53,6 +53,7 @@ import { readProviderAllowFromStore, upsertProviderPairingRequest, } from "../pairing/pairing-store.js"; +import { buildPairingReply } from "../pairing/pairing-messages.js"; import { buildAgentSessionKey, resolveAgentRoute, @@ -591,16 +592,11 @@ export function createDiscordMessageHandler(params: { try { await sendMessageDiscord( `user:${author.id}`, - [ - "Clawdbot: access not configured.", - "", - `Your Discord user id: ${author.id}`, - "", - `Pairing code: ${code}`, - "", - "Ask the bot owner to approve with:", - "clawdbot pairing approve --provider discord ", - ].join("\n"), + buildPairingReply({ + provider: "discord", + idLine: `Your Discord user id: ${author.id}`, + code, + }), { token, rest: client.rest, accountId }, ); } catch (err) { @@ -1435,16 +1431,11 @@ function createDiscordNativeCommand(params: { }); if (created) { await interaction.reply({ - content: [ - "Clawdbot: access not configured.", - "", - `Your Discord user id: ${user.id}`, - "", - `Pairing code: ${code}`, - "", - "Ask the bot owner to approve with:", - "clawdbot pairing approve --provider discord ", - ].join("\n"), + content: buildPairingReply({ + provider: "discord", + idLine: `Your Discord user id: ${user.id}`, + code, + }), ephemeral: true, }); } diff --git a/src/imessage/monitor.ts b/src/imessage/monitor.ts index e4afe389a..a2dbbfad9 100644 --- a/src/imessage/monitor.ts +++ b/src/imessage/monitor.ts @@ -21,6 +21,7 @@ import { readProviderAllowFromStore, upsertProviderPairingRequest, } from "../pairing/pairing-store.js"; +import { buildPairingReply } from "../pairing/pairing-messages.js"; import { resolveAgentRoute } from "../routing/resolve-route.js"; import type { RuntimeEnv } from "../runtime.js"; import { resolveIMessageAccount } from "./accounts.js"; @@ -256,16 +257,11 @@ export async function monitorIMessageProvider( try { await sendMessageIMessage( sender, - [ - "Clawdbot: access not configured.", - "", - `Your iMessage sender id: ${senderId}`, - "", - `Pairing code: ${code}`, - "", - "Ask the bot owner to approve with:", - "clawdbot pairing approve --provider imessage ", - ].join("\n"), + buildPairingReply({ + provider: "imessage", + idLine: `Your iMessage sender id: ${senderId}`, + code, + }), { client, maxBytes: mediaMaxBytes, diff --git a/src/pairing/pairing-labels.ts b/src/pairing/pairing-labels.ts new file mode 100644 index 000000000..258b4e5f2 --- /dev/null +++ b/src/pairing/pairing-labels.ts @@ -0,0 +1,10 @@ +import type { PairingProvider } from "./pairing-store.js"; + +export const PROVIDER_ID_LABELS: Record = { + telegram: "telegramUserId", + discord: "discordUserId", + slack: "slackUserId", + signal: "signalNumber", + imessage: "imessageSenderId", + whatsapp: "whatsappSenderId", +}; diff --git a/src/pairing/pairing-messages.test.ts b/src/pairing/pairing-messages.test.ts new file mode 100644 index 000000000..671f5c247 --- /dev/null +++ b/src/pairing/pairing-messages.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from "vitest"; + +import { buildPairingReply } from "./pairing-messages.js"; + +describe("buildPairingReply", () => { + const cases = [ + { + provider: "discord", + idLine: "Your Discord user id: 1", + code: "ABC123", + }, + { + provider: "slack", + idLine: "Your Slack user id: U1", + code: "DEF456", + }, + { + provider: "signal", + idLine: "Your Signal number: +15550001111", + code: "GHI789", + }, + { + provider: "imessage", + idLine: "Your iMessage sender id: +15550002222", + code: "JKL012", + }, + { + provider: "whatsapp", + idLine: "Your WhatsApp sender id: +15550003333", + code: "MNO345", + }, + ] as const; + + for (const testCase of cases) { + it(`formats pairing reply for ${testCase.provider}`, () => { + const text = buildPairingReply(testCase); + expect(text).toContain(testCase.idLine); + expect(text).toContain(`Pairing code: ${testCase.code}`); + expect(text).toContain( + `clawdbot pairing approve --provider ${testCase.provider} `, + ); + }); + } +}); diff --git a/src/pairing/pairing-messages.ts b/src/pairing/pairing-messages.ts new file mode 100644 index 000000000..2569d6bc8 --- /dev/null +++ b/src/pairing/pairing-messages.ts @@ -0,0 +1,19 @@ +import type { PairingProvider } from "./pairing-store.js"; + +export function buildPairingReply(params: { + provider: PairingProvider; + idLine: string; + code: string; +}): string { + const { provider, idLine, code } = params; + return [ + "Clawdbot: access not configured.", + "", + idLine, + "", + `Pairing code: ${code}`, + "", + "Ask the bot owner to approve with:", + `clawdbot pairing approve --provider ${provider} `, + ].join("\n"); +} diff --git a/src/signal/identity.ts b/src/signal/identity.ts index 3cae59c34..b270eb627 100644 --- a/src/signal/identity.ts +++ b/src/signal/identity.ts @@ -53,6 +53,13 @@ export function formatSignalSenderDisplay(sender: SignalSender): string { return sender.kind === "phone" ? sender.e164 : `uuid:${sender.raw}`; } +export function formatSignalPairingIdLine(sender: SignalSender): string { + if (sender.kind === "phone") { + return `Your Signal number: ${sender.e164}`; + } + return `Your Signal sender id: ${formatSignalSenderId(sender)}`; +} + export function resolveSignalRecipient(sender: SignalSender): string { return sender.kind === "phone" ? sender.e164 : sender.raw; } diff --git a/src/signal/monitor.ts b/src/signal/monitor.ts index df72d28f1..dc843d161 100644 --- a/src/signal/monitor.ts +++ b/src/signal/monitor.ts @@ -22,11 +22,13 @@ import { spawnSignalDaemon } from "./daemon.js"; import { formatSignalSenderDisplay, formatSignalSenderId, + formatSignalPairingIdLine, isSignalSenderAllowed, resolveSignalPeerId, resolveSignalRecipient, resolveSignalSender, } from "./identity.js"; +import { buildPairingReply } from "../pairing/pairing-messages.js"; import { sendMessageSignal } from "./send.js"; import { runSignalSseLoop } from "./sse-reconnect.js"; @@ -317,11 +319,8 @@ export async function monitorSignalProvider( const senderRecipient = resolveSignalRecipient(sender); const senderPeerId = resolveSignalPeerId(sender); const senderAllowId = formatSignalSenderId(sender); - const senderIdLine = - sender.kind === "phone" - ? `Your Signal number: ${sender.e164}` - : `Your Signal sender id: ${senderAllowId}`; if (!senderRecipient) return; + const senderIdLine = formatSignalPairingIdLine(sender); const groupId = dataMessage.groupInfo?.groupId ?? undefined; const groupName = dataMessage.groupInfo?.groupName ?? undefined; const isGroup = Boolean(groupId); @@ -352,16 +351,11 @@ export async function monitorSignalProvider( try { await sendMessageSignal( `signal:${senderRecipient}`, - [ - "Clawdbot: access not configured.", - "", - senderIdLine, - "", - `Pairing code: ${code}`, - "", - "Ask the bot owner to approve with:", - "clawdbot pairing approve --provider signal ", - ].join("\n"), + buildPairingReply({ + provider: "signal", + idLine: senderIdLine, + code, + }), { baseUrl, account, diff --git a/src/slack/monitor.ts b/src/slack/monitor.ts index f8e5c2215..c8be2a293 100644 --- a/src/slack/monitor.ts +++ b/src/slack/monitor.ts @@ -47,6 +47,7 @@ import { readProviderAllowFromStore, upsertProviderPairingRequest, } from "../pairing/pairing-store.js"; +import { buildPairingReply } from "../pairing/pairing-messages.js"; import { resolveAgentRoute } from "../routing/resolve-route.js"; import { resolveThreadSessionKeys } from "../routing/session-key.js"; import type { RuntimeEnv } from "../runtime.js"; @@ -824,16 +825,11 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) { try { await sendMessageSlack( message.channel, - [ - "Clawdbot: access not configured.", - "", - `Your Slack user id: ${directUserId}`, - "", - `Pairing code: ${code}`, - "", - "Ask the bot owner to approve with:", - "clawdbot pairing approve --provider slack ", - ].join("\n"), + buildPairingReply({ + provider: "slack", + idLine: `Your Slack user id: ${directUserId}`, + code, + }), { token: botToken, client: app.client, @@ -1719,16 +1715,11 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) { }); if (created) { await respond({ - text: [ - "Clawdbot: access not configured.", - "", - `Your Slack user id: ${command.user_id}`, - "", - `Pairing code: ${code}`, - "", - "Ask the bot owner to approve with:", - "clawdbot pairing approve --provider slack ", - ].join("\n"), + text: buildPairingReply({ + provider: "slack", + idLine: `Your Slack user id: ${command.user_id}`, + code, + }), response_type: "ephemeral", }); } diff --git a/src/web/inbound.ts b/src/web/inbound.ts index a25eeeeb9..6bbde2155 100644 --- a/src/web/inbound.ts +++ b/src/web/inbound.ts @@ -21,6 +21,7 @@ import { readProviderAllowFromStore, upsertProviderPairingRequest, } from "../pairing/pairing-store.js"; +import { buildPairingReply } from "../pairing/pairing-messages.js"; import { formatLocationText, type NormalizedLocation, @@ -310,16 +311,11 @@ export async function monitorWebInbox(options: { ); try { await sock.sendMessage(remoteJid, { - text: [ - "Clawdbot: access not configured.", - "", - `Your WhatsApp sender id: ${candidate}`, - "", - `Pairing code: ${code}`, - "", - "Ask the bot owner to approve with:", - "clawdbot pairing approve --provider whatsapp ", - ].join("\n"), + text: buildPairingReply({ + provider: "whatsapp", + idLine: `Your WhatsApp sender id: ${candidate}`, + code, + }), }); } catch (err) { logVerbose(