import type { AgentToolResult } from "@mariozechner/pi-agent-core"; import { handleDiscordAction } from "../agents/tools/discord-actions.js"; import { handleSlackAction } from "../agents/tools/slack-actions.js"; import { handleTelegramAction } from "../agents/tools/telegram-actions.js"; import { handleWhatsAppAction } from "../agents/tools/whatsapp-actions.js"; import type { CliDeps } from "../cli/deps.js"; import { withProgress } from "../cli/progress.js"; import { loadConfig } from "../config/config.js"; import { success } from "../globals.js"; import type { OutboundDeliveryResult, OutboundSendDeps, } from "../infra/outbound/deliver.js"; import { buildOutboundResultEnvelope } from "../infra/outbound/envelope.js"; import { buildOutboundDeliveryJson, formatGatewaySummary, formatOutboundDeliverySummary, } from "../infra/outbound/format.js"; import { type MessagePollResult, type MessageSendResult, sendMessage, sendPoll, } from "../infra/outbound/message.js"; import { resolveMessageProviderSelection } from "../infra/outbound/provider-selection.js"; import type { RuntimeEnv } from "../runtime.js"; type MessageAction = | "send" | "poll" | "react" | "reactions" | "read" | "edit" | "delete" | "pin" | "unpin" | "list-pins" | "permissions" | "thread-create" | "thread-list" | "thread-reply" | "search" | "sticker" | "member-info" | "role-info" | "emoji-list" | "emoji-upload" | "sticker-upload" | "role-add" | "role-remove" | "channel-info" | "channel-list" | "voice-status" | "event-list" | "event-create" | "timeout" | "kick" | "ban"; type MessageCommandOpts = { action?: string; provider?: string; to?: string; message?: string; media?: string; buttonsJson?: string; messageId?: string; replyTo?: string; threadId?: string; account?: string; emoji?: string; remove?: boolean; limit?: string; before?: string; after?: string; around?: string; pollQuestion?: string; pollOption?: string[] | string; pollDurationHours?: string; pollMulti?: boolean; channelId?: string; channelIds?: string[] | string; guildId?: string; userId?: string; authorId?: string; authorIds?: string[] | string; roleId?: string; roleIds?: string[] | string; emojiName?: string; stickerId?: string[] | string; stickerName?: string; stickerDesc?: string; stickerTags?: string; threadName?: string; autoArchiveMin?: string; query?: string; eventName?: string; eventType?: string; startTime?: string; endTime?: string; desc?: string; location?: string; durationMin?: string; until?: string; reason?: string; deleteDays?: string; includeArchived?: boolean; participant?: string; fromMe?: boolean; dryRun?: boolean; json?: boolean; gifPlayback?: boolean; }; type MessageSendOpts = { to: string; message: string; provider: string; json?: boolean; dryRun?: boolean; media?: string; gifPlayback?: boolean; account?: string; }; function normalizeAction(value?: string): MessageAction { const raw = value?.trim().toLowerCase() || "send"; return raw as MessageAction; } function parseIntOption(value: unknown, label: string): number | undefined { if (value === undefined || value === null) return undefined; if (typeof value === "number" && Number.isFinite(value)) return value; if (typeof value !== "string" || value.trim().length === 0) return undefined; const parsed = Number.parseInt(value, 10); if (!Number.isFinite(parsed)) { throw new Error(`${label} must be a number`); } return parsed; } function requireString(value: unknown, label: string): string { if (typeof value !== "string") { throw new Error(`${label} required`); } const trimmed = value.trim(); if (!trimmed) { throw new Error(`${label} required`); } return trimmed; } function optionalString(value: unknown): string | undefined { if (typeof value !== "string") return undefined; const trimmed = value.trim(); return trimmed ? trimmed : undefined; } function toStringArray(value: unknown): string[] { if (Array.isArray(value)) { return value.map((entry) => String(entry).trim()).filter(Boolean); } if (typeof value === "string") { const trimmed = value.trim(); return trimmed ? [trimmed] : []; } return []; } function extractToolPayload(result: AgentToolResult): unknown { if (result.details !== undefined) return result.details; const textBlock = Array.isArray(result.content) ? result.content.find( (block) => block && typeof block === "object" && (block as { type?: unknown }).type === "text" && typeof (block as { text?: unknown }).text === "string", ) : undefined; const text = (textBlock as { text?: string } | undefined)?.text; if (text) { try { return JSON.parse(text); } catch { return text; } } return result.content ?? result; } function logSendDryRun(opts: MessageSendOpts, runtime: RuntimeEnv) { runtime.log( `[dry-run] would send via ${opts.provider} -> ${opts.to}: ${opts.message}${ opts.media ? ` (media ${opts.media})` : "" }`, ); } function logPollDryRun(result: MessagePollResult, runtime: RuntimeEnv) { runtime.log( `[dry-run] would send poll via ${result.provider} -> ${result.to}:\n Question: ${result.question}\n Options: ${result.options.join( ", ", )}\n Max selections: ${result.maxSelections}`, ); } function logSendResult( result: MessageSendResult, opts: MessageSendOpts, runtime: RuntimeEnv, ) { if (result.via === "direct") { const directResult = result.result as OutboundDeliveryResult | undefined; const summary = formatOutboundDeliverySummary( result.provider, directResult, ); runtime.log(success(summary)); if (opts.json) { runtime.log( JSON.stringify( buildOutboundDeliveryJson({ provider: result.provider, via: "direct", to: opts.to, result: directResult, mediaUrl: opts.media ?? null, }), null, 2, ), ); } return; } const gatewayResult = result.result as { messageId?: string } | undefined; runtime.log( success( formatGatewaySummary({ provider: result.provider, messageId: gatewayResult?.messageId ?? null, }), ), ); if (opts.json) { runtime.log( JSON.stringify( buildOutboundResultEnvelope({ delivery: buildOutboundDeliveryJson({ provider: result.provider, via: "gateway", to: opts.to, result: gatewayResult, mediaUrl: opts.media ?? null, }), }), null, 2, ), ); } } export async function messageCommand( opts: MessageCommandOpts, deps: CliDeps, runtime: RuntimeEnv, ) { const cfg = loadConfig(); const action = normalizeAction(opts.action); const providerSelection = await resolveMessageProviderSelection({ cfg, provider: opts.provider, }); const provider = providerSelection.provider; const outboundDeps: OutboundSendDeps = { sendWhatsApp: deps.sendMessageWhatsApp, sendTelegram: deps.sendMessageTelegram, sendDiscord: deps.sendMessageDiscord, sendSlack: deps.sendMessageSlack, sendSignal: deps.sendMessageSignal, sendIMessage: deps.sendMessageIMessage, sendMSTeams: (to, text, opts) => deps.sendMessageMSTeams({ cfg, to, text, mediaUrl: opts?.mediaUrl }), }; if (opts.dryRun && action !== "send" && action !== "poll") { runtime.log(`[dry-run] would run ${action} via ${provider}`); return; } if (action === "send") { const to = requireString(opts.to, "to"); const message = requireString(opts.message, "message"); const sendOpts: MessageSendOpts = { to, message, provider, json: opts.json, dryRun: opts.dryRun, media: optionalString(opts.media), gifPlayback: opts.gifPlayback, account: optionalString(opts.account), }; if (opts.dryRun) { logSendDryRun(sendOpts, runtime); return; } if (provider === "discord") { const result = await handleDiscordAction( { action: "sendMessage", to, content: message, mediaUrl: optionalString(opts.media), replyTo: optionalString(opts.replyTo), }, cfg, ); const payload = extractToolPayload(result); if (opts.json) { runtime.log(JSON.stringify(payload, null, 2)); } else { runtime.log(success(`Sent via ${provider}.`)); } return; } if (provider === "slack") { const result = await handleSlackAction( { action: "sendMessage", to, content: message, mediaUrl: optionalString(opts.media), threadTs: optionalString(opts.threadId) ?? optionalString(opts.replyTo), accountId: optionalString(opts.account), }, cfg, ); const payload = extractToolPayload(result); if (opts.json) { runtime.log(JSON.stringify(payload, null, 2)); } else { runtime.log(success(`Sent via ${provider}.`)); } return; } if (provider === "telegram") { const buttonsJson = optionalString(opts.buttonsJson); let buttons: unknown; if (buttonsJson) { try { buttons = JSON.parse(buttonsJson); } catch { throw new Error("buttons-json must be valid JSON"); } } const result = await handleTelegramAction( { action: "sendMessage", to, content: message, mediaUrl: optionalString(opts.media), replyToMessageId: optionalString(opts.replyTo), messageThreadId: optionalString(opts.threadId), accountId: optionalString(opts.account), buttons, }, cfg, ); const payload = extractToolPayload(result); if (opts.json) { runtime.log(JSON.stringify(payload, null, 2)); } else { runtime.log(success(`Sent via ${provider}.`)); } return; } const result = await withProgress( { label: `Sending via ${provider}...`, indeterminate: true, enabled: opts.json !== true, }, async () => await sendMessage({ cfg, to, content: message, provider, mediaUrl: optionalString(opts.media), gifPlayback: opts.gifPlayback, accountId: optionalString(opts.account), dryRun: opts.dryRun, deps: outboundDeps, gateway: { clientName: "cli", mode: "cli" }, }), ); logSendResult(result, sendOpts, runtime); return; } if (action === "poll") { const to = requireString(opts.to, "to"); const question = requireString(opts.pollQuestion, "poll-question"); const options = toStringArray(opts.pollOption); if (options.length < 2) { throw new Error("poll-option requires at least two values"); } const durationHours = parseIntOption( opts.pollDurationHours, "poll-duration-hours", ); const allowMultiselect = Boolean(opts.pollMulti); const maxSelections = allowMultiselect ? Math.max(2, options.length) : 1; if (opts.dryRun) { const result = await sendPoll({ cfg, to, question, options, maxSelections, durationHours, provider, dryRun: true, gateway: { clientName: "cli", mode: "cli" }, }); logPollDryRun(result, runtime); return; } if (provider === "discord") { const result = await handleDiscordAction( { action: "poll", to, question, answers: options, allowMultiselect, durationHours: durationHours ?? undefined, content: optionalString(opts.message), }, cfg, ); const payload = extractToolPayload(result); if (opts.json) { runtime.log(JSON.stringify(payload, null, 2)); } else { runtime.log(success(`Poll sent via ${provider}.`)); } return; } const result = await withProgress( { label: `Sending poll via ${provider}...`, indeterminate: true, enabled: opts.json !== true, }, async () => await sendPoll({ cfg, to, question, options, maxSelections, durationHours, provider, dryRun: opts.dryRun, gateway: { clientName: "cli", mode: "cli" }, }), ); runtime.log( success( formatGatewaySummary({ action: "Poll sent", provider, messageId: result.result?.messageId ?? null, }), ), ); const pollId = (result.result as { pollId?: string } | undefined)?.pollId; if (pollId) { runtime.log(success(`Poll id: ${pollId}`)); } if (opts.json) { runtime.log( JSON.stringify( { ...buildOutboundResultEnvelope({ delivery: buildOutboundDeliveryJson({ provider, via: "gateway", to, result: result.result, mediaUrl: null, }), }), question: result.question, options: result.options, maxSelections: result.maxSelections, durationHours: result.durationHours, pollId, }, null, 2, ), ); } return; } if (action === "react") { const messageId = requireString(opts.messageId, "message-id"); const emoji = optionalString(opts.emoji) ?? ""; if (provider === "discord") { const result = await handleDiscordAction( { action: "react", channelId: requireString(opts.channelId ?? opts.to, "to"), messageId, emoji, remove: opts.remove, }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (provider === "slack") { const result = await handleSlackAction( { action: "react", channelId: requireString(opts.channelId ?? opts.to, "to"), messageId, emoji, remove: opts.remove, accountId: optionalString(opts.account), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (provider === "telegram") { const result = await handleTelegramAction( { action: "react", chatId: requireString(opts.to, "to"), messageId, emoji, remove: opts.remove, accountId: optionalString(opts.account), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (provider === "whatsapp") { const result = await handleWhatsAppAction( { action: "react", chatJid: requireString(opts.to, "to"), messageId, emoji, remove: opts.remove, participant: optionalString(opts.participant), accountId: optionalString(opts.account), fromMe: opts.fromMe, }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } throw new Error(`React is not supported for provider ${provider}.`); } if (action === "reactions") { const messageId = requireString(opts.messageId, "message-id"); const limit = parseIntOption(opts.limit, "limit"); if (provider === "discord") { const result = await handleDiscordAction( { action: "reactions", channelId: requireString(opts.channelId ?? opts.to, "to"), messageId, limit, }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (provider === "slack") { const result = await handleSlackAction( { action: "reactions", channelId: requireString(opts.channelId ?? opts.to, "to"), messageId, limit, accountId: optionalString(opts.account), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } throw new Error(`Reactions are not supported for provider ${provider}.`); } if (action === "read") { const limit = parseIntOption(opts.limit, "limit"); if (provider === "discord") { const result = await handleDiscordAction( { action: "readMessages", channelId: requireString(opts.channelId ?? opts.to, "to"), limit, before: optionalString(opts.before), after: optionalString(opts.after), around: optionalString(opts.around), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (provider === "slack") { const result = await handleSlackAction( { action: "readMessages", channelId: requireString(opts.channelId ?? opts.to, "to"), limit, before: optionalString(opts.before), after: optionalString(opts.after), accountId: optionalString(opts.account), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } throw new Error(`Read is not supported for provider ${provider}.`); } if (action === "edit") { const messageId = requireString(opts.messageId, "message-id"); const message = requireString(opts.message, "message"); if (provider === "discord") { const result = await handleDiscordAction( { action: "editMessage", channelId: requireString(opts.channelId ?? opts.to, "to"), messageId, content: message, }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (provider === "slack") { const result = await handleSlackAction( { action: "editMessage", channelId: requireString(opts.channelId ?? opts.to, "to"), messageId, content: message, accountId: optionalString(opts.account), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } throw new Error(`Edit is not supported for provider ${provider}.`); } if (action === "delete") { const messageId = requireString(opts.messageId, "message-id"); if (provider === "discord") { const result = await handleDiscordAction( { action: "deleteMessage", channelId: requireString(opts.channelId ?? opts.to, "to"), messageId, }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (provider === "slack") { const result = await handleSlackAction( { action: "deleteMessage", channelId: requireString(opts.channelId ?? opts.to, "to"), messageId, accountId: optionalString(opts.account), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } throw new Error(`Delete is not supported for provider ${provider}.`); } if (action === "pin" || action === "unpin" || action === "list-pins") { const channelId = requireString(opts.channelId ?? opts.to, "to"); const messageId = action === "list-pins" ? undefined : requireString(opts.messageId, "message-id"); if (provider === "discord") { const result = await handleDiscordAction( { action: action === "pin" ? "pinMessage" : action === "unpin" ? "unpinMessage" : "listPins", channelId, messageId, }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (provider === "slack") { const result = await handleSlackAction( { action: action === "pin" ? "pinMessage" : action === "unpin" ? "unpinMessage" : "listPins", channelId, messageId, accountId: optionalString(opts.account), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } throw new Error(`Pins are not supported for provider ${provider}.`); } if (action === "permissions") { if (provider !== "discord") { throw new Error( `Permissions are only supported for Discord (provider=${provider}).`, ); } const result = await handleDiscordAction( { action: "permissions", channelId: requireString(opts.channelId ?? opts.to, "to"), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (action === "thread-create") { if (provider !== "discord") { throw new Error( `Thread create is only supported for Discord (provider=${provider}).`, ); } const result = await handleDiscordAction( { action: "threadCreate", channelId: requireString(opts.channelId ?? opts.to, "to"), name: requireString(opts.threadName, "thread-name"), messageId: optionalString(opts.messageId), autoArchiveMinutes: parseIntOption( opts.autoArchiveMin, "auto-archive-min", ), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (action === "thread-list") { if (provider !== "discord") { throw new Error( `Thread list is only supported for Discord (provider=${provider}).`, ); } const result = await handleDiscordAction( { action: "threadList", guildId: requireString(opts.guildId, "guild-id"), channelId: optionalString(opts.channelId), includeArchived: opts.includeArchived, before: optionalString(opts.before), limit: parseIntOption(opts.limit, "limit"), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (action === "thread-reply") { if (provider !== "discord") { throw new Error( `Thread reply is only supported for Discord (provider=${provider}).`, ); } const result = await handleDiscordAction( { action: "threadReply", channelId: requireString(opts.channelId ?? opts.to, "to"), content: requireString(opts.message, "message"), mediaUrl: optionalString(opts.media), replyTo: optionalString(opts.replyTo), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (action === "search") { if (provider !== "discord") { throw new Error( `Search is only supported for Discord (provider=${provider}).`, ); } const result = await handleDiscordAction( { action: "searchMessages", guildId: requireString(opts.guildId, "guild-id"), content: requireString(opts.query, "query"), channelId: optionalString(opts.channelId), channelIds: toStringArray(opts.channelIds), authorId: optionalString(opts.authorId), authorIds: toStringArray(opts.authorIds), limit: parseIntOption(opts.limit, "limit"), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (action === "sticker") { if (provider !== "discord") { throw new Error( `Sticker send is only supported for Discord (provider=${provider}).`, ); } const stickerIds = toStringArray(opts.stickerId); if (stickerIds.length === 0) { throw new Error("sticker-id required"); } const result = await handleDiscordAction( { action: "sticker", to: requireString(opts.to, "to"), stickerIds, content: optionalString(opts.message), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (action === "member-info") { const userId = requireString(opts.userId, "user-id"); if (provider === "discord") { const result = await handleDiscordAction( { action: "memberInfo", guildId: requireString(opts.guildId, "guild-id"), userId, }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (provider === "slack") { const result = await handleSlackAction( { action: "memberInfo", userId, accountId: optionalString(opts.account), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } throw new Error(`Member info is not supported for provider ${provider}.`); } if (action === "role-info") { if (provider !== "discord") { throw new Error( `Role info is only supported for Discord (provider=${provider}).`, ); } const result = await handleDiscordAction( { action: "roleInfo", guildId: requireString(opts.guildId, "guild-id") }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (action === "emoji-list") { if (provider === "discord") { const result = await handleDiscordAction( { action: "emojiList", guildId: requireString(opts.guildId, "guild-id"), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (provider === "slack") { const result = await handleSlackAction( { action: "emojiList", accountId: optionalString(opts.account) }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } throw new Error(`Emoji list is not supported for provider ${provider}.`); } if (action === "emoji-upload") { if (provider !== "discord") { throw new Error( `Emoji upload is only supported for Discord (provider=${provider}).`, ); } const result = await handleDiscordAction( { action: "emojiUpload", guildId: requireString(opts.guildId, "guild-id"), name: requireString(opts.emojiName, "emoji-name"), mediaUrl: requireString(opts.media, "media"), roleIds: toStringArray(opts.roleIds), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (action === "sticker-upload") { if (provider !== "discord") { throw new Error( `Sticker upload is only supported for Discord (provider=${provider}).`, ); } const result = await handleDiscordAction( { action: "stickerUpload", guildId: requireString(opts.guildId, "guild-id"), name: requireString(opts.stickerName, "sticker-name"), description: requireString(opts.stickerDesc, "sticker-desc"), tags: requireString(opts.stickerTags, "sticker-tags"), mediaUrl: requireString(opts.media, "media"), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (action === "role-add" || action === "role-remove") { if (provider !== "discord") { throw new Error( `Role changes are only supported for Discord (provider=${provider}).`, ); } const result = await handleDiscordAction( { action: action === "role-add" ? "roleAdd" : "roleRemove", guildId: requireString(opts.guildId, "guild-id"), userId: requireString(opts.userId, "user-id"), roleId: requireString(opts.roleId, "role-id"), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (action === "channel-info") { if (provider !== "discord") { throw new Error( `Channel info is only supported for Discord (provider=${provider}).`, ); } const result = await handleDiscordAction( { action: "channelInfo", channelId: requireString(opts.channelId, "channel-id"), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (action === "channel-list") { if (provider !== "discord") { throw new Error( `Channel list is only supported for Discord (provider=${provider}).`, ); } const result = await handleDiscordAction( { action: "channelList", guildId: requireString(opts.guildId, "guild-id"), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (action === "voice-status") { if (provider !== "discord") { throw new Error( `Voice status is only supported for Discord (provider=${provider}).`, ); } const result = await handleDiscordAction( { action: "voiceStatus", guildId: requireString(opts.guildId, "guild-id"), userId: requireString(opts.userId, "user-id"), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (action === "event-list") { if (provider !== "discord") { throw new Error( `Event list is only supported for Discord (provider=${provider}).`, ); } const result = await handleDiscordAction( { action: "eventList", guildId: requireString(opts.guildId, "guild-id") }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (action === "event-create") { if (provider !== "discord") { throw new Error( `Event create is only supported for Discord (provider=${provider}).`, ); } const result = await handleDiscordAction( { action: "eventCreate", guildId: requireString(opts.guildId, "guild-id"), name: requireString(opts.eventName, "event-name"), startTime: requireString(opts.startTime, "start-time"), endTime: optionalString(opts.endTime), description: optionalString(opts.desc), channelId: optionalString(opts.channelId), location: optionalString(opts.location), entityType: optionalString(opts.eventType), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } if (action === "timeout" || action === "kick" || action === "ban") { if (provider !== "discord") { throw new Error( `Moderation actions are only supported for Discord (provider=${provider}).`, ); } const result = await handleDiscordAction( { action: action as "timeout" | "kick" | "ban", guildId: requireString(opts.guildId, "guild-id"), userId: requireString(opts.userId, "user-id"), durationMinutes: parseIntOption(opts.durationMin, "duration-min"), until: optionalString(opts.until), reason: optionalString(opts.reason), deleteMessageDays: parseIntOption(opts.deleteDays, "delete-days"), }, cfg, ); runtime.log(JSON.stringify(extractToolPayload(result), null, 2)); return; } throw new Error(`Unknown action: ${opts.action ?? "unknown"}`); }