diff --git a/src/commands/poll.test.ts b/src/commands/poll.test.ts index 50b3ba5f6..89b8b5e07 100644 --- a/src/commands/poll.test.ts +++ b/src/commands/poll.test.ts @@ -36,6 +36,9 @@ const deps: CliDeps = { describe("pollCommand", () => { beforeEach(() => { callGatewayMock.mockReset(); + runtime.log.mockReset(); + runtime.error.mockReset(); + runtime.exit.mockReset(); testConfig = {}; }); @@ -74,4 +77,34 @@ describe("pollCommand", () => { | undefined; expect(args?.url).toBeUndefined(); }); + + it("emits json output with gateway metadata", async () => { + callGatewayMock.mockResolvedValueOnce({ messageId: "p1", channelId: "C1" }); + await pollCommand( + { + to: "channel:C1", + question: "hi?", + option: ["y", "n"], + provider: "discord", + json: true, + }, + deps, + runtime, + ); + const lastLog = runtime.log.mock.calls.at(-1)?.[0] as string | undefined; + expect(lastLog).toBeDefined(); + const payload = JSON.parse(lastLog ?? "{}") as Record; + expect(payload).toMatchObject({ + provider: "discord", + via: "gateway", + to: "channel:C1", + messageId: "p1", + channelId: "C1", + mediaUrl: null, + question: "hi?", + options: ["y", "n"], + maxSelections: 1, + durationHours: null, + }); + }); }); diff --git a/src/commands/poll.ts b/src/commands/poll.ts index 44f546c25..5927e4052 100644 --- a/src/commands/poll.ts +++ b/src/commands/poll.ts @@ -1,6 +1,10 @@ import type { CliDeps } from "../cli/deps.js"; import { callGateway, randomIdempotencyKey } from "../gateway/call.js"; import { success } from "../globals.js"; +import { + buildOutboundDeliveryJson, + formatGatewaySummary, +} from "../infra/outbound/format.js"; import { normalizePollInput, type PollInput } from "../polls.js"; import type { RuntimeEnv } from "../runtime.js"; @@ -74,19 +78,24 @@ export async function pollCommand( runtime.log( success( - `✅ Poll sent via gateway (${provider}). Message ID: ${result.messageId ?? "unknown"}`, + formatGatewaySummary({ + action: "Poll sent", + provider, + messageId: result.messageId ?? null, + }), ), ); if (opts.json) { runtime.log( JSON.stringify( { - provider, - via: "gateway", - to: opts.to, - toJid: result.toJid ?? null, - channelId: result.channelId ?? null, - messageId: result.messageId, + ...buildOutboundDeliveryJson({ + provider, + via: "gateway", + to: opts.to, + result, + mediaUrl: null, + }), question: normalized.question, options: normalized.options, maxSelections: normalized.maxSelections, diff --git a/src/commands/send.ts b/src/commands/send.ts index 0e74698a6..b7543df0a 100644 --- a/src/commands/send.ts +++ b/src/commands/send.ts @@ -5,6 +5,7 @@ import { success } from "../globals.js"; import { deliverOutboundPayloads } from "../infra/outbound/deliver.js"; import { buildOutboundDeliveryJson, + formatGatewaySummary, formatOutboundDeliverySummary, } from "../infra/outbound/format.js"; import { resolveOutboundTarget } from "../infra/outbound/targets.js"; @@ -107,7 +108,7 @@ export async function sendCommand( runtime.log( success( - `✅ Sent via gateway. Message ID: ${result.messageId ?? "unknown"}`, + formatGatewaySummary({ provider, messageId: result.messageId ?? null }), ), ); if (opts.json) { diff --git a/src/infra/outbound/format.test.ts b/src/infra/outbound/format.test.ts index 92e2beeb0..145d8b5eb 100644 --- a/src/infra/outbound/format.test.ts +++ b/src/infra/outbound/format.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest"; import { buildOutboundDeliveryJson, + formatGatewaySummary, formatOutboundDeliverySummary, } from "./format.js"; @@ -87,3 +88,21 @@ describe("buildOutboundDeliveryJson", () => { }); }); }); + +describe("formatGatewaySummary", () => { + it("formats gateway summaries with provider", () => { + expect( + formatGatewaySummary({ provider: "whatsapp", messageId: "m1" }), + ).toBe("✅ Sent via gateway (whatsapp). Message ID: m1"); + }); + + it("supports custom actions", () => { + expect( + formatGatewaySummary({ + action: "Poll sent", + provider: "discord", + messageId: "p1", + }), + ).toBe("✅ Poll sent via gateway (discord). Message ID: p1"); + }); +}); diff --git a/src/infra/outbound/format.ts b/src/infra/outbound/format.ts index b8104ce95..7d7817dcb 100644 --- a/src/infra/outbound/format.ts +++ b/src/infra/outbound/format.ts @@ -12,6 +12,14 @@ export type OutboundDeliveryJson = { toJid?: string; }; +type OutboundDeliveryMeta = { + messageId?: string; + chatId?: string; + channelId?: string; + timestamp?: number; + toJid?: string; +}; + const resolveProviderLabel = (provider: string) => provider === "imessage" ? "iMessage" : provider; @@ -34,7 +42,7 @@ export function formatOutboundDeliverySummary( export function buildOutboundDeliveryJson(params: { provider: string; to: string; - result?: OutboundDeliveryResult; + result?: OutboundDeliveryMeta | OutboundDeliveryResult; via?: "direct" | "gateway"; mediaUrl?: string | null; }): OutboundDeliveryJson { @@ -48,12 +56,21 @@ export function buildOutboundDeliveryJson(params: { mediaUrl: params.mediaUrl ?? null, }; - if (result && "chatId" in result) payload.chatId = result.chatId; - if (result && "channelId" in result) payload.channelId = result.channelId; - if (result && "timestamp" in result && result.timestamp !== undefined) { - payload.timestamp = result.timestamp; - } - if (result && "toJid" in result) payload.toJid = result.toJid; + if (result?.chatId !== undefined) payload.chatId = result.chatId; + if (result?.channelId !== undefined) payload.channelId = result.channelId; + if (result?.timestamp !== undefined) payload.timestamp = result.timestamp; + if (result?.toJid !== undefined) payload.toJid = result.toJid; return payload; } + +export function formatGatewaySummary(params: { + action?: string; + provider?: string; + messageId?: string | null; +}): string { + const action = params.action ?? "Sent"; + const providerSuffix = params.provider ? ` (${params.provider})` : ""; + const messageId = params.messageId ?? "unknown"; + return `✅ ${action} via gateway${providerSuffix}. Message ID: ${messageId}`; +}