diff --git a/CHANGELOG.md b/CHANGELOG.md index dcbbcb6c9..7d9a210e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ - Onboarding: QuickStart jumps straight into provider selection with Telegram preselected when unset. - Onboarding: QuickStart auto-installs the Gateway daemon with Node (no runtime picker). - Onboarding: clarify WhatsApp owner number prompt and label pairing phone number. +- Onboarding: add hosted MiniMax M2.1 API key flow + config. (#495) — thanks @tobiasbischoff - Daemon runtime: remove Bun from selection options. - CLI: restore hidden `gateway-daemon` alias for legacy launchd configs. - Onboarding/Configure: add OpenAI API key flow that stores in shared `~/.clawdbot/.env` for launchd; simplify Anthropic token prompt order. diff --git a/docs/cli/index.md b/docs/cli/index.md index 4e1f708a3..e61fc9e94 100644 --- a/docs/cli/index.md +++ b/docs/cli/index.md @@ -1,5 +1,5 @@ --- -summary: "CLI reference for clawdbot commands, subcommands, and options" +summary: "Clawdbot CLI reference for `clawdbot` commands, subcommands, and options" read_when: - Adding or modifying CLI commands or options - Documenting new command surfaces @@ -7,13 +7,13 @@ read_when: # CLI reference -This page mirrors `src/cli/*` and is the source of truth for CLI behavior. -If you change the CLI code, update this doc. +This page describes the current CLI behavior. If commands change, update this doc. ## Global flags - `--dev`: isolate state under `~/.clawdbot-dev` and shift default ports. - `--profile `: isolate state under `~/.clawdbot-`. +- `--no-color`: disable ANSI colors. - `-V`, `--version`, `-v`: print version and exit. ## Output styling @@ -21,11 +21,12 @@ If you change the CLI code, update this doc. - ANSI colors and progress indicators only render in TTY sessions. - OSC-8 hyperlinks render as clickable links in supported terminals; otherwise we fall back to plain URLs. - `--json` (and `--plain` where supported) disables styling for clean output. +- `--no-color` disables ANSI styling; `NO_COLOR=1` is also respected. - Long-running commands show a progress indicator (OSC 9;4 when supported). ## Color palette -Clawdbot uses a lobster palette for CLI output. Source of truth: `src/terminal/theme.ts`. +Clawdbot uses a lobster palette for CLI output. - `accent` (#FF5A2D): headings, provider labels, primary highlights. - `accentBright` (#FF7A3D): command names, emphasis. @@ -36,6 +37,8 @@ Clawdbot uses a lobster palette for CLI output. Source of truth: `src/terminal/t - `error` (#E23D2D): errors, failures. - `muted` (#8B7F77): de-emphasis, metadata. +Palette source of truth: `src/terminal/palette.ts` (aka “lobster seam”). + ## Command tree ``` @@ -55,8 +58,7 @@ clawdbot [--dev] [--profile ] list info check - send - poll + message agent agents list @@ -69,6 +71,7 @@ clawdbot [--dev] [--profile ] call health status + discover models list status @@ -206,7 +209,8 @@ Manage chat provider accounts (WhatsApp/Telegram/Discord/Slack/Signal/iMessage). Subcommands: - `providers list`: show configured chat providers and auth profiles (Claude Code + Codex CLI OAuth sync included). -- `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 and run small provider audits; use `status --deep` for local-only probes). +- Tip: `providers status` prints warnings with suggested fixes when it can detect common misconfigurations (then points you to `clawdbot doctor`). - `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 login`: interactive provider login (WhatsApp Web only). @@ -231,7 +235,9 @@ Common options: - `--json`: output JSON (includes usage unless `--no-usage` is set). OAuth sync sources: -- `~/.claude/.credentials.json` → `anthropic:claude-cli` +- Claude Code → `anthropic:claude-cli` + - macOS: Keychain item "Claude Code-credentials" (choose "Always Allow" to avoid launchd prompts) + - Linux/Windows: `~/.claude/.credentials.json` - `~/.codex/auth.json` → `openai-codex:codex-cli` More detail: [/concepts/oauth](/concepts/oauth) @@ -282,37 +288,25 @@ Options: ## Messaging + agent -### `send` -Send a message through a provider. +### `message` +Unified outbound messaging + provider actions. -Required: -- `--to ` -- `--message ` +See: [/cli/message](/cli/message) -Options: -- `--media ` -- `--gif-playback` -- `--provider ` -- `--account ` (WhatsApp) -- `--dry-run` -- `--json` -- `--verbose` +Subcommands: +- `message send|poll|react|reactions|read|edit|delete|pin|unpin|pins|permissions|search|timeout|kick|ban` +- `message thread ` +- `message emoji ` +- `message sticker ` +- `message role ` +- `message channel ` +- `message member info` +- `message voice status` +- `message event ` -### `poll` -Create a poll (WhatsApp or Discord). - -Required: -- `--to ` -- `--question ` -- `--option ` (repeat 2-12 times) - -Options: -- `--max-selections ` -- `--duration-hours ` (Discord) -- `--provider ` -- `--dry-run` -- `--json` -- `--verbose` +Examples: +- `clawdbot message send --to +15555550123 --message "Hi"` +- `clawdbot message poll --provider discord --to channel:123 --poll-question "Snack?" --poll-option Pizza --poll-option Sushi` ### `agent` Run one agent turn via the Gateway (or `--local` embedded). @@ -416,6 +410,8 @@ Options: - `--tailscale ` - `--tailscale-reset-on-exit` - `--allow-unconfigured` +- `--dev` +- `--reset` - `--force` (kill existing listener on port) - `--verbose` - `--ws-log ` @@ -443,10 +439,17 @@ Notes: ### `logs` Tail Gateway file logs via RPC. +Notes: +- TTY sessions render a colorized, structured view; non-TTY falls back to plain text. +- `--json` emits line-delimited JSON (one log event per line). + Examples: ```bash clawdbot logs --follow clawdbot logs --limit 200 +clawdbot logs --plain +clawdbot logs --json +clawdbot logs --no-color ``` ### `gateway ` @@ -480,6 +483,9 @@ Options: Options: - `--json` - `--plain` +- `--check` (exit 1=expired/missing, 2=expiring) + +Always includes the auth overview and OAuth expiry status for profiles in the auth store. ### `models set ` Set `agent.model.primary`. diff --git a/src/cli/program.ts b/src/cli/program.ts index 2117e71aa..18372f58c 100644 --- a/src/cli/program.ts +++ b/src/cli/program.ts @@ -8,9 +8,8 @@ import { import { configureCommand } from "../commands/configure.js"; import { doctorCommand } from "../commands/doctor.js"; import { healthCommand } from "../commands/health.js"; +import { messageCommand } from "../commands/message.js"; import { onboardCommand } from "../commands/onboard.js"; -import { pollCommand } from "../commands/poll.js"; -import { sendCommand } from "../commands/send.js"; import { sessionsCommand } from "../commands/sessions.js"; import { setupCommand } from "../commands/setup.js"; import { statusCommand } from "../commands/status.js"; @@ -26,7 +25,11 @@ import { autoMigrateLegacyState } from "../infra/state-migrations.js"; import { defaultRuntime } from "../runtime.js"; import { isRich, theme } from "../terminal/theme.js"; import { VERSION } from "../version.js"; -import { emitCliBanner, formatCliBannerLine } from "./banner.js"; +import { + emitCliBanner, + formatCliBannerArt, + formatCliBannerLine, +} from "./banner.js"; import { registerBrowserCli } from "./browser-cli.js"; import { hasExplicitOptions } from "./command-options.js"; import { registerCronCli } from "./cron-cli.js"; @@ -70,6 +73,8 @@ export function buildProgram() { "Use a named profile (isolates CLAWDBOT_STATE_DIR/CLAWDBOT_CONFIG_PATH under ~/.clawdbot-)", ); + program.option("--no-color", "Disable ANSI colors", false); + program.configureHelp({ optionTerm: (option) => theme.option(option.flags), subcommandTerm: (cmd) => theme.command(cmd.name()), @@ -97,8 +102,10 @@ export function buildProgram() { } program.addHelpText("beforeAll", () => { - const line = formatCliBannerLine(PROGRAM_VERSION, { richTty: isRich() }); - return `\n${line}\n`; + const rich = isRich(); + const art = formatCliBannerArt({ richTty: rich }); + const line = formatCliBannerLine(PROGRAM_VERSION, { richTty: rich }); + return `\n${art}\n${line}\n`; }); program.hook("preAction", async (_thisCommand, actionCommand) => { @@ -147,7 +154,7 @@ export function buildProgram() { "Link personal WhatsApp Web and show QR + connection logs.", ], [ - 'clawdbot send --to +15555550123 --message "Hi" --json', + 'clawdbot message send --to +15555550123 --message "Hi" --json', "Send via your web session and print JSON result.", ], ["clawdbot gateway --port 18789", "Run the WebSocket Gateway locally."], @@ -165,7 +172,7 @@ export function buildProgram() { "Talk directly to the agent using the Gateway; optionally send the WhatsApp reply.", ], [ - 'clawdbot send --provider telegram --to @mychat --message "Hi"', + 'clawdbot message send --provider telegram --to @mychat --message "Hi"', "Send via your Telegram bot.", ], ] as const; @@ -339,6 +346,12 @@ export function buildProgram() { false, ) .option("--yes", "Accept defaults without prompting", false) + .option("--repair", "Apply recommended repairs without prompting", false) + .option( + "--force", + "Apply aggressive repairs (overwrites custom service config)", + false, + ) .option( "--non-interactive", "Run without prompts (safe migrations only)", @@ -350,6 +363,8 @@ export function buildProgram() { await doctorCommand(defaultRuntime, { workspaceSuggestions: opts.workspaceSuggestions, yes: Boolean(opts.yes), + repair: Boolean(opts.repair), + force: Boolean(opts.force), nonInteractive: Boolean(opts.nonInteractive), deep: Boolean(opts.deep), }); @@ -402,107 +417,472 @@ export function buildProgram() { } }); - program - .command("send") - .description( - "Send a message (WhatsApp Web, Telegram bot, Discord, Slack, Signal, iMessage)", + const message = program + .command("message") + .description("Send messages and provider actions") + .addHelpText( + "after", + ` +Examples: + clawdbot message send --to +15555550123 --message "Hi" + clawdbot message send --to +15555550123 --message "Hi" --media photo.jpg + clawdbot message poll --provider discord --to channel:123 --poll-question "Snack?" --poll-option Pizza --poll-option Sushi + clawdbot message react --provider discord --to 123 --message-id 456 --emoji "✅"`, ) - .requiredOption( - "-t, --to ", - "Recipient: E.164 for WhatsApp/Signal, Telegram chat id/@username, Discord channel/user, or iMessage handle/chat_id", + .action(() => { + message.help({ error: true }); + }); + + const withMessageBase = (command: Command) => + command + .option( + "--provider ", + "Provider: whatsapp|telegram|discord|slack|signal|imessage", + ) + .option("--account ", "Provider account id") + .option("--json", "Output result as JSON", false) + .option("--dry-run", "Print payload and skip sending", false) + .option("--verbose", "Verbose logging", false); + + const withMessageTarget = (command: Command) => + command.option( + "-t, --to ", + "Recipient/channel: E.164 for WhatsApp/Signal, Telegram chat id/@username, Discord/Slack channel/user, or iMessage handle/chat_id", + ); + const withRequiredMessageTarget = (command: Command) => + command.requiredOption( + "-t, --to ", + "Recipient/channel: E.164 for WhatsApp/Signal, Telegram chat id/@username, Discord/Slack channel/user, or iMessage handle/chat_id", + ); + + const runMessageAction = async ( + action: string, + opts: Record, + ) => { + setVerbose(Boolean(opts.verbose)); + const deps = createDefaultDeps(); + try { + await messageCommand( + { + ...opts, + action, + account: opts.account as string | undefined, + }, + deps, + defaultRuntime, + ); + } catch (err) { + defaultRuntime.error(String(err)); + defaultRuntime.exit(1); + } + }; + + withMessageBase( + withRequiredMessageTarget( + message + .command("send") + .description("Send a message") + .requiredOption("-m, --message ", "Message body"), ) - .requiredOption("-m, --message ", "Message body") + .option( + "--media ", + "Attach media (image/audio/video/document). Accepts local paths or URLs.", + ) + .option("--reply-to ", "Reply-to message id") + .option("--thread-id ", "Thread id (Telegram forum thread)") + .option( + "--gif-playback", + "Treat video media as GIF playback (WhatsApp only).", + false, + ), + ).action(async (opts) => { + await runMessageAction("send", opts); + }); + + withMessageBase( + withRequiredMessageTarget( + message.command("poll").description("Send a poll"), + ), + ) + .requiredOption("--poll-question ", "Poll question") + .option( + "--poll-option ", + "Poll option (repeat 2-12 times)", + collectOption, + [] as string[], + ) + .option("--poll-multi", "Allow multiple selections", false) + .option("--poll-duration-hours ", "Poll duration (Discord)") + .option("-m, --message ", "Optional message body") + .action(async (opts) => { + await runMessageAction("poll", opts); + }); + + withMessageBase( + withMessageTarget( + message.command("react").description("Add or remove a reaction"), + ), + ) + .requiredOption("--message-id ", "Message id") + .option("--emoji ", "Emoji for reactions") + .option("--remove", "Remove reaction", false) + .option("--participant ", "WhatsApp reaction participant") + .option("--from-me", "WhatsApp reaction fromMe", false) + .option("--channel-id ", "Channel id (defaults to --to)") + .action(async (opts) => { + await runMessageAction("react", opts); + }); + + withMessageBase( + withMessageTarget( + message.command("reactions").description("List reactions on a message"), + ), + ) + .requiredOption("--message-id ", "Message id") + .option("--limit ", "Result limit") + .option("--channel-id ", "Channel id (defaults to --to)") + .action(async (opts) => { + await runMessageAction("reactions", opts); + }); + + withMessageBase( + withMessageTarget( + message.command("read").description("Read recent messages"), + ), + ) + .option("--limit ", "Result limit") + .option("--before ", "Read/search before id") + .option("--after ", "Read/search after id") + .option("--around ", "Read around id (Discord)") + .option("--channel-id ", "Channel id (defaults to --to)") + .action(async (opts) => { + await runMessageAction("read", opts); + }); + + withMessageBase( + withMessageTarget( + message + .command("edit") + .description("Edit a message") + .requiredOption("-m, --message ", "Message body"), + ), + ) + .requiredOption("--message-id ", "Message id") + .option("--channel-id ", "Channel id (defaults to --to)") + .action(async (opts) => { + await runMessageAction("edit", opts); + }); + + withMessageBase( + withMessageTarget( + message.command("delete").description("Delete a message"), + ), + ) + .requiredOption("--message-id ", "Message id") + .option("--channel-id ", "Channel id (defaults to --to)") + .action(async (opts) => { + await runMessageAction("delete", opts); + }); + + withMessageBase( + withMessageTarget(message.command("pin").description("Pin a message")), + ) + .requiredOption("--message-id ", "Message id") + .option("--channel-id ", "Channel id (defaults to --to)") + .action(async (opts) => { + await runMessageAction("pin", opts); + }); + + withMessageBase( + withMessageTarget(message.command("unpin").description("Unpin a message")), + ) + .option("--message-id ", "Message id") + .option("--channel-id ", "Channel id (defaults to --to)") + .action(async (opts) => { + await runMessageAction("unpin", opts); + }); + + withMessageBase( + withMessageTarget( + message.command("pins").description("List pinned messages"), + ), + ) + .option("--channel-id ", "Channel id (defaults to --to)") + .action(async (opts) => { + await runMessageAction("list-pins", opts); + }); + + withMessageBase( + withMessageTarget( + message.command("permissions").description("Fetch channel permissions"), + ), + ) + .option("--channel-id ", "Channel id (defaults to --to)") + .action(async (opts) => { + await runMessageAction("permissions", opts); + }); + + withMessageBase( + message.command("search").description("Search Discord messages"), + ) + .requiredOption("--guild-id ", "Guild id") + .requiredOption("--query ", "Search query") + .option("--channel-id ", "Channel id") + .option( + "--channel-ids ", + "Channel id (repeat)", + collectOption, + [] as string[], + ) + .option("--author-id ", "Author id") + .option( + "--author-ids ", + "Author id (repeat)", + collectOption, + [] as string[], + ) + .option("--limit ", "Result limit") + .action(async (opts) => { + await runMessageAction("search", opts); + }); + + const thread = message.command("thread").description("Thread actions"); + + withMessageBase( + withMessageTarget( + thread + .command("create") + .description("Create a thread") + .requiredOption("--thread-name ", "Thread name"), + ), + ) + .option("--channel-id ", "Channel id (defaults to --to)") + .option("--message-id ", "Message id (optional)") + .option("--auto-archive-min ", "Thread auto-archive minutes") + .action(async (opts) => { + await runMessageAction("thread-create", opts); + }); + + withMessageBase( + thread + .command("list") + .description("List threads") + .requiredOption("--guild-id ", "Guild id"), + ) + .option("--channel-id ", "Channel id") + .option("--include-archived", "Include archived threads", false) + .option("--before ", "Read/search before id") + .option("--limit ", "Result limit") + .action(async (opts) => { + await runMessageAction("thread-list", opts); + }); + + withMessageBase( + withRequiredMessageTarget( + thread + .command("reply") + .description("Reply in a thread") + .requiredOption("-m, --message ", "Message body"), + ), + ) .option( "--media ", "Attach media (image/audio/video/document). Accepts local paths or URLs.", ) - .option( - "--gif-playback", - "Treat video media as GIF playback (WhatsApp only).", - false, - ) - .option( - "--provider ", - "Delivery provider: whatsapp|telegram|discord|slack|signal|imessage (default: whatsapp)", - ) - .option("--account ", "WhatsApp account id (accountId)") - .option("--dry-run", "Print payload and skip sending", false) - .option("--json", "Output result as JSON", false) - .option("--verbose", "Verbose logging", false) - .addHelpText( - "after", - ` -Examples: - clawdbot send --to +15555550123 --message "Hi" - clawdbot send --to +15555550123 --message "Hi" --media photo.jpg - clawdbot send --to +15555550123 --message "Hi" --dry-run # print payload only - clawdbot send --to +15555550123 --message "Hi" --json # machine-readable result`, - ) + .option("--reply-to ", "Reply-to message id") .action(async (opts) => { - setVerbose(Boolean(opts.verbose)); - const deps = createDefaultDeps(); - try { - await sendCommand( - { - ...opts, - account: opts.account as string | undefined, - }, - deps, - defaultRuntime, - ); - } catch (err) { - defaultRuntime.error(String(err)); - defaultRuntime.exit(1); - } + await runMessageAction("thread-reply", opts); }); - program - .command("poll") - .description("Create a poll via WhatsApp or Discord") - .requiredOption( - "-t, --to ", - "Recipient: WhatsApp JID/number or Discord channel/user", - ) - .requiredOption("-q, --question ", "Poll question") - .requiredOption( - "-o, --option ", - "Poll option (use multiple times, 2-12 required)", - (value: string, previous: string[]) => previous.concat([value]), + const emoji = message.command("emoji").description("Emoji actions"); + withMessageBase(emoji.command("list").description("List emojis")) + .option("--guild-id ", "Guild id (Discord)") + .action(async (opts) => { + await runMessageAction("emoji-list", opts); + }); + + withMessageBase( + emoji + .command("upload") + .description("Upload an emoji") + .requiredOption("--guild-id ", "Guild id"), + ) + .requiredOption("--emoji-name ", "Emoji name") + .requiredOption("--media ", "Emoji media (path or URL)") + .option( + "--role-ids ", + "Role id (repeat)", + collectOption, [] as string[], ) - .option( - "-s, --max-selections ", - "How many options can be selected (default: 1)", - ) - .option( - "--duration-hours ", - "Poll duration in hours (Discord only, default: 24)", - ) - .option( - "--provider ", - "Delivery provider: whatsapp|discord (default: whatsapp)", - ) - .option("--dry-run", "Print payload and skip sending", false) - .option("--json", "Output result as JSON", false) - .option("--verbose", "Verbose logging", false) - .addHelpText( - "after", - ` -Examples: - clawdbot poll --to +15555550123 -q "Lunch today?" -o "Yes" -o "No" -o "Maybe" - clawdbot poll --to 123456789@g.us -q "Meeting time?" -o "10am" -o "2pm" -o "4pm" -s 2 - clawdbot poll --to channel:123456789 -q "Snack?" -o "Pizza" -o "Sushi" --provider discord - clawdbot poll --to channel:123456789 -q "Plan?" -o "A" -o "B" --provider discord --duration-hours 48`, - ) .action(async (opts) => { - setVerbose(Boolean(opts.verbose)); - const deps = createDefaultDeps(); - try { - await pollCommand(opts, deps, defaultRuntime); - } catch (err) { - defaultRuntime.error(String(err)); - defaultRuntime.exit(1); - } + await runMessageAction("emoji-upload", opts); + }); + + const sticker = message.command("sticker").description("Sticker actions"); + withMessageBase( + withRequiredMessageTarget( + sticker.command("send").description("Send stickers"), + ), + ) + .requiredOption("--sticker-id ", "Sticker id (repeat)", collectOption) + .option("-m, --message ", "Optional message body") + .action(async (opts) => { + await runMessageAction("sticker", opts); + }); + + withMessageBase( + sticker + .command("upload") + .description("Upload a sticker") + .requiredOption("--guild-id ", "Guild id"), + ) + .requiredOption("--sticker-name ", "Sticker name") + .requiredOption("--sticker-desc ", "Sticker description") + .requiredOption("--sticker-tags ", "Sticker tags") + .requiredOption("--media ", "Sticker media (path or URL)") + .action(async (opts) => { + await runMessageAction("sticker-upload", opts); + }); + + const role = message.command("role").description("Role actions"); + withMessageBase( + role + .command("info") + .description("List roles") + .requiredOption("--guild-id ", "Guild id"), + ).action(async (opts) => { + await runMessageAction("role-info", opts); + }); + + withMessageBase( + role + .command("add") + .description("Add role to a member") + .requiredOption("--guild-id ", "Guild id") + .requiredOption("--user-id ", "User id") + .requiredOption("--role-id ", "Role id"), + ).action(async (opts) => { + await runMessageAction("role-add", opts); + }); + + withMessageBase( + role + .command("remove") + .description("Remove role from a member") + .requiredOption("--guild-id ", "Guild id") + .requiredOption("--user-id ", "User id") + .requiredOption("--role-id ", "Role id"), + ).action(async (opts) => { + await runMessageAction("role-remove", opts); + }); + + const channel = message.command("channel").description("Channel actions"); + withMessageBase( + channel + .command("info") + .description("Fetch channel info") + .requiredOption("--channel-id ", "Channel id"), + ).action(async (opts) => { + await runMessageAction("channel-info", opts); + }); + + withMessageBase( + channel + .command("list") + .description("List channels") + .requiredOption("--guild-id ", "Guild id"), + ).action(async (opts) => { + await runMessageAction("channel-list", opts); + }); + + const member = message.command("member").description("Member actions"); + withMessageBase( + member + .command("info") + .description("Fetch member info") + .requiredOption("--user-id ", "User id"), + ) + .option("--guild-id ", "Guild id (Discord)") + .action(async (opts) => { + await runMessageAction("member-info", opts); + }); + + const voice = message.command("voice").description("Voice actions"); + withMessageBase( + voice + .command("status") + .description("Fetch voice status") + .requiredOption("--guild-id ", "Guild id") + .requiredOption("--user-id ", "User id"), + ).action(async (opts) => { + await runMessageAction("voice-status", opts); + }); + + const event = message.command("event").description("Event actions"); + withMessageBase( + event + .command("list") + .description("List scheduled events") + .requiredOption("--guild-id ", "Guild id"), + ).action(async (opts) => { + await runMessageAction("event-list", opts); + }); + + withMessageBase( + event + .command("create") + .description("Create a scheduled event") + .requiredOption("--guild-id ", "Guild id") + .requiredOption("--event-name ", "Event name") + .requiredOption("--start-time ", "Event start time"), + ) + .option("--end-time ", "Event end time") + .option("--desc ", "Event description") + .option("--channel-id ", "Channel id") + .option("--location ", "Event location") + .option("--event-type ", "Event type") + .action(async (opts) => { + await runMessageAction("event-create", opts); + }); + + withMessageBase( + message + .command("timeout") + .description("Timeout a member") + .requiredOption("--guild-id ", "Guild id") + .requiredOption("--user-id ", "User id"), + ) + .option("--duration-min ", "Timeout duration minutes") + .option("--until ", "Timeout until") + .option("--reason ", "Moderation reason") + .action(async (opts) => { + await runMessageAction("timeout", opts); + }); + + withMessageBase( + message + .command("kick") + .description("Kick a member") + .requiredOption("--guild-id ", "Guild id") + .requiredOption("--user-id ", "User id"), + ) + .option("--reason ", "Moderation reason") + .action(async (opts) => { + await runMessageAction("kick", opts); + }); + + withMessageBase( + message + .command("ban") + .description("Ban a member") + .requiredOption("--guild-id ", "Guild id") + .requiredOption("--user-id ", "User id"), + ) + .option("--reason ", "Moderation reason") + .option("--delete-days ", "Ban delete message days") + .action(async (opts) => { + await runMessageAction("ban", opts); }); program diff --git a/src/commands/onboard-auth.ts b/src/commands/onboard-auth.ts index 71f42b9f4..726eba3c8 100644 --- a/src/commands/onboard-auth.ts +++ b/src/commands/onboard-auth.ts @@ -2,6 +2,7 @@ import type { OAuthCredentials, OAuthProvider } from "@mariozechner/pi-ai"; import { resolveDefaultAgentDir } from "../agents/agent-scope.js"; import { upsertAuthProfile } from "../agents/auth-profiles.js"; import type { ClawdbotConfig } from "../config/config.js"; +import type { ModelDefinitionConfig } from "../config/types.js"; const DEFAULT_MINIMAX_BASE_URL = "https://api.minimax.io/v1"; export const MINIMAX_HOSTED_MODEL_ID = "MiniMax-M2.1"; @@ -173,7 +174,7 @@ export function applyMinimaxHostedProviderConfig( }; const providers = { ...cfg.models?.providers }; - const hostedModel = { + const hostedModel: ModelDefinitionConfig = { id: MINIMAX_HOSTED_MODEL_ID, name: "MiniMax M2.1", reasoning: false, diff --git a/src/telegram/send.ts b/src/telegram/send.ts index 16ee5e3d8..5309a5f89 100644 --- a/src/telegram/send.ts +++ b/src/telegram/send.ts @@ -1,5 +1,5 @@ import type { ReactionType, ReactionTypeEmoji } from "@grammyjs/types"; -import { Bot, InputFile, type ApiClientOptions } from "grammy"; +import { type ApiClientOptions, Bot, InputFile } from "grammy"; import { loadConfig } from "../config/config.js"; import { formatErrorMessage } from "../infra/errors.js"; import { recordProviderActivity } from "../infra/provider-activity.js"; @@ -122,9 +122,7 @@ export async function sendMessageTelegram( const client: ApiClientOptions | undefined = fetchImpl ? { fetch: fetchImpl as unknown as ApiClientOptions["fetch"] } : undefined; - const api = - opts.api ?? - new Bot(token, client ? { client } : undefined).api; + const api = opts.api ?? new Bot(token, client ? { client } : undefined).api; const mediaUrl = opts.mediaUrl?.trim(); // Build optional params for forum topics and reply threading. @@ -296,9 +294,7 @@ export async function reactMessageTelegram( const client: ApiClientOptions | undefined = fetchImpl ? { fetch: fetchImpl as unknown as ApiClientOptions["fetch"] } : undefined; - const api = - opts.api ?? - new Bot(token, client ? { client } : undefined).api; + const api = opts.api ?? new Bot(token, client ? { client } : undefined).api; const request = createTelegramRetryRunner({ retry: opts.retry, configRetry: account.config.retry, diff --git a/src/telegram/webhook-set.ts b/src/telegram/webhook-set.ts index fd68a84be..eced660e6 100644 --- a/src/telegram/webhook-set.ts +++ b/src/telegram/webhook-set.ts @@ -1,4 +1,4 @@ -import { Bot, type ApiClientOptions } from "grammy"; +import { type ApiClientOptions, Bot } from "grammy"; import { resolveTelegramFetch } from "./fetch.js"; export async function setTelegramWebhook(opts: { @@ -11,10 +11,7 @@ export async function setTelegramWebhook(opts: { const client: ApiClientOptions | undefined = fetchImpl ? { fetch: fetchImpl as unknown as ApiClientOptions["fetch"] } : undefined; - const bot = new Bot( - opts.token, - client ? { client } : undefined, - ); + const bot = new Bot(opts.token, client ? { client } : undefined); await bot.api.setWebhook(opts.url, { secret_token: opts.secret, drop_pending_updates: opts.dropPendingUpdates ?? false, @@ -26,9 +23,6 @@ export async function deleteTelegramWebhook(opts: { token: string }) { const client: ApiClientOptions | undefined = fetchImpl ? { fetch: fetchImpl as unknown as ApiClientOptions["fetch"] } : undefined; - const bot = new Bot( - opts.token, - client ? { client } : undefined, - ); + const bot = new Bot(opts.token, client ? { client } : undefined); await bot.api.deleteWebhook(); }