UNPKG

@gguf/claw

Version:

Multi-channel AI gateway with extensible messaging integrations

698 lines (681 loc) 33.7 kB
import { D as getChatChannelMeta, Dt as theme, Et as isRich, _ as defaultRuntime, ht as danger, j as normalizeChatChannelId, xt as setVerbose } from "./entry.js"; import "./auth-profiles-DFa1zzNy.js"; import "./exec-CBKBIMpA.js"; import "./agent-scope-RzK9Zcks.js"; import "./github-copilot-token-DuFIqfeC.js"; import "./model-DY9t1aT6.js"; import "./pi-model-discovery-Do3xMEtM.js"; import "./frontmatter-D-YR-Ghi.js"; import "./skills-DJmGZazd.js"; import "./manifest-registry-DS2iK5AZ.js"; import { i as loadConfig } from "./config-B2kL1ciP.js"; import "./client-CDjZdZtI.js"; import "./call-DXhJGwEy.js"; import { h as GATEWAY_CLIENT_NAMES, m as GATEWAY_CLIENT_MODES } from "./message-channel-CVHJDItx.js"; import "./pairing-token-Byh6drgn.js"; import { $ as runMessageAction, D as createOutboundSendDeps, Mt as CHANNEL_TARGET_DESCRIPTION, O as createDefaultDeps, ct as CHANNEL_MESSAGE_ACTION_NAMES, jt as CHANNEL_TARGETS_DESCRIPTION, ot as formatTargetDisplay } from "./subagent-registry-C7Edpn23.js"; import "./sessions-BD5dyLxb.js"; import "./tokens-D5RzuaYP.js"; import "./normalize-Db7Xtx2v.js"; import "./accounts-BpzwDfBB.js"; import "./bindings-KqaGKS1E.js"; import "./logging-CFvkxgcX.js"; import "./send-BFiRlO6V.js"; import { t as getChannelPlugin } from "./plugins-RqhjLCb6.js"; import "./send-CWDtU8Gi.js"; import "./with-timeout-nVWy7PWz.js"; import { T as runGlobalGatewayStopSafely } from "./deliver-CeVE1Jh-.js"; import "./diagnostic-774btyou.js"; import "./diagnostic-session-state-HO94DMou.js"; import "./accounts-B3D4iWUP.js"; import "./send-DCCBUsNn.js"; import "./image-ops-lDlFpoR2.js"; import "./pi-embedded-helpers-NVEtaJwl.js"; import "./sandbox-CO-R8v6J.js"; import "./common-DcSh1hZE.js"; import "./chrome-52ZF7gmE.js"; import "./tailscale-BzRVNhMW.js"; import "./auth-Dq2pFnjj.js"; import "./server-context-C1f7cijA.js"; import "./routes-BJmh1Ify.js"; import "./redact-C2s6sr73.js"; import "./errors-CFEPAPWc.js"; import "./fs-safe-3YwsxSr5.js"; import "./paths-CXKNzNjU.js"; import "./ssrf-Cv2DiHsm.js"; import "./store-e6fIrP17.js"; import "./ports-59j-bA53.js"; import "./trash-DVLJ0k6q.js"; import "./dock-BZuwgj1O.js"; import "./accounts-Jg3_1Y-r.js"; import "./paths-CXpciDEv.js"; import "./thinking-CJPPUYWd.js"; import "./models-config-5zJ39mUb.js"; import "./reply-prefix-DJOqzjBt.js"; import "./memory-cli-C3uas9sI.js"; import "./manager-D3ZmOwqt.js"; import "./gemini-auth-CdgAkz2K.js"; import "./sqlite-1-TCahE6.js"; import "./retry-BcjnIuo-.js"; import "./chunk-Z2NYDchs.js"; import "./markdown-tables-GCHx-nGT.js"; import "./fetch-guard-B5BAqVyD.js"; import "./local-roots-SkDT1Wv8.js"; import "./ir-DAwVi0a7.js"; import "./render-e7fENCYH.js"; import "./commands-registry-CRmMPJQ9.js"; import "./image-QGUQCdGh.js"; import "./tool-display-Cs1uRaRV.js"; import "./runner-D81xREgc.js"; import "./model-catalog-DLoDxnxL.js"; import "./session-utils-C3Oi9cXA.js"; import "./skill-commands-D2rmj6w8.js"; import "./workspace-dirs-BXftTDSV.js"; import "./pairing-store-CruPwgBw.js"; import "./fetch-vg2oFVIH.js"; import "./exec-approvals-BZA6z4HM.js"; import "./nodes-screen-x7YKy8Ay.js"; import "./session-cost-usage-CpxG9Mup.js"; import "./pi-tools.policy-DeRwGLEO.js"; import "./control-service-DGleRjGJ.js"; import "./stagger-CArN2YVJ.js"; import "./channel-selection-DoXlsBhe.js"; import "./send-C3RIrBTr.js"; import "./outbound-attachment-CdFWGHgJ.js"; import "./delivery-queue-DKdWlR61.js"; import "./send-0Nz3O9Jc.js"; import "./resolve-route-BONxzeyg.js"; import "./channel-activity-BYGpAtHP.js"; import "./tables-BrqD0SUa.js"; import "./proxy-DL3MD6-P.js"; import { t as formatDocsLink } from "./links-CW8Bx7rK.js"; import { n as runCommandWithRuntime } from "./cli-utils-CCaEbxAz.js"; import { t as formatHelpExamples } from "./help-format-B0pWGnZs.js"; import { n as withProgress } from "./progress-BAHiAaDW.js"; import "./replies-CuUAAFF2.js"; import "./onboard-helpers-CEx2tGVB.js"; import "./prompt-style-DwCXob2h.js"; import "./pairing-labels-DtxjElSq.js"; import { t as ensurePluginRegistryLoaded } from "./plugin-registry-CXQ59uvq.js"; import { t as collectOption } from "./helpers-B7ilMWgy.js"; import { t as renderTable } from "./table-Ca0mornk.js"; //#region src/infra/outbound/format.ts const resolveChannelLabel$1 = (channel) => { const pluginLabel = getChannelPlugin(channel)?.meta.label; if (pluginLabel) return pluginLabel; const normalized = normalizeChatChannelId(channel); if (normalized) return getChatChannelMeta(normalized).label; return channel; }; function formatOutboundDeliverySummary(channel, result) { if (!result) return `✅ Sent via ${resolveChannelLabel$1(channel)}. Message ID: unknown`; const base = `✅ Sent via ${resolveChannelLabel$1(result.channel)}. Message ID: ${result.messageId}`; if ("chatId" in result) return `${base} (chat ${result.chatId})`; if ("channelId" in result) return `${base} (channel ${result.channelId})`; if ("roomId" in result) return `${base} (room ${result.roomId})`; if ("conversationId" in result) return `${base} (conversation ${result.conversationId})`; return base; } function formatGatewaySummary(params) { return `✅ ${params.action ?? "Sent"} via gateway${params.channel ? ` (${params.channel})` : ""}. Message ID: ${params.messageId ?? "unknown"}`; } //#endregion //#region src/commands/message-format.ts const shortenText = (value, maxLen) => { const chars = Array.from(value); if (chars.length <= maxLen) return value; return `${chars.slice(0, Math.max(0, maxLen - 1)).join("")}…`; }; const resolveChannelLabel = (channel) => getChannelPlugin(channel)?.meta.label ?? channel; function extractMessageId(payload) { if (!payload || typeof payload !== "object") return null; const direct = payload.messageId; if (typeof direct === "string" && direct.trim()) return direct.trim(); const result = payload.result; if (result && typeof result === "object") { const nested = result.messageId; if (typeof nested === "string" && nested.trim()) return nested.trim(); } return null; } function buildMessageCliJson(result) { return { action: result.action, channel: result.channel, dryRun: result.dryRun, handledBy: result.handledBy, payload: result.payload }; } function renderObjectSummary(payload, opts) { if (!payload || typeof payload !== "object") return [String(payload)]; const obj = payload; const keys = Object.keys(obj); if (keys.length === 0) return [theme.muted("(empty)")]; const rows = keys.slice(0, 20).map((k) => { const v = obj[k]; return { Key: k, Value: shortenText(v == null ? "null" : Array.isArray(v) ? `${v.length} items` : typeof v === "object" ? "object" : typeof v === "string" ? v : typeof v === "number" ? String(v) : typeof v === "boolean" ? v ? "true" : "false" : typeof v === "bigint" ? v.toString() : typeof v === "symbol" ? v.toString() : typeof v === "function" ? "function" : "unknown", 96) }; }); return [renderTable({ width: opts.width, columns: [{ key: "Key", header: "Key", minWidth: 16 }, { key: "Value", header: "Value", flex: true, minWidth: 24 }], rows }).trimEnd()]; } function renderMessageList(messages, opts, emptyLabel) { const rows = messages.slice(0, 25).map((m) => { const msg = m; const id = typeof msg.id === "string" && msg.id || typeof msg.ts === "string" && msg.ts || typeof msg.messageId === "string" && msg.messageId || ""; const authorObj = msg.author; const author = typeof msg.authorTag === "string" && msg.authorTag || typeof authorObj?.username === "string" && authorObj.username || typeof msg.user === "string" && msg.user || ""; const time = typeof msg.timestamp === "string" && msg.timestamp || typeof msg.ts === "string" && msg.ts || ""; const text = typeof msg.content === "string" && msg.content || typeof msg.text === "string" && msg.text || ""; return { Time: shortenText(time, 28), Author: shortenText(author, 22), Text: shortenText(text.replace(/\s+/g, " ").trim(), 90), Id: shortenText(id, 22) }; }); if (rows.length === 0) return [theme.muted(emptyLabel)]; return [renderTable({ width: opts.width, columns: [ { key: "Time", header: "Time", minWidth: 14 }, { key: "Author", header: "Author", minWidth: 10 }, { key: "Text", header: "Text", flex: true, minWidth: 24 }, { key: "Id", header: "Id", minWidth: 10 } ], rows }).trimEnd()]; } function renderMessagesFromPayload(payload, opts) { if (!payload || typeof payload !== "object") return null; const messages = payload.messages; if (!Array.isArray(messages)) return null; return renderMessageList(messages, opts, "No messages."); } function renderPinsFromPayload(payload, opts) { if (!payload || typeof payload !== "object") return null; const pins = payload.pins; if (!Array.isArray(pins)) return null; return renderMessageList(pins, opts, "No pins."); } function extractDiscordSearchResultsMessages(results) { if (!results || typeof results !== "object") return null; const raw = results.messages; if (!Array.isArray(raw)) return null; const flattened = []; for (const entry of raw) if (Array.isArray(entry) && entry.length > 0) flattened.push(entry[0]); else if (entry && typeof entry === "object") flattened.push(entry); return flattened.length ? flattened : null; } function renderReactions(payload, opts) { if (!payload || typeof payload !== "object") return null; const reactions = payload.reactions; if (!Array.isArray(reactions)) return null; const rows = reactions.slice(0, 50).map((r) => { const entry = r; const emojiObj = entry.emoji; return { Emoji: typeof emojiObj?.raw === "string" && emojiObj.raw || typeof entry.name === "string" && entry.name || typeof entry.emoji === "string" && entry.emoji || "", Count: typeof entry.count === "number" ? String(entry.count) : "", Users: shortenText((Array.isArray(entry.users) ? entry.users.slice(0, 8).map((u) => { if (typeof u === "string") return u; if (!u || typeof u !== "object") return ""; const user = u; return typeof user.tag === "string" && user.tag || typeof user.username === "string" && user.username || typeof user.id === "string" && user.id || ""; }).filter(Boolean) : []).join(", "), 72) }; }); if (rows.length === 0) return [theme.muted("No reactions.")]; return [renderTable({ width: opts.width, columns: [ { key: "Emoji", header: "Emoji", minWidth: 8 }, { key: "Count", header: "Count", align: "right", minWidth: 6 }, { key: "Users", header: "Users", flex: true, minWidth: 20 } ], rows }).trimEnd()]; } function formatMessageCliText(result) { const rich = isRich(); const ok = (text) => rich ? theme.success(text) : text; const muted = (text) => rich ? theme.muted(text) : text; const heading = (text) => rich ? theme.heading(text) : text; const opts = { width: Math.max(60, (process.stdout.columns ?? 120) - 1) }; if (result.handledBy === "dry-run") return [muted(`[dry-run] would run ${result.action} via ${result.channel}`)]; if (result.kind === "broadcast") { const results = result.payload.results ?? []; const rows = results.map((entry) => ({ Channel: resolveChannelLabel(entry.channel), Target: shortenText(formatTargetDisplay({ channel: entry.channel, target: entry.to }), 36), Status: entry.ok ? "ok" : "error", Error: entry.ok ? "" : shortenText(entry.error ?? "unknown error", 48) })); const okCount = results.filter((entry) => entry.ok).length; const total = results.length; return [ok(`✅ Broadcast complete (${okCount}/${total} succeeded, ${total - okCount} failed)`), renderTable({ width: opts.width, columns: [ { key: "Channel", header: "Channel", minWidth: 10 }, { key: "Target", header: "Target", minWidth: 12, flex: true }, { key: "Status", header: "Status", minWidth: 6 }, { key: "Error", header: "Error", minWidth: 20, flex: true } ], rows: rows.slice(0, 50) }).trimEnd()]; } if (result.kind === "send") { if (result.handledBy === "core" && result.sendResult) { const send = result.sendResult; if (send.via === "direct") { const directResult = send.result; return [ok(formatOutboundDeliverySummary(send.channel, directResult))]; } const gatewayResult = send.result; return [ok(formatGatewaySummary({ channel: send.channel, messageId: gatewayResult?.messageId ?? null }))]; } const label = resolveChannelLabel(result.channel); const msgId = extractMessageId(result.payload); return [ok(`✅ Sent via ${label}.${msgId ? ` Message ID: ${msgId}` : ""}`)]; } if (result.kind === "poll") { if (result.handledBy === "core" && result.pollResult) { const poll = result.pollResult; const pollId = poll.result?.pollId; const msgId = poll.result?.messageId ?? null; const lines = [ok(formatGatewaySummary({ action: "Poll sent", channel: poll.channel, messageId: msgId }))]; if (pollId) lines.push(ok(`Poll id: ${pollId}`)); return lines; } const label = resolveChannelLabel(result.channel); const msgId = extractMessageId(result.payload); return [ok(`✅ Poll sent via ${label}.${msgId ? ` Message ID: ${msgId}` : ""}`)]; } const payload = result.payload; const lines = []; if (result.action === "react") { const added = payload.added; const removed = payload.removed; if (typeof added === "string" && added.trim()) { lines.push(ok(`✅ Reaction added: ${added.trim()}`)); return lines; } if (typeof removed === "string" && removed.trim()) { lines.push(ok(`✅ Reaction removed: ${removed.trim()}`)); return lines; } if (Array.isArray(removed)) { const list = removed.map((x) => String(x).trim()).filter(Boolean).join(", "); lines.push(ok(`✅ Reactions removed${list ? `: ${list}` : ""}`)); return lines; } lines.push(ok("✅ Reaction updated.")); return lines; } const reactionsTable = renderReactions(payload, opts); if (reactionsTable && result.action === "reactions") { lines.push(heading("Reactions")); lines.push(reactionsTable[0] ?? ""); return lines; } if (result.action === "read") { const messagesTable = renderMessagesFromPayload(payload, opts); if (messagesTable) { lines.push(heading("Messages")); lines.push(messagesTable[0] ?? ""); return lines; } } if (result.action === "list-pins") { const pinsTable = renderPinsFromPayload(payload, opts); if (pinsTable) { lines.push(heading("Pinned messages")); lines.push(pinsTable[0] ?? ""); return lines; } } if (result.action === "search") { const results = payload.results; const list = extractDiscordSearchResultsMessages(results); if (list) { lines.push(heading("Search results")); lines.push(renderMessageList(list, opts, "No results.")[0] ?? ""); return lines; } } lines.push(ok(`✅ ${result.action} via ${resolveChannelLabel(result.channel)}.`)); const summary = renderObjectSummary(payload, opts); if (summary.length) { lines.push(""); lines.push(...summary); lines.push(""); lines.push(muted("Tip: use --json for full output.")); } return lines; } //#endregion //#region src/commands/message.ts async function messageCommand(opts, deps, runtime) { const cfg = loadConfig(); const actionInput = (typeof opts.action === "string" ? opts.action.trim() : "") || "send"; const actionMatch = CHANNEL_MESSAGE_ACTION_NAMES.find((name) => name.toLowerCase() === actionInput.toLowerCase()); if (!actionMatch) throw new Error(`Unknown message action: ${actionInput}`); const action = actionMatch; const outboundDeps = createOutboundSendDeps(deps); const run = async () => await runMessageAction({ cfg, action, params: opts, deps: outboundDeps, gateway: { clientName: GATEWAY_CLIENT_NAMES.CLI, mode: GATEWAY_CLIENT_MODES.CLI } }); const json = opts.json === true; const dryRun = opts.dryRun === true; const result = !json && !dryRun && (action === "send" || action === "poll") ? await withProgress({ label: action === "poll" ? "Sending poll..." : "Sending...", indeterminate: true, enabled: true }, run) : await run(); if (json) { runtime.log(JSON.stringify(buildMessageCliJson(result), null, 2)); return; } for (const line of formatMessageCliText(result)) runtime.log(line); } //#endregion //#region src/cli/program/message/helpers.ts function normalizeMessageOptions(opts) { const { account, ...rest } = opts; return { ...rest, accountId: typeof account === "string" ? account : void 0 }; } async function runPluginStopHooks() { await runGlobalGatewayStopSafely({ event: { reason: "cli message action complete" }, ctx: {}, onError: (err) => defaultRuntime.error(danger(`gateway_stop hook failed: ${String(err)}`)) }); } function createMessageCliHelpers(message, messageChannelOptions) { const withMessageBase = (command) => command.option("--channel <channel>", `Channel: ${messageChannelOptions}`).option("--account <id>", "Channel account id (accountId)").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.option("-t, --target <dest>", CHANNEL_TARGET_DESCRIPTION); const withRequiredMessageTarget = (command) => command.requiredOption("-t, --target <dest>", CHANNEL_TARGET_DESCRIPTION); const runMessageAction = async (action, opts) => { setVerbose(Boolean(opts.verbose)); ensurePluginRegistryLoaded(); const deps = createDefaultDeps(); let failed = false; await runCommandWithRuntime(defaultRuntime, async () => { await messageCommand({ ...normalizeMessageOptions(opts), action }, deps, defaultRuntime); }, (err) => { failed = true; defaultRuntime.error(danger(String(err))); }); await runPluginStopHooks(); defaultRuntime.exit(failed ? 1 : 0); }; return { withMessageBase, withMessageTarget, withRequiredMessageTarget, runMessageAction }; } //#endregion //#region src/cli/program/message/register.broadcast.ts function registerMessageBroadcastCommand(message, helpers) { helpers.withMessageBase(message.command("broadcast").description("Broadcast a message to multiple targets")).requiredOption("--targets <target...>", CHANNEL_TARGETS_DESCRIPTION).option("--message <text>", "Message to send").option("--media <url>", "Media URL").action(async (options) => { await helpers.runMessageAction("broadcast", options); }); } //#endregion //#region src/cli/program/message/register.discord-admin.ts function registerMessageDiscordAdminCommands(message, helpers) { const role = message.command("role").description("Role actions"); helpers.withMessageBase(role.command("info").description("List roles").requiredOption("--guild-id <id>", "Guild id")).action(async (opts) => { await helpers.runMessageAction("role-info", opts); }); helpers.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 helpers.runMessageAction("role-add", opts); }); helpers.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 helpers.runMessageAction("role-remove", opts); }); const channel = message.command("channel").description("Channel actions"); helpers.withMessageBase(helpers.withRequiredMessageTarget(channel.command("info").description("Fetch channel info"))).action(async (opts) => { await helpers.runMessageAction("channel-info", opts); }); helpers.withMessageBase(channel.command("list").description("List channels").requiredOption("--guild-id <id>", "Guild id")).action(async (opts) => { await helpers.runMessageAction("channel-list", opts); }); const member = message.command("member").description("Member actions"); helpers.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 helpers.runMessageAction("member-info", opts); }); const voice = message.command("voice").description("Voice actions"); helpers.withMessageBase(voice.command("status").description("Fetch voice status").requiredOption("--guild-id <id>", "Guild id").requiredOption("--user-id <id>", "User id")).action(async (opts) => { await helpers.runMessageAction("voice-status", opts); }); const event = message.command("event").description("Event actions"); helpers.withMessageBase(event.command("list").description("List scheduled events").requiredOption("--guild-id <id>", "Guild id")).action(async (opts) => { await helpers.runMessageAction("event-list", opts); }); helpers.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 helpers.runMessageAction("event-create", opts); }); helpers.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 helpers.runMessageAction("timeout", opts); }); helpers.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 helpers.runMessageAction("kick", opts); }); helpers.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 helpers.runMessageAction("ban", opts); }); } //#endregion //#region src/cli/program/message/register.emoji-sticker.ts function registerMessageEmojiCommands(message, helpers) { const emoji = message.command("emoji").description("Emoji actions"); helpers.withMessageBase(emoji.command("list").description("List emojis")).option("--guild-id <id>", "Guild id (Discord)").action(async (opts) => { await helpers.runMessageAction("emoji-list", opts); }); helpers.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("--role-ids <id>", "Role id (repeat)", collectOption, []).action(async (opts) => { await helpers.runMessageAction("emoji-upload", opts); }); } function registerMessageStickerCommands(message, helpers) { const sticker = message.command("sticker").description("Sticker actions"); helpers.withMessageBase(helpers.withRequiredMessageTarget(sticker.command("send").description("Send stickers"))).requiredOption("--sticker-id <id>", "Sticker id (repeat)", collectOption).option("-m, --message <text>", "Optional message body").action(async (opts) => { await helpers.runMessageAction("sticker", opts); }); helpers.withMessageBase(sticker.command("upload").description("Upload a sticker").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 helpers.runMessageAction("sticker-upload", opts); }); } //#endregion //#region src/cli/program/message/register.permissions-search.ts function registerMessagePermissionsCommand(message, helpers) { helpers.withMessageBase(helpers.withRequiredMessageTarget(message.command("permissions").description("Fetch channel permissions"))).action(async (opts) => { await helpers.runMessageAction("permissions", opts); }); } function registerMessageSearchCommand(message, helpers) { helpers.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, []).option("--author-id <id>", "Author id").option("--author-ids <id>", "Author id (repeat)", collectOption, []).option("--limit <n>", "Result limit").action(async (opts) => { await helpers.runMessageAction("search", opts); }); } //#endregion //#region src/cli/program/message/register.pins.ts function registerMessagePinCommands(message, helpers) { helpers.withMessageBase(helpers.withRequiredMessageTarget(message.command("pin").description("Pin a message"))).requiredOption("--message-id <id>", "Message id").action(async (opts) => { await helpers.runMessageAction("pin", opts); }), helpers.withMessageBase(helpers.withRequiredMessageTarget(message.command("unpin").description("Unpin a message"))).requiredOption("--message-id <id>", "Message id").action(async (opts) => { await helpers.runMessageAction("unpin", opts); }), helpers.withMessageBase(helpers.withRequiredMessageTarget(message.command("pins").description("List pinned messages"))).option("--limit <n>", "Result limit").action(async (opts) => { await helpers.runMessageAction("list-pins", opts); }); } //#endregion //#region src/cli/program/message/register.poll.ts function registerMessagePollCommand(message, helpers) { helpers.withMessageBase(helpers.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, []).option("--poll-multi", "Allow multiple selections", false).option("--poll-duration-hours <n>", "Poll duration in hours (Discord)").option("--poll-duration-seconds <n>", "Poll duration in seconds (Telegram; 5-600)").option("--poll-anonymous", "Send an anonymous poll (Telegram)", false).option("--poll-public", "Send a non-anonymous poll (Telegram)", false).option("-m, --message <text>", "Optional message body").option("--silent", "Send poll silently without notification (Telegram + Discord where supported)", false).option("--thread-id <id>", "Thread id (Telegram forum topic / Slack thread ts)").action(async (opts) => { await helpers.runMessageAction("poll", opts); }); } //#endregion //#region src/cli/program/message/register.reactions.ts function registerMessageReactionsCommands(message, helpers) { helpers.withMessageBase(helpers.withRequiredMessageTarget(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("--target-author <id>", "Signal reaction target author (uuid or phone)").option("--target-author-uuid <uuid>", "Signal reaction target author uuid").action(async (opts) => { await helpers.runMessageAction("react", opts); }); helpers.withMessageBase(helpers.withRequiredMessageTarget(message.command("reactions").description("List reactions on a message"))).requiredOption("--message-id <id>", "Message id").option("--limit <n>", "Result limit").action(async (opts) => { await helpers.runMessageAction("reactions", opts); }); } //#endregion //#region src/cli/program/message/register.read-edit-delete.ts function registerMessageReadEditDeleteCommands(message, helpers) { helpers.withMessageBase(helpers.withRequiredMessageTarget(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").option("--include-thread", "Include thread replies (Discord)", false).action(async (opts) => { await helpers.runMessageAction("read", opts); }); helpers.withMessageBase(helpers.withRequiredMessageTarget(message.command("edit").description("Edit a message").requiredOption("--message-id <id>", "Message id").requiredOption("-m, --message <text>", "Message body"))).option("--thread-id <id>", "Thread id (Telegram forum thread)").action(async (opts) => { await helpers.runMessageAction("edit", opts); }); helpers.withMessageBase(helpers.withRequiredMessageTarget(message.command("delete").description("Delete a message").requiredOption("--message-id <id>", "Message id"))).action(async (opts) => { await helpers.runMessageAction("delete", opts); }); } //#endregion //#region src/cli/program/message/register.send.ts function registerMessageSendCommand(message, helpers) { helpers.withMessageBase(helpers.withRequiredMessageTarget(message.command("send").description("Send a message").option("-m, --message <text>", "Message body (required unless --media is set)")).option("--media <path-or-url>", "Attach media (image/audio/video/document). Accepts local paths or URLs.").option("--buttons <json>", "Telegram inline keyboard buttons as JSON (array of button rows)").option("--components <json>", "Discord components payload as JSON").option("--card <json>", "Adaptive Card JSON object (when supported by the channel)").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).option("--silent", "Send message silently without notification (Telegram + Discord)", false)).action(async (opts) => { await helpers.runMessageAction("send", opts); }); } //#endregion //#region src/cli/program/message/register.thread.ts function registerMessageThreadCommands(message, helpers) { const thread = message.command("thread").description("Thread actions"); helpers.withMessageBase(helpers.withRequiredMessageTarget(thread.command("create").description("Create a thread").requiredOption("--thread-name <name>", "Thread name"))).option("--message-id <id>", "Message id (optional)").option("-m, --message <text>", "Initial thread message text").option("--auto-archive-min <n>", "Thread auto-archive minutes").action(async (opts) => { await helpers.runMessageAction("thread-create", opts); }); helpers.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 helpers.runMessageAction("thread-list", opts); }); helpers.withMessageBase(helpers.withRequiredMessageTarget(thread.command("reply").description("Reply in a thread").requiredOption("-m, --message <text>", "Message body"))).option("--media <path-or-url>", "Attach media (image/audio/video/document). Accepts local paths or URLs.").option("--reply-to <id>", "Reply-to message id").action(async (opts) => { await helpers.runMessageAction("thread-reply", opts); }); } //#endregion //#region src/cli/program/register.message.ts function registerMessageCommands(program, ctx) { const message = program.command("message").description("Send, read, and manage messages and channel actions").addHelpText("after", () => ` ${theme.heading("Examples:")} ${formatHelpExamples([ ["openclaw message send --target +15555550123 --message \"Hi\"", "Send a text message."], ["openclaw message send --target +15555550123 --message \"Hi\" --media photo.jpg", "Send a message with media."], ["openclaw message poll --channel discord --target channel:123 --poll-question \"Snack?\" --poll-option Pizza --poll-option Sushi", "Create a Discord poll."], ["openclaw message react --channel discord --target 123 --message-id 456 --emoji \"✅\"", "React to a message."] ])} ${theme.muted("Docs:")} ${formatDocsLink("/cli/message", "docs.openclaw.ai/cli/message")}`).action(() => { message.help({ error: true }); }); const helpers = createMessageCliHelpers(message, ctx.messageChannelOptions); registerMessageSendCommand(message, helpers); registerMessageBroadcastCommand(message, helpers); registerMessagePollCommand(message, helpers); registerMessageReactionsCommands(message, helpers); registerMessageReadEditDeleteCommands(message, helpers); registerMessagePinCommands(message, helpers); registerMessagePermissionsCommand(message, helpers); registerMessageSearchCommand(message, helpers); registerMessageThreadCommands(message, helpers); registerMessageEmojiCommands(message, helpers); registerMessageStickerCommands(message, helpers); registerMessageDiscordAdminCommands(message, helpers); } //#endregion export { registerMessageCommands };