mirror of
https://github.com/clawdbot/clawdbot.git
synced 2026-01-31 19:37:45 +01:00
Onboarding: add MiniMax hosted API key option
This commit is contained in:
committed by
Peter Steinberger
parent
7b79823b24
commit
a0bb2bccaf
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
summary: "Clawdbot CLI reference for `clawdbot` commands, subcommands, and options"
|
summary: "CLI reference for clawdbot commands, subcommands, and options"
|
||||||
read_when:
|
read_when:
|
||||||
- Adding or modifying CLI commands or options
|
- Adding or modifying CLI commands or options
|
||||||
- Documenting new command surfaces
|
- Documenting new command surfaces
|
||||||
@@ -7,13 +7,13 @@ read_when:
|
|||||||
|
|
||||||
# CLI reference
|
# CLI reference
|
||||||
|
|
||||||
This page describes the current CLI behavior. If commands change, update this doc.
|
This page mirrors `src/cli/*` and is the source of truth for CLI behavior.
|
||||||
|
If you change the CLI code, update this doc.
|
||||||
|
|
||||||
## Global flags
|
## Global flags
|
||||||
|
|
||||||
- `--dev`: isolate state under `~/.clawdbot-dev` and shift default ports.
|
- `--dev`: isolate state under `~/.clawdbot-dev` and shift default ports.
|
||||||
- `--profile <name>`: isolate state under `~/.clawdbot-<name>`.
|
- `--profile <name>`: isolate state under `~/.clawdbot-<name>`.
|
||||||
- `--no-color`: disable ANSI colors.
|
|
||||||
- `-V`, `--version`, `-v`: print version and exit.
|
- `-V`, `--version`, `-v`: print version and exit.
|
||||||
|
|
||||||
## Output styling
|
## Output styling
|
||||||
@@ -21,12 +21,11 @@ This page describes the current CLI behavior. If commands change, update this do
|
|||||||
- ANSI colors and progress indicators only render in TTY sessions.
|
- 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.
|
- 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.
|
- `--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).
|
- Long-running commands show a progress indicator (OSC 9;4 when supported).
|
||||||
|
|
||||||
## Color palette
|
## Color palette
|
||||||
|
|
||||||
Clawdbot uses a lobster palette for CLI output.
|
Clawdbot uses a lobster palette for CLI output. Source of truth: `src/terminal/theme.ts`.
|
||||||
|
|
||||||
- `accent` (#FF5A2D): headings, provider labels, primary highlights.
|
- `accent` (#FF5A2D): headings, provider labels, primary highlights.
|
||||||
- `accentBright` (#FF7A3D): command names, emphasis.
|
- `accentBright` (#FF7A3D): command names, emphasis.
|
||||||
@@ -37,8 +36,6 @@ Clawdbot uses a lobster palette for CLI output.
|
|||||||
- `error` (#E23D2D): errors, failures.
|
- `error` (#E23D2D): errors, failures.
|
||||||
- `muted` (#8B7F77): de-emphasis, metadata.
|
- `muted` (#8B7F77): de-emphasis, metadata.
|
||||||
|
|
||||||
Palette source of truth: `src/terminal/palette.ts` (aka “lobster seam”).
|
|
||||||
|
|
||||||
## Command tree
|
## Command tree
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -58,7 +55,8 @@ clawdbot [--dev] [--profile <name>] <command>
|
|||||||
list
|
list
|
||||||
info
|
info
|
||||||
check
|
check
|
||||||
message
|
send
|
||||||
|
poll
|
||||||
agent
|
agent
|
||||||
agents
|
agents
|
||||||
list
|
list
|
||||||
@@ -71,7 +69,6 @@ clawdbot [--dev] [--profile <name>] <command>
|
|||||||
call
|
call
|
||||||
health
|
health
|
||||||
status
|
status
|
||||||
discover
|
|
||||||
models
|
models
|
||||||
list
|
list
|
||||||
status
|
status
|
||||||
@@ -169,10 +166,11 @@ Options:
|
|||||||
- `--workspace <dir>`
|
- `--workspace <dir>`
|
||||||
- `--non-interactive`
|
- `--non-interactive`
|
||||||
- `--mode <local|remote>`
|
- `--mode <local|remote>`
|
||||||
- `--auth-choice <oauth|claude-cli|token|openai-codex|openai-api-key|codex-cli|antigravity|gemini-api-key|apiKey|minimax|skip>`
|
- `--auth-choice <oauth|claude-cli|token|openai-codex|openai-api-key|codex-cli|antigravity|gemini-api-key|apiKey|minimax-cloud|minimax|skip>`
|
||||||
- `--anthropic-api-key <key>`
|
- `--anthropic-api-key <key>`
|
||||||
- `--openai-api-key <key>`
|
- `--openai-api-key <key>`
|
||||||
- `--gemini-api-key <key>`
|
- `--gemini-api-key <key>`
|
||||||
|
- `--minimax-api-key <key>`
|
||||||
- `--gateway-port <port>`
|
- `--gateway-port <port>`
|
||||||
- `--gateway-bind <loopback|lan|tailnet|auto>`
|
- `--gateway-bind <loopback|lan|tailnet|auto>`
|
||||||
- `--gateway-auth <off|token|password>`
|
- `--gateway-auth <off|token|password>`
|
||||||
@@ -208,8 +206,7 @@ Manage chat provider accounts (WhatsApp/Telegram/Discord/Slack/Signal/iMessage).
|
|||||||
|
|
||||||
Subcommands:
|
Subcommands:
|
||||||
- `providers list`: show configured chat providers and auth profiles (Claude Code + Codex CLI OAuth sync included).
|
- `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 and run small provider audits; 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).
|
||||||
- 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 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 login`: interactive provider login (WhatsApp Web only).
|
||||||
@@ -234,9 +231,7 @@ Common options:
|
|||||||
- `--json`: output JSON (includes usage unless `--no-usage` is set).
|
- `--json`: output JSON (includes usage unless `--no-usage` is set).
|
||||||
|
|
||||||
OAuth sync sources:
|
OAuth sync sources:
|
||||||
- Claude Code → `anthropic:claude-cli`
|
- `~/.claude/.credentials.json` → `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`
|
- `~/.codex/auth.json` → `openai-codex:codex-cli`
|
||||||
|
|
||||||
More detail: [/concepts/oauth](/concepts/oauth)
|
More detail: [/concepts/oauth](/concepts/oauth)
|
||||||
@@ -287,25 +282,37 @@ Options:
|
|||||||
|
|
||||||
## Messaging + agent
|
## Messaging + agent
|
||||||
|
|
||||||
### `message`
|
### `send`
|
||||||
Unified outbound messaging + provider actions.
|
Send a message through a provider.
|
||||||
|
|
||||||
See: [/cli/message](/cli/message)
|
Required:
|
||||||
|
- `--to <dest>`
|
||||||
|
- `--message <text>`
|
||||||
|
|
||||||
Subcommands:
|
Options:
|
||||||
- `message send|poll|react|reactions|read|edit|delete|pin|unpin|pins|permissions|search|timeout|kick|ban`
|
- `--media <path-or-url>`
|
||||||
- `message thread <create|list|reply>`
|
- `--gif-playback`
|
||||||
- `message emoji <list|upload>`
|
- `--provider <whatsapp|telegram|discord|slack|signal|imessage>`
|
||||||
- `message sticker <send|upload>`
|
- `--account <id>` (WhatsApp)
|
||||||
- `message role <info|add|remove>`
|
- `--dry-run`
|
||||||
- `message channel <info|list>`
|
- `--json`
|
||||||
- `message member info`
|
- `--verbose`
|
||||||
- `message voice status`
|
|
||||||
- `message event <list|create>`
|
|
||||||
|
|
||||||
Examples:
|
### `poll`
|
||||||
- `clawdbot message send --to +15555550123 --message "Hi"`
|
Create a poll (WhatsApp or Discord).
|
||||||
- `clawdbot message poll --provider discord --to channel:123 --poll-question "Snack?" --poll-option Pizza --poll-option Sushi`
|
|
||||||
|
Required:
|
||||||
|
- `--to <id>`
|
||||||
|
- `--question <text>`
|
||||||
|
- `--option <choice>` (repeat 2-12 times)
|
||||||
|
|
||||||
|
Options:
|
||||||
|
- `--max-selections <n>`
|
||||||
|
- `--duration-hours <n>` (Discord)
|
||||||
|
- `--provider <whatsapp|discord>`
|
||||||
|
- `--dry-run`
|
||||||
|
- `--json`
|
||||||
|
- `--verbose`
|
||||||
|
|
||||||
### `agent`
|
### `agent`
|
||||||
Run one agent turn via the Gateway (or `--local` embedded).
|
Run one agent turn via the Gateway (or `--local` embedded).
|
||||||
@@ -409,8 +416,6 @@ Options:
|
|||||||
- `--tailscale <off|serve|funnel>`
|
- `--tailscale <off|serve|funnel>`
|
||||||
- `--tailscale-reset-on-exit`
|
- `--tailscale-reset-on-exit`
|
||||||
- `--allow-unconfigured`
|
- `--allow-unconfigured`
|
||||||
- `--dev`
|
|
||||||
- `--reset`
|
|
||||||
- `--force` (kill existing listener on port)
|
- `--force` (kill existing listener on port)
|
||||||
- `--verbose`
|
- `--verbose`
|
||||||
- `--ws-log <auto|full|compact>`
|
- `--ws-log <auto|full|compact>`
|
||||||
@@ -438,17 +443,10 @@ Notes:
|
|||||||
### `logs`
|
### `logs`
|
||||||
Tail Gateway file logs via RPC.
|
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:
|
Examples:
|
||||||
```bash
|
```bash
|
||||||
clawdbot logs --follow
|
clawdbot logs --follow
|
||||||
clawdbot logs --limit 200
|
clawdbot logs --limit 200
|
||||||
clawdbot logs --plain
|
|
||||||
clawdbot logs --json
|
|
||||||
clawdbot logs --no-color
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### `gateway <subcommand>`
|
### `gateway <subcommand>`
|
||||||
@@ -482,9 +480,6 @@ Options:
|
|||||||
Options:
|
Options:
|
||||||
- `--json`
|
- `--json`
|
||||||
- `--plain`
|
- `--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 <model>`
|
### `models set <model>`
|
||||||
Set `agent.model.primary`.
|
Set `agent.model.primary`.
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ async function main(): Promise<void> {
|
|||||||
const minimaxBaseUrl =
|
const minimaxBaseUrl =
|
||||||
process.env.MINIMAX_BASE_URL?.trim() || "https://api.minimax.io/v1";
|
process.env.MINIMAX_BASE_URL?.trim() || "https://api.minimax.io/v1";
|
||||||
const minimaxModelId =
|
const minimaxModelId =
|
||||||
process.env.MINIMAX_MODEL?.trim() || "minimax-m2.1";
|
process.env.MINIMAX_MODEL?.trim() || "MiniMax-M2.1";
|
||||||
|
|
||||||
const minimaxModel: Model<"openai-completions"> = {
|
const minimaxModel: Model<"openai-completions"> = {
|
||||||
id: minimaxModelId,
|
id: minimaxModelId,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { describe, expect, it } from "vitest";
|
|||||||
const MINIMAX_KEY = process.env.MINIMAX_API_KEY ?? "";
|
const MINIMAX_KEY = process.env.MINIMAX_API_KEY ?? "";
|
||||||
const MINIMAX_BASE_URL =
|
const MINIMAX_BASE_URL =
|
||||||
process.env.MINIMAX_BASE_URL?.trim() || "https://api.minimax.io/v1";
|
process.env.MINIMAX_BASE_URL?.trim() || "https://api.minimax.io/v1";
|
||||||
const MINIMAX_MODEL = process.env.MINIMAX_MODEL?.trim() || "minimax-m2.1";
|
const MINIMAX_MODEL = process.env.MINIMAX_MODEL?.trim() || "MiniMax-M2.1";
|
||||||
const LIVE = process.env.MINIMAX_LIVE_TEST === "1" || process.env.LIVE === "1";
|
const LIVE = process.env.MINIMAX_LIVE_TEST === "1" || process.env.LIVE === "1";
|
||||||
|
|
||||||
const describeLive = LIVE && MINIMAX_KEY ? describe : describe.skip;
|
const describeLive = LIVE && MINIMAX_KEY ? describe : describe.skip;
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null {
|
|||||||
cerebras: "CEREBRAS_API_KEY",
|
cerebras: "CEREBRAS_API_KEY",
|
||||||
xai: "XAI_API_KEY",
|
xai: "XAI_API_KEY",
|
||||||
openrouter: "OPENROUTER_API_KEY",
|
openrouter: "OPENROUTER_API_KEY",
|
||||||
|
minimax: "MINIMAX_API_KEY",
|
||||||
zai: "ZAI_API_KEY",
|
zai: "ZAI_API_KEY",
|
||||||
mistral: "MISTRAL_API_KEY",
|
mistral: "MISTRAL_API_KEY",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ import {
|
|||||||
import { configureCommand } from "../commands/configure.js";
|
import { configureCommand } from "../commands/configure.js";
|
||||||
import { doctorCommand } from "../commands/doctor.js";
|
import { doctorCommand } from "../commands/doctor.js";
|
||||||
import { healthCommand } from "../commands/health.js";
|
import { healthCommand } from "../commands/health.js";
|
||||||
import { messageCommand } from "../commands/message.js";
|
|
||||||
import { onboardCommand } from "../commands/onboard.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 { sessionsCommand } from "../commands/sessions.js";
|
||||||
import { setupCommand } from "../commands/setup.js";
|
import { setupCommand } from "../commands/setup.js";
|
||||||
import { statusCommand } from "../commands/status.js";
|
import { statusCommand } from "../commands/status.js";
|
||||||
@@ -25,11 +26,7 @@ import { autoMigrateLegacyState } from "../infra/state-migrations.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 {
|
import { emitCliBanner, formatCliBannerLine } from "./banner.js";
|
||||||
emitCliBanner,
|
|
||||||
formatCliBannerArt,
|
|
||||||
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";
|
||||||
import { registerCronCli } from "./cron-cli.js";
|
import { registerCronCli } from "./cron-cli.js";
|
||||||
@@ -73,8 +70,6 @@ export function buildProgram() {
|
|||||||
"Use a named profile (isolates CLAWDBOT_STATE_DIR/CLAWDBOT_CONFIG_PATH under ~/.clawdbot-<name>)",
|
"Use a named profile (isolates CLAWDBOT_STATE_DIR/CLAWDBOT_CONFIG_PATH under ~/.clawdbot-<name>)",
|
||||||
);
|
);
|
||||||
|
|
||||||
program.option("--no-color", "Disable ANSI colors", false);
|
|
||||||
|
|
||||||
program.configureHelp({
|
program.configureHelp({
|
||||||
optionTerm: (option) => theme.option(option.flags),
|
optionTerm: (option) => theme.option(option.flags),
|
||||||
subcommandTerm: (cmd) => theme.command(cmd.name()),
|
subcommandTerm: (cmd) => theme.command(cmd.name()),
|
||||||
@@ -102,10 +97,8 @@ export function buildProgram() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
program.addHelpText("beforeAll", () => {
|
program.addHelpText("beforeAll", () => {
|
||||||
const rich = isRich();
|
const line = formatCliBannerLine(PROGRAM_VERSION, { richTty: isRich() });
|
||||||
const art = formatCliBannerArt({ richTty: rich });
|
return `\n${line}\n`;
|
||||||
const line = formatCliBannerLine(PROGRAM_VERSION, { richTty: rich });
|
|
||||||
return `\n${art}\n${line}\n`;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
program.hook("preAction", async (_thisCommand, actionCommand) => {
|
program.hook("preAction", async (_thisCommand, actionCommand) => {
|
||||||
@@ -154,7 +147,7 @@ export function buildProgram() {
|
|||||||
"Link personal WhatsApp Web and show QR + connection logs.",
|
"Link personal WhatsApp Web and show QR + connection logs.",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'clawdbot message send --to +15555550123 --message "Hi" --json',
|
'clawdbot send --to +15555550123 --message "Hi" --json',
|
||||||
"Send via your web session and print JSON result.",
|
"Send via your web session and print JSON result.",
|
||||||
],
|
],
|
||||||
["clawdbot gateway --port 18789", "Run the WebSocket Gateway locally."],
|
["clawdbot gateway --port 18789", "Run the WebSocket Gateway locally."],
|
||||||
@@ -172,7 +165,7 @@ export function buildProgram() {
|
|||||||
"Talk directly to the agent using the Gateway; optionally send the WhatsApp reply.",
|
"Talk directly to the agent using the Gateway; optionally send the WhatsApp reply.",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'clawdbot message send --provider telegram --to @mychat --message "Hi"',
|
'clawdbot send --provider telegram --to @mychat --message "Hi"',
|
||||||
"Send via your Telegram bot.",
|
"Send via your Telegram bot.",
|
||||||
],
|
],
|
||||||
] as const;
|
] as const;
|
||||||
@@ -240,11 +233,12 @@ export function buildProgram() {
|
|||||||
.option("--mode <mode>", "Wizard mode: local|remote")
|
.option("--mode <mode>", "Wizard mode: local|remote")
|
||||||
.option(
|
.option(
|
||||||
"--auth-choice <choice>",
|
"--auth-choice <choice>",
|
||||||
"Auth: oauth|claude-cli|token|openai-codex|openai-api-key|codex-cli|antigravity|gemini-api-key|apiKey|minimax|skip",
|
"Auth: oauth|claude-cli|token|openai-codex|openai-api-key|codex-cli|antigravity|gemini-api-key|apiKey|minimax-cloud|minimax|skip",
|
||||||
)
|
)
|
||||||
.option("--anthropic-api-key <key>", "Anthropic API key")
|
.option("--anthropic-api-key <key>", "Anthropic API key")
|
||||||
.option("--openai-api-key <key>", "OpenAI API key")
|
.option("--openai-api-key <key>", "OpenAI API key")
|
||||||
.option("--gemini-api-key <key>", "Gemini API key")
|
.option("--gemini-api-key <key>", "Gemini API key")
|
||||||
|
.option("--minimax-api-key <key>", "MiniMax API key")
|
||||||
.option("--gateway-port <port>", "Gateway port")
|
.option("--gateway-port <port>", "Gateway port")
|
||||||
.option("--gateway-bind <mode>", "Gateway bind: loopback|lan|tailnet|auto")
|
.option("--gateway-bind <mode>", "Gateway bind: loopback|lan|tailnet|auto")
|
||||||
.option("--gateway-auth <mode>", "Gateway auth: off|token|password")
|
.option("--gateway-auth <mode>", "Gateway auth: off|token|password")
|
||||||
@@ -277,12 +271,14 @@ export function buildProgram() {
|
|||||||
| "antigravity"
|
| "antigravity"
|
||||||
| "gemini-api-key"
|
| "gemini-api-key"
|
||||||
| "apiKey"
|
| "apiKey"
|
||||||
|
| "minimax-cloud"
|
||||||
| "minimax"
|
| "minimax"
|
||||||
| "skip"
|
| "skip"
|
||||||
| undefined,
|
| undefined,
|
||||||
anthropicApiKey: opts.anthropicApiKey as string | undefined,
|
anthropicApiKey: opts.anthropicApiKey as string | undefined,
|
||||||
openaiApiKey: opts.openaiApiKey as string | undefined,
|
openaiApiKey: opts.openaiApiKey as string | undefined,
|
||||||
geminiApiKey: opts.geminiApiKey as string | undefined,
|
geminiApiKey: opts.geminiApiKey as string | undefined,
|
||||||
|
minimaxApiKey: opts.minimaxApiKey as string | undefined,
|
||||||
gatewayPort:
|
gatewayPort:
|
||||||
typeof opts.gatewayPort === "string"
|
typeof opts.gatewayPort === "string"
|
||||||
? Number.parseInt(opts.gatewayPort, 10)
|
? Number.parseInt(opts.gatewayPort, 10)
|
||||||
@@ -343,12 +339,6 @@ export function buildProgram() {
|
|||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.option("--yes", "Accept defaults without prompting", 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(
|
.option(
|
||||||
"--non-interactive",
|
"--non-interactive",
|
||||||
"Run without prompts (safe migrations only)",
|
"Run without prompts (safe migrations only)",
|
||||||
@@ -360,8 +350,6 @@ export function buildProgram() {
|
|||||||
await doctorCommand(defaultRuntime, {
|
await doctorCommand(defaultRuntime, {
|
||||||
workspaceSuggestions: opts.workspaceSuggestions,
|
workspaceSuggestions: opts.workspaceSuggestions,
|
||||||
yes: Boolean(opts.yes),
|
yes: Boolean(opts.yes),
|
||||||
repair: Boolean(opts.repair),
|
|
||||||
force: Boolean(opts.force),
|
|
||||||
nonInteractive: Boolean(opts.nonInteractive),
|
nonInteractive: Boolean(opts.nonInteractive),
|
||||||
deep: Boolean(opts.deep),
|
deep: Boolean(opts.deep),
|
||||||
});
|
});
|
||||||
@@ -414,472 +402,107 @@ export function buildProgram() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const message = program
|
program
|
||||||
.command("message")
|
.command("send")
|
||||||
.description("Send messages and provider actions")
|
.description(
|
||||||
.addHelpText(
|
"Send a message (WhatsApp Web, Telegram bot, Discord, Slack, Signal, iMessage)",
|
||||||
"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 "✅"`,
|
|
||||||
)
|
)
|
||||||
.action(() => {
|
.requiredOption(
|
||||||
message.help({ error: true });
|
"-t, --to <number>",
|
||||||
});
|
"Recipient: E.164 for WhatsApp/Signal, Telegram chat id/@username, Discord channel/user, or iMessage handle/chat_id",
|
||||||
|
|
||||||
const withMessageBase = (command: Command) =>
|
|
||||||
command
|
|
||||||
.option(
|
|
||||||
"--provider <provider>",
|
|
||||||
"Provider: whatsapp|telegram|discord|slack|signal|imessage",
|
|
||||||
)
|
|
||||||
.option("--account <id>", "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 <dest>",
|
|
||||||
"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 <dest>",
|
|
||||||
"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<string, unknown>,
|
|
||||||
) => {
|
|
||||||
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 <text>", "Message body"),
|
|
||||||
)
|
)
|
||||||
.option(
|
.requiredOption("-m, --message <text>", "Message body")
|
||||||
"--media <path-or-url>",
|
|
||||||
"Attach media (image/audio/video/document). Accepts local paths or URLs.",
|
|
||||||
)
|
|
||||||
.option("--reply-to <id>", "Reply-to message id")
|
|
||||||
.option("--thread-id <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 <text>", "Poll question")
|
|
||||||
.option(
|
|
||||||
"--poll-option <choice>",
|
|
||||||
"Poll option (repeat 2-12 times)",
|
|
||||||
collectOption,
|
|
||||||
[] as string[],
|
|
||||||
)
|
|
||||||
.option("--poll-multi", "Allow multiple selections", false)
|
|
||||||
.option("--poll-duration-hours <n>", "Poll duration (Discord)")
|
|
||||||
.option("-m, --message <text>", "Optional message body")
|
|
||||||
.action(async (opts) => {
|
|
||||||
await runMessageAction("poll", opts);
|
|
||||||
});
|
|
||||||
|
|
||||||
withMessageBase(
|
|
||||||
withMessageTarget(
|
|
||||||
message.command("react").description("Add or remove a reaction"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.requiredOption("--message-id <id>", "Message id")
|
|
||||||
.option("--emoji <emoji>", "Emoji for reactions")
|
|
||||||
.option("--remove", "Remove reaction", false)
|
|
||||||
.option("--participant <id>", "WhatsApp reaction participant")
|
|
||||||
.option("--from-me", "WhatsApp reaction fromMe", false)
|
|
||||||
.option("--channel-id <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 <id>", "Message id")
|
|
||||||
.option("--limit <n>", "Result limit")
|
|
||||||
.option("--channel-id <id>", "Channel id (defaults to --to)")
|
|
||||||
.action(async (opts) => {
|
|
||||||
await runMessageAction("reactions", opts);
|
|
||||||
});
|
|
||||||
|
|
||||||
withMessageBase(
|
|
||||||
withMessageTarget(
|
|
||||||
message.command("read").description("Read recent messages"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.option("--limit <n>", "Result limit")
|
|
||||||
.option("--before <id>", "Read/search before id")
|
|
||||||
.option("--after <id>", "Read/search after id")
|
|
||||||
.option("--around <id>", "Read around id (Discord)")
|
|
||||||
.option("--channel-id <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 <text>", "Message body"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.requiredOption("--message-id <id>", "Message id")
|
|
||||||
.option("--channel-id <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 <id>", "Message id")
|
|
||||||
.option("--channel-id <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 <id>", "Message id")
|
|
||||||
.option("--channel-id <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 <id>", "Message id")
|
|
||||||
.option("--channel-id <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 <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 <id>", "Channel id (defaults to --to)")
|
|
||||||
.action(async (opts) => {
|
|
||||||
await runMessageAction("permissions", opts);
|
|
||||||
});
|
|
||||||
|
|
||||||
withMessageBase(
|
|
||||||
message.command("search").description("Search Discord messages"),
|
|
||||||
)
|
|
||||||
.requiredOption("--guild-id <id>", "Guild id")
|
|
||||||
.requiredOption("--query <text>", "Search query")
|
|
||||||
.option("--channel-id <id>", "Channel id")
|
|
||||||
.option(
|
|
||||||
"--channel-ids <id>",
|
|
||||||
"Channel id (repeat)",
|
|
||||||
collectOption,
|
|
||||||
[] as string[],
|
|
||||||
)
|
|
||||||
.option("--author-id <id>", "Author id")
|
|
||||||
.option(
|
|
||||||
"--author-ids <id>",
|
|
||||||
"Author id (repeat)",
|
|
||||||
collectOption,
|
|
||||||
[] as string[],
|
|
||||||
)
|
|
||||||
.option("--limit <n>", "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 <name>", "Thread name"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.option("--channel-id <id>", "Channel id (defaults to --to)")
|
|
||||||
.option("--message-id <id>", "Message id (optional)")
|
|
||||||
.option("--auto-archive-min <n>", "Thread auto-archive minutes")
|
|
||||||
.action(async (opts) => {
|
|
||||||
await runMessageAction("thread-create", opts);
|
|
||||||
});
|
|
||||||
|
|
||||||
withMessageBase(
|
|
||||||
thread
|
|
||||||
.command("list")
|
|
||||||
.description("List threads")
|
|
||||||
.requiredOption("--guild-id <id>", "Guild id"),
|
|
||||||
)
|
|
||||||
.option("--channel-id <id>", "Channel id")
|
|
||||||
.option("--include-archived", "Include archived threads", false)
|
|
||||||
.option("--before <id>", "Read/search before id")
|
|
||||||
.option("--limit <n>", "Result limit")
|
|
||||||
.action(async (opts) => {
|
|
||||||
await runMessageAction("thread-list", opts);
|
|
||||||
});
|
|
||||||
|
|
||||||
withMessageBase(
|
|
||||||
withRequiredMessageTarget(
|
|
||||||
thread
|
|
||||||
.command("reply")
|
|
||||||
.description("Reply in a thread")
|
|
||||||
.requiredOption("-m, --message <text>", "Message body"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.option(
|
.option(
|
||||||
"--media <path-or-url>",
|
"--media <path-or-url>",
|
||||||
"Attach media (image/audio/video/document). Accepts local paths or URLs.",
|
"Attach media (image/audio/video/document). Accepts local paths or URLs.",
|
||||||
)
|
)
|
||||||
.option("--reply-to <id>", "Reply-to message id")
|
|
||||||
.action(async (opts) => {
|
|
||||||
await runMessageAction("thread-reply", opts);
|
|
||||||
});
|
|
||||||
|
|
||||||
const emoji = message.command("emoji").description("Emoji actions");
|
|
||||||
withMessageBase(emoji.command("list").description("List emojis"))
|
|
||||||
.option("--guild-id <id>", "Guild id (Discord)")
|
|
||||||
.action(async (opts) => {
|
|
||||||
await runMessageAction("emoji-list", opts);
|
|
||||||
});
|
|
||||||
|
|
||||||
withMessageBase(
|
|
||||||
emoji
|
|
||||||
.command("upload")
|
|
||||||
.description("Upload an emoji")
|
|
||||||
.requiredOption("--guild-id <id>", "Guild id"),
|
|
||||||
)
|
|
||||||
.requiredOption("--emoji-name <name>", "Emoji name")
|
|
||||||
.requiredOption("--media <path-or-url>", "Emoji media (path or URL)")
|
|
||||||
.option(
|
.option(
|
||||||
"--role-ids <id>",
|
"--gif-playback",
|
||||||
"Role id (repeat)",
|
"Treat video media as GIF playback (WhatsApp only).",
|
||||||
collectOption,
|
false,
|
||||||
[] as string[],
|
)
|
||||||
|
.option(
|
||||||
|
"--provider <provider>",
|
||||||
|
"Delivery provider: whatsapp|telegram|discord|slack|signal|imessage (default: whatsapp)",
|
||||||
|
)
|
||||||
|
.option("--account <id>", "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`,
|
||||||
)
|
)
|
||||||
.action(async (opts) => {
|
.action(async (opts) => {
|
||||||
await runMessageAction("emoji-upload", 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);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const sticker = message.command("sticker").description("Sticker actions");
|
program
|
||||||
withMessageBase(
|
.command("poll")
|
||||||
withRequiredMessageTarget(
|
.description("Create a poll via WhatsApp or Discord")
|
||||||
sticker.command("send").description("Send stickers"),
|
.requiredOption(
|
||||||
),
|
"-t, --to <id>",
|
||||||
)
|
"Recipient: WhatsApp JID/number or Discord channel/user",
|
||||||
.requiredOption("--sticker-id <id>", "Sticker id (repeat)", collectOption)
|
)
|
||||||
.option("-m, --message <text>", "Optional message body")
|
.requiredOption("-q, --question <text>", "Poll question")
|
||||||
|
.requiredOption(
|
||||||
|
"-o, --option <choice>",
|
||||||
|
"Poll option (use multiple times, 2-12 required)",
|
||||||
|
(value: string, previous: string[]) => previous.concat([value]),
|
||||||
|
[] as string[],
|
||||||
|
)
|
||||||
|
.option(
|
||||||
|
"-s, --max-selections <n>",
|
||||||
|
"How many options can be selected (default: 1)",
|
||||||
|
)
|
||||||
|
.option(
|
||||||
|
"--duration-hours <n>",
|
||||||
|
"Poll duration in hours (Discord only, default: 24)",
|
||||||
|
)
|
||||||
|
.option(
|
||||||
|
"--provider <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) => {
|
.action(async (opts) => {
|
||||||
await runMessageAction("sticker", opts);
|
setVerbose(Boolean(opts.verbose));
|
||||||
});
|
const deps = createDefaultDeps();
|
||||||
|
try {
|
||||||
withMessageBase(
|
await pollCommand(opts, deps, defaultRuntime);
|
||||||
sticker
|
} catch (err) {
|
||||||
.command("upload")
|
defaultRuntime.error(String(err));
|
||||||
.description("Upload a sticker")
|
defaultRuntime.exit(1);
|
||||||
.requiredOption("--guild-id <id>", "Guild id"),
|
}
|
||||||
)
|
|
||||||
.requiredOption("--sticker-name <name>", "Sticker name")
|
|
||||||
.requiredOption("--sticker-desc <text>", "Sticker description")
|
|
||||||
.requiredOption("--sticker-tags <tags>", "Sticker tags")
|
|
||||||
.requiredOption("--media <path-or-url>", "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 <id>", "Guild id"),
|
|
||||||
).action(async (opts) => {
|
|
||||||
await runMessageAction("role-info", opts);
|
|
||||||
});
|
|
||||||
|
|
||||||
withMessageBase(
|
|
||||||
role
|
|
||||||
.command("add")
|
|
||||||
.description("Add role to a member")
|
|
||||||
.requiredOption("--guild-id <id>", "Guild id")
|
|
||||||
.requiredOption("--user-id <id>", "User id")
|
|
||||||
.requiredOption("--role-id <id>", "Role id"),
|
|
||||||
).action(async (opts) => {
|
|
||||||
await runMessageAction("role-add", opts);
|
|
||||||
});
|
|
||||||
|
|
||||||
withMessageBase(
|
|
||||||
role
|
|
||||||
.command("remove")
|
|
||||||
.description("Remove role from a member")
|
|
||||||
.requiredOption("--guild-id <id>", "Guild id")
|
|
||||||
.requiredOption("--user-id <id>", "User id")
|
|
||||||
.requiredOption("--role-id <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 <id>", "Channel id"),
|
|
||||||
).action(async (opts) => {
|
|
||||||
await runMessageAction("channel-info", opts);
|
|
||||||
});
|
|
||||||
|
|
||||||
withMessageBase(
|
|
||||||
channel
|
|
||||||
.command("list")
|
|
||||||
.description("List channels")
|
|
||||||
.requiredOption("--guild-id <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 <id>", "User id"),
|
|
||||||
)
|
|
||||||
.option("--guild-id <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 <id>", "Guild id")
|
|
||||||
.requiredOption("--user-id <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 <id>", "Guild id"),
|
|
||||||
).action(async (opts) => {
|
|
||||||
await runMessageAction("event-list", opts);
|
|
||||||
});
|
|
||||||
|
|
||||||
withMessageBase(
|
|
||||||
event
|
|
||||||
.command("create")
|
|
||||||
.description("Create a scheduled event")
|
|
||||||
.requiredOption("--guild-id <id>", "Guild id")
|
|
||||||
.requiredOption("--event-name <name>", "Event name")
|
|
||||||
.requiredOption("--start-time <iso>", "Event start time"),
|
|
||||||
)
|
|
||||||
.option("--end-time <iso>", "Event end time")
|
|
||||||
.option("--desc <text>", "Event description")
|
|
||||||
.option("--channel-id <id>", "Channel id")
|
|
||||||
.option("--location <text>", "Event location")
|
|
||||||
.option("--event-type <stage|external|voice>", "Event type")
|
|
||||||
.action(async (opts) => {
|
|
||||||
await runMessageAction("event-create", opts);
|
|
||||||
});
|
|
||||||
|
|
||||||
withMessageBase(
|
|
||||||
message
|
|
||||||
.command("timeout")
|
|
||||||
.description("Timeout a member")
|
|
||||||
.requiredOption("--guild-id <id>", "Guild id")
|
|
||||||
.requiredOption("--user-id <id>", "User id"),
|
|
||||||
)
|
|
||||||
.option("--duration-min <n>", "Timeout duration minutes")
|
|
||||||
.option("--until <iso>", "Timeout until")
|
|
||||||
.option("--reason <text>", "Moderation reason")
|
|
||||||
.action(async (opts) => {
|
|
||||||
await runMessageAction("timeout", opts);
|
|
||||||
});
|
|
||||||
|
|
||||||
withMessageBase(
|
|
||||||
message
|
|
||||||
.command("kick")
|
|
||||||
.description("Kick a member")
|
|
||||||
.requiredOption("--guild-id <id>", "Guild id")
|
|
||||||
.requiredOption("--user-id <id>", "User id"),
|
|
||||||
)
|
|
||||||
.option("--reason <text>", "Moderation reason")
|
|
||||||
.action(async (opts) => {
|
|
||||||
await runMessageAction("kick", opts);
|
|
||||||
});
|
|
||||||
|
|
||||||
withMessageBase(
|
|
||||||
message
|
|
||||||
.command("ban")
|
|
||||||
.description("Ban a member")
|
|
||||||
.requiredOption("--guild-id <id>", "Guild id")
|
|
||||||
.requiredOption("--user-id <id>", "User id"),
|
|
||||||
)
|
|
||||||
.option("--reason <text>", "Moderation reason")
|
|
||||||
.option("--delete-days <n>", "Ban delete message days")
|
|
||||||
.action(async (opts) => {
|
|
||||||
await runMessageAction("ban", opts);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
program
|
program
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ export function buildAuthChoiceOptions(params: {
|
|||||||
options.push({ value: "gemini-api-key", label: "Google Gemini API key" });
|
options.push({ value: "gemini-api-key", label: "Google Gemini API key" });
|
||||||
options.push({ value: "apiKey", label: "Anthropic API key" });
|
options.push({ value: "apiKey", label: "Anthropic API key" });
|
||||||
// Token flow is currently Anthropic-only; use CLI for advanced providers.
|
// Token flow is currently Anthropic-only; use CLI for advanced providers.
|
||||||
|
options.push({ value: "minimax-cloud", label: "MiniMax M2.1 (minimax.io)" });
|
||||||
options.push({ value: "minimax", label: "Minimax M2.1 (LM Studio)" });
|
options.push({ value: "minimax", label: "Minimax M2.1 (LM Studio)" });
|
||||||
if (params.includeSkip) {
|
if (params.includeSkip) {
|
||||||
options.push({ value: "skip", label: "Skip for now" });
|
options.push({ value: "skip", label: "Skip for now" });
|
||||||
|
|||||||
@@ -37,9 +37,13 @@ import {
|
|||||||
import {
|
import {
|
||||||
applyAuthProfileConfig,
|
applyAuthProfileConfig,
|
||||||
applyMinimaxConfig,
|
applyMinimaxConfig,
|
||||||
|
applyMinimaxHostedConfig,
|
||||||
|
applyMinimaxHostedProviderConfig,
|
||||||
applyMinimaxProviderConfig,
|
applyMinimaxProviderConfig,
|
||||||
|
MINIMAX_HOSTED_MODEL_REF,
|
||||||
setAnthropicApiKey,
|
setAnthropicApiKey,
|
||||||
setGeminiApiKey,
|
setGeminiApiKey,
|
||||||
|
setMinimaxApiKey,
|
||||||
writeOAuthCredentials,
|
writeOAuthCredentials,
|
||||||
} from "./onboard-auth.js";
|
} from "./onboard-auth.js";
|
||||||
import { openUrl } from "./onboard-helpers.js";
|
import { openUrl } from "./onboard-helpers.js";
|
||||||
@@ -529,6 +533,24 @@ export async function applyAuthChoice(params: {
|
|||||||
provider: "anthropic",
|
provider: "anthropic",
|
||||||
mode: "api_key",
|
mode: "api_key",
|
||||||
});
|
});
|
||||||
|
} else if (params.authChoice === "minimax-cloud") {
|
||||||
|
const key = await params.prompter.text({
|
||||||
|
message: "Enter MiniMax API key",
|
||||||
|
validate: (value) => (value?.trim() ? undefined : "Required"),
|
||||||
|
});
|
||||||
|
await setMinimaxApiKey(String(key).trim(), params.agentDir);
|
||||||
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
|
profileId: "minimax:default",
|
||||||
|
provider: "minimax",
|
||||||
|
mode: "api_key",
|
||||||
|
});
|
||||||
|
if (params.setDefaultModel) {
|
||||||
|
nextConfig = applyMinimaxHostedConfig(nextConfig);
|
||||||
|
} else {
|
||||||
|
nextConfig = applyMinimaxHostedProviderConfig(nextConfig);
|
||||||
|
agentModelOverride = MINIMAX_HOSTED_MODEL_REF;
|
||||||
|
await noteAgentModel(MINIMAX_HOSTED_MODEL_REF);
|
||||||
|
}
|
||||||
} else if (params.authChoice === "minimax") {
|
} else if (params.authChoice === "minimax") {
|
||||||
if (params.setDefaultModel) {
|
if (params.setDefaultModel) {
|
||||||
nextConfig = applyMinimaxConfig(nextConfig);
|
nextConfig = applyMinimaxConfig(nextConfig);
|
||||||
|
|||||||
@@ -69,8 +69,10 @@ import { healthCommand } from "./health.js";
|
|||||||
import {
|
import {
|
||||||
applyAuthProfileConfig,
|
applyAuthProfileConfig,
|
||||||
applyMinimaxConfig,
|
applyMinimaxConfig,
|
||||||
|
applyMinimaxHostedConfig,
|
||||||
setAnthropicApiKey,
|
setAnthropicApiKey,
|
||||||
setGeminiApiKey,
|
setGeminiApiKey,
|
||||||
|
setMinimaxApiKey,
|
||||||
writeOAuthCredentials,
|
writeOAuthCredentials,
|
||||||
} from "./onboard-auth.js";
|
} from "./onboard-auth.js";
|
||||||
import {
|
import {
|
||||||
@@ -357,6 +359,7 @@ async function promptAuthConfig(
|
|||||||
| "antigravity"
|
| "antigravity"
|
||||||
| "gemini-api-key"
|
| "gemini-api-key"
|
||||||
| "apiKey"
|
| "apiKey"
|
||||||
|
| "minimax-cloud"
|
||||||
| "minimax"
|
| "minimax"
|
||||||
| "skip";
|
| "skip";
|
||||||
|
|
||||||
@@ -691,6 +694,21 @@ async function promptAuthConfig(
|
|||||||
provider: "anthropic",
|
provider: "anthropic",
|
||||||
mode: "api_key",
|
mode: "api_key",
|
||||||
});
|
});
|
||||||
|
} else if (authChoice === "minimax-cloud") {
|
||||||
|
const key = guardCancel(
|
||||||
|
await text({
|
||||||
|
message: "Enter MiniMax API key",
|
||||||
|
validate: (value) => (value?.trim() ? undefined : "Required"),
|
||||||
|
}),
|
||||||
|
runtime,
|
||||||
|
);
|
||||||
|
await setMinimaxApiKey(String(key).trim());
|
||||||
|
next = applyAuthProfileConfig(next, {
|
||||||
|
profileId: "minimax:default",
|
||||||
|
provider: "minimax",
|
||||||
|
mode: "api_key",
|
||||||
|
});
|
||||||
|
next = applyMinimaxHostedConfig(next);
|
||||||
} else if (authChoice === "minimax") {
|
} else if (authChoice === "minimax") {
|
||||||
next = applyMinimaxConfig(next);
|
next = applyMinimaxConfig(next);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,12 @@ import { resolveDefaultAgentDir } from "../agents/agent-scope.js";
|
|||||||
import { upsertAuthProfile } from "../agents/auth-profiles.js";
|
import { upsertAuthProfile } from "../agents/auth-profiles.js";
|
||||||
import type { ClawdbotConfig } from "../config/config.js";
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
|
|
||||||
|
const DEFAULT_MINIMAX_BASE_URL = "https://api.minimax.io/v1";
|
||||||
|
export const MINIMAX_HOSTED_MODEL_ID = "MiniMax-M2.1";
|
||||||
|
const DEFAULT_MINIMAX_CONTEXT_WINDOW = 200000;
|
||||||
|
const DEFAULT_MINIMAX_MAX_TOKENS = 8192;
|
||||||
|
export const MINIMAX_HOSTED_MODEL_REF = `minimax/${MINIMAX_HOSTED_MODEL_ID}`;
|
||||||
|
|
||||||
export async function writeOAuthCredentials(
|
export async function writeOAuthCredentials(
|
||||||
provider: OAuthProvider,
|
provider: OAuthProvider,
|
||||||
creds: OAuthCredentials,
|
creds: OAuthCredentials,
|
||||||
@@ -46,6 +52,19 @@ export async function setGeminiApiKey(key: string, agentDir?: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function setMinimaxApiKey(key: string, agentDir?: string) {
|
||||||
|
// Write to the multi-agent path so gateway finds credentials on startup
|
||||||
|
upsertAuthProfile({
|
||||||
|
profileId: "minimax:default",
|
||||||
|
credential: {
|
||||||
|
type: "api_key",
|
||||||
|
provider: "minimax",
|
||||||
|
key,
|
||||||
|
},
|
||||||
|
agentDir: agentDir ?? resolveDefaultAgentDir(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function applyAuthProfileConfig(
|
export function applyAuthProfileConfig(
|
||||||
cfg: ClawdbotConfig,
|
cfg: ClawdbotConfig,
|
||||||
params: {
|
params: {
|
||||||
@@ -143,6 +162,57 @@ export function applyMinimaxProviderConfig(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function applyMinimaxHostedProviderConfig(
|
||||||
|
cfg: ClawdbotConfig,
|
||||||
|
params?: { baseUrl?: string },
|
||||||
|
): ClawdbotConfig {
|
||||||
|
const models = { ...cfg.agent?.models };
|
||||||
|
models[MINIMAX_HOSTED_MODEL_REF] = {
|
||||||
|
...models[MINIMAX_HOSTED_MODEL_REF],
|
||||||
|
alias: models[MINIMAX_HOSTED_MODEL_REF]?.alias ?? "Minimax",
|
||||||
|
};
|
||||||
|
|
||||||
|
const providers = { ...cfg.models?.providers };
|
||||||
|
const hostedModel = {
|
||||||
|
id: MINIMAX_HOSTED_MODEL_ID,
|
||||||
|
name: "MiniMax M2.1",
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text"],
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
contextWindow: DEFAULT_MINIMAX_CONTEXT_WINDOW,
|
||||||
|
maxTokens: DEFAULT_MINIMAX_MAX_TOKENS,
|
||||||
|
};
|
||||||
|
const existingProvider = providers.minimax;
|
||||||
|
const existingModels = Array.isArray(existingProvider?.models)
|
||||||
|
? existingProvider.models
|
||||||
|
: [];
|
||||||
|
const hasHostedModel = existingModels.some(
|
||||||
|
(model) => model.id === MINIMAX_HOSTED_MODEL_ID,
|
||||||
|
);
|
||||||
|
const mergedModels = hasHostedModel
|
||||||
|
? existingModels
|
||||||
|
: [...existingModels, hostedModel];
|
||||||
|
providers.minimax = {
|
||||||
|
...existingProvider,
|
||||||
|
baseUrl: params?.baseUrl?.trim() || DEFAULT_MINIMAX_BASE_URL,
|
||||||
|
apiKey: "minimax",
|
||||||
|
api: "openai-completions",
|
||||||
|
models: mergedModels.length > 0 ? mergedModels : [hostedModel],
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...cfg,
|
||||||
|
agent: {
|
||||||
|
...cfg.agent,
|
||||||
|
models,
|
||||||
|
},
|
||||||
|
models: {
|
||||||
|
mode: cfg.models?.mode ?? "merge",
|
||||||
|
providers,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function applyMinimaxConfig(cfg: ClawdbotConfig): ClawdbotConfig {
|
export function applyMinimaxConfig(cfg: ClawdbotConfig): ClawdbotConfig {
|
||||||
const next = applyMinimaxProviderConfig(cfg);
|
const next = applyMinimaxProviderConfig(cfg);
|
||||||
return {
|
return {
|
||||||
@@ -162,3 +232,26 @@ export function applyMinimaxConfig(cfg: ClawdbotConfig): ClawdbotConfig {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function applyMinimaxHostedConfig(
|
||||||
|
cfg: ClawdbotConfig,
|
||||||
|
params?: { baseUrl?: string },
|
||||||
|
): ClawdbotConfig {
|
||||||
|
const next = applyMinimaxHostedProviderConfig(cfg, params);
|
||||||
|
return {
|
||||||
|
...next,
|
||||||
|
agent: {
|
||||||
|
...next.agent,
|
||||||
|
model: {
|
||||||
|
...(next.agent?.model &&
|
||||||
|
"fallbacks" in (next.agent.model as Record<string, unknown>)
|
||||||
|
? {
|
||||||
|
fallbacks: (next.agent.model as { fallbacks?: string[] })
|
||||||
|
.fallbacks,
|
||||||
|
}
|
||||||
|
: undefined),
|
||||||
|
primary: MINIMAX_HOSTED_MODEL_REF,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,8 +30,10 @@ import { healthCommand } from "./health.js";
|
|||||||
import {
|
import {
|
||||||
applyAuthProfileConfig,
|
applyAuthProfileConfig,
|
||||||
applyMinimaxConfig,
|
applyMinimaxConfig,
|
||||||
|
applyMinimaxHostedConfig,
|
||||||
setAnthropicApiKey,
|
setAnthropicApiKey,
|
||||||
setGeminiApiKey,
|
setGeminiApiKey,
|
||||||
|
setMinimaxApiKey,
|
||||||
} from "./onboard-auth.js";
|
} from "./onboard-auth.js";
|
||||||
import {
|
import {
|
||||||
applyWizardMetadata,
|
applyWizardMetadata,
|
||||||
@@ -150,6 +152,20 @@ export async function runNonInteractiveOnboarding(
|
|||||||
});
|
});
|
||||||
process.env.OPENAI_API_KEY = key;
|
process.env.OPENAI_API_KEY = key;
|
||||||
runtime.log(`Saved OPENAI_API_KEY to ${result.path}`);
|
runtime.log(`Saved OPENAI_API_KEY to ${result.path}`);
|
||||||
|
} else if (authChoice === "minimax-cloud") {
|
||||||
|
const key = opts.minimaxApiKey?.trim();
|
||||||
|
if (!key) {
|
||||||
|
runtime.error("Missing --minimax-api-key");
|
||||||
|
runtime.exit(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await setMinimaxApiKey(key);
|
||||||
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
|
profileId: "minimax:default",
|
||||||
|
provider: "minimax",
|
||||||
|
mode: "api_key",
|
||||||
|
});
|
||||||
|
nextConfig = applyMinimaxHostedConfig(nextConfig);
|
||||||
} else if (authChoice === "claude-cli") {
|
} else if (authChoice === "claude-cli") {
|
||||||
const store = ensureAuthProfileStore(undefined, {
|
const store = ensureAuthProfileStore(undefined, {
|
||||||
allowKeychainPrompt: false,
|
allowKeychainPrompt: false,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export type AuthChoice =
|
|||||||
| "antigravity"
|
| "antigravity"
|
||||||
| "apiKey"
|
| "apiKey"
|
||||||
| "gemini-api-key"
|
| "gemini-api-key"
|
||||||
|
| "minimax-cloud"
|
||||||
| "minimax"
|
| "minimax"
|
||||||
| "skip";
|
| "skip";
|
||||||
export type GatewayAuthChoice = "off" | "token" | "password";
|
export type GatewayAuthChoice = "off" | "token" | "password";
|
||||||
@@ -29,6 +30,7 @@ export type OnboardOptions = {
|
|||||||
anthropicApiKey?: string;
|
anthropicApiKey?: string;
|
||||||
openaiApiKey?: string;
|
openaiApiKey?: string;
|
||||||
geminiApiKey?: string;
|
geminiApiKey?: string;
|
||||||
|
minimaxApiKey?: string;
|
||||||
gatewayPort?: number;
|
gatewayPort?: number;
|
||||||
gatewayBind?: GatewayBind;
|
gatewayBind?: GatewayBind;
|
||||||
gatewayAuth?: GatewayAuthChoice;
|
gatewayAuth?: GatewayAuthChoice;
|
||||||
|
|||||||
Reference in New Issue
Block a user