fix(pairing): accept positional provider args
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
- Auth: update Claude Code keychain credentials in-place during refresh sync; extract CLI sync helpers + coverage.
|
- Auth: update Claude Code keychain credentials in-place during refresh sync; extract CLI sync helpers + coverage.
|
||||||
- Onboarding/Gateway: persist non-interactive gateway token auth in config; add WS wizard + gateway tool-calling regression coverage.
|
- Onboarding/Gateway: persist non-interactive gateway token auth in config; add WS wizard + gateway tool-calling regression coverage.
|
||||||
- CLI: `clawdbot sessions` now includes `elev:*` + `usage:*` flags in the table output.
|
- CLI: `clawdbot sessions` now includes `elev:*` + `usage:*` flags in the table output.
|
||||||
|
- CLI/Pairing: accept positional provider for `pairing list|approve` (npm-run compatible); update docs/bot hints.
|
||||||
- Branding: normalize user-facing “ClawdBot”/“CLAWDBOT” → “Clawdbot” (CLI, status, docs).
|
- Branding: normalize user-facing “ClawdBot”/“CLAWDBOT” → “Clawdbot” (CLI, status, docs).
|
||||||
- Doctor: avoid false legacy workspace warning when install dir is `~/clawdbot`. (#660)
|
- Doctor: avoid false legacy workspace warning when install dir is `~/clawdbot`. (#660)
|
||||||
- iMessage: fix reasoning persistence across DMs; avoid partial/duplicate replies when reasoning is enabled. (#655) — thanks @antons.
|
- iMessage: fix reasoning persistence across DMs; avoid partial/duplicate replies when reasoning is enabled. (#655) — thanks @antons.
|
||||||
@@ -91,7 +92,7 @@
|
|||||||
- Previously, if you didn’t configure an allowlist, your bot could be **open to anyone** (especially discoverable Telegram bots).
|
- Previously, if you didn’t configure an allowlist, your bot could be **open to anyone** (especially discoverable Telegram bots).
|
||||||
- New default: DM pairing (`dmPolicy="pairing"` / `discord.dm.policy="pairing"` / `slack.dm.policy="pairing"`).
|
- New default: DM pairing (`dmPolicy="pairing"` / `discord.dm.policy="pairing"` / `slack.dm.policy="pairing"`).
|
||||||
- To keep old “open to everyone” behavior: set `dmPolicy="open"` and include `"*"` in the relevant `allowFrom` (Discord/Slack: `discord.dm.allowFrom` / `slack.dm.allowFrom`).
|
- To keep old “open to everyone” behavior: set `dmPolicy="open"` and include `"*"` in the relevant `allowFrom` (Discord/Slack: `discord.dm.allowFrom` / `slack.dm.allowFrom`).
|
||||||
- Approve requests via `clawdbot pairing list --provider <provider>` + `clawdbot pairing approve --provider <provider> <code>`.
|
- Approve requests via `clawdbot pairing list <provider>` + `clawdbot pairing approve <provider> <code>`.
|
||||||
- Sandbox: default `agent.sandbox.scope` to `"agent"` (one container/workspace per agent). Use `"session"` for per-session isolation; `"shared"` disables cross-session isolation.
|
- Sandbox: default `agent.sandbox.scope` to `"agent"` (one container/workspace per agent). Use `"session"` for per-session isolation; `"shared"` disables cross-session isolation.
|
||||||
- Timestamps in agent envelopes are now UTC (compact `YYYY-MM-DDTHH:mmZ`); removed `messages.timestampPrefix`. Add `agent.userTimezone` to tell the model the user’s local time (system prompt only).
|
- Timestamps in agent envelopes are now UTC (compact `YYYY-MM-DDTHH:mmZ`); removed `messages.timestampPrefix`. Add `agent.userTimezone` to tell the model the user’s local time (system prompt only).
|
||||||
- Model config schema changes (auth profiles + model lists); doctor auto-migrates and the gateway rewrites legacy configs on startup.
|
- Model config schema changes (auth profiles + model lists); doctor auto-migrates and the gateway rewrites legacy configs on startup.
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ Full security guide: [Security](https://docs.clawd.bot/security)
|
|||||||
|
|
||||||
Default behavior on Telegram/WhatsApp/Signal/iMessage/Discord/Slack:
|
Default behavior on Telegram/WhatsApp/Signal/iMessage/Discord/Slack:
|
||||||
- **DM pairing** (`dmPolicy="pairing"` / `discord.dm.policy="pairing"` / `slack.dm.policy="pairing"`): unknown senders receive a short pairing code and the bot does not process their message.
|
- **DM pairing** (`dmPolicy="pairing"` / `discord.dm.policy="pairing"` / `slack.dm.policy="pairing"`): unknown senders receive a short pairing code and the bot does not process their message.
|
||||||
- Approve with: `clawdbot pairing approve --provider <provider> <code>` (then the sender is added to a local allowlist store).
|
- Approve with: `clawdbot pairing approve <provider> <code>` (then the sender is added to a local allowlist store).
|
||||||
- Public inbound DMs require an explicit opt-in: set `dmPolicy="open"` and include `"*"` in the provider allowlist (`allowFrom` / `discord.dm.allowFrom` / `slack.dm.allowFrom`).
|
- Public inbound DMs require an explicit opt-in: set `dmPolicy="open"` and include `"*"` in the provider allowlist (`allowFrom` / `discord.dm.allowFrom` / `slack.dm.allowFrom`).
|
||||||
|
|
||||||
Run `clawdbot doctor` to surface risky/misconfigured DM policies.
|
Run `clawdbot doctor` to surface risky/misconfigured DM policies.
|
||||||
|
|||||||
@@ -285,8 +285,8 @@ Tip: use `npx clawdhub` to search, install, and sync skills.
|
|||||||
Approve DM pairing requests across providers.
|
Approve DM pairing requests across providers.
|
||||||
|
|
||||||
Subcommands:
|
Subcommands:
|
||||||
- `pairing list --provider <telegram|signal|imessage|discord|slack|whatsapp> [--json]`
|
- `pairing list <provider> [--json]`
|
||||||
- `pairing approve --provider <...> <code> [--notify]`
|
- `pairing approve <provider> <code> [--notify]`
|
||||||
|
|
||||||
### `hooks gmail`
|
### `hooks gmail`
|
||||||
Gmail Pub/Sub hook setup + runner. See [/automation/gmail-pubsub](/automation/gmail-pubsub).
|
Gmail Pub/Sub hook setup + runner. See [/automation/gmail-pubsub](/automation/gmail-pubsub).
|
||||||
|
|||||||
@@ -251,8 +251,8 @@ Controls how WhatsApp direct chats (DMs) are handled:
|
|||||||
Pairing codes expire after 1 hour; the bot only sends a pairing code when a new request is created. Pending DM pairing requests are capped at **3 per provider** by default.
|
Pairing codes expire after 1 hour; the bot only sends a pairing code when a new request is created. Pending DM pairing requests are capped at **3 per provider** by default.
|
||||||
|
|
||||||
Pairing approvals:
|
Pairing approvals:
|
||||||
- `clawdbot pairing list --provider whatsapp`
|
- `clawdbot pairing list whatsapp`
|
||||||
- `clawdbot pairing approve --provider whatsapp <code>`
|
- `clawdbot pairing approve whatsapp <code>`
|
||||||
|
|
||||||
### `whatsapp.allowFrom`
|
### `whatsapp.allowFrom`
|
||||||
|
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ All current DM-capable providers support a DM policy (`dmPolicy` or `*.dm.policy
|
|||||||
Approve via CLI:
|
Approve via CLI:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
clawdbot pairing list --provider <provider>
|
clawdbot pairing list <provider>
|
||||||
clawdbot pairing approve --provider <provider> <code>
|
clawdbot pairing approve <provider> <code>
|
||||||
```
|
```
|
||||||
|
|
||||||
Details + files on disk: [Pairing](/start/pairing)
|
Details + files on disk: [Pairing](/start/pairing)
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ If `dmPolicy` is `pairing`, unknown senders should receive a code and their mess
|
|||||||
|
|
||||||
**Check 1:** Is a pending request already waiting?
|
**Check 1:** Is a pending request already waiting?
|
||||||
```bash
|
```bash
|
||||||
clawdbot pairing list --provider <provider>
|
clawdbot pairing list <provider>
|
||||||
```
|
```
|
||||||
|
|
||||||
Pending DM pairing requests are capped at **3 per provider** by default. If the list is full, new requests won’t generate a code until one is approved or expires.
|
Pending DM pairing requests are capped at **3 per provider** by default. If the list is full, new requests won’t generate a code until one is approved or expires.
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ Status: ready for DM and guild text channels via the official Discord bot gatewa
|
|||||||
- If you prefer env vars, still add `discord: { enabled: true }` to `~/.clawdbot/clawdbot.json` and set `DISCORD_BOT_TOKEN`.
|
- If you prefer env vars, still add `discord: { enabled: true }` to `~/.clawdbot/clawdbot.json` and set `DISCORD_BOT_TOKEN`.
|
||||||
5. Direct chats: use `user:<id>` (or a `<@id>` mention) when delivering; all turns land in the shared `main` session. Bare numeric IDs are ambiguous and rejected.
|
5. Direct chats: use `user:<id>` (or a `<@id>` mention) when delivering; all turns land in the shared `main` session. Bare numeric IDs are ambiguous and rejected.
|
||||||
6. Guild channels: use `channel:<channelId>` for delivery. Mentions are required by default and can be set per guild or per channel.
|
6. Guild channels: use `channel:<channelId>` for delivery. Mentions are required by default and can be set per guild or per channel.
|
||||||
7. Direct chats: secure by default via `discord.dm.policy` (default: `"pairing"`). Unknown senders get a pairing code (expires after 1 hour); approve via `clawdbot pairing approve --provider discord <code>`.
|
7. Direct chats: secure by default via `discord.dm.policy` (default: `"pairing"`). Unknown senders get a pairing code (expires after 1 hour); approve via `clawdbot pairing approve discord <code>`.
|
||||||
- To keep old “open to anyone” behavior: set `discord.dm.policy="open"` and `discord.dm.allowFrom=["*"]`.
|
- To keep old “open to anyone” behavior: set `discord.dm.policy="open"` and `discord.dm.allowFrom=["*"]`.
|
||||||
- To hard-allowlist: set `discord.dm.policy="allowlist"` and list senders in `discord.dm.allowFrom`.
|
- To hard-allowlist: set `discord.dm.policy="allowlist"` and list senders in `discord.dm.allowFrom`.
|
||||||
- To ignore all DMs: set `discord.dm.enabled=false` or `discord.dm.policy="disabled"`.
|
- To ignore all DMs: set `discord.dm.enabled=false` or `discord.dm.policy="disabled"`.
|
||||||
|
|||||||
@@ -86,8 +86,8 @@ DMs:
|
|||||||
- Default: `imessage.dmPolicy = "pairing"`.
|
- Default: `imessage.dmPolicy = "pairing"`.
|
||||||
- Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
- Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||||
- Approve via:
|
- Approve via:
|
||||||
- `clawdbot pairing list --provider imessage`
|
- `clawdbot pairing list imessage`
|
||||||
- `clawdbot pairing approve --provider imessage <CODE>`
|
- `clawdbot pairing approve imessage <CODE>`
|
||||||
- Pairing is the default token exchange for iMessage DMs. Details: [Pairing](/start/pairing)
|
- Pairing is the default token exchange for iMessage DMs. Details: [Pairing](/start/pairing)
|
||||||
|
|
||||||
Groups:
|
Groups:
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ DMs:
|
|||||||
- Default: `signal.dmPolicy = "pairing"`.
|
- Default: `signal.dmPolicy = "pairing"`.
|
||||||
- Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
- Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||||
- Approve via:
|
- Approve via:
|
||||||
- `clawdbot pairing list --provider signal`
|
- `clawdbot pairing list signal`
|
||||||
- `clawdbot pairing approve --provider signal <CODE>`
|
- `clawdbot pairing approve signal <CODE>`
|
||||||
- Pairing is the default token exchange for Signal DMs. Details: [Pairing](/start/pairing)
|
- Pairing is the default token exchange for Signal DMs. Details: [Pairing](/start/pairing)
|
||||||
- UUID-only senders (from `sourceUuid`) are stored as `uuid:<id>` in `signal.allowFrom`.
|
- UUID-only senders (from `sourceUuid`) are stored as `uuid:<id>` in `signal.allowFrom`.
|
||||||
|
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ For fine-grained control, use these tags in agent responses:
|
|||||||
|
|
||||||
## DM security (pairing)
|
## DM security (pairing)
|
||||||
- Default: `slack.dm.policy="pairing"` — unknown DM senders get a pairing code (expires after 1 hour).
|
- Default: `slack.dm.policy="pairing"` — unknown DM senders get a pairing code (expires after 1 hour).
|
||||||
- Approve via: `clawdbot pairing approve --provider slack <code>`.
|
- Approve via: `clawdbot pairing approve slack <code>`.
|
||||||
- To allow anyone: set `slack.dm.policy="open"` and `slack.dm.allowFrom=["*"]`.
|
- To allow anyone: set `slack.dm.policy="open"` and `slack.dm.allowFrom=["*"]`.
|
||||||
|
|
||||||
## Group policy
|
## Group policy
|
||||||
|
|||||||
@@ -151,8 +151,8 @@ Private topics (DM forum mode) also include `message_thread_id`. Clawdbot:
|
|||||||
### DM access
|
### DM access
|
||||||
- Default: `telegram.dmPolicy = "pairing"`. Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
- Default: `telegram.dmPolicy = "pairing"`. Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||||
- Approve via:
|
- Approve via:
|
||||||
- `clawdbot pairing list --provider telegram`
|
- `clawdbot pairing list telegram`
|
||||||
- `clawdbot pairing approve --provider telegram <CODE>`
|
- `clawdbot pairing approve telegram <CODE>`
|
||||||
- Pairing is the default token exchange used for Telegram DMs. Details: [Pairing](/start/pairing)
|
- Pairing is the default token exchange used for Telegram DMs. Details: [Pairing](/start/pairing)
|
||||||
|
|
||||||
### Group access
|
### Group access
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ Use a **separate phone number** for Clawdbot. Best UX, clean routing, no self-ch
|
|||||||
|
|
||||||
**Pairing mode (optional):**
|
**Pairing mode (optional):**
|
||||||
If you want pairing instead of allowlist, set `whatsapp.dmPolicy` to `pairing`. Unknown senders get a pairing code; approve with:
|
If you want pairing instead of allowlist, set `whatsapp.dmPolicy` to `pairing`. Unknown senders get a pairing code; approve with:
|
||||||
`clawdbot pairing approve --provider whatsapp <code>`
|
`clawdbot pairing approve whatsapp <code>`
|
||||||
|
|
||||||
### Personal number (fallback)
|
### Personal number (fallback)
|
||||||
Quick fallback: run Clawdbot on **your own number**. Message yourself (WhatsApp “Message yourself”) for testing so you don’t spam contacts. Expect to read verification codes on your main phone during setup and experiments. **Must enable self-chat mode.**
|
Quick fallback: run Clawdbot on **your own number**. Message yourself (WhatsApp “Message yourself”) for testing so you don’t spam contacts. Expect to read verification codes on your main phone during setup and experiments. **Must enable self-chat mode.**
|
||||||
@@ -95,7 +95,7 @@ on outbound replies.
|
|||||||
- Status/broadcast chats are ignored.
|
- Status/broadcast chats are ignored.
|
||||||
- Direct chats use E.164; groups use group JID.
|
- Direct chats use E.164; groups use group JID.
|
||||||
- **DM policy**: `whatsapp.dmPolicy` controls direct chat access (default: `pairing`).
|
- **DM policy**: `whatsapp.dmPolicy` controls direct chat access (default: `pairing`).
|
||||||
- Pairing: unknown senders get a pairing code (approve via `clawdbot pairing approve --provider whatsapp <code>`; codes expire after 1 hour).
|
- Pairing: unknown senders get a pairing code (approve via `clawdbot pairing approve whatsapp <code>`; codes expire after 1 hour).
|
||||||
- Open: requires `whatsapp.allowFrom` to include `"*"`.
|
- Open: requires `whatsapp.allowFrom` to include `"*"`.
|
||||||
- Self messages are always allowed; “self-chat mode” still requires `whatsapp.allowFrom` to include your own number.
|
- Self messages are always allowed; “self-chat mode” still requires `whatsapp.allowFrom` to include your own number.
|
||||||
|
|
||||||
|
|||||||
@@ -631,8 +631,8 @@ Treat inbound DMs as untrusted input. Defaults are designed to reduce risk:
|
|||||||
|
|
||||||
- Default behavior on DM‑capable providers is **pairing**:
|
- Default behavior on DM‑capable providers is **pairing**:
|
||||||
- Unknown senders receive a pairing code; the bot does not process their message.
|
- Unknown senders receive a pairing code; the bot does not process their message.
|
||||||
- Approve with: `clawdbot pairing approve --provider <provider> <code>`
|
- Approve with: `clawdbot pairing approve <provider> <code>`
|
||||||
- Pending requests are capped at **3 per provider**; check `clawdbot pairing list --provider <provider>` if a code didn’t arrive.
|
- Pending requests are capped at **3 per provider**; check `clawdbot pairing list <provider>` if a code didn’t arrive.
|
||||||
- Opening DMs publicly requires explicit opt‑in (`dmPolicy: "open"` and allowlist `"*"`).
|
- Opening DMs publicly requires explicit opt‑in (`dmPolicy: "open"` and allowlist `"*"`).
|
||||||
|
|
||||||
Run `clawdbot doctor` to surface risky DM policies.
|
Run `clawdbot doctor` to surface risky DM policies.
|
||||||
|
|||||||
@@ -128,8 +128,8 @@ Default posture: unknown DMs get a short code and messages are not processed unt
|
|||||||
If your first DM gets no reply, approve the pairing:
|
If your first DM gets no reply, approve the pairing:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
clawdbot pairing list --provider whatsapp
|
clawdbot pairing list whatsapp
|
||||||
clawdbot pairing approve --provider whatsapp <code>
|
clawdbot pairing approve whatsapp <code>
|
||||||
```
|
```
|
||||||
|
|
||||||
Pairing doc: [Pairing](/start/pairing)
|
Pairing doc: [Pairing](/start/pairing)
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ Pairing codes:
|
|||||||
### Approve a sender
|
### Approve a sender
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
clawdbot pairing list --provider telegram
|
clawdbot pairing list telegram
|
||||||
clawdbot pairing approve --provider telegram <CODE>
|
clawdbot pairing approve telegram <CODE>
|
||||||
```
|
```
|
||||||
|
|
||||||
Supported providers: `telegram`, `whatsapp`, `signal`, `imessage`, `discord`, `slack`.
|
Supported providers: `telegram`, `whatsapp`, `signal`, `imessage`, `discord`, `slack`.
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ Tip: `--json` does **not** imply non-interactive mode. Use `--non-interactive` (
|
|||||||
- Discord: bot token.
|
- Discord: bot token.
|
||||||
- Signal: optional `signal-cli` install + account config.
|
- Signal: optional `signal-cli` install + account config.
|
||||||
- iMessage: local `imsg` CLI path + DB access.
|
- iMessage: local `imsg` CLI path + DB access.
|
||||||
- DM security: default is pairing. First DM sends a code; approve via `clawdbot pairing approve --provider <provider> <code>` or use allowlists.
|
- DM security: default is pairing. First DM sends a code; approve via `clawdbot pairing approve <provider> <code>` or use allowlists.
|
||||||
|
|
||||||
6) **Daemon install**
|
6) **Daemon install**
|
||||||
- macOS: LaunchAgent
|
- macOS: LaunchAgent
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import { Command } from "commander";
|
|||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
const listProviderPairingRequests = vi.fn();
|
const listProviderPairingRequests = vi.fn();
|
||||||
|
const approveProviderPairingCode = vi.fn();
|
||||||
|
|
||||||
vi.mock("../pairing/pairing-store.js", () => ({
|
vi.mock("../pairing/pairing-store.js", () => ({
|
||||||
listProviderPairingRequests,
|
listProviderPairingRequests,
|
||||||
approveProviderPairingCode: vi.fn(),
|
approveProviderPairingCode,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("../telegram/send.js", () => ({
|
vi.mock("../telegram/send.js", () => ({
|
||||||
@@ -61,6 +62,18 @@ describe("pairing cli", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("accepts provider as positional for list", async () => {
|
||||||
|
const { registerPairingCli } = await import("./pairing-cli.js");
|
||||||
|
listProviderPairingRequests.mockResolvedValueOnce([]);
|
||||||
|
|
||||||
|
const program = new Command();
|
||||||
|
program.name("test");
|
||||||
|
registerPairingCli(program);
|
||||||
|
await program.parseAsync(["pairing", "list", "telegram"], { from: "user" });
|
||||||
|
|
||||||
|
expect(listProviderPairingRequests).toHaveBeenCalledWith("telegram");
|
||||||
|
});
|
||||||
|
|
||||||
it("labels Discord ids as discordUserId", async () => {
|
it("labels Discord ids as discordUserId", async () => {
|
||||||
const { registerPairingCli } = await import("./pairing-cli.js");
|
const { registerPairingCli } = await import("./pairing-cli.js");
|
||||||
listProviderPairingRequests.mockResolvedValueOnce([
|
listProviderPairingRequests.mockResolvedValueOnce([
|
||||||
@@ -84,4 +97,31 @@ describe("pairing cli", () => {
|
|||||||
expect.stringContaining("discordUserId=999"),
|
expect.stringContaining("discordUserId=999"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("accepts provider as positional for approve (npm-run compatible)", async () => {
|
||||||
|
const { registerPairingCli } = await import("./pairing-cli.js");
|
||||||
|
approveProviderPairingCode.mockResolvedValueOnce({
|
||||||
|
id: "123",
|
||||||
|
entry: {
|
||||||
|
id: "123",
|
||||||
|
code: "ABCDEFGH",
|
||||||
|
createdAt: "2026-01-08T00:00:00Z",
|
||||||
|
lastSeenAt: "2026-01-08T00:00:00Z",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const log = vi.spyOn(console, "log").mockImplementation(() => {});
|
||||||
|
const program = new Command();
|
||||||
|
program.name("test");
|
||||||
|
registerPairingCli(program);
|
||||||
|
await program.parseAsync(["pairing", "approve", "telegram", "ABCDEFGH"], {
|
||||||
|
from: "user",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(approveProviderPairingCode).toHaveBeenCalledWith({
|
||||||
|
provider: "telegram",
|
||||||
|
code: "ABCDEFGH",
|
||||||
|
});
|
||||||
|
expect(log).toHaveBeenCalledWith(expect.stringContaining("Approved"));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -83,13 +83,17 @@ export function registerPairingCli(program: Command) {
|
|||||||
pairing
|
pairing
|
||||||
.command("list")
|
.command("list")
|
||||||
.description("List pending pairing requests")
|
.description("List pending pairing requests")
|
||||||
.requiredOption(
|
.option("--provider <provider>", `Provider (${PROVIDERS.join(", ")})`)
|
||||||
"--provider <provider>",
|
.argument("[provider]", `Provider (${PROVIDERS.join(", ")})`)
|
||||||
`Provider (${PROVIDERS.join(", ")})`,
|
|
||||||
)
|
|
||||||
.option("--json", "Print JSON", false)
|
.option("--json", "Print JSON", false)
|
||||||
.action(async (opts) => {
|
.action(async (providerArg, opts) => {
|
||||||
const provider = parseProvider(opts.provider);
|
const providerRaw = opts.provider ?? providerArg;
|
||||||
|
if (!providerRaw) {
|
||||||
|
throw new Error(
|
||||||
|
`Provider required. Use --provider <provider> or pass it as the first argument (expected one of: ${PROVIDERS.join(", ")})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const provider = parseProvider(providerRaw);
|
||||||
const requests = await listProviderPairingRequests(provider);
|
const requests = await listProviderPairingRequests(provider);
|
||||||
if (opts.json) {
|
if (opts.json) {
|
||||||
console.log(JSON.stringify({ provider, requests }, null, 2));
|
console.log(JSON.stringify({ provider, requests }, null, 2));
|
||||||
@@ -111,20 +115,35 @@ export function registerPairingCli(program: Command) {
|
|||||||
pairing
|
pairing
|
||||||
.command("approve")
|
.command("approve")
|
||||||
.description("Approve a pairing code and allow that sender")
|
.description("Approve a pairing code and allow that sender")
|
||||||
.requiredOption(
|
.option("--provider <provider>", `Provider (${PROVIDERS.join(", ")})`)
|
||||||
"--provider <provider>",
|
.argument(
|
||||||
`Provider (${PROVIDERS.join(", ")})`,
|
"<codeOrProvider>",
|
||||||
|
"Pairing code (or provider when using 2 args)",
|
||||||
)
|
)
|
||||||
.argument("<code>", "Pairing code (shown to the requester)")
|
.argument("[code]", "Pairing code (when provider is passed as the 1st arg)")
|
||||||
.option("--notify", "Notify the requester on the same provider", false)
|
.option("--notify", "Notify the requester on the same provider", false)
|
||||||
.action(async (code, opts) => {
|
.action(async (codeOrProvider, code, opts) => {
|
||||||
const provider = parseProvider(opts.provider);
|
const providerRaw = opts.provider ?? codeOrProvider;
|
||||||
|
const resolvedCode = opts.provider ? codeOrProvider : code;
|
||||||
|
if (!opts.provider && !code) {
|
||||||
|
throw new Error(
|
||||||
|
`Usage: clawdbot pairing approve <provider> <code> (or: clawdbot pairing approve --provider <provider> <code>)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (opts.provider && code != null) {
|
||||||
|
throw new Error(
|
||||||
|
`Too many arguments. Use: clawdbot pairing approve --provider <provider> <code>`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const provider = parseProvider(providerRaw);
|
||||||
const approved = await approveProviderPairingCode({
|
const approved = await approveProviderPairingCode({
|
||||||
provider,
|
provider,
|
||||||
code: String(code),
|
code: String(resolvedCode),
|
||||||
});
|
});
|
||||||
if (!approved) {
|
if (!approved) {
|
||||||
throw new Error(`No pending pairing request found for code: ${code}`);
|
throw new Error(
|
||||||
|
`No pending pairing request found for code: ${String(resolvedCode)}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Approved ${provider} sender ${approved.id}.`);
|
console.log(`Approved ${provider} sender ${approved.id}.`);
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ export async function noteSecurityWarnings(cfg: ClawdbotConfig) {
|
|||||||
`- Telegram DMs: locked (telegram.dmPolicy="${dmPolicy}") with no allowlist; unknown senders will be blocked / get a pairing code.`,
|
`- Telegram DMs: locked (telegram.dmPolicy="${dmPolicy}") with no allowlist; unknown senders will be blocked / get a pairing code.`,
|
||||||
);
|
);
|
||||||
warnings.push(
|
warnings.push(
|
||||||
` Approve via: clawdbot pairing list --provider telegram / clawdbot pairing approve --provider telegram <code>`,
|
` Approve via: clawdbot pairing list telegram / clawdbot pairing approve telegram <code>`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ export async function noteSecurityWarnings(cfg: ClawdbotConfig) {
|
|||||||
allowFrom: cfg.discord?.dm?.allowFrom ?? [],
|
allowFrom: cfg.discord?.dm?.allowFrom ?? [],
|
||||||
allowFromPath: "discord.dm.",
|
allowFromPath: "discord.dm.",
|
||||||
approveHint:
|
approveHint:
|
||||||
"Approve via: clawdbot pairing list --provider discord / clawdbot pairing approve --provider discord <code>",
|
"Approve via: clawdbot pairing list discord / clawdbot pairing approve discord <code>",
|
||||||
normalizeEntry: (raw) =>
|
normalizeEntry: (raw) =>
|
||||||
raw.replace(/^(discord|user):/i, "").replace(/^<@!?(\d+)>$/, "$1"),
|
raw.replace(/^(discord|user):/i, "").replace(/^<@!?(\d+)>$/, "$1"),
|
||||||
});
|
});
|
||||||
@@ -151,7 +151,7 @@ export async function noteSecurityWarnings(cfg: ClawdbotConfig) {
|
|||||||
allowFrom: cfg.slack?.dm?.allowFrom ?? [],
|
allowFrom: cfg.slack?.dm?.allowFrom ?? [],
|
||||||
allowFromPath: "slack.dm.",
|
allowFromPath: "slack.dm.",
|
||||||
approveHint:
|
approveHint:
|
||||||
"Approve via: clawdbot pairing list --provider slack / clawdbot pairing approve --provider slack <code>",
|
"Approve via: clawdbot pairing list slack / clawdbot pairing approve slack <code>",
|
||||||
normalizeEntry: (raw) => raw.replace(/^(slack|user):/i, ""),
|
normalizeEntry: (raw) => raw.replace(/^(slack|user):/i, ""),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -164,7 +164,7 @@ export async function noteSecurityWarnings(cfg: ClawdbotConfig) {
|
|||||||
allowFrom: cfg.signal?.allowFrom ?? [],
|
allowFrom: cfg.signal?.allowFrom ?? [],
|
||||||
allowFromPath: "signal.",
|
allowFromPath: "signal.",
|
||||||
approveHint:
|
approveHint:
|
||||||
"Approve via: clawdbot pairing list --provider signal / clawdbot pairing approve --provider signal <code>",
|
"Approve via: clawdbot pairing list signal / clawdbot pairing approve signal <code>",
|
||||||
normalizeEntry: (raw) =>
|
normalizeEntry: (raw) =>
|
||||||
normalizeE164(raw.replace(/^signal:/i, "").trim()),
|
normalizeE164(raw.replace(/^signal:/i, "").trim()),
|
||||||
});
|
});
|
||||||
@@ -178,7 +178,7 @@ export async function noteSecurityWarnings(cfg: ClawdbotConfig) {
|
|||||||
allowFrom: cfg.imessage?.allowFrom ?? [],
|
allowFrom: cfg.imessage?.allowFrom ?? [],
|
||||||
allowFromPath: "imessage.",
|
allowFromPath: "imessage.",
|
||||||
approveHint:
|
approveHint:
|
||||||
"Approve via: clawdbot pairing list --provider imessage / clawdbot pairing approve --provider imessage <code>",
|
"Approve via: clawdbot pairing list imessage / clawdbot pairing approve imessage <code>",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,7 +190,7 @@ export async function noteSecurityWarnings(cfg: ClawdbotConfig) {
|
|||||||
allowFrom: cfg.whatsapp?.allowFrom ?? [],
|
allowFrom: cfg.whatsapp?.allowFrom ?? [],
|
||||||
allowFromPath: "whatsapp.",
|
allowFromPath: "whatsapp.",
|
||||||
approveHint:
|
approveHint:
|
||||||
"Approve via: clawdbot pairing list --provider whatsapp / clawdbot pairing approve --provider whatsapp <code>",
|
"Approve via: clawdbot pairing list whatsapp / clawdbot pairing approve whatsapp <code>",
|
||||||
normalizeEntry: (raw) => normalizeE164(raw),
|
normalizeEntry: (raw) => normalizeE164(raw),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ async function noteProviderPrimer(prompter: WizardPrompter): Promise<void> {
|
|||||||
await prompter.note(
|
await prompter.note(
|
||||||
[
|
[
|
||||||
"DM security: default is pairing; unknown DMs get a pairing code.",
|
"DM security: default is pairing; unknown DMs get a pairing code.",
|
||||||
"Approve with: clawdbot pairing approve --provider <provider> <code>",
|
"Approve with: clawdbot pairing approve <provider> <code>",
|
||||||
'Public DMs require dmPolicy="open" + allowFrom=["*"].',
|
'Public DMs require dmPolicy="open" + allowFrom=["*"].',
|
||||||
`Docs: ${formatDocsLink("/start/pairing", "start/pairing")}`,
|
`Docs: ${formatDocsLink("/start/pairing", "start/pairing")}`,
|
||||||
"",
|
"",
|
||||||
@@ -401,7 +401,7 @@ async function maybeConfigureDmPolicies(params: {
|
|||||||
await prompter.note(
|
await prompter.note(
|
||||||
[
|
[
|
||||||
"Default: pairing (unknown DMs get a pairing code).",
|
"Default: pairing (unknown DMs get a pairing code).",
|
||||||
`Approve: clawdbot pairing approve --provider ${params.provider} <code>`,
|
`Approve: clawdbot pairing approve ${params.provider} <code>`,
|
||||||
`Public DMs: ${params.policyKey}="open" + ${params.allowFromKey} includes "*".`,
|
`Public DMs: ${params.policyKey}="open" + ${params.allowFromKey} includes "*".`,
|
||||||
`Docs: ${formatDocsLink("/start/pairing", "start/pairing")}`,
|
`Docs: ${formatDocsLink("/start/pairing", "start/pairing")}`,
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ describe("buildPairingReply", () => {
|
|||||||
expect(text).toContain(testCase.idLine);
|
expect(text).toContain(testCase.idLine);
|
||||||
expect(text).toContain(`Pairing code: ${testCase.code}`);
|
expect(text).toContain(`Pairing code: ${testCase.code}`);
|
||||||
expect(text).toContain(
|
expect(text).toContain(
|
||||||
`clawdbot pairing approve --provider ${testCase.provider} <code>`,
|
`clawdbot pairing approve ${testCase.provider} <code>`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,6 @@ export function buildPairingReply(params: {
|
|||||||
`Pairing code: ${code}`,
|
`Pairing code: ${code}`,
|
||||||
"",
|
"",
|
||||||
"Ask the bot owner to approve with:",
|
"Ask the bot owner to approve with:",
|
||||||
`clawdbot pairing approve --provider ${provider} <code>`,
|
`clawdbot pairing approve ${provider} <code>`,
|
||||||
].join("\n");
|
].join("\n");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -496,7 +496,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
|||||||
`Pairing code: ${code}`,
|
`Pairing code: ${code}`,
|
||||||
"",
|
"",
|
||||||
"Ask the bot owner to approve with:",
|
"Ask the bot owner to approve with:",
|
||||||
"clawdbot pairing approve --provider telegram <code>",
|
"clawdbot pairing approve telegram <code>",
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user