@zapier/trello-connector 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/LICENSE +93 -0
  2. package/NOTICE +8 -0
  3. package/README.md +135 -2
  4. package/SKILL.md +139 -0
  5. package/cli.js +71 -0
  6. package/cli.ts +5 -0
  7. package/connections.ts +26 -0
  8. package/dist/cli.js +4 -0
  9. package/dist/index.js +2654 -0
  10. package/index.ts +145 -0
  11. package/package.json +59 -4
  12. package/preflight.sh +157 -0
  13. package/references/trello-api-gotchas.md +81 -0
  14. package/scripts/.gitkeep +0 -0
  15. package/scripts/addCardAttachment.ts +107 -0
  16. package/scripts/addCardLabel.ts +93 -0
  17. package/scripts/addCardMember.ts +93 -0
  18. package/scripts/addChecklistItem.ts +98 -0
  19. package/scripts/addMemberToBoard.ts +57 -0
  20. package/scripts/archiveCard.ts +67 -0
  21. package/scripts/closeBoard.ts +62 -0
  22. package/scripts/completeChecklistItem.ts +56 -0
  23. package/scripts/copyBoard.ts +81 -0
  24. package/scripts/createBoard.ts +72 -0
  25. package/scripts/createCard.ts +266 -0
  26. package/scripts/createChecklist.ts +50 -0
  27. package/scripts/createComment.ts +71 -0
  28. package/scripts/createLabel.ts +72 -0
  29. package/scripts/createList.ts +100 -0
  30. package/scripts/deleteChecklist.ts +38 -0
  31. package/scripts/findBoard.ts +62 -0
  32. package/scripts/findChecklist.ts +51 -0
  33. package/scripts/findChecklistItem.ts +49 -0
  34. package/scripts/findLabel.ts +54 -0
  35. package/scripts/findList.ts +55 -0
  36. package/scripts/findOrganizationMember.ts +59 -0
  37. package/scripts/getAction.ts +58 -0
  38. package/scripts/getBoard.ts +52 -0
  39. package/scripts/getCard.ts +79 -0
  40. package/scripts/getChecklist.ts +45 -0
  41. package/scripts/getChecklistItem.ts +48 -0
  42. package/scripts/getCurrentMember.ts +52 -0
  43. package/scripts/getLabel.ts +46 -0
  44. package/scripts/getList.ts +47 -0
  45. package/scripts/getMember.ts +52 -0
  46. package/scripts/getOrganization.ts +46 -0
  47. package/scripts/listBoardMembers.ts +58 -0
  48. package/scripts/listBoards.ts +78 -0
  49. package/scripts/listCardAttachments.ts +55 -0
  50. package/scripts/listCards.ts +129 -0
  51. package/scripts/listCustomFields.ts +62 -0
  52. package/scripts/listLabels.ts +52 -0
  53. package/scripts/listLists.ts +61 -0
  54. package/scripts/listOrganizations.ts +50 -0
  55. package/scripts/moveCard.ts +68 -0
  56. package/scripts/removeCardLabel.ts +43 -0
  57. package/scripts/searchCards.ts +153 -0
  58. package/scripts/updateCard.ts +184 -0
  59. package/tsup.config.ts +63 -0
package/index.ts ADDED
@@ -0,0 +1,145 @@
1
+ import { defineConnector, toFunctions } from "@zapier/connectors-sdk";
2
+
3
+ import { connectionResolvers } from "./connections.ts";
4
+ import addCardAttachmentDefinition from "./scripts/addCardAttachment.ts";
5
+ import addCardLabelDefinition from "./scripts/addCardLabel.ts";
6
+ import addCardMemberDefinition from "./scripts/addCardMember.ts";
7
+ import addChecklistItemDefinition from "./scripts/addChecklistItem.ts";
8
+ import addMemberToBoardDefinition from "./scripts/addMemberToBoard.ts";
9
+ import archiveCardDefinition from "./scripts/archiveCard.ts";
10
+ import closeBoardDefinition from "./scripts/closeBoard.ts";
11
+ import completeChecklistItemDefinition from "./scripts/completeChecklistItem.ts";
12
+ import copyBoardDefinition from "./scripts/copyBoard.ts";
13
+ import createBoardDefinition from "./scripts/createBoard.ts";
14
+ import createCardDefinition from "./scripts/createCard.ts";
15
+ import createChecklistDefinition from "./scripts/createChecklist.ts";
16
+ import createCommentDefinition from "./scripts/createComment.ts";
17
+ import createLabelDefinition from "./scripts/createLabel.ts";
18
+ import createListDefinition from "./scripts/createList.ts";
19
+ import deleteChecklistDefinition from "./scripts/deleteChecklist.ts";
20
+ import findBoardDefinition from "./scripts/findBoard.ts";
21
+ import findChecklistDefinition from "./scripts/findChecklist.ts";
22
+ import findChecklistItemDefinition from "./scripts/findChecklistItem.ts";
23
+ import findLabelDefinition from "./scripts/findLabel.ts";
24
+ import findListDefinition from "./scripts/findList.ts";
25
+ import findOrganizationMemberDefinition from "./scripts/findOrganizationMember.ts";
26
+ import getActionDefinition from "./scripts/getAction.ts";
27
+ import getBoardDefinition from "./scripts/getBoard.ts";
28
+ import getCardDefinition from "./scripts/getCard.ts";
29
+ import getChecklistDefinition from "./scripts/getChecklist.ts";
30
+ import getChecklistItemDefinition from "./scripts/getChecklistItem.ts";
31
+ import getCurrentMemberDefinition from "./scripts/getCurrentMember.ts";
32
+ import getLabelDefinition from "./scripts/getLabel.ts";
33
+ import getListDefinition from "./scripts/getList.ts";
34
+ import getMemberDefinition from "./scripts/getMember.ts";
35
+ import getOrganizationDefinition from "./scripts/getOrganization.ts";
36
+ import listBoardMembersDefinition from "./scripts/listBoardMembers.ts";
37
+ import listBoardsDefinition from "./scripts/listBoards.ts";
38
+ import listCardAttachmentsDefinition from "./scripts/listCardAttachments.ts";
39
+ import listCardsDefinition from "./scripts/listCards.ts";
40
+ import listCustomFieldsDefinition from "./scripts/listCustomFields.ts";
41
+ import listLabelsDefinition from "./scripts/listLabels.ts";
42
+ import listListsDefinition from "./scripts/listLists.ts";
43
+ import listOrganizationsDefinition from "./scripts/listOrganizations.ts";
44
+ import moveCardDefinition from "./scripts/moveCard.ts";
45
+ import removeCardLabelDefinition from "./scripts/removeCardLabel.ts";
46
+ import searchCardsDefinition from "./scripts/searchCards.ts";
47
+ import updateCardDefinition from "./scripts/updateCard.ts";
48
+
49
+ const connector = defineConnector({
50
+ scripts: {
51
+ addCardAttachment: addCardAttachmentDefinition,
52
+ addCardLabel: addCardLabelDefinition,
53
+ addCardMember: addCardMemberDefinition,
54
+ addChecklistItem: addChecklistItemDefinition,
55
+ addMemberToBoard: addMemberToBoardDefinition,
56
+ archiveCard: archiveCardDefinition,
57
+ closeBoard: closeBoardDefinition,
58
+ completeChecklistItem: completeChecklistItemDefinition,
59
+ copyBoard: copyBoardDefinition,
60
+ createBoard: createBoardDefinition,
61
+ createCard: createCardDefinition,
62
+ createChecklist: createChecklistDefinition,
63
+ createComment: createCommentDefinition,
64
+ createLabel: createLabelDefinition,
65
+ createList: createListDefinition,
66
+ deleteChecklist: deleteChecklistDefinition,
67
+ findBoard: findBoardDefinition,
68
+ findChecklist: findChecklistDefinition,
69
+ findChecklistItem: findChecklistItemDefinition,
70
+ findLabel: findLabelDefinition,
71
+ findList: findListDefinition,
72
+ findOrganizationMember: findOrganizationMemberDefinition,
73
+ getAction: getActionDefinition,
74
+ getBoard: getBoardDefinition,
75
+ getCard: getCardDefinition,
76
+ getChecklist: getChecklistDefinition,
77
+ getChecklistItem: getChecklistItemDefinition,
78
+ getCurrentMember: getCurrentMemberDefinition,
79
+ getLabel: getLabelDefinition,
80
+ getList: getListDefinition,
81
+ getMember: getMemberDefinition,
82
+ getOrganization: getOrganizationDefinition,
83
+ listBoardMembers: listBoardMembersDefinition,
84
+ listBoards: listBoardsDefinition,
85
+ listCardAttachments: listCardAttachmentsDefinition,
86
+ listCards: listCardsDefinition,
87
+ listCustomFields: listCustomFieldsDefinition,
88
+ listLabels: listLabelsDefinition,
89
+ listLists: listListsDefinition,
90
+ listOrganizations: listOrganizationsDefinition,
91
+ moveCard: moveCardDefinition,
92
+ removeCardLabel: removeCardLabelDefinition,
93
+ searchCards: searchCardsDefinition,
94
+ updateCard: updateCardDefinition,
95
+ },
96
+ connectionResolvers,
97
+ });
98
+
99
+ export default connector;
100
+ export const {
101
+ addCardAttachment,
102
+ addCardLabel,
103
+ addCardMember,
104
+ addChecklistItem,
105
+ addMemberToBoard,
106
+ archiveCard,
107
+ closeBoard,
108
+ completeChecklistItem,
109
+ copyBoard,
110
+ createBoard,
111
+ createCard,
112
+ createChecklist,
113
+ createComment,
114
+ createLabel,
115
+ createList,
116
+ deleteChecklist,
117
+ findBoard,
118
+ findChecklist,
119
+ findChecklistItem,
120
+ findLabel,
121
+ findList,
122
+ findOrganizationMember,
123
+ getAction,
124
+ getBoard,
125
+ getCard,
126
+ getChecklist,
127
+ getChecklistItem,
128
+ getCurrentMember,
129
+ getLabel,
130
+ getList,
131
+ getMember,
132
+ getOrganization,
133
+ listBoardMembers,
134
+ listBoards,
135
+ listCardAttachments,
136
+ listCards,
137
+ listCustomFields,
138
+ listLabels,
139
+ listLists,
140
+ listOrganizations,
141
+ moveCard,
142
+ removeCardLabel,
143
+ searchCards,
144
+ updateCard,
145
+ } = toFunctions(connector);
package/package.json CHANGED
@@ -1,7 +1,62 @@
1
1
  {
2
2
  "name": "@zapier/trello-connector",
3
- "version": "0.0.0",
4
- "description": "Placeholder published to enable OIDC Trusted Publisher setup. The real package is published via GitLab CI.",
3
+ "description": "Agent-callable Trello tools — create and update cards, manage boards, lists, labels, checklists, and search. Use when the user mentions Trello or wants to create cards, move tasks, or manage boards, even if they do not name Trello explicitly.",
5
4
  "license": "Elastic-2.0",
6
- "private": false
7
- }
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "import": "./dist/index.js",
9
+ "types": "./index.ts"
10
+ }
11
+ },
12
+ "bin": {
13
+ "@zapier/trello-connector": "./cli.js"
14
+ },
15
+ "files": [
16
+ "dist/",
17
+ "cli.js",
18
+ "*.ts",
19
+ "scripts/",
20
+ "preflight.sh",
21
+ "SKILL.md",
22
+ "README.md",
23
+ "LICENSE",
24
+ "references/",
25
+ "NOTICE"
26
+ ],
27
+ "dependencies": {
28
+ "@zapier/connectors-sdk": "^0.2.0",
29
+ "zod": "^4.0.0",
30
+ "@modelcontextprotocol/sdk": "^1.0.0"
31
+ },
32
+ "peerDependencies": {
33
+ "@zapier/zapier-sdk": ">=0.59.0 <1.0.0"
34
+ },
35
+ "keywords": [
36
+ "trello",
37
+ "zapier",
38
+ "connector",
39
+ "tools",
40
+ "skills",
41
+ "mcp",
42
+ "agent",
43
+ "ai",
44
+ "automation"
45
+ ],
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "https://github.com/zapier/connectors.git",
49
+ "directory": "apps/trello"
50
+ },
51
+ "devDependencies": {
52
+ "tsup": "^8.0.0",
53
+ "typescript": "^5.0.0"
54
+ },
55
+ "version": "0.1.0",
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
59
+ "scripts": {
60
+ "build": "tsup"
61
+ }
62
+ }
package/preflight.sh ADDED
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env sh
2
+ # Connector pre-flight readiness check.
3
+ #
4
+ # Managed by @zapier/connectors-dev — do not edit; synced byte-for-byte
5
+ # across every connector.
6
+ #
7
+ # Runs inside whatever agent harness installed the connector (Cursor, Claude
8
+ # Code, Codex, Gemini CLI, Goose, ...) — often a minimal container — and answers
9
+ # ONE question: how do I run the TypeScript scripts here? It picks a runtime —
10
+ # Node 22.18+ when it can already resolve the connector's deps, else an explicit
11
+ # install step (`npm install`, or `bun install` when only Bun is present) — and
12
+ # tells the agent the exact command to run (see EXIT CODES). When deps are
13
+ # missing it disambiguates the two sandbox failures that block an install: a
14
+ # read-only connector dir (must run unsandboxed / be granted write) vs. a
15
+ # blocked home dir (point the package cache inside this dir). Both surface as a
16
+ # misleading `EPERM`, so the recommendation names the actual fix.
17
+ #
18
+ # NEEDS_ACTION is a single self-verifying step (install a runtime / deps), not a
19
+ # loop: do it, then run a script. The action confirms its own success and the
20
+ # first `--help` run is the authoritative check, so re-running this pre-flight to
21
+ # reconfirm is optional, not required.
22
+ #
23
+ # THE AGENT CONTRACT IS THE STDOUT, NOT THIS HEADER. Agents don't read this file;
24
+ # they run it and parse the `PREFLIGHT_*` lines — each value starts with a stable
25
+ # token (parse as `KEY: (\w+)`), with an optional human gloss in parens, and
26
+ # `PREFLIGHT_RECOMMENDATION` is the one-line next step. SKILL.md "Step 0" is the
27
+ # agent-facing spec; this header is for maintainers of the canonical script.
28
+ #
29
+ # WHY POSIX sh (not bash)
30
+ # Minimal sandboxes often ship only BusyBox `sh` with no bash. This script runs
31
+ # unchanged under BusyBox sh, dash, and bash, and never hard-requires
32
+ # node/bun/npm — a missing runtime degrades to a NEEDS_ACTION instruction.
33
+ #
34
+ # EXIT CODES (the verdict; also emitted on PREFLIGHT_STATUS)
35
+ # 0 READY a runtime + deps are in place; run the scripts
36
+ # 1 NEEDS_ACTION perform the printed action (install runtime / deps), then
37
+ # run a script — re-running this check is optional
38
+
39
+ set -u
40
+
41
+ EXIT_READY=0
42
+ EXIT_NEEDS_ACTION=1
43
+
44
+ # Directory this script lives in — deps + scripts are resolved relative to it,
45
+ # not the caller's cwd, so `./preflight.sh` works from anywhere.
46
+ SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
47
+
48
+ has() {
49
+ command -v "$1" >/dev/null 2>&1
50
+ }
51
+
52
+ # Node >= 22.18 is the connector baseline (native .ts stripping). Anything older
53
+ # is treated as "no Node" so we fall back to Bun.
54
+ node_ge_2218() {
55
+ has node || return 1
56
+ v=$(node -v 2>/dev/null) || return 1
57
+ v=${v#v}
58
+ major=${v%%.*}
59
+ rest=${v#*.}
60
+ minor=${rest%%.*}
61
+ case "$major" in '' | *[!0-9]*) return 1 ;; esac
62
+ case "$minor" in '' | *[!0-9]*) minor=0 ;; esac
63
+ [ "$major" -gt 22 ] && return 0
64
+ [ "$major" -eq 22 ] && [ "$minor" -ge 18 ] && return 0
65
+ return 1
66
+ }
67
+
68
+ # Are the connector's declared deps installed where Node would find them? Node
69
+ # won't fetch — they must be on disk (this dir's node_modules or an ancestor's).
70
+ # Reads the connector's own package.json, so this stays connector-agnostic. A
71
+ # bare `[ -d node_modules ]` is the wrong test: under pnpm/monorepo layouts the
72
+ # deps can live in an ancestor (or be hoisted), and a local node_modules can
73
+ # exist without the package being present. We check each dep's package.json
74
+ # exists in one of Node's resolution paths rather than `require.resolve(name)`,
75
+ # because resolving the package ENTRY can fail for ESM-only / `exports`-map
76
+ # packages even when they're fully installed and importable.
77
+ node_resolves() {
78
+ ( CDPATH= cd -- "$SCRIPT_DIR" && node -e 'const fs=require("fs"),path=require("path");const d=require("./package.json").dependencies||{};for(const k of Object.keys(d)){const ps=require.resolve.paths(k)||[];if(!ps.some(b=>fs.existsSync(path.join(b,k,"package.json"))))process.exit(1);}' ) >/dev/null 2>&1
79
+ }
80
+
81
+ # Can we actually WRITE into this directory right now? Any dep install must land
82
+ # `node_modules/` here, so if the harness mounts the connector read-only (common
83
+ # when skills live under ~/.<agent>/skills, outside the agent's writable
84
+ # workspace) no install can succeed in place — the only fixes are to run it
85
+ # unsandboxed or grant write access. Two deliberate choices:
86
+ # - Probe with a real create+remove, not `[ -w ]`: a sandbox denies the write
87
+ # at the syscall while the permission bits still look writable.
88
+ # - Probe by creating a DIRECTORY (`mkdir`), not a file: that's the install's
89
+ # very first on-disk action (node_modules/ and the cache dirs), and at least
90
+ # one sandbox (Claude Code) permits creating a file here while denying
91
+ # `mkdir` — a file-based probe reports writable and the install then EPERMs.
92
+ dir_writable() {
93
+ _t="$SCRIPT_DIR/.preflight-write-test.$$"
94
+ mkdir "$_t" 2>/dev/null || return 1
95
+ rmdir "$_t" 2>/dev/null
96
+ return 0
97
+ }
98
+
99
+ # ---- 1) Pick a runtime -----------------------------------------------------
100
+ # Node 22.18+ (native TS strip) is the baseline and the preferred runner whenever
101
+ # it's present — it runs the .ts scripts directly. Bun is the fallback runner
102
+ # only when there's no usable Node. We DON'T lean on Bun's implicit auto-install:
103
+ # it's silently suppressed by any ancestor node_modules (monorepo layouts) and
104
+ # fails the same way Node's `npm install` does under a sandbox that blocks Bun's
105
+ # home cache — so missing deps are always an explicit, cache-local install step.
106
+ nresolves=false
107
+ node_ge_2218 && node_resolves && nresolves=true
108
+
109
+ if [ "$nresolves" = true ]; then
110
+ runner=node
111
+ ready=true
112
+ elif node_ge_2218; then
113
+ runner=node # present but deps unresolved — install step below
114
+ ready=false
115
+ elif has bun; then
116
+ runner=bun # no usable Node — Bun runs the scripts; install step below
117
+ ready=false
118
+ else
119
+ echo "PREFLIGHT_STATUS: NEEDS_ACTION"
120
+ echo "PREFLIGHT_RECOMMENDATION: no Node 22.18+ or Bun found — install Node 22.18+ (ships npm) or Bun, then run \`<runner> ${SCRIPT_DIR}/scripts/<name>.ts --help\` to discover arguments and credentials."
121
+ exit "$EXIT_NEEDS_ACTION"
122
+ fi
123
+
124
+ # ---- 2) Install deps if the chosen runtime can't run as-is -----------------
125
+ # Two distinct sandbox failures to disambiguate, because their fixes differ:
126
+ # a) This directory is READ-ONLY (skills installed outside the agent's
127
+ # writable workspace). No install can place node_modules here — the agent
128
+ # must run it unsandboxed or be granted write access. A cache flag can't
129
+ # help.
130
+ # b) The directory is writable but the HOME dir is sandboxed, so the default
131
+ # ~/.npm / ~/.bun cache write is what EPERMs mid-install. Pointing the cache
132
+ # INSIDE this directory sidesteps that without disabling the sandbox (and is
133
+ # harmless otherwise — just an unused cache dir).
134
+ # So probe writability first and recommend accordingly.
135
+ if [ "$ready" != true ]; then
136
+ echo "PREFLIGHT_STATUS: NEEDS_ACTION"
137
+ [ "$runner" = bun ] && install_cmd="bun install" || install_cmd="npm install"
138
+ if ! dir_writable; then
139
+ echo "PREFLIGHT_RECOMMENDATION: dependencies are not installed and ${SCRIPT_DIR} is read-only in the current sandbox (a test write there failed) — \`${install_cmd}\` can't place node_modules here. Run the install with the sandbox disabled, or grant the agent write access to ${SCRIPT_DIR} (a cache flag won't help — node_modules must land in this directory); then run \`${runner} ${SCRIPT_DIR}/scripts/<name>.ts --help\` to discover arguments and credentials."
140
+ elif [ "$runner" = bun ]; then
141
+ echo "PREFLIGHT_RECOMMENDATION: dependencies are not installed — run \`BUN_INSTALL_CACHE_DIR=\"${SCRIPT_DIR}/.bun-cache\" bun install\` in ${SCRIPT_DIR} (the workspace-local cache survives a sandbox that blocks ~/.bun; plain \`bun install\` works otherwise), then run \`${runner} ${SCRIPT_DIR}/scripts/<name>.ts --help\` to discover arguments and credentials."
142
+ elif has npm; then
143
+ echo "PREFLIGHT_RECOMMENDATION: dependencies are not installed — run \`npm install --cache \"${SCRIPT_DIR}/.npm-cache\"\` in ${SCRIPT_DIR} (the workspace-local --cache survives a sandbox that blocks ~/.npm; plain \`npm install\` works otherwise), then run \`${runner} ${SCRIPT_DIR}/scripts/<name>.ts --help\` to discover arguments and credentials."
144
+ else
145
+ # node >= 22.18 ships npm, so a missing npm means it was removed from the
146
+ # Node install. Restore it, then install with a workspace-local cache.
147
+ echo "PREFLIGHT_RECOMMENDATION: npm is missing (it ships with Node 22.18+) — reinstall/repair Node 22.18+, run \`npm install --cache \"${SCRIPT_DIR}/.npm-cache\"\` in ${SCRIPT_DIR}, then run \`${runner} ${SCRIPT_DIR}/scripts/<name>.ts --help\` to discover arguments and credentials."
148
+ fi
149
+ exit "$EXIT_NEEDS_ACTION"
150
+ fi
151
+
152
+ # ---- 3) Ready --------------------------------------------------------------
153
+ # Runtime + deps are in place — the scripts run.
154
+ echo "PREFLIGHT_STATUS: READY"
155
+ echo "PREFLIGHT_RUNNER: ${runner}"
156
+ echo "PREFLIGHT_RECOMMENDATION: run \`${runner} ${SCRIPT_DIR}/scripts/<name>.ts --help\` to discover arguments and credentials, then run the script with the required env vars set."
157
+ exit "$EXIT_READY"
@@ -0,0 +1,81 @@
1
+ # Trello API — gotchas & durable knowledge
2
+
3
+ Per-app behavior the agent needs to call Trello's REST API correctly. Every claim
4
+ here is sourced from [Trello's public developer documentation](https://developer.atlassian.com/cloud/trello/).
5
+ Mechanical details (auth wiring, Zod shapes, connector helpers) live in code, not here.
6
+
7
+ ## Auth & tokens
8
+
9
+ - **OAuth 1.0a via `Authorization` header.** Trello accepts
10
+ `Authorization: OAuth oauth_consumer_key="{{apiKey}}", oauth_token="{{apiToken}}"`
11
+ ([Authorization guide](https://developer.atlassian.com/cloud/trello/guides/rest-api/authorization/)).
12
+ Query `key=` / `token=` parameters and PUT/POST body fields are alternate paths; this connector uses the header.
13
+ - **The API key is public; the token is secret.** "[An API key by itself doesn't grant access to a user's Trello data](https://developer.atlassian.com/cloud/trello/guides/rest-api/api-introduction/)"
14
+ but tokens grant full account access and "[should be kept secret](https://developer.atlassian.com/cloud/trello/guides/rest-api/authorization/)."
15
+ - **Revoked or invalid tokens return 401.** When a token has been revoked, the API responds with HTTP 401 and
16
+ `invalid token`; integrations should prompt re-authorization
17
+ ([Authorization guide](https://developer.atlassian.com/cloud/trello/guides/rest-api/authorization/)).
18
+ - **Member email requires `account` scope.** "[Member emails can only be accessed when the `account` scope is requested](https://developer.atlassian.com/cloud/trello/guides/rest-api/authorization/)."
19
+
20
+ ## Object IDs
21
+
22
+ - **Trello ids are 24-character MongoDB ObjectIds** (hex). They appear on boards, lists, cards, members, labels, etc.
23
+ Search and list responses use the same shape (e.g. `"id": "5abbe4b7ddc1b351ef961414"` in the
24
+ [Search API reference](https://developer.atlassian.com/cloud/trello/rest/api-group-search/)).
25
+ - **Resolve ids before writes.** List/find tools return candidate arrays; the agent must pick the correct id — the API does not auto-select a single match for you.
26
+
27
+ ## Rate limits
28
+
29
+ - **300 requests per 10 seconds per API key; 100 per 10 seconds per token.**
30
+ ([Rate limits](https://developer.atlassian.com/cloud/trello/guides/rest-api/rate-limits/))
31
+ - **429 responses name the limit.** Token exhaustion returns `{ "error": "API_TOKEN_LIMIT_EXCEEDED", ... }`;
32
+ key exhaustion returns `{ "error": "API_KEY_LIMIT_EXCEEDED", ... }`
33
+ ([Rate limits](https://developer.atlassian.com/cloud/trello/guides/rest-api/rate-limits/)).
34
+ - **`/1/members/` has an extra limit: 100 requests per 900 seconds** (anti-enumeration)
35
+ ([Rate limits](https://developer.atlassian.com/cloud/trello/guides/rest-api/rate-limits/)).
36
+ - **Search and member search are "special routes"** with stricter limits — use nested resources when possible
37
+ (e.g. `/1/boards/:id/members` instead of hammering `/1/members`)
38
+ ([Rate limits](https://developer.atlassian.com/cloud/trello/guides/rest-api/rate-limits/)).
39
+ - **Honor `x-rate-limit-*` response headers** to stay within limits
40
+ ([Rate limits](https://developer.atlassian.com/cloud/trello/guides/rest-api/rate-limits/)).
41
+
42
+ ## Search
43
+
44
+ - **`GET /1/search` requires a `query` string** and accepts `modelTypes` to restrict results to boards, cards, members, etc.
45
+ ([Search API](https://developer.atlassian.com/cloud/trello/rest/api-group-search/)).
46
+ - **Use search sparingly** — it counts against special-route limits; prefer list/find on a known board when the scope is narrow.
47
+ - **Member search is a separate endpoint:** `GET /1/search/members/` with `query`, optional `idBoard` / `idOrganization`
48
+ ([Search API](https://developer.atlassian.com/cloud/trello/rest/api-group-search/)).
49
+
50
+ ## Nested resources & pagination
51
+
52
+ - **Prefer nested routes over many single-object GETs.** Example: all cards on a board via
53
+ `GET /1/boards/{boardId}/cards` instead of iterating individual card ids
54
+ ([Nested resources](https://developer.atlassian.com/cloud/trello/guides/rest-api/nested-resources/),
55
+ [Rate limits — Working With Rate Limits](https://developer.atlassian.com/cloud/trello/guides/rest-api/rate-limits/)).
56
+ - **Card list endpoints support cursor-style paging** via `before` (card id) and `limit` — return one page and loop explicitly; do not assume full-board hydration in one call.
57
+ - **Large action loads can fail.** Requesting all board cards with `actions=all` may hit
58
+ `API_TOO_MANY_CARDS_REQUESTED`; fetch actions in follow-up calls
59
+ ([Rate limits — Response Size Limits](https://developer.atlassian.com/cloud/trello/guides/rest-api/rate-limits/)).
60
+
61
+ ## Cards, lists, and boards
62
+
63
+ - **Creating a card requires `idList`.** `POST /1/cards` takes `idList` (and optional name, desc, due, etc.)
64
+ ([Cards API](https://developer.atlassian.com/cloud/trello/rest/api-group-cards/)).
65
+ - **`closed: true` archives a card or board** (soft-close). There is no hard-delete tool in this connector; reopen with `closed: false` via update tools.
66
+ - **Moving a card changes `idList` (and optionally `idBoard`).** Resolve the destination list id via `listLists` or `findList` before calling move/update.
67
+ - **Comments are actions.** Adding a comment creates a `commentCard` action on the card; `getAction` retrieves it by action id.
68
+
69
+ ## Writes & attachments
70
+
71
+ - **POST/PUT bodies may use JSON or form fields.** The authorization guide shows JSON bodies with embedded `key`/`token`;
72
+ many Trello write endpoints also accept standard form fields. Prefer `application/x-www-form-urlencoded` for writes when the API accepts it.
73
+ - **URL attachments vs file upload.** This connector's `addCardAttachment` accepts a URL or remote file URL — not local binary upload. Multipart upload is out of scope.
74
+
75
+ ## Connector output shape
76
+
77
+ - **List/find tools return `{ items: [...] }`.** The SDK requires object-shaped outputs; array results are wrapped — read `.items`, not the raw top-level array.
78
+
79
+ ## Out of scope (say so, don't fake it)
80
+
81
+ - Webhooks, Butler automations, Power-Ups admin, enterprise governance APIs, and batch/plugin-only surfaces are not exposed by this connector.
File without changes
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ // Authored by the implementation agent: multipart file upload or URL form POST to
3
+ // POST /cards/{id}/attachments — outside codegen's single JSON-call model.
4
+ import { defineTool, handleIfScriptMain } from "@zapier/connectors-sdk";
5
+ import { z } from "zod";
6
+
7
+ import { connectionResolvers } from "../connections.ts";
8
+ import {
9
+ TRELLO_BASE,
10
+ TRELLO_ID_REGEX,
11
+ trelloError,
12
+ trelloFormBody,
13
+ trelloFormHeaders,
14
+ } from "../lib/trello.ts";
15
+
16
+ const inputSchema = z
17
+ .object({
18
+ id: z.string().describe("24-char hex card id."),
19
+ url: z
20
+ .string()
21
+ .url()
22
+ .describe(
23
+ "URL to attach (link attachment). Mutually exclusive with fileUrl.",
24
+ )
25
+ .optional(),
26
+ fileUrl: z
27
+ .string()
28
+ .url()
29
+ .describe(
30
+ "Remote file URL to download and upload. Mutually exclusive with url.",
31
+ )
32
+ .optional(),
33
+ name: z.string().describe("Attachment display name.").optional(),
34
+ mimeType: z.string().optional(),
35
+ })
36
+ .strict()
37
+ .refine((v) => (v.url ? !v.fileUrl : !!v.fileUrl), {
38
+ message: "Provide exactly one of url or fileUrl.",
39
+ });
40
+
41
+ const outputSchema = z.object({
42
+ id: z.string().regex(TRELLO_ID_REGEX),
43
+ name: z.string(),
44
+ url: z.string(),
45
+ mimeType: z.string().nullable().optional(),
46
+ bytes: z.number().nullable().optional(),
47
+ isUpload: z.boolean().nullable().optional(),
48
+ });
49
+
50
+ const definition = defineTool({
51
+ name: "addCardAttachment",
52
+ title: "Add Card Attachment",
53
+ description:
54
+ "Add a URL link attachment or upload a file from a remote URL to a card. Provide url OR fileUrl, not both.",
55
+ inputSchema,
56
+ outputSchema,
57
+ annotations: {
58
+ readOnlyHint: false,
59
+ destructiveHint: false,
60
+ idempotentHint: false,
61
+ openWorldHint: true,
62
+ },
63
+ connection: "trello",
64
+ run: async (input, ctx) => {
65
+ const cardPath = `${TRELLO_BASE}/cards/${encodeURIComponent(input.id)}/attachments`;
66
+
67
+ if (input.fileUrl) {
68
+ const fileRes = await ctx.fetch(input.fileUrl);
69
+ if (!fileRes.ok) await trelloError("addCardAttachment", fileRes);
70
+ const buffer = await fileRes.arrayBuffer();
71
+ const fileName = input.name ?? "attachment";
72
+ const form = new FormData();
73
+ form.append(
74
+ "file",
75
+ new Blob([buffer], {
76
+ type:
77
+ input.mimeType ?? fileRes.headers.get("content-type") ?? undefined,
78
+ }),
79
+ fileName,
80
+ );
81
+ form.append("name", fileName);
82
+ if (input.mimeType) form.append("mimeType", input.mimeType);
83
+ const uploadRes = await ctx.fetch(cardPath, {
84
+ method: "POST",
85
+ body: form,
86
+ });
87
+ if (!uploadRes.ok) await trelloError("addCardAttachment", uploadRes);
88
+ return uploadRes.json() as Promise<z.infer<typeof outputSchema>>;
89
+ }
90
+
91
+ const linkRes = await ctx.fetch(cardPath, {
92
+ method: "POST",
93
+ headers: trelloFormHeaders,
94
+ body: trelloFormBody({
95
+ url: input.url!,
96
+ name: input.name ?? input.url!,
97
+ mimeType: input.mimeType,
98
+ }),
99
+ });
100
+ if (!linkRes.ok) await trelloError("addCardAttachment", linkRes);
101
+ return linkRes.json() as Promise<z.infer<typeof outputSchema>>;
102
+ },
103
+ });
104
+
105
+ export default definition;
106
+
107
+ await handleIfScriptMain(import.meta, definition, { connectionResolvers });
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ import { defineTool, handleIfScriptMain } from "@zapier/connectors-sdk";
3
+ import { z } from "zod";
4
+
5
+ import { connectionResolvers } from "../connections.ts";
6
+
7
+ const inputSchema = z
8
+ .object({
9
+ id: z.string().describe("24-char hex card id."),
10
+ value: z.string().describe("Label id from listLabels."),
11
+ })
12
+ .strict();
13
+ const outputSchema = z.object({
14
+ id: z
15
+ .string()
16
+ .regex(new RegExp("^[0-9a-fA-F]{24}$"))
17
+ .describe("Trello object id (24 hex chars)."),
18
+ name: z.string(),
19
+ desc: z.string().nullable().optional(),
20
+ closed: z.boolean().nullable().optional(),
21
+ idBoard: z.string(),
22
+ idList: z.string(),
23
+ idShort: z.number().int().nullable().optional(),
24
+ shortLink: z.string().nullable().optional(),
25
+ shortUrl: z.string().nullable().optional(),
26
+ url: z.string().nullable().optional(),
27
+ due: z.union([z.string().datetime({ offset: true }), z.null()]).optional(),
28
+ dueComplete: z.boolean().nullable().optional(),
29
+ dateLastActivity: z.string().datetime({ offset: true }).nullable().optional(),
30
+ idLabels: z.array(z.string()).nullable().optional(),
31
+ idMembers: z.array(z.string()).nullable().optional(),
32
+ labels: z
33
+ .array(
34
+ z.object({
35
+ id: z
36
+ .string()
37
+ .regex(new RegExp("^[0-9a-fA-F]{24}$"))
38
+ .describe("Trello object id (24 hex chars)."),
39
+ idBoard: z.string(),
40
+ name: z.string().nullable().optional(),
41
+ color: z.string().nullable().optional(),
42
+ }),
43
+ )
44
+ .nullable()
45
+ .optional(),
46
+ pos: z.number().nullable().optional(),
47
+ customFields: z
48
+ .record(z.string(), z.any())
49
+ .nullable()
50
+ .describe("Custom field values keyed by field name when requested.")
51
+ .optional(),
52
+ });
53
+
54
+ const definition = defineTool({
55
+ name: "addCardLabel",
56
+ title: "Add Card Label",
57
+ description: "Add an existing board label to a card by label id.",
58
+ inputSchema,
59
+ outputSchema,
60
+ annotations: {
61
+ readOnlyHint: false,
62
+ destructiveHint: false,
63
+ idempotentHint: false,
64
+ openWorldHint: true,
65
+ },
66
+ connection: "trello",
67
+ run: async (input, ctx) => {
68
+ const url = `https://api.trello.com/1/cards/${encodeURIComponent(input.id)}/idLabels`;
69
+ const body: Record<string, unknown> = {};
70
+ if (input.value !== undefined) body["value"] = input.value;
71
+ const res = await ctx.fetch(url, {
72
+ method: "POST",
73
+ headers: { "Content-Type": "application/json" },
74
+ body: JSON.stringify(body),
75
+ });
76
+ if (!res.ok) {
77
+ const errBody = await res.text();
78
+ throw new Error(`Trello addCardLabel ${res.status}: ${errBody}`);
79
+ }
80
+ const cardRes = await ctx.fetch(
81
+ `https://api.trello.com/1/cards/${encodeURIComponent(input.id)}`,
82
+ );
83
+ if (!cardRes.ok) {
84
+ const errBody = await cardRes.text();
85
+ throw new Error(`Trello addCardLabel ${cardRes.status}: ${errBody}`);
86
+ }
87
+ return cardRes.json();
88
+ },
89
+ });
90
+
91
+ export default definition;
92
+
93
+ await handleIfScriptMain(import.meta, definition, { connectionResolvers });