trelly 0.1.1 → 0.2.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/.claude-plugin/plugin.json +15 -0
- package/.codex-plugin/plugin.json +25 -0
- package/.cursor-plugin/mcp.json +11 -0
- package/.cursor-plugin/plugin.json +15 -0
- package/.mcp.json +8 -0
- package/PLUGIN.md +85 -0
- package/PRIVACY.md +43 -0
- package/README.md +58 -9
- package/assets/logo.svg +6 -0
- package/assets/terminal.gif +0 -0
- package/bin/install-cursor-plugin-local.sh +15 -0
- package/package.json +18 -4
- package/skills/README.md +115 -0
- package/skills/trelly/SKILL.md +105 -0
- package/skills/trelly-mcp/SKILL.md +115 -0
- package/src/api/client.ts +18 -3
- package/src/cli/commands/boards.ts +6 -6
- package/src/cli/commands/cards.ts +19 -1
- package/src/cli/ui/app.tsx +263 -70
- package/src/index.test.ts +9 -0
- package/src/util/attachment.ts +29 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: trelly-mcp
|
|
3
|
+
description: >-
|
|
4
|
+
Configure and use the trelly MCP stdio server (trello_boards_list,
|
|
5
|
+
trello_card_create, trello_search, etc.). Use when wiring Cursor/Claude MCP,
|
|
6
|
+
calling Trello from an IDE agent, or choosing between MCP tools vs trelly CLI.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# trelly-mcp
|
|
10
|
+
|
|
11
|
+
MCP server for Trello (npm package **trelly**, bin **`trelly-mcp`**). Returns JSON
|
|
12
|
+
envelope on every tool: `{ ok, profile, data }` /
|
|
13
|
+
`{ ok: false, error, status?, details? }`. Never uses CLI human/Ink output.
|
|
14
|
+
|
|
15
|
+
## Setup
|
|
16
|
+
|
|
17
|
+
### 1. Auth (CLI, once)
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
trelly auth setup
|
|
21
|
+
trelly auth login
|
|
22
|
+
trelly auth list
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 2. Cursor — plugin or `~/.cursor/mcp.json`
|
|
26
|
+
|
|
27
|
+
**Plugin (skills + MCP):** after `npm install -g trelly`:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
cp -R "$(npm root -g)/trelly" ~/.cursor/plugins/local/trelly
|
|
31
|
+
# or from clone: ./bin/install-cursor-plugin-local.sh
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**MCP only** — `~/.cursor/mcp.json`:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"mcpServers": {
|
|
39
|
+
"trelly": {
|
|
40
|
+
"command": "trelly-mcp",
|
|
41
|
+
"env": {
|
|
42
|
+
"TRELLO_PROFILE": "default"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
See [mcp.example.json](../../mcp.example.json).
|
|
50
|
+
|
|
51
|
+
### 3. Restart the IDE
|
|
52
|
+
|
|
53
|
+
Reload MCP servers after editing config.
|
|
54
|
+
|
|
55
|
+
### 4. Smoke test (optional)
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
cd trello-cli && bun run mcp
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Starts stdio MCP manually (IDE normally launches `trelly-mcp` itself).
|
|
62
|
+
|
|
63
|
+
## Using tools
|
|
64
|
+
|
|
65
|
+
- Pass **`profile`** on any tool for a non-default account (or set `TRELLO_PROFILE` in env).
|
|
66
|
+
- Read **`ok`** before using **`data`**.
|
|
67
|
+
- Prefer **`trello_card_archive`** / **`trello_board_archive`** to close items.
|
|
68
|
+
- **`trello_card_delete`** is **permanent** — no MCP board-delete tool.
|
|
69
|
+
|
|
70
|
+
## Tool catalog
|
|
71
|
+
|
|
72
|
+
| Tool | Purpose |
|
|
73
|
+
|------|---------|
|
|
74
|
+
| `trello_profiles_list` | Saved profiles + default |
|
|
75
|
+
| `trello_member_me` | Authenticated member |
|
|
76
|
+
| `trello_boards_list` | Member boards (`filter`, `fields`) |
|
|
77
|
+
| `trello_board_get` | Board by id |
|
|
78
|
+
| `trello_board_create` | Create board |
|
|
79
|
+
| `trello_board_archive` | Close board (reversible) |
|
|
80
|
+
| `trello_board_lists` | Lists on board |
|
|
81
|
+
| `trello_board_cards` | All cards on board |
|
|
82
|
+
| `trello_list_create` | Create list |
|
|
83
|
+
| `trello_list_cards` | Cards in list |
|
|
84
|
+
| `trello_card_get` | Card (`fields` optional) |
|
|
85
|
+
| `trello_card_create` | Create card on list |
|
|
86
|
+
| `trello_card_update` | Update card fields map |
|
|
87
|
+
| `trello_card_move` | Move to another list |
|
|
88
|
+
| `trello_card_comments` | List comments |
|
|
89
|
+
| `trello_card_comment` | Add comment |
|
|
90
|
+
| `trello_card_archive` | Close card (reversible) |
|
|
91
|
+
| `trello_card_delete` | **Permanent** delete |
|
|
92
|
+
| `trello_checklist_create` | Checklist on card |
|
|
93
|
+
| `trello_checklist_add_item` | Checklist item |
|
|
94
|
+
| `trello_label_create` | Board label |
|
|
95
|
+
| `trello_card_add_label` | Label on card |
|
|
96
|
+
| `trello_search` | Search Trello |
|
|
97
|
+
| `trello_webhooks_list` | Token webhooks |
|
|
98
|
+
| `trello_webhook_create` | Create webhook |
|
|
99
|
+
| `trello_webhook_delete` | Delete webhook |
|
|
100
|
+
| `trello_api` | Raw REST (`method`, `path`, `query`, `body`) |
|
|
101
|
+
|
|
102
|
+
## When to use MCP vs CLI
|
|
103
|
+
|
|
104
|
+
| Use MCP | Use CLI |
|
|
105
|
+
|---------|---------|
|
|
106
|
+
| IDE agent managing Trello in chat | Terminal, shell scripts, jq pipelines |
|
|
107
|
+
| Structured tool calls with schemas | `trello ui` interactive board |
|
|
108
|
+
| Cursor/Codex/Claude with MCP wired | CI with `--json` |
|
|
109
|
+
|
|
110
|
+
For terminal work, invoke the **trelly** skill instead.
|
|
111
|
+
|
|
112
|
+
## Extending
|
|
113
|
+
|
|
114
|
+
New tools: `src/mcp/tools/` — zod input, envelope `outputSchema`,
|
|
115
|
+
`readOnlyHint` / `destructiveHint`. See [AGENTS.md](../../AGENTS.md).
|
package/src/api/client.ts
CHANGED
|
@@ -28,7 +28,7 @@ export class TrelloClient {
|
|
|
28
28
|
method: string,
|
|
29
29
|
path: string,
|
|
30
30
|
query: Query = {},
|
|
31
|
-
body?: JsonValue,
|
|
31
|
+
body?: JsonValue | FormData,
|
|
32
32
|
): Promise<T> {
|
|
33
33
|
const url = new URL(`${this.base}${path.startsWith("/") ? path : `/${path}`}`);
|
|
34
34
|
|
|
@@ -43,7 +43,9 @@ export class TrelloClient {
|
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
const init: RequestInit = { method, headers };
|
|
46
|
-
if (body
|
|
46
|
+
if (body instanceof FormData) {
|
|
47
|
+
init.body = body; // fetch sets the multipart boundary
|
|
48
|
+
} else if (body !== undefined) {
|
|
47
49
|
headers["Content-Type"] = "application/json";
|
|
48
50
|
init.body = JSON.stringify(body);
|
|
49
51
|
}
|
|
@@ -63,7 +65,11 @@ export class TrelloClient {
|
|
|
63
65
|
return this.request("GET", path, query);
|
|
64
66
|
}
|
|
65
67
|
|
|
66
|
-
post<T = JsonValue>(
|
|
68
|
+
post<T = JsonValue>(
|
|
69
|
+
path: string,
|
|
70
|
+
query?: Query,
|
|
71
|
+
body?: JsonValue | FormData,
|
|
72
|
+
): Promise<T> {
|
|
67
73
|
return this.request("POST", path, query, body);
|
|
68
74
|
}
|
|
69
75
|
|
|
@@ -218,6 +224,15 @@ export class TrelloClient {
|
|
|
218
224
|
return this.post(`/cards/${id}/attachments`, query);
|
|
219
225
|
}
|
|
220
226
|
|
|
227
|
+
/** Upload a local file as a card attachment (multipart). */
|
|
228
|
+
cardUploadAttachment(id: string, form: FormData) {
|
|
229
|
+
return this.post(`/cards/${id}/attachments`, {}, form);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
cardDeleteAttachment(id: string, attachmentId: string) {
|
|
233
|
+
return this.delete(`/cards/${id}/attachments/${attachmentId}`);
|
|
234
|
+
}
|
|
235
|
+
|
|
221
236
|
cardCustomFieldItems(id: string) {
|
|
222
237
|
return this.get(`/cards/${id}/customFieldItems`);
|
|
223
238
|
}
|
|
@@ -71,7 +71,7 @@ export function registerBoardCommands(program: Command): void {
|
|
|
71
71
|
boards
|
|
72
72
|
.command("archive <id>")
|
|
73
73
|
.description("Close (archive) a board — reversible in Trello UI")
|
|
74
|
-
.action((id, cmd) =>
|
|
74
|
+
.action((id, _opts, cmd) =>
|
|
75
75
|
run(cmd, async () => {
|
|
76
76
|
const { client } = getClient(rootOpts(cmd).profile);
|
|
77
77
|
return client.boardArchive(id);
|
|
@@ -81,7 +81,7 @@ export function registerBoardCommands(program: Command): void {
|
|
|
81
81
|
boards
|
|
82
82
|
.command("delete <id>")
|
|
83
83
|
.description("Permanently delete a board — irreversible")
|
|
84
|
-
.action((id, cmd) =>
|
|
84
|
+
.action((id, _opts, cmd) =>
|
|
85
85
|
run(cmd, async () => {
|
|
86
86
|
const { client } = getClient(rootOpts(cmd).profile);
|
|
87
87
|
return client.boardDelete(id);
|
|
@@ -102,7 +102,7 @@ export function registerBoardCommands(program: Command): void {
|
|
|
102
102
|
boards
|
|
103
103
|
.command("cards <boardId>")
|
|
104
104
|
.description("List cards on a board")
|
|
105
|
-
.action((boardId, cmd) =>
|
|
105
|
+
.action((boardId, _opts, cmd) =>
|
|
106
106
|
run(cmd, async () => {
|
|
107
107
|
const { client } = getClient(rootOpts(cmd).profile);
|
|
108
108
|
return client.boardCards(boardId);
|
|
@@ -112,7 +112,7 @@ export function registerBoardCommands(program: Command): void {
|
|
|
112
112
|
boards
|
|
113
113
|
.command("labels <boardId>")
|
|
114
114
|
.description("List labels on a board")
|
|
115
|
-
.action((boardId, cmd) =>
|
|
115
|
+
.action((boardId, _opts, cmd) =>
|
|
116
116
|
run(cmd, async () => {
|
|
117
117
|
const { client } = getClient(rootOpts(cmd).profile);
|
|
118
118
|
return client.boardLabels(boardId);
|
|
@@ -122,7 +122,7 @@ export function registerBoardCommands(program: Command): void {
|
|
|
122
122
|
boards
|
|
123
123
|
.command("members <boardId>")
|
|
124
124
|
.description("List board members")
|
|
125
|
-
.action((boardId, cmd) =>
|
|
125
|
+
.action((boardId, _opts, cmd) =>
|
|
126
126
|
run(cmd, async () => {
|
|
127
127
|
const { client } = getClient(rootOpts(cmd).profile);
|
|
128
128
|
return client.boardMembers(boardId);
|
|
@@ -143,7 +143,7 @@ export function registerBoardCommands(program: Command): void {
|
|
|
143
143
|
boards
|
|
144
144
|
.command("custom-fields <boardId>")
|
|
145
145
|
.description("List custom field definitions")
|
|
146
|
-
.action((boardId, cmd) =>
|
|
146
|
+
.action((boardId, _opts, cmd) =>
|
|
147
147
|
run(cmd, async () => {
|
|
148
148
|
const { client } = getClient(rootOpts(cmd).profile);
|
|
149
149
|
return client.boardCustomFields(boardId);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
|
+
import { attachmentForm } from "../../util/attachment.ts";
|
|
2
3
|
import { getClient, parseJsonFlag, parseKvPairs } from "../context.ts";
|
|
3
4
|
import { rootOpts, run } from "./run.ts";
|
|
4
5
|
|
|
@@ -176,15 +177,32 @@ export function registerCardCommands(program: Command): void {
|
|
|
176
177
|
|
|
177
178
|
cards
|
|
178
179
|
.command("add-attachment <id>")
|
|
179
|
-
.
|
|
180
|
+
.option("--url <url>", "Attachment URL")
|
|
181
|
+
.option("--file <path>", "Local file to upload")
|
|
180
182
|
.option("--name <name>", "Attachment name")
|
|
181
183
|
.action((id, opts, cmd) =>
|
|
182
184
|
run(cmd, async () => {
|
|
183
185
|
const { client } = getClient(rootOpts(cmd).profile);
|
|
186
|
+
if (!opts.url === !opts.file) {
|
|
187
|
+
throw new Error("Pass exactly one of --url or --file");
|
|
188
|
+
}
|
|
189
|
+
if (opts.file) {
|
|
190
|
+
return client.cardUploadAttachment(id, attachmentForm(opts.file, opts.name));
|
|
191
|
+
}
|
|
184
192
|
return client.cardAddAttachment(id, { url: opts.url, name: opts.name });
|
|
185
193
|
}),
|
|
186
194
|
);
|
|
187
195
|
|
|
196
|
+
cards
|
|
197
|
+
.command("delete-attachment <id> <attachmentId>")
|
|
198
|
+
.description("Delete an attachment from a card")
|
|
199
|
+
.action((id, attachmentId, _opts, cmd) =>
|
|
200
|
+
run(cmd, async () => {
|
|
201
|
+
const { client } = getClient(rootOpts(cmd).profile);
|
|
202
|
+
return client.cardDeleteAttachment(id, attachmentId);
|
|
203
|
+
}),
|
|
204
|
+
);
|
|
205
|
+
|
|
188
206
|
cards.command("custom-fields <id>").action((id, _opts, cmd) =>
|
|
189
207
|
run(cmd, async () => {
|
|
190
208
|
const { client } = getClient(rootOpts(cmd).profile);
|