@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.
- package/LICENSE +93 -0
- package/NOTICE +8 -0
- package/README.md +135 -2
- package/SKILL.md +139 -0
- package/cli.js +71 -0
- package/cli.ts +5 -0
- package/connections.ts +26 -0
- package/dist/cli.js +4 -0
- package/dist/index.js +2654 -0
- package/index.ts +145 -0
- package/package.json +59 -4
- package/preflight.sh +157 -0
- package/references/trello-api-gotchas.md +81 -0
- package/scripts/.gitkeep +0 -0
- package/scripts/addCardAttachment.ts +107 -0
- package/scripts/addCardLabel.ts +93 -0
- package/scripts/addCardMember.ts +93 -0
- package/scripts/addChecklistItem.ts +98 -0
- package/scripts/addMemberToBoard.ts +57 -0
- package/scripts/archiveCard.ts +67 -0
- package/scripts/closeBoard.ts +62 -0
- package/scripts/completeChecklistItem.ts +56 -0
- package/scripts/copyBoard.ts +81 -0
- package/scripts/createBoard.ts +72 -0
- package/scripts/createCard.ts +266 -0
- package/scripts/createChecklist.ts +50 -0
- package/scripts/createComment.ts +71 -0
- package/scripts/createLabel.ts +72 -0
- package/scripts/createList.ts +100 -0
- package/scripts/deleteChecklist.ts +38 -0
- package/scripts/findBoard.ts +62 -0
- package/scripts/findChecklist.ts +51 -0
- package/scripts/findChecklistItem.ts +49 -0
- package/scripts/findLabel.ts +54 -0
- package/scripts/findList.ts +55 -0
- package/scripts/findOrganizationMember.ts +59 -0
- package/scripts/getAction.ts +58 -0
- package/scripts/getBoard.ts +52 -0
- package/scripts/getCard.ts +79 -0
- package/scripts/getChecklist.ts +45 -0
- package/scripts/getChecklistItem.ts +48 -0
- package/scripts/getCurrentMember.ts +52 -0
- package/scripts/getLabel.ts +46 -0
- package/scripts/getList.ts +47 -0
- package/scripts/getMember.ts +52 -0
- package/scripts/getOrganization.ts +46 -0
- package/scripts/listBoardMembers.ts +58 -0
- package/scripts/listBoards.ts +78 -0
- package/scripts/listCardAttachments.ts +55 -0
- package/scripts/listCards.ts +129 -0
- package/scripts/listCustomFields.ts +62 -0
- package/scripts/listLabels.ts +52 -0
- package/scripts/listLists.ts +61 -0
- package/scripts/listOrganizations.ts +50 -0
- package/scripts/moveCard.ts +68 -0
- package/scripts/removeCardLabel.ts +43 -0
- package/scripts/searchCards.ts +153 -0
- package/scripts/updateCard.ts +184 -0
- 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
|
-
"
|
|
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
|
-
"
|
|
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.
|
package/scripts/.gitkeep
ADDED
|
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 });
|