smashspace 0.2.2 → 0.4.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/README.md +39 -0
- package/dist/smash.mjs +193 -12
- package/dist/smashspace-mcp.mjs +88 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -112,6 +112,45 @@ smash card update <cardId> --desc-file spec.md # multi-line body from a file /
|
|
|
112
112
|
Pick a board with `-b <label>` (defaults to `main`), or bypass config with
|
|
113
113
|
`--board-id <id>`.
|
|
114
114
|
|
|
115
|
+
## Agent inbox: who said what to whom
|
|
116
|
+
|
|
117
|
+
Comments carry an optional **recipient**, so several agents can hand work to
|
|
118
|
+
each other on the card it is about — no separate channel, and a human reads the
|
|
119
|
+
same thread in the UI.
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
smash comment add <cardId> "PR is up, please review" --author codex --to claude-code
|
|
123
|
+
smash inbox --to claude-code # only what is addressed to me
|
|
124
|
+
smash inbox --since <cursor> # only what I have not read yet
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`smash inbox` prints a `cursor` at the end; pass it as the next `--since` and
|
|
128
|
+
you read each message once. `--json` gives `{ comments, cursor }`.
|
|
129
|
+
|
|
130
|
+
## Write from CI or a cloud job (no CLI, no MCP)
|
|
131
|
+
|
|
132
|
+
For environments where you cannot install anything — a scheduled cloud run, a
|
|
133
|
+
webhook handler, a GitHub Action — issue a **board-scoped write-only token** and
|
|
134
|
+
post with `curl`:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
smash token create <boardId> "GitHub Actions" # shown once; store it now
|
|
138
|
+
smash token list <boardId> # prefix / created / last used
|
|
139
|
+
smash token revoke <boardId> <tokenId> # immediate
|
|
140
|
+
|
|
141
|
+
curl -X POST https://smashspace.app/api/ingest \
|
|
142
|
+
-H "Authorization: Bearer smash_ing_xxxxxxxx" \
|
|
143
|
+
-H "Content-Type: application/json" \
|
|
144
|
+
-H "Idempotency-Key: my-event-123" \
|
|
145
|
+
-d '{"list":"やること","title":"支払いエラー","labels":["緊急"]}'
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The token can only create cards and comments on that one board — it cannot read
|
|
149
|
+
or delete. Lists and labels are matched by name, so the caller needs no internal
|
|
150
|
+
ids, and a repeated `Idempotency-Key` returns the first card instead of making
|
|
151
|
+
another (keys never expire). Tokens can also be issued from the space's
|
|
152
|
+
**Settings → Connect** tab.
|
|
153
|
+
|
|
115
154
|
## Multiple repos on one machine
|
|
116
155
|
|
|
117
156
|
`smash` is **directory-aware**: it walks up from your current directory to the
|
package/dist/smash.mjs
CHANGED
|
@@ -4202,15 +4202,64 @@ cardCmd.command("search <query>").description(
|
|
|
4202
4202
|
console.log(`
|
|
4203
4203
|
${allHits.length} card(s) found`);
|
|
4204
4204
|
});
|
|
4205
|
-
cardCmd.command("comment <cardId> <body>").description("Add a comment to a card").option("--author <name>", "Author name").
|
|
4205
|
+
cardCmd.command("comment <cardId> <body>").description("Add a comment to a card").option("--author <name>", "Author name").option(
|
|
4206
|
+
"--to <id>",
|
|
4207
|
+
"Address the comment to an assignee id (repeatable). Agents poll their own with `smash inbox --to <id>`.",
|
|
4208
|
+
collectRepeated,
|
|
4209
|
+
[]
|
|
4210
|
+
).action(
|
|
4206
4211
|
async (cardId, body, opts) => {
|
|
4207
4212
|
const comment = await api(`/api/cards/${cardId}/comments`, {
|
|
4208
4213
|
method: "POST",
|
|
4209
|
-
body: JSON.stringify({
|
|
4214
|
+
body: JSON.stringify({
|
|
4215
|
+
body,
|
|
4216
|
+
authorName: opts.author,
|
|
4217
|
+
...opts.to.length > 0 ? { recipients: opts.to } : {}
|
|
4218
|
+
})
|
|
4210
4219
|
});
|
|
4211
|
-
show(comment, [
|
|
4220
|
+
show(comment, [
|
|
4221
|
+
`\u2713 comment ${comment.id} added${comment.recipients?.length ? ` \u2192 ${comment.recipients.join(", ")}` : ""}`
|
|
4222
|
+
]);
|
|
4212
4223
|
}
|
|
4213
4224
|
);
|
|
4225
|
+
function collectRepeated(value, previous) {
|
|
4226
|
+
return [...previous, value];
|
|
4227
|
+
}
|
|
4228
|
+
program2.command("inbox").description(
|
|
4229
|
+
`New comments across the resolved board (${CONFIG_FILENAME}). Poll with --since <cursor> to read only what you have not seen.`
|
|
4230
|
+
).option("-b, --board <label>", "board label from config").option("--board-id <id>", "explicit board id (bypass config)").option("--since <iso>", "only comments created after this ISO timestamp (exclusive)").option("--to <id>", "only comments addressed to this assignee id").option("--limit <n>", "max comments to read (default 100, max 500)").action(async (opts) => {
|
|
4231
|
+
const ctx = resolveContext(opts);
|
|
4232
|
+
const params = new URLSearchParams();
|
|
4233
|
+
if (opts.since)
|
|
4234
|
+
params.set("since", opts.since);
|
|
4235
|
+
if (opts.to)
|
|
4236
|
+
params.set("to", opts.to);
|
|
4237
|
+
if (opts.limit)
|
|
4238
|
+
params.set("limit", opts.limit);
|
|
4239
|
+
const qs = params.toString();
|
|
4240
|
+
const result = await api(
|
|
4241
|
+
`/api/boards/${ctx.boardId}/comments${qs ? `?${qs}` : ""}`,
|
|
4242
|
+
void 0,
|
|
4243
|
+
ctx.baseUrl
|
|
4244
|
+
);
|
|
4245
|
+
if (getOpts().json) {
|
|
4246
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4247
|
+
return;
|
|
4248
|
+
}
|
|
4249
|
+
if (result.comments.length === 0) {
|
|
4250
|
+
console.log(`(no new comments)${result.cursor ? ` cursor: ${result.cursor}` : ""}`);
|
|
4251
|
+
return;
|
|
4252
|
+
}
|
|
4253
|
+
for (const c of result.comments) {
|
|
4254
|
+
const to = c.recipients?.length ? ` \u2192 ${c.recipients.join(", ")}` : "";
|
|
4255
|
+
console.log(
|
|
4256
|
+
`[${c.createdAt}] ${c.authorName ?? "anon"}${to} (${c.listTitle} / ${c.cardTitle} \xB7 ${c.cardId})`
|
|
4257
|
+
);
|
|
4258
|
+
console.log(` ${c.body.replace(/\n/g, "\n ")}`);
|
|
4259
|
+
}
|
|
4260
|
+
console.log(`
|
|
4261
|
+
cursor: ${result.cursor}`);
|
|
4262
|
+
});
|
|
4214
4263
|
var archiveCmd = program2.command("archive [cardId]").description("Archive a card (soft-delete). Without cardId: manage archived cards (list/restore/purge)").option("-b, --board <label>", `board label from ${CONFIG_FILENAME}`).option("--board-id <id>", "explicit board id (bypass config)").action(async (cardId, opts) => {
|
|
4215
4264
|
if (!cardId) {
|
|
4216
4265
|
archiveCmd.help();
|
|
@@ -4278,12 +4327,18 @@ checklistCmd.command("delete <itemId>").description("Delete a checklist item").a
|
|
|
4278
4327
|
console.log(`\u2713 checklist item ${itemId} deleted`);
|
|
4279
4328
|
});
|
|
4280
4329
|
var commentCmd = program2.command("comment").description("Card comments (add / edit / delete)");
|
|
4281
|
-
commentCmd.command("add <cardId> <body>").description("Add a comment to a card").option("--author <name>", "Author name").action(async (cardId, body, opts) => {
|
|
4330
|
+
commentCmd.command("add <cardId> <body>").description("Add a comment to a card").option("--author <name>", "Author name").option("--to <id>", "Address the comment to an assignee id (repeatable)", collectRepeated, []).action(async (cardId, body, opts) => {
|
|
4282
4331
|
const comment = await api(`/api/cards/${cardId}/comments`, {
|
|
4283
4332
|
method: "POST",
|
|
4284
|
-
body: JSON.stringify({
|
|
4333
|
+
body: JSON.stringify({
|
|
4334
|
+
body,
|
|
4335
|
+
authorName: opts.author,
|
|
4336
|
+
...opts.to.length > 0 ? { recipients: opts.to } : {}
|
|
4337
|
+
})
|
|
4285
4338
|
});
|
|
4286
|
-
show(comment, [
|
|
4339
|
+
show(comment, [
|
|
4340
|
+
`\u2713 comment ${comment.id} added${comment.recipients?.length ? ` \u2192 ${comment.recipients.join(", ")}` : ""}`
|
|
4341
|
+
]);
|
|
4287
4342
|
});
|
|
4288
4343
|
commentCmd.command("edit <commentId> <body>").description("Edit (replace body of) an existing comment").action(async (commentId, body) => {
|
|
4289
4344
|
const comment = await api(`/api/comments/${commentId}`, {
|
|
@@ -4396,6 +4451,48 @@ attachmentCmd.command("delete <attachmentId>").description("Delete an attachment
|
|
|
4396
4451
|
await api(`/api/attachments/${attachmentId}`, { method: "DELETE" });
|
|
4397
4452
|
console.log(`\u2713 attachment ${attachmentId} deleted`);
|
|
4398
4453
|
});
|
|
4454
|
+
var tokenCmd = program2.command("token").description("Board-scoped write tokens for external services (POST /api/ingest)");
|
|
4455
|
+
tokenCmd.command("create <boardId> <name>").description("Issue a write-only token for one board. The token is shown once.").action(async (boardId, name) => {
|
|
4456
|
+
const created = await api(
|
|
4457
|
+
`/api/boards/${boardId}/ingest-tokens`,
|
|
4458
|
+
{ method: "POST", body: JSON.stringify({ name }) }
|
|
4459
|
+
);
|
|
4460
|
+
show(created, [
|
|
4461
|
+
`\u2713 token ${created.id} created for ${boardId}`,
|
|
4462
|
+
"",
|
|
4463
|
+
created.token,
|
|
4464
|
+
"",
|
|
4465
|
+
"Store it now \u2014 it is not shown again. It can only create cards and",
|
|
4466
|
+
"comments on this board:",
|
|
4467
|
+
` curl -X POST ${resolveBaseUrl()}/api/ingest \\`,
|
|
4468
|
+
` -H "Authorization: Bearer ${created.token}" \\`,
|
|
4469
|
+
' -H "Content-Type: application/json" \\',
|
|
4470
|
+
` -d '{"list":"\u3084\u308B\u3053\u3068","title":"\u2026"}'`
|
|
4471
|
+
]);
|
|
4472
|
+
});
|
|
4473
|
+
tokenCmd.command("list <boardId>").description("List a board's write tokens (prefix, created, last used)").action(async (boardId) => {
|
|
4474
|
+
const items = await api(`/api/boards/${boardId}/ingest-tokens`);
|
|
4475
|
+
if (getOpts().json) {
|
|
4476
|
+
console.log(JSON.stringify(items, null, 2));
|
|
4477
|
+
return;
|
|
4478
|
+
}
|
|
4479
|
+
if (items.length === 0) {
|
|
4480
|
+
console.log("(no tokens)");
|
|
4481
|
+
return;
|
|
4482
|
+
}
|
|
4483
|
+
for (const t of items) {
|
|
4484
|
+
console.log(
|
|
4485
|
+
`${t.id} ${t.name} ${t.tokenPrefix}\u2026 created ${t.createdAt} last used ${t.lastUsedAt ?? "never"}`
|
|
4486
|
+
);
|
|
4487
|
+
}
|
|
4488
|
+
});
|
|
4489
|
+
tokenCmd.command("revoke <boardId> <tokenId>").description("Revoke a write token (takes effect immediately)").action(async (boardId, tokenId) => {
|
|
4490
|
+
const revoked = await api(
|
|
4491
|
+
`/api/boards/${boardId}/ingest-tokens/${tokenId}`,
|
|
4492
|
+
{ method: "DELETE" }
|
|
4493
|
+
);
|
|
4494
|
+
show(revoked, [`\u2713 token ${revoked.id} revoked at ${revoked.revokedAt}`]);
|
|
4495
|
+
});
|
|
4399
4496
|
var labelCmd = program2.command("label").description("Labels");
|
|
4400
4497
|
labelCmd.command("create <boardId> <name>").description("Create a board label").option(
|
|
4401
4498
|
"--color <color>",
|
|
@@ -4410,18 +4507,56 @@ labelCmd.command("create <boardId> <name>").description("Create a board label").
|
|
|
4410
4507
|
show(label, [`\u2713 label ${label.id} created (${label.name}/${label.color})`]);
|
|
4411
4508
|
}
|
|
4412
4509
|
);
|
|
4413
|
-
labelCmd.command("
|
|
4510
|
+
labelCmd.command("list [boardId]").description(`List a board's labels (id, name, color, card count). Board from ${CONFIG_FILENAME} when omitted`).option("-b, --board <label>", "board label from config").option("--board-id <id>", "explicit board id (bypass config)").action(async (boardIdArg, opts) => {
|
|
4511
|
+
const ctx = resolveContext({ board: opts.board, boardId: boardIdArg ?? opts.boardId });
|
|
4512
|
+
const detail = await fetchBoard(ctx.boardId, ctx.baseUrl);
|
|
4513
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4514
|
+
for (const l of detail.lists)
|
|
4515
|
+
for (const c of l.cards)
|
|
4516
|
+
for (const id of c.labelIds)
|
|
4517
|
+
counts.set(id, (counts.get(id) ?? 0) + 1);
|
|
4518
|
+
const rows = detail.labels.map((l) => ({ ...l, cardCount: counts.get(l.id) ?? 0 }));
|
|
4519
|
+
if (getOpts().json) {
|
|
4520
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
4521
|
+
return;
|
|
4522
|
+
}
|
|
4523
|
+
if (rows.length === 0) {
|
|
4524
|
+
console.log("(no labels)");
|
|
4525
|
+
return;
|
|
4526
|
+
}
|
|
4527
|
+
for (const r of rows)
|
|
4528
|
+
console.log(`${r.id} ${r.name} (${r.color ?? "none"}) ${r.cardCount} cards`);
|
|
4529
|
+
});
|
|
4530
|
+
async function resolveCardLabel(cardId, idOrName) {
|
|
4531
|
+
const detail = await api(`/api/cards/${cardId}`);
|
|
4532
|
+
const labels = await api(`/api/boards/${detail.card.boardId}/labels`);
|
|
4533
|
+
const byId = labels.find((l) => l.id === idOrName);
|
|
4534
|
+
if (byId)
|
|
4535
|
+
return byId;
|
|
4536
|
+
const lc = idOrName.toLowerCase();
|
|
4537
|
+
const byName = labels.filter((l) => l.name.toLowerCase() === lc);
|
|
4538
|
+
if (byName.length === 1)
|
|
4539
|
+
return byName[0];
|
|
4540
|
+
if (byName.length > 1)
|
|
4541
|
+
throw new Error(`Label name "${idOrName}" matches ${byName.length} labels; pass the id (${byName.map((l) => l.id).join(", ")})`);
|
|
4542
|
+
throw new Error(
|
|
4543
|
+
`Label "${idOrName}" not found on this board. Labels: ${labels.map((l) => l.name).join(", ") || "(none)"}`
|
|
4544
|
+
);
|
|
4545
|
+
}
|
|
4546
|
+
labelCmd.command("attach <cardId> <label>").description("Attach a label to a card (label id or name)").action(async (cardId, idOrName) => {
|
|
4547
|
+
const label = await resolveCardLabel(cardId, idOrName);
|
|
4414
4548
|
await api(`/api/cards/${cardId}/labels`, {
|
|
4415
4549
|
method: "POST",
|
|
4416
|
-
body: JSON.stringify({ labelId })
|
|
4550
|
+
body: JSON.stringify({ labelId: label.id })
|
|
4417
4551
|
});
|
|
4418
|
-
console.log(`\u2713 label ${
|
|
4552
|
+
console.log(`\u2713 label ${label.name} (${label.id}) attached to ${cardId}`);
|
|
4419
4553
|
});
|
|
4420
|
-
labelCmd.command("detach <cardId> <
|
|
4421
|
-
await
|
|
4554
|
+
labelCmd.command("detach <cardId> <label>").description("Detach a label from a card (label id or name)").action(async (cardId, idOrName) => {
|
|
4555
|
+
const label = await resolveCardLabel(cardId, idOrName);
|
|
4556
|
+
await api(`/api/cards/${cardId}/labels/${label.id}`, {
|
|
4422
4557
|
method: "DELETE"
|
|
4423
4558
|
});
|
|
4424
|
-
console.log(`\u2713 label ${
|
|
4559
|
+
console.log(`\u2713 label ${label.name} (${label.id}) detached from ${cardId}`);
|
|
4425
4560
|
});
|
|
4426
4561
|
labelCmd.command("update <labelId>").description("Update a label's name and/or color").option("--name <name>", "New label name").option("--color <color>", "Color (green/lime/yellow/orange/red/pink/purple/blue/sky/gray), or empty string to clear").action(async (labelId, opts) => {
|
|
4427
4562
|
const patch = {};
|
|
@@ -4581,6 +4716,52 @@ assigneesCmd.command("add <cardId>").description("Append a single assignee (read
|
|
|
4581
4716
|
});
|
|
4582
4717
|
show(card, [`\u2713 card ${card.id} assignees now has ${next.length} entries`]);
|
|
4583
4718
|
});
|
|
4719
|
+
assigneesCmd.command("update <cardId> <assigneeId>").description("Change an assignee's icon (emoji) and/or display name").option("--emoji <emoji>", "New avatar_emoji").option("--no-emoji", "Remove the avatar_emoji (fall back to the default icon)").option("--name <name>", "New display_name").action(async (cardId, assigneeId, opts) => {
|
|
4720
|
+
if (opts.emoji === void 0 && opts.name === void 0) {
|
|
4721
|
+
console.error("Pass --emoji <e>, --no-emoji, or --name <name>");
|
|
4722
|
+
process.exit(1);
|
|
4723
|
+
}
|
|
4724
|
+
if (opts.name !== void 0 && !opts.name.trim()) {
|
|
4725
|
+
console.error("--name must not be empty");
|
|
4726
|
+
process.exit(1);
|
|
4727
|
+
}
|
|
4728
|
+
const detail = await api(`/api/cards/${cardId}`);
|
|
4729
|
+
const current = detail.card.assignees ?? [];
|
|
4730
|
+
if (!current.some((a) => a.id === assigneeId)) {
|
|
4731
|
+
console.error(`No assignee with id "${assigneeId}" on card ${cardId}`);
|
|
4732
|
+
process.exit(1);
|
|
4733
|
+
}
|
|
4734
|
+
const next = current.map((a) => {
|
|
4735
|
+
if (a.id !== assigneeId)
|
|
4736
|
+
return a;
|
|
4737
|
+
const { avatar_emoji, ...rest } = a;
|
|
4738
|
+
const emoji = opts.emoji === void 0 ? avatar_emoji : opts.emoji || void 0;
|
|
4739
|
+
return {
|
|
4740
|
+
...rest,
|
|
4741
|
+
...opts.name !== void 0 ? { display_name: opts.name } : {},
|
|
4742
|
+
...emoji ? { avatar_emoji: emoji } : {}
|
|
4743
|
+
};
|
|
4744
|
+
});
|
|
4745
|
+
const card = await api(`/api/cards/${cardId}`, {
|
|
4746
|
+
method: "PATCH",
|
|
4747
|
+
body: JSON.stringify({ assignees: next })
|
|
4748
|
+
});
|
|
4749
|
+
show(card, [`\u2713 card ${card.id} assignee ${assigneeId} updated`]);
|
|
4750
|
+
});
|
|
4751
|
+
assigneesCmd.command("remove <cardId> <assigneeId>").description("Remove a single assignee by id").action(async (cardId, assigneeId) => {
|
|
4752
|
+
const detail = await api(`/api/cards/${cardId}`);
|
|
4753
|
+
const current = detail.card.assignees ?? [];
|
|
4754
|
+
const next = current.filter((a) => a.id !== assigneeId);
|
|
4755
|
+
if (next.length === current.length) {
|
|
4756
|
+
console.error(`No assignee with id "${assigneeId}" on card ${cardId}`);
|
|
4757
|
+
process.exit(1);
|
|
4758
|
+
}
|
|
4759
|
+
const card = await api(`/api/cards/${cardId}`, {
|
|
4760
|
+
method: "PATCH",
|
|
4761
|
+
body: JSON.stringify({ assignees: next.length === 0 ? null : next })
|
|
4762
|
+
});
|
|
4763
|
+
show(card, [`\u2713 card ${card.id} assignees now has ${next.length} entries`]);
|
|
4764
|
+
});
|
|
4584
4765
|
assigneesCmd.command("clear <cardId>").description("Clear assignees on a card (set to null)").action(async (cardId) => {
|
|
4585
4766
|
const card = await api(`/api/cards/${cardId}`, {
|
|
4586
4767
|
method: "PATCH",
|
package/dist/smashspace-mcp.mjs
CHANGED
|
@@ -15469,6 +15469,24 @@ async function api(path, init = {}, baseUrlOverride) {
|
|
|
15469
15469
|
return void 0;
|
|
15470
15470
|
return await res.json();
|
|
15471
15471
|
}
|
|
15472
|
+
async function resolveCardLabel(cardId, idOrName) {
|
|
15473
|
+
if (typeof idOrName !== "string" || !idOrName)
|
|
15474
|
+
throw new Error("`label` (id or name) is required");
|
|
15475
|
+
const detail = await api(`/api/cards/${cardId}`);
|
|
15476
|
+
const labels = await api(`/api/boards/${detail.card.boardId}/labels`);
|
|
15477
|
+
const byId = labels.find((l) => l.id === idOrName);
|
|
15478
|
+
if (byId)
|
|
15479
|
+
return byId;
|
|
15480
|
+
const lc = idOrName.toLowerCase();
|
|
15481
|
+
const byName = labels.filter((l) => l.name.toLowerCase() === lc);
|
|
15482
|
+
if (byName.length === 1)
|
|
15483
|
+
return byName[0];
|
|
15484
|
+
if (byName.length > 1)
|
|
15485
|
+
throw new Error(`Label name "${idOrName}" matches ${byName.length} labels; pass the id (${byName.map((l) => l.id).join(", ")})`);
|
|
15486
|
+
throw new Error(
|
|
15487
|
+
`Label "${idOrName}" not found on this board. Labels: ${labels.map((l) => l.name).join(", ") || "(none)"}`
|
|
15488
|
+
);
|
|
15489
|
+
}
|
|
15472
15490
|
var tools = [
|
|
15473
15491
|
{
|
|
15474
15492
|
name: "smash_create_board",
|
|
@@ -15635,7 +15653,7 @@ var tools = [
|
|
|
15635
15653
|
},
|
|
15636
15654
|
{
|
|
15637
15655
|
name: "smash_set_agent_meta",
|
|
15638
|
-
description: "Set or clear a card's agent_meta (structured field for external agents like Claude Code). Pass agentMeta=null to clear. Common shape: {primary_agent, status: 'idle'|'working'|'waiting_review'|'blocked'|'done', turn: 'human'|'agent'|'blocked', started_at, updated_at, progress (0..1), current_step}. Extra keys are accepted for forward-compat.",
|
|
15656
|
+
description: "Set or clear a card's agent_meta (structured field for external agents like Claude Code). Pass agentMeta=null to clear. Common shape: {primary_agent, status: 'idle'|'working'|'waiting_review'|'blocked'|'done', turn: 'human'|'agent'|'blocked', started_at, updated_at, progress (0..1), current_step}. avatar_emoji is shown on the board's cards, so give each agent a distinct emoji (and reuse the same id) to make ownership visible at a glance. Extra keys are accepted for forward-compat.",
|
|
15639
15657
|
inputSchema: {
|
|
15640
15658
|
type: "object",
|
|
15641
15659
|
properties: {
|
|
@@ -15748,21 +15766,55 @@ var tools = [
|
|
|
15748
15766
|
},
|
|
15749
15767
|
{
|
|
15750
15768
|
name: "smash_add_comment",
|
|
15751
|
-
description: "Add a comment to a card. Markdown is supported.",
|
|
15769
|
+
description: "Add a comment to a card. Markdown is supported. Use `recipients` to address the comment to specific assignee ids (the same ids as smash_set_assignees), so the addressee can poll just its own messages with smash_list_new_comments. Agent-to-agent hand-offs belong on the card they are about \u2014 there is no separate channel, which keeps the exchange attached to the work.",
|
|
15752
15770
|
inputSchema: {
|
|
15753
15771
|
type: "object",
|
|
15754
15772
|
properties: {
|
|
15755
15773
|
cardId: { type: "string" },
|
|
15756
15774
|
body: { type: "string" },
|
|
15757
|
-
authorName: { type: "string" }
|
|
15775
|
+
authorName: { type: "string" },
|
|
15776
|
+
recipients: {
|
|
15777
|
+
type: "array",
|
|
15778
|
+
items: { type: "string" },
|
|
15779
|
+
description: "Assignee ids this comment is addressed to (e.g. ['claude-code'])"
|
|
15780
|
+
}
|
|
15758
15781
|
},
|
|
15759
15782
|
required: ["cardId", "body"]
|
|
15760
15783
|
},
|
|
15761
15784
|
handler: async (args) => api(`/api/cards/${args.cardId}/comments`, {
|
|
15762
15785
|
method: "POST",
|
|
15763
|
-
body: JSON.stringify({
|
|
15786
|
+
body: JSON.stringify({
|
|
15787
|
+
body: args.body,
|
|
15788
|
+
authorName: args.authorName,
|
|
15789
|
+
...Array.isArray(args.recipients) && args.recipients.length > 0 ? { recipients: args.recipients } : {}
|
|
15790
|
+
})
|
|
15764
15791
|
})
|
|
15765
15792
|
},
|
|
15793
|
+
{
|
|
15794
|
+
name: "smash_list_new_comments",
|
|
15795
|
+
description: "Read comments across a whole board in created order \u2014 an agent inbox. Pass `since` (an ISO timestamp, exclusive) to get only what you have not seen, and `to` to keep just the comments addressed to you. The result carries a `cursor`; store it and pass it as the next `since`. Prefer this over re-reading the board: it is one request and returns only new messages.",
|
|
15796
|
+
inputSchema: {
|
|
15797
|
+
type: "object",
|
|
15798
|
+
properties: {
|
|
15799
|
+
boardId: { type: "string" },
|
|
15800
|
+
since: { type: "string", description: "ISO timestamp; returns comments created after it" },
|
|
15801
|
+
to: { type: "string", description: "Only comments addressed to this assignee id" },
|
|
15802
|
+
limit: { type: "number", description: "Max comments to read (default 100, max 500)" }
|
|
15803
|
+
},
|
|
15804
|
+
required: ["boardId"]
|
|
15805
|
+
},
|
|
15806
|
+
handler: async (args) => {
|
|
15807
|
+
const params = new URLSearchParams();
|
|
15808
|
+
if (typeof args.since === "string" && args.since)
|
|
15809
|
+
params.set("since", args.since);
|
|
15810
|
+
if (typeof args.to === "string" && args.to)
|
|
15811
|
+
params.set("to", args.to);
|
|
15812
|
+
if (typeof args.limit === "number")
|
|
15813
|
+
params.set("limit", String(args.limit));
|
|
15814
|
+
const qs = params.toString();
|
|
15815
|
+
return api(`/api/boards/${args.boardId}/comments${qs ? `?${qs}` : ""}`);
|
|
15816
|
+
}
|
|
15817
|
+
},
|
|
15766
15818
|
{
|
|
15767
15819
|
name: "smash_add_checklist_item",
|
|
15768
15820
|
description: "Add a checklist item to a card.",
|
|
@@ -15839,42 +15891,64 @@ var tools = [
|
|
|
15839
15891
|
body: JSON.stringify({ name: args.name, color: args.color })
|
|
15840
15892
|
})
|
|
15841
15893
|
},
|
|
15894
|
+
{
|
|
15895
|
+
name: "smash_list_labels",
|
|
15896
|
+
description: "List a board's labels with id, name, color, and how many cards carry each. Use this to find a label before attaching it.",
|
|
15897
|
+
inputSchema: {
|
|
15898
|
+
type: "object",
|
|
15899
|
+
properties: { boardId: { type: "string" } },
|
|
15900
|
+
required: ["boardId"]
|
|
15901
|
+
},
|
|
15902
|
+
handler: async (args) => {
|
|
15903
|
+
const detail = await api(`/api/boards/${args.boardId}`);
|
|
15904
|
+
const counts = /* @__PURE__ */ new Map();
|
|
15905
|
+
for (const l of detail.lists)
|
|
15906
|
+
for (const c of l.cards)
|
|
15907
|
+
for (const id of c.labelIds)
|
|
15908
|
+
counts.set(id, (counts.get(id) ?? 0) + 1);
|
|
15909
|
+
return detail.labels.map((l) => ({ ...l, cardCount: counts.get(l.id) ?? 0 }));
|
|
15910
|
+
}
|
|
15911
|
+
},
|
|
15842
15912
|
{
|
|
15843
15913
|
name: "smash_attach_label",
|
|
15844
|
-
description: "Attach a board label to a card.",
|
|
15914
|
+
description: "Attach a board label to a card. `label` may be the label id or its name (case-insensitive) on the card's board.",
|
|
15845
15915
|
inputSchema: {
|
|
15846
15916
|
type: "object",
|
|
15847
15917
|
properties: {
|
|
15848
15918
|
cardId: { type: "string" },
|
|
15849
|
-
|
|
15919
|
+
label: { type: "string", description: "Label id or name" },
|
|
15920
|
+
labelId: { type: "string", description: "Deprecated alias of `label`" }
|
|
15850
15921
|
},
|
|
15851
|
-
required: ["cardId"
|
|
15922
|
+
required: ["cardId"]
|
|
15852
15923
|
},
|
|
15853
15924
|
handler: async (args) => {
|
|
15925
|
+
const label = await resolveCardLabel(String(args.cardId), args.label ?? args.labelId);
|
|
15854
15926
|
await api(`/api/cards/${args.cardId}/labels`, {
|
|
15855
15927
|
method: "POST",
|
|
15856
|
-
body: JSON.stringify({ labelId:
|
|
15928
|
+
body: JSON.stringify({ labelId: label.id })
|
|
15857
15929
|
});
|
|
15858
|
-
return { ok: true };
|
|
15930
|
+
return { ok: true, label };
|
|
15859
15931
|
}
|
|
15860
15932
|
},
|
|
15861
15933
|
{
|
|
15862
15934
|
name: "smash_detach_label",
|
|
15863
|
-
description: "Detach a label from a card.",
|
|
15935
|
+
description: "Detach a label from a card. `label` may be the label id or its name (case-insensitive).",
|
|
15864
15936
|
inputSchema: {
|
|
15865
15937
|
type: "object",
|
|
15866
15938
|
properties: {
|
|
15867
15939
|
cardId: { type: "string" },
|
|
15868
|
-
|
|
15940
|
+
label: { type: "string", description: "Label id or name" },
|
|
15941
|
+
labelId: { type: "string", description: "Deprecated alias of `label`" }
|
|
15869
15942
|
},
|
|
15870
|
-
required: ["cardId"
|
|
15943
|
+
required: ["cardId"]
|
|
15871
15944
|
},
|
|
15872
15945
|
handler: async (args) => {
|
|
15946
|
+
const label = await resolveCardLabel(String(args.cardId), args.label ?? args.labelId);
|
|
15873
15947
|
await api(
|
|
15874
|
-
`/api/cards/${args.cardId}/labels/${
|
|
15948
|
+
`/api/cards/${args.cardId}/labels/${label.id}`,
|
|
15875
15949
|
{ method: "DELETE" }
|
|
15876
15950
|
);
|
|
15877
|
-
return { ok: true };
|
|
15951
|
+
return { ok: true, label };
|
|
15878
15952
|
}
|
|
15879
15953
|
},
|
|
15880
15954
|
{
|