trelly 0.3.0 → 0.3.1
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/README.md +9 -18
- package/package.json +1 -1
- package/skills/README.md +3 -2
- package/skills/trelly/SKILL.md +9 -2
- package/skills/trelly-card-display.md +39 -0
- package/skills/trelly-mcp/SKILL.md +16 -9
- package/src/index.test.ts +28 -0
- package/src/mcp/handlers.ts +62 -1
- package/src/mcp/tools/boards.ts +11 -7
- package/src/mcp/tools/lists.ts +14 -5
- package/src/util/card-display.ts +100 -0
package/README.md
CHANGED
|
@@ -211,29 +211,20 @@ CI runs the same via `bun install --frozen-lockfile`. See `AGENTS.md` for conven
|
|
|
211
211
|
|
|
212
212
|
## Agent skills & plugins
|
|
213
213
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
[skills/README.md](skills/README.md).
|
|
214
|
+
The npm package ships agent skills plus plugin manifests, so Claude Code, Cursor,
|
|
215
|
+
and pi agents learn the trelly CLI + MCP conventions automatically.
|
|
217
216
|
|
|
218
|
-
|
|
219
|
-
npm install -g trelly
|
|
220
|
-
trelly auth setup && trelly auth login
|
|
217
|
+
Install trelly and log in first (`npm install -g trelly && trelly auth setup && trelly auth login`), then:
|
|
221
218
|
|
|
222
|
-
|
|
223
|
-
claude plugin install "$(npm root -g)/trelly"
|
|
224
|
-
|
|
219
|
+
```bash
|
|
220
|
+
claude plugin install "$(npm root -g)/trelly" # Claude Code
|
|
221
|
+
"$(npm root -g)/trelly/bin/install-cursor-plugin-local.sh" # Cursor (copies plugin, then reload window)
|
|
222
|
+
pi install npm:trelly # pi
|
|
225
223
|
```
|
|
226
224
|
|
|
227
|
-
MCP-only (no plugin): add `trelly-mcp` to `~/.cursor/mcp.json` — [mcp.example.json](mcp.example.json).
|
|
228
|
-
|
|
229
|
-
### Marketplace submission
|
|
230
|
-
|
|
231
|
-
| Platform | Submit |
|
|
232
|
-
|----------|--------|
|
|
233
|
-
| **Cursor** | [cursor.com/marketplace/publish](https://cursor.com/marketplace/publish) → `https://github.com/brandonkramer/trelly` |
|
|
234
|
-
| **Claude Code** | [clau.de/plugin-directory-submission](https://clau.de/plugin-directory-submission) |
|
|
225
|
+
MCP-only (no plugin): add `trelly-mcp` to `~/.cursor/mcp.json` — see [mcp.example.json](mcp.example.json).
|
|
235
226
|
|
|
236
|
-
|
|
227
|
+
Details: [PLUGIN.md](PLUGIN.md) · [PRIVACY.md](PRIVACY.md) · [skills/README.md](skills/README.md)
|
|
237
228
|
|
|
238
229
|
## License
|
|
239
230
|
|
package/package.json
CHANGED
package/skills/README.md
CHANGED
|
@@ -6,8 +6,9 @@ developing this repo.
|
|
|
6
6
|
|
|
7
7
|
| Skill | Use when |
|
|
8
8
|
|-------|----------|
|
|
9
|
-
| [trelly](trelly/SKILL.md) | Terminal / bash:
|
|
10
|
-
| [trelly-mcp](trelly-mcp/SKILL.md) | IDE
|
|
9
|
+
| [trelly](trelly/SKILL.md) | Terminal / bash: human card lists, auth, attachments, GitHub links, `--json` |
|
|
10
|
+
| [trelly-mcp](trelly-mcp/SKILL.md) | IDE + MCP: paste `display` from list tools, GitHub links, safety |
|
|
11
|
+
| [trelly-card-display](trelly-card-display.md) | **Required** when user asks to see/list cards (all platforms) |
|
|
11
12
|
|
|
12
13
|
**One copy of the content:** `skills/trelly/` and `skills/trelly-mcp/` in the installed
|
|
13
14
|
package. Plugins and Pi load from there — you don't maintain separate copies.
|
package/skills/trelly/SKILL.md
CHANGED
|
@@ -4,7 +4,8 @@ description: >-
|
|
|
4
4
|
Operate the trelly Trello CLI (npm trelly; bins trelly/trello): auth setup/login,
|
|
5
5
|
human vs --json output, boards/lists/cards, search, attachments, GitHub PR/commit
|
|
6
6
|
links on cards, trello ui, trello api. Use when the user asks to run trelly/trello
|
|
7
|
-
commands, link a GitHub PR or commit to a
|
|
7
|
+
commands, list or show Trello cards/todos, link a GitHub PR or commit to a card, or
|
|
8
|
+
automate Trello with trelly.
|
|
8
9
|
---
|
|
9
10
|
|
|
10
11
|
# trelly
|
|
@@ -12,6 +13,10 @@ description: >-
|
|
|
12
13
|
Fast Trello CLI (`npm install -g trelly`). **Human Trello-styled output by default**;
|
|
13
14
|
**`--json` for scripts**. Commands: **`trelly`** or **`trello`** (same binary).
|
|
14
15
|
|
|
16
|
+
> **Showing cards to a user?** Read [trelly-card-display.md](trelly-card-display.md).
|
|
17
|
+
> Pi has no MCP — use **human CLI** (`trelly lists cards LIST_ID` without `--json`)
|
|
18
|
+
> or format per that contract. Never reply with titles-only.
|
|
19
|
+
|
|
15
20
|
## Prerequisites
|
|
16
21
|
|
|
17
22
|
```bash
|
|
@@ -41,7 +46,9 @@ Credentials: `~/.config/trelly/config.json` (migrates from `~/.config/trello-cli
|
|
|
41
46
|
|
|
42
47
|
- **Scripts:** always `--json` if parsing stdout.
|
|
43
48
|
- **Token cost:** raw `--json` returns full Trello objects (a board list ≈ 10k tokens) — trim with `jq` (e.g. `| jq '.data[] | {id,name}'`) or request fewer fields (`--fields`, `--query "fields=id,name"`).
|
|
44
|
-
- **Showing cards to a human?**
|
|
49
|
+
- **Showing cards to a human?** See [trelly-card-display.md](trelly-card-display.md).
|
|
50
|
+
Human CLI (`trelly lists cards LIST_ID`) already matches the contract. With `--json`,
|
|
51
|
+
slim first: `jq '.data[] | {name, shortUrl, labels: [.labels[] | {name, color}], badges: {comments: .badges.comments, attachments: .badges.attachments, checkItems: .badges.checkItems, checkItemsChecked: .badges.checkItemsChecked}, due, dueComplete}'`.
|
|
45
52
|
- **`--pretty` alone** does not emit JSON; it only indents `--json` output.
|
|
46
53
|
- Errors: red `✗` in human mode; exit code `1`.
|
|
47
54
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Card list display contract (all agents)
|
|
2
|
+
|
|
3
|
+
When the user asks to **see**, **list**, or **show** Trello cards (todo, backlog,
|
|
4
|
+
board, column, "what's on…"), you MUST NOT reply with a plain numbered title list.
|
|
5
|
+
|
|
6
|
+
## MCP (Cursor, Claude Code, Codex with trelly-mcp)
|
|
7
|
+
|
|
8
|
+
`trello_list_cards` and `trello_board_cards` return:
|
|
9
|
+
|
|
10
|
+
- `display` — markdown-v1, ready to paste (linked titles + badges)
|
|
11
|
+
- `data` — slim JSON for automation
|
|
12
|
+
|
|
13
|
+
**Rule: paste `display` verbatim** (optionally add a heading via `displayHeading`).
|
|
14
|
+
Do not re-summarize `data` into your own format.
|
|
15
|
+
|
|
16
|
+
Example line:
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
1. [Publish module behavior](https://trello.com/c/1jH2UTAu) · 💬 2 · 📎 2
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Badge keys (omit when zero): 💬 comments · 📎 attachments · ✓ checked/total · ⏰ due.
|
|
23
|
+
Labels: colored dot + `` `name` `` (🔴 🟠 🟡 🟢 🔵 🟣 ⚫ ⚪).
|
|
24
|
+
|
|
25
|
+
## CLI / Pi (no MCP)
|
|
26
|
+
|
|
27
|
+
**Preferred:** human output (no `--json`):
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
trelly lists cards LIST_ID
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**If using `--json`:** format with the jq recipe in `skills/trelly/SKILL.md` Output
|
|
34
|
+
contract, or call MCP when available.
|
|
35
|
+
|
|
36
|
+
## Automation only
|
|
37
|
+
|
|
38
|
+
Plain `data` / names-only output is OK when the user wants scripts, counts, or IDs —
|
|
39
|
+
not when they asked to **see** the board.
|
|
@@ -3,7 +3,8 @@ name: trelly-mcp
|
|
|
3
3
|
description: >-
|
|
4
4
|
Configure and use the trelly MCP stdio server (trello_boards_list,
|
|
5
5
|
trello_card_create, trello_search, trello_api, etc.). Use when wiring Cursor/Claude
|
|
6
|
-
MCP,
|
|
6
|
+
MCP, listing or showing Trello cards/todos, linking GitHub PRs/commits, or choosing
|
|
7
|
+
MCP vs CLI.
|
|
7
8
|
---
|
|
8
9
|
|
|
9
10
|
# trelly-mcp
|
|
@@ -12,15 +13,22 @@ MCP server for Trello (npm package **trelly**, bin **`trelly-mcp`**). Returns JS
|
|
|
12
13
|
envelope on every tool: `{ ok, profile, data }` /
|
|
13
14
|
`{ ok: false, error, status?, details? }`. Never uses CLI human/Ink output.
|
|
14
15
|
|
|
16
|
+
> **Listing cards for a user?** Read [trelly-card-display.md](trelly-card-display.md).
|
|
17
|
+
> **`trello_list_cards` / `trello_board_cards` include `display` — paste it verbatim.**
|
|
18
|
+
|
|
15
19
|
List/get tools default to lean `fields` (id, name, url, due, …) to keep responses
|
|
16
|
-
token-cheap; pass `fields: "all"`
|
|
17
|
-
`trello_list_cards` and `
|
|
18
|
-
(
|
|
20
|
+
token-cheap; pass `fields: "all"` when you need more.
|
|
21
|
+
`trello_list_cards` and `trello_board_cards` include slim `badges`, `labels`, and
|
|
22
|
+
pre-rendered **`display`** (markdown-v1).
|
|
19
23
|
|
|
20
24
|
## Rendering card lists for humans
|
|
21
25
|
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
**Do not reformat.** When `display` is present, show it to the user unchanged
|
|
27
|
+
(you may prepend context from `displayHeading` or your own board/list title).
|
|
28
|
+
|
|
29
|
+
MCP tool text also leads with `display`, then JSON — prefer the markdown block.
|
|
30
|
+
|
|
31
|
+
Manual format (only if `display` missing — e.g. raw `trello_api`):
|
|
24
32
|
|
|
25
33
|
```
|
|
26
34
|
1. [Publish module behavior](https://trello.com/c/1jH2UTAu) 🔴 `Priority: Highest` · 💬 4 · 📎 2 · ✓ 2/5 · ⏰ Jul 5
|
|
@@ -29,7 +37,6 @@ card — title linked to the card, then only the badges that are non-zero:
|
|
|
29
37
|
- Title → `[name](shortUrl)`. Counts from `badges`: 💬 comments · 📎 attachments · ✓ checkItemsChecked/checkItems.
|
|
30
38
|
- Due: `⏰ Jul 5`, add `(overdue)` if past and not `dueComplete`; `✓` if complete.
|
|
31
39
|
- Labels: colored dot + name in backticks. Trello color → emoji: red 🔴 · orange 🟠 · yellow 🟡 · green/lime 🟢 · blue/sky 🔵 · purple/pink 🟣 · black ⚫ · none ⚪.
|
|
32
|
-
- Whole board: add `fields: "…,badges,labels"` to `trello_board_cards` (lean by default).
|
|
33
40
|
- Custom-field chips (e.g. `Priority: Highest`): fetch defs once via `trello_api` GET
|
|
34
41
|
`/boards/{boardId}/customFields`, request `customFieldItems` on cards, match
|
|
35
42
|
`idValue` → option text/color. Skip unless the user wants them — it's an extra call.
|
|
@@ -100,9 +107,9 @@ Starts stdio MCP manually (IDE normally launches `trelly-mcp` itself).
|
|
|
100
107
|
| `trello_board_create` | Create board |
|
|
101
108
|
| `trello_board_archive` | Close board (reversible) |
|
|
102
109
|
| `trello_board_lists` | Lists on board |
|
|
103
|
-
| `trello_board_cards` | All cards on board |
|
|
110
|
+
| `trello_board_cards` | All cards on board (**returns `display` — paste for users**) |
|
|
104
111
|
| `trello_list_create` | Create list |
|
|
105
|
-
| `trello_list_cards` | Cards in list |
|
|
112
|
+
| `trello_list_cards` | Cards in list (**returns `display` markdown — paste for users**) |
|
|
106
113
|
| `trello_card_get` | Card (`fields` optional) |
|
|
107
114
|
| `trello_card_create` | Create card on list |
|
|
108
115
|
| `trello_card_update` | Update card fields map |
|
package/src/index.test.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { customFieldChips } from "./cli/ui/custom-fields.ts";
|
|
|
7
7
|
import { dueStatus, labelHex, listAccentHex } from "./cli/ui/palette.ts";
|
|
8
8
|
import { isBoard, isCard, isLabel, isList } from "./cli/ui/shapes.ts";
|
|
9
9
|
import { attachmentMime } from "./util/attachment.ts";
|
|
10
|
+
import { formatCardLine, formatCardListMarkdown } from "./util/card-display.ts";
|
|
10
11
|
|
|
11
12
|
describe("parseKvPairs", () => {
|
|
12
13
|
it("parses key=value pairs", () => {
|
|
@@ -124,3 +125,30 @@ describe("ui shapes", () => {
|
|
|
124
125
|
assert.ok(isBoard(board) && !isBoard(card) && !isBoard(list));
|
|
125
126
|
});
|
|
126
127
|
});
|
|
128
|
+
|
|
129
|
+
describe("card display markdown", () => {
|
|
130
|
+
it("formats linked title with labels and non-zero badges only", () => {
|
|
131
|
+
const line = formatCardLine({
|
|
132
|
+
name: "Publish module behavior",
|
|
133
|
+
shortUrl: "https://trello.com/c/1jH2UTAu",
|
|
134
|
+
labels: [{ name: "Backend", color: "blue" }],
|
|
135
|
+
badges: { comments: 2, attachments: 2, checkItems: 0, checkItemsChecked: 0 },
|
|
136
|
+
});
|
|
137
|
+
assert.match(
|
|
138
|
+
line,
|
|
139
|
+
/^\[Publish module behavior\]\(https:\/\/trello\.com\/c\/1jH2UTAu\)/,
|
|
140
|
+
);
|
|
141
|
+
assert.match(line, /🔵 `Backend`/);
|
|
142
|
+
assert.match(line, /💬 2/);
|
|
143
|
+
assert.match(line, /📎 2/);
|
|
144
|
+
assert.doesNotMatch(line, /✓/);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("builds numbered list with optional heading", () => {
|
|
148
|
+
const md = formatCardListMarkdown(
|
|
149
|
+
[{ name: "A", shortUrl: "https://trello.com/c/a" }],
|
|
150
|
+
"**To do**",
|
|
151
|
+
);
|
|
152
|
+
assert.equal(md, "**To do**\n\n1. [A](https://trello.com/c/a)");
|
|
153
|
+
});
|
|
154
|
+
});
|
package/src/mcp/handlers.ts
CHANGED
|
@@ -2,11 +2,19 @@ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { type JsonValue, type TrelloClient, TrelloError } from "../api/client.ts";
|
|
4
4
|
import { getClient } from "../cli/context.ts";
|
|
5
|
+
import {
|
|
6
|
+
CARD_LIST_DISPLAY_FORMAT,
|
|
7
|
+
type CardDisplayInput,
|
|
8
|
+
formatCardListMarkdown,
|
|
9
|
+
} from "../util/card-display.ts";
|
|
5
10
|
|
|
6
11
|
export const toolEnvelopeSchema = z.object({
|
|
7
12
|
ok: z.boolean(),
|
|
8
13
|
profile: z.string().optional(),
|
|
9
14
|
data: z.unknown().optional(),
|
|
15
|
+
/** Pre-rendered markdown card list — agents MUST show this to users when present. */
|
|
16
|
+
display: z.string().optional(),
|
|
17
|
+
displayFormat: z.string().optional(),
|
|
10
18
|
error: z.string().optional(),
|
|
11
19
|
status: z.number().optional(),
|
|
12
20
|
details: z.unknown().optional(),
|
|
@@ -17,13 +25,34 @@ export type ToolEnvelope = z.infer<typeof toolEnvelopeSchema>;
|
|
|
17
25
|
export const profileField = z.string().optional().describe("Auth profile name");
|
|
18
26
|
|
|
19
27
|
export function toolResult(envelope: ToolEnvelope): CallToolResult {
|
|
28
|
+
const text =
|
|
29
|
+
envelope.display && envelope.ok
|
|
30
|
+
? `${envelope.display}\n\n---\n${JSON.stringify(envelope)}`
|
|
31
|
+
: JSON.stringify(envelope);
|
|
20
32
|
return {
|
|
21
33
|
structuredContent: envelope,
|
|
22
|
-
content: [{ type: "text", text
|
|
34
|
+
content: [{ type: "text", text }],
|
|
23
35
|
isError: envelope.ok === false,
|
|
24
36
|
};
|
|
25
37
|
}
|
|
26
38
|
|
|
39
|
+
/** Attach markdown-v1 card list for agent display (see skills/trelly-mcp). */
|
|
40
|
+
export function cardListDisplay(
|
|
41
|
+
cards: CardDisplayInput[],
|
|
42
|
+
heading?: string,
|
|
43
|
+
): Pick<ToolEnvelope, "display" | "displayFormat"> {
|
|
44
|
+
if (cards.length === 0) {
|
|
45
|
+
return {
|
|
46
|
+
display: heading ? `${heading}\n\n(no cards)` : "(no cards)",
|
|
47
|
+
displayFormat: CARD_LIST_DISPLAY_FORMAT,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
display: formatCardListMarkdown(cards, heading),
|
|
52
|
+
displayFormat: CARD_LIST_DISPLAY_FORMAT,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
27
56
|
// Trello's badges object is ~400 chars of mostly unused keys; keep the 4 that matter.
|
|
28
57
|
const BADGE_KEYS = ["comments", "attachments", "checkItems", "checkItemsChecked"];
|
|
29
58
|
|
|
@@ -62,6 +91,38 @@ export function slimCards(data: unknown): unknown {
|
|
|
62
91
|
return data;
|
|
63
92
|
}
|
|
64
93
|
|
|
94
|
+
export async function withCardListResult(
|
|
95
|
+
profile: string | undefined,
|
|
96
|
+
fn: (client: TrelloClient) => Promise<unknown>,
|
|
97
|
+
heading?: string,
|
|
98
|
+
): Promise<CallToolResult> {
|
|
99
|
+
try {
|
|
100
|
+
const { profileName, client } = getClient(profile);
|
|
101
|
+
const raw = await fn(client);
|
|
102
|
+
const cards = slimCards(raw);
|
|
103
|
+
const arr = Array.isArray(cards) ? (cards as CardDisplayInput[]) : [];
|
|
104
|
+
return toolResult({
|
|
105
|
+
ok: true,
|
|
106
|
+
profile: profileName,
|
|
107
|
+
data: arr,
|
|
108
|
+
...cardListDisplay(arr, heading),
|
|
109
|
+
});
|
|
110
|
+
} catch (err) {
|
|
111
|
+
if (err instanceof TrelloError) {
|
|
112
|
+
return toolResult({
|
|
113
|
+
ok: false,
|
|
114
|
+
error: err.message,
|
|
115
|
+
status: err.status,
|
|
116
|
+
details: err.body,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return toolResult({
|
|
120
|
+
ok: false,
|
|
121
|
+
error: err instanceof Error ? err.message : String(err),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
65
126
|
export async function withClient<T>(
|
|
66
127
|
profile: string | undefined,
|
|
67
128
|
fn: (client: TrelloClient, profileName: string) => Promise<T>,
|
package/src/mcp/tools/boards.ts
CHANGED
|
@@ -2,8 +2,8 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import {
|
|
4
4
|
profileField,
|
|
5
|
-
slimCards,
|
|
6
5
|
toolEnvelopeSchema,
|
|
6
|
+
withCardListResult,
|
|
7
7
|
withClient,
|
|
8
8
|
} from "../handlers.ts";
|
|
9
9
|
|
|
@@ -120,23 +120,27 @@ export function registerBoardTools(server: McpServer): void {
|
|
|
120
120
|
server.registerTool(
|
|
121
121
|
"trello_board_cards",
|
|
122
122
|
{
|
|
123
|
-
description:
|
|
123
|
+
description:
|
|
124
|
+
"List all cards on a board. Default fields omit badges/labels — pass fields including badges,labels for rich `display`. When showing cards to the user, paste response `display` verbatim.",
|
|
124
125
|
inputSchema: {
|
|
125
126
|
profile: profileField,
|
|
126
127
|
boardId: z.string().min(1),
|
|
127
128
|
fields: z
|
|
128
129
|
.string()
|
|
129
|
-
.default("id,name,idList,due,dueComplete,shortUrl,closed")
|
|
130
|
+
.default("id,name,idList,due,dueComplete,shortUrl,closed,badges,labels")
|
|
130
131
|
.describe(
|
|
131
|
-
'comma-separated fields, "all" for everything;
|
|
132
|
+
'comma-separated fields, "all" for everything; badges,labels included by default for display',
|
|
132
133
|
),
|
|
134
|
+
displayHeading: z.string().optional(),
|
|
133
135
|
},
|
|
134
136
|
annotations: { readOnlyHint: true },
|
|
135
137
|
outputSchema,
|
|
136
138
|
},
|
|
137
|
-
async ({ profile, boardId, fields }) =>
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
async ({ profile, boardId, fields, displayHeading }) =>
|
|
140
|
+
withCardListResult(
|
|
141
|
+
profile,
|
|
142
|
+
(client) => client.boardCards(boardId, { fields }),
|
|
143
|
+
displayHeading,
|
|
140
144
|
),
|
|
141
145
|
);
|
|
142
146
|
}
|
package/src/mcp/tools/lists.ts
CHANGED
|
@@ -2,8 +2,8 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import {
|
|
4
4
|
profileField,
|
|
5
|
-
slimCards,
|
|
6
5
|
toolEnvelopeSchema,
|
|
6
|
+
withCardListResult,
|
|
7
7
|
withClient,
|
|
8
8
|
} from "../handlers.ts";
|
|
9
9
|
|
|
@@ -31,7 +31,8 @@ export function registerListTools(server: McpServer): void {
|
|
|
31
31
|
server.registerTool(
|
|
32
32
|
"trello_list_cards",
|
|
33
33
|
{
|
|
34
|
-
description:
|
|
34
|
+
description:
|
|
35
|
+
"List cards in a list. Response includes `display` (markdown-v1, linked titles + 💬📎✓⏰ badges) — when the user should SEE cards, paste `display` verbatim; do not reformat as a plain title list.",
|
|
35
36
|
inputSchema: {
|
|
36
37
|
profile: profileField,
|
|
37
38
|
listId: z.string().min(1),
|
|
@@ -39,13 +40,21 @@ export function registerListTools(server: McpServer): void {
|
|
|
39
40
|
.string()
|
|
40
41
|
.default("id,name,idList,due,dueComplete,shortUrl,closed,badges,labels")
|
|
41
42
|
.describe('comma-separated fields, "all" for everything'),
|
|
43
|
+
displayHeading: z
|
|
44
|
+
.string()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe(
|
|
47
|
+
'Optional markdown heading prepended to `display` (e.g. "**Dogster → To do**")',
|
|
48
|
+
),
|
|
42
49
|
},
|
|
43
50
|
annotations: { readOnlyHint: true },
|
|
44
51
|
outputSchema,
|
|
45
52
|
},
|
|
46
|
-
async ({ profile, listId, fields }) =>
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
async ({ profile, listId, fields, displayHeading }) =>
|
|
54
|
+
withCardListResult(
|
|
55
|
+
profile,
|
|
56
|
+
(client) => client.listCards(listId, { fields }),
|
|
57
|
+
displayHeading,
|
|
49
58
|
),
|
|
50
59
|
);
|
|
51
60
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { dueStatus, formatDue } from "../cli/ui/palette.ts";
|
|
2
|
+
|
|
3
|
+
/** Markdown emoji for Trello label colors (agent-facing card lists). */
|
|
4
|
+
const LABEL_DOT: Record<string, string> = {
|
|
5
|
+
red: "🔴",
|
|
6
|
+
orange: "🟠",
|
|
7
|
+
yellow: "🟡",
|
|
8
|
+
green: "🟢",
|
|
9
|
+
lime: "🟢",
|
|
10
|
+
blue: "🔵",
|
|
11
|
+
sky: "🔵",
|
|
12
|
+
sky_light: "🔵",
|
|
13
|
+
sky_dark: "🔵",
|
|
14
|
+
purple: "🟣",
|
|
15
|
+
pink: "🟣",
|
|
16
|
+
black: "⚫",
|
|
17
|
+
none: "⚪",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type CardDisplayInput = {
|
|
21
|
+
name?: string;
|
|
22
|
+
shortUrl?: string;
|
|
23
|
+
url?: string;
|
|
24
|
+
due?: string | null;
|
|
25
|
+
dueComplete?: boolean;
|
|
26
|
+
badges?: {
|
|
27
|
+
comments?: number;
|
|
28
|
+
attachments?: number;
|
|
29
|
+
checkItems?: number;
|
|
30
|
+
checkItemsChecked?: number;
|
|
31
|
+
};
|
|
32
|
+
labels?: Array<{ name?: string; color?: string | null }>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function labelDot(color: string | null | undefined): string {
|
|
36
|
+
if (!color) return "⚪";
|
|
37
|
+
const base = color.split("_")[0] ?? color;
|
|
38
|
+
return LABEL_DOT[color] ?? LABEL_DOT[base] ?? "⚪";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function labelParts(labels: CardDisplayInput["labels"]): string[] {
|
|
42
|
+
if (!labels?.length) return [];
|
|
43
|
+
return labels
|
|
44
|
+
.map((label) => {
|
|
45
|
+
const name = (label.name ?? "").trim();
|
|
46
|
+
const dot = labelDot(label.color);
|
|
47
|
+
return name ? `${dot} \`${name}\`` : dot;
|
|
48
|
+
})
|
|
49
|
+
.filter(Boolean);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function badgeParts(badges: CardDisplayInput["badges"]): string[] {
|
|
53
|
+
if (!badges) return [];
|
|
54
|
+
const parts: string[] = [];
|
|
55
|
+
const comments = badges.comments ?? 0;
|
|
56
|
+
const attachments = badges.attachments ?? 0;
|
|
57
|
+
const total = badges.checkItems ?? 0;
|
|
58
|
+
const checked = badges.checkItemsChecked ?? 0;
|
|
59
|
+
if (comments > 0) parts.push(`💬 ${comments}`);
|
|
60
|
+
if (attachments > 0) parts.push(`📎 ${attachments}`);
|
|
61
|
+
if (total > 0) parts.push(`✓ ${checked}/${total}`);
|
|
62
|
+
return parts;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function duePart(
|
|
66
|
+
due: string | null | undefined,
|
|
67
|
+
dueComplete: boolean | undefined,
|
|
68
|
+
): string {
|
|
69
|
+
if (!due) return "";
|
|
70
|
+
const status = dueStatus(due, dueComplete);
|
|
71
|
+
const label = formatDue(due);
|
|
72
|
+
if (status === "complete") return `⏰ ${label} ✓`;
|
|
73
|
+
if (status === "overdue") return `⏰ ${label} (overdue)`;
|
|
74
|
+
return `⏰ ${label}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** One markdown line: `[name](shortUrl)` + labels + non-zero badges + due. */
|
|
78
|
+
export function formatCardLine(card: CardDisplayInput, index?: number): string {
|
|
79
|
+
const name = card.name ?? "(untitled)";
|
|
80
|
+
const href = card.shortUrl ?? card.url ?? "";
|
|
81
|
+
const prefix = index === undefined ? "" : `${index}. `;
|
|
82
|
+
const title = href ? `[${name}](${href})` : name;
|
|
83
|
+
const extras = [...labelParts(card.labels), ...badgeParts(card.badges)];
|
|
84
|
+
const due = duePart(card.due, card.dueComplete);
|
|
85
|
+
if (due) extras.push(due);
|
|
86
|
+
if (extras.length === 0) return `${prefix}${title}`;
|
|
87
|
+
return `${prefix}${title} · ${extras.join(" · ")}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Numbered markdown list for agent/user display (`displayFormat: markdown-v1`). */
|
|
91
|
+
export function formatCardListMarkdown(
|
|
92
|
+
cards: CardDisplayInput[],
|
|
93
|
+
heading?: string,
|
|
94
|
+
): string {
|
|
95
|
+
const lines = cards.map((card, i) => formatCardLine(card, i + 1));
|
|
96
|
+
if (!heading) return lines.join("\n");
|
|
97
|
+
return `${heading}\n\n${lines.join("\n")}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const CARD_LIST_DISPLAY_FORMAT = "markdown-v1";
|