diff --git a/src/commands/send.ts b/src/commands/send.ts index dc1039490..0e74698a6 100644 --- a/src/commands/send.ts +++ b/src/commands/send.ts @@ -2,8 +2,11 @@ import type { CliDeps } from "../cli/deps.js"; import { loadConfig } from "../config/config.js"; import { callGateway, randomIdempotencyKey } from "../gateway/call.js"; import { success } from "../globals.js"; -import type { OutboundDeliveryResult } from "../infra/outbound/deliver.js"; import { deliverOutboundPayloads } from "../infra/outbound/deliver.js"; +import { + buildOutboundDeliveryJson, + formatOutboundDeliverySummary, +} from "../infra/outbound/format.js"; import { resolveOutboundTarget } from "../infra/outbound/targets.js"; import type { RuntimeEnv } from "../runtime.js"; @@ -60,25 +63,18 @@ export async function sendCommand( }, }); const last = results.at(-1); - const summary = formatDirectSendSummary(provider, last); + const summary = formatOutboundDeliverySummary(provider, last); runtime.log(success(summary)); if (opts.json) { runtime.log( JSON.stringify( - { + buildOutboundDeliveryJson({ provider, via: "direct", to: opts.to, - messageId: last?.messageId ?? "unknown", - ...(last && "chatId" in last ? { chatId: last.chatId } : {}), - ...(last && "channelId" in last - ? { channelId: last.channelId } - : {}), - ...(last && "timestamp" in last - ? { timestamp: last.timestamp } - : {}), - mediaUrl: opts.media ?? null, - }, + result: last, + mediaUrl: opts.media, + }), null, 2, ), @@ -130,28 +126,3 @@ export async function sendCommand( ); } } - -function formatDirectSendSummary( - provider: string, - result: OutboundDeliveryResult | undefined, -): string { - if (!result) { - return `✅ Sent via ${provider}. Message ID: unknown`; - } - if (result.provider === "telegram") { - return `✅ Sent via telegram. Message ID: ${result.messageId} (chat ${result.chatId})`; - } - if (result.provider === "discord") { - return `✅ Sent via discord. Message ID: ${result.messageId} (channel ${result.channelId})`; - } - if (result.provider === "slack") { - return `✅ Sent via slack. Message ID: ${result.messageId} (channel ${result.channelId})`; - } - if (result.provider === "signal") { - return `✅ Sent via signal. Message ID: ${result.messageId}`; - } - if (result.provider === "imessage") { - return `✅ Sent via iMessage. Message ID: ${result.messageId}`; - } - return `✅ Sent via ${provider}. Message ID: ${result.messageId}`; -} diff --git a/src/infra/outbound/format.test.ts b/src/infra/outbound/format.test.ts new file mode 100644 index 000000000..92e2beeb0 --- /dev/null +++ b/src/infra/outbound/format.test.ts @@ -0,0 +1,89 @@ +import { describe, expect, it } from "vitest"; + +import { + buildOutboundDeliveryJson, + formatOutboundDeliverySummary, +} from "./format.js"; + +describe("formatOutboundDeliverySummary", () => { + it("falls back when result is missing", () => { + expect(formatOutboundDeliverySummary("telegram")).toBe( + "✅ Sent via telegram. Message ID: unknown", + ); + expect(formatOutboundDeliverySummary("imessage")).toBe( + "✅ Sent via iMessage. Message ID: unknown", + ); + }); + + it("adds chat or channel details", () => { + expect( + formatOutboundDeliverySummary("telegram", { + provider: "telegram", + messageId: "m1", + chatId: "c1", + }), + ).toBe("✅ Sent via telegram. Message ID: m1 (chat c1)"); + + expect( + formatOutboundDeliverySummary("discord", { + provider: "discord", + messageId: "d1", + channelId: "chan", + }), + ).toBe("✅ Sent via discord. Message ID: d1 (channel chan)"); + }); +}); + +describe("buildOutboundDeliveryJson", () => { + it("builds direct delivery payloads", () => { + expect( + buildOutboundDeliveryJson({ + provider: "telegram", + to: "123", + result: { provider: "telegram", messageId: "m1", chatId: "c1" }, + mediaUrl: "https://example.com/a.png", + }), + ).toEqual({ + provider: "telegram", + via: "direct", + to: "123", + messageId: "m1", + mediaUrl: "https://example.com/a.png", + chatId: "c1", + }); + }); + + it("supports whatsapp metadata when present", () => { + expect( + buildOutboundDeliveryJson({ + provider: "whatsapp", + to: "+1", + result: { provider: "whatsapp", messageId: "w1", toJid: "jid" }, + }), + ).toEqual({ + provider: "whatsapp", + via: "direct", + to: "+1", + messageId: "w1", + mediaUrl: null, + toJid: "jid", + }); + }); + + it("keeps timestamp for signal", () => { + expect( + buildOutboundDeliveryJson({ + provider: "signal", + to: "+1", + result: { provider: "signal", messageId: "s1", timestamp: 123 }, + }), + ).toEqual({ + provider: "signal", + via: "direct", + to: "+1", + messageId: "s1", + mediaUrl: null, + timestamp: 123, + }); + }); +}); diff --git a/src/infra/outbound/format.ts b/src/infra/outbound/format.ts new file mode 100644 index 000000000..b8104ce95 --- /dev/null +++ b/src/infra/outbound/format.ts @@ -0,0 +1,59 @@ +import type { OutboundDeliveryResult } from "./deliver.js"; + +export type OutboundDeliveryJson = { + provider: string; + via: "direct" | "gateway"; + to: string; + messageId: string; + mediaUrl: string | null; + chatId?: string; + channelId?: string; + timestamp?: number; + toJid?: string; +}; + +const resolveProviderLabel = (provider: string) => + provider === "imessage" ? "iMessage" : provider; + +export function formatOutboundDeliverySummary( + provider: string, + result?: OutboundDeliveryResult, +): string { + if (!result) { + return `✅ Sent via ${resolveProviderLabel(provider)}. Message ID: unknown`; + } + + const label = resolveProviderLabel(result.provider); + const base = `✅ Sent via ${label}. Message ID: ${result.messageId}`; + + if ("chatId" in result) return `${base} (chat ${result.chatId})`; + if ("channelId" in result) return `${base} (channel ${result.channelId})`; + return base; +} + +export function buildOutboundDeliveryJson(params: { + provider: string; + to: string; + result?: OutboundDeliveryResult; + via?: "direct" | "gateway"; + mediaUrl?: string | null; +}): OutboundDeliveryJson { + const { provider, to, result } = params; + const messageId = result?.messageId ?? "unknown"; + const payload: OutboundDeliveryJson = { + provider, + via: params.via ?? "direct", + to, + messageId, + 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; + + return payload; +}