import * as z from "zod" import { defineAction } from "../../../automation/actions" import { TRELLO_READ_SCOPE, TRELLO_WRITE_SCOPE } from "./scopes" import { getTrelloApi, normalizeTrelloCardId, } from "@automate.ax/integration-contracts/trello" import { TRELLO_ATTACHMENT_SCHEMA, TRELLO_COMMENT_SCHEMA, TRELLO_PROVIDER_ATTACHMENT_SCHEMA, TRELLO_PROVIDER_COMMENT_SCHEMA, toTrelloAttachment, toTrelloComment, } from "@automate.ax/integration-contracts/trello" const TRELLO_ACCOUNT = "trello" /** Adds a comment to a Trello card. */ export const addTrelloComment = defineAction("Add Trello comment") .describe("Adds a comment to a Trello card.") .account(TRELLO_ACCOUNT, TRELLO_WRITE_SCOPE) .input( z.object({ /** Card ID, short link, or standard Trello card URL. */ card: z.string().trim().min(1), /** Comment text. */ text: z.string().trim().min(1), }), ) .output(TRELLO_COMMENT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => toTrelloComment( await getTrelloApi(account.secret).request( `cards/${encodeURIComponent(normalizeTrelloCardId(input.card))}/actions/comments`, { method: "POST", query: { text: input.text }, responseSchema: TRELLO_PROVIDER_COMMENT_SCHEMA, }, ), ), ) /** Lists comments on a Trello card, newest first. */ export const listTrelloComments = defineAction("List Trello comments") .describe("Lists recent comments on a Trello card, newest first.") .account(TRELLO_ACCOUNT, TRELLO_READ_SCOPE) .input( z.object({ /** Return comments before this action ID. */ before: z.string().trim().min(1).optional(), /** Card ID, short link, or standard Trello card URL. */ card: z.string().trim().min(1), /** Maximum comments to return. */ limit: z.number().int().min(1).max(1000).prefault(100), /** Return comments since this action ID. */ since: z.string().trim().min(1).optional(), }), ) .output(TRELLO_COMMENT_SCHEMA.array()) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => ( await getTrelloApi(account.secret).request( `cards/${encodeURIComponent(normalizeTrelloCardId(input.card))}/actions`, { query: { before: input.before, filter: "commentCard", limit: input.limit, memberCreator: true, memberCreator_fields: "id,fullName,username,initials,avatarUrl,url", since: input.since, }, responseSchema: TRELLO_PROVIDER_COMMENT_SCHEMA.array(), }, ) ).map(toTrelloComment), ) /** Updates an existing Trello card comment. */ export const updateTrelloComment = defineAction("Update Trello comment") .describe("Updates the text of an existing Trello card comment.") .account(TRELLO_ACCOUNT, TRELLO_WRITE_SCOPE) .input( z.object({ /** Card ID, short link, or standard Trello card URL. */ card: z.string().trim().min(1), /** Stable Trello action ID for the comment. */ commentId: z.string().trim().min(1), /** Replacement comment text. */ text: z.string().trim().min(1), }), ) .output(TRELLO_COMMENT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => toTrelloComment( await getTrelloApi(account.secret).request( `cards/${encodeURIComponent(normalizeTrelloCardId(input.card))}/actions/${encodeURIComponent(input.commentId)}/comments`, { method: "PUT", query: { text: input.text }, responseSchema: TRELLO_PROVIDER_COMMENT_SCHEMA, }, ), ), ) /** Deletes a comment from a Trello card. */ export const deleteTrelloComment = defineAction("Delete Trello comment") .describe("Deletes a comment from a Trello card.") .account(TRELLO_ACCOUNT, TRELLO_WRITE_SCOPE) .input( z.object({ card: z.string().trim().min(1), commentId: z.string().trim().min(1), }), ) .output(z.object({ commentId: z.string(), deleted: z.literal(true) })) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await getTrelloApi(account.secret).request( `cards/${encodeURIComponent(normalizeTrelloCardId(input.card))}/actions/${encodeURIComponent(input.commentId)}/comments`, { method: "DELETE", responseSchema: z.unknown() }, ) return { commentId: input.commentId, deleted: true as const } }) /** Adds a URL attachment to a Trello card. */ export const addTrelloAttachment = defineAction("Add Trello attachment") .describe("Attaches a URL to a Trello card and optionally uses it as cover.") .account(TRELLO_ACCOUNT, TRELLO_WRITE_SCOPE) .input( z.object({ /** Card ID, short link, or standard Trello card URL. */ card: z.string().trim().min(1), /** Optional attachment display name. */ name: z.string().trim().min(1).optional(), /** Whether to use this attachment as the card cover. */ setCover: z.boolean().prefault(false), /** Public URL to attach. */ url: z.url(), }), ) .output(TRELLO_ATTACHMENT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => toTrelloAttachment( await getTrelloApi(account.secret).request( `cards/${encodeURIComponent(normalizeTrelloCardId(input.card))}/attachments`, { method: "POST", query: { name: input.name, setCover: input.setCover, url: input.url, }, responseSchema: TRELLO_PROVIDER_ATTACHMENT_SCHEMA, }, ), ), ) /** Uploads a binary attachment to a Trello card. */ export const uploadTrelloAttachment = defineAction("Upload Trello attachment") .describe("Uploads a file to a Trello card and optionally uses it as cover.") .account(TRELLO_ACCOUNT, TRELLO_WRITE_SCOPE) .input( z.object({ /** Card ID, short link, or standard Trello card URL. */ card: z.string().trim().min(1), /** File data, media type, and default filename. */ file: z.instanceof(File), /** Attachment display name. */ name: z.string().trim().min(1).max(256).optional(), /** Whether to use this attachment as the card cover. */ setCover: z.boolean().prefault(false), }), ) .output(TRELLO_ATTACHMENT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const body = new FormData() body.set("file", input.file, input.file.name) if (input.name) body.set("name", input.name) body.set("setCover", String(input.setCover)) const result = await getTrelloApi(account.secret).request( `cards/${encodeURIComponent(normalizeTrelloCardId(input.card))}/attachments`, { body, method: "POST", responseSchema: z.union([ TRELLO_PROVIDER_ATTACHMENT_SCHEMA, TRELLO_PROVIDER_ATTACHMENT_SCHEMA.array().min(1), ]), }, ) const attachment = Array.isArray(result) ? result[0] : result if (!attachment) throw new Error("Trello uploaded the file without an attachment.") return toTrelloAttachment(attachment) }) /** Lists attachments on a Trello card. */ export const listTrelloAttachments = defineAction("List Trello attachments") .describe("Lists URL and uploaded-file attachments on a Trello card.") .account(TRELLO_ACCOUNT, TRELLO_READ_SCOPE) .input(z.object({ card: z.string().trim().min(1) })) .output(TRELLO_ATTACHMENT_SCHEMA.array()) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => ( await getTrelloApi(account.secret).request( `cards/${encodeURIComponent(normalizeTrelloCardId(input.card))}/attachments`, { query: { fields: "id,name,url,bytes,date,mimeType,isUpload,pos" }, responseSchema: TRELLO_PROVIDER_ATTACHMENT_SCHEMA.array(), }, ) ).map(toTrelloAttachment), ) /** Deletes an attachment from a Trello card. */ export const deleteTrelloAttachment = defineAction("Delete Trello attachment") .describe("Deletes an attachment from a Trello card.") .account(TRELLO_ACCOUNT, TRELLO_WRITE_SCOPE) .input( z.object({ attachmentId: z.string().trim().min(1), card: z.string().trim().min(1), }), ) .output(z.object({ attachmentId: z.string(), deleted: z.literal(true) })) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await getTrelloApi(account.secret).request( `cards/${encodeURIComponent(normalizeTrelloCardId(input.card))}/attachments/${encodeURIComponent(input.attachmentId)}`, { method: "DELETE", responseSchema: z.unknown() }, ) return { attachmentId: input.attachmentId, deleted: true as const } })