smashspace 0.5.0 → 0.8.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/dist/smash.mjs CHANGED
@@ -4128,7 +4128,7 @@ ${detail.card.description}`);
4128
4128
  }
4129
4129
  }
4130
4130
  });
4131
- cardCmd.command("update <cardId>").description("Update card fields (title, description, due date, cover, checklist title)").option("--title <title>", "New title").option("-d, --description <description>", "New description (markdown)").option("--desc <description>", "alias for --description").option("--desc-file <path>", "read description from a file (or - for stdin)").option("--due <iso>", "Due date as ISO string, or empty string to clear").option("--cover <attachmentId>", "Cover attachment ID, or empty string to clear").option("--checklist-title <title>", "Custom checklist section title, or empty string to clear").action(
4131
+ cardCmd.command("update <cardId>").description("Update card fields (title, description, due date, cover, checklist title)").option("--title <title>", "New title").option("-d, --description <description>", "New description (markdown)").option("--desc <description>", "alias for --description").option("--desc-file <path>", "read description from a file (or - for stdin)").option("--due <iso>", "Due date as ISO string, or empty string to clear").option("--cover <attachmentId>", "Cover attachment ID, or empty string to clear").option("--checklist-title <title>", "Custom checklist section title, or empty string to clear").option("--comment <body>", "Add a comment in the same call").option("--comment-author <name>", "Author name for --comment").option("--to <id>", "Address the comment to an assignee id (repeatable)", collectRepeated, []).option("--list <name>", "Move the card to this list (name or id) in the same call").option("--done", "Move the card to the board's done list (wins over --list)").action(
4132
4132
  async (cardId, opts) => {
4133
4133
  const patch = {};
4134
4134
  if (opts.title !== void 0)
@@ -4144,15 +4144,35 @@ cardCmd.command("update <cardId>").description("Update card fields (title, descr
4144
4144
  patch.coverAttachmentId = opts.cover === "" ? null : opts.cover;
4145
4145
  if (opts.checklistTitle !== void 0)
4146
4146
  patch.checklistTitle = opts.checklistTitle === "" ? null : opts.checklistTitle;
4147
+ if (opts.comment !== void 0) {
4148
+ patch.comment = {
4149
+ body: opts.comment,
4150
+ ...opts.commentAuthor ? { authorName: opts.commentAuthor } : {},
4151
+ ...opts.to.length > 0 ? { recipients: opts.to } : {}
4152
+ };
4153
+ }
4154
+ if (opts.done)
4155
+ patch.complete = true;
4156
+ else if (opts.list !== void 0)
4157
+ patch.list = opts.list;
4147
4158
  if (Object.keys(patch).length === 0) {
4148
- console.error("Provide at least one of: --title, --description, --desc-file, --due, --cover, --checklist-title");
4159
+ console.error(
4160
+ "Provide at least one of: --title, --description, --desc-file, --due, --cover, --checklist-title, --comment, --list, --done"
4161
+ );
4149
4162
  process.exit(1);
4150
4163
  }
4151
4164
  const card = await api(`/api/cards/${cardId}`, {
4152
4165
  method: "PATCH",
4153
4166
  body: JSON.stringify(patch)
4154
4167
  });
4155
- show(card, [`\u2713 card ${card.id} updated`]);
4168
+ const done = [];
4169
+ if (opts.comment !== void 0)
4170
+ done.push("commented");
4171
+ if (opts.done)
4172
+ done.push("completed");
4173
+ else if (opts.list !== void 0)
4174
+ done.push(`moved to ${opts.list}`);
4175
+ show(card, [`\u2713 card ${card.id} updated${done.length ? ` (${done.join(", ")})` : ""}`]);
4156
4176
  }
4157
4177
  );
4158
4178
  cardCmd.command("move <cardId>").description("Move a card to a list (and optionally a position)").requiredOption("--list <listId>", "Destination list ID").option("--position <n>", "Target index (0-based)", "0").action(
@@ -4259,18 +4279,34 @@ cardCmd.command("comment <cardId> <body>").description("Add a comment to a card"
4259
4279
  function collectRepeated(value, previous) {
4260
4280
  return [...previous, value];
4261
4281
  }
4282
+ program2.command("done <cardId>").description(
4283
+ "Mark a card done \u2014 moves it to the board's done list (it stays on the board). Not the same as `smash archive`, which takes it off."
4284
+ ).action(async (cardId) => {
4285
+ const result = await api(`/api/cards/${cardId}/complete`, {
4286
+ method: "POST"
4287
+ });
4288
+ show(result.card, [`\u2713 card ${result.card.id} \u2192 ${result.listTitle}`]);
4289
+ });
4262
4290
  program2.command("cards").description(
4263
4291
  "List cards on the resolved board \u2014 light by default (no description). Filter by list/assignee, pick columns with --fields."
4264
- ).option("-b, --board <label>", "board label from config").option("--board-id <id>", "explicit board id (bypass config)").option("-l, --list <name>", "only cards in this list (name or id)").option("--assignee <idOrName>", "only cards assigned to this id or display name").option(
4292
+ ).option("-b, --board <label>", "board label from config").option("--board-id <id>", "explicit board id (bypass config)").option("-l, --list <name>", "only cards in this list (name or id)").option("--assignee <idOrName>", "only cards assigned to this id or display name").option("--status <status>", "open (skip cards in lists marked done) | done | all (default)").option("--stale <days>", "only cards untouched for N days, oldest first (adds lastActivityAt)").option(
4265
4293
  "--fields <list>",
4266
4294
  "comma-separated columns (default: id,title,listId,listTitle,updatedAt). Add description only when you need bodies."
4267
4295
  ).option("--limit <n>", "max cards per page (default 100, max 500)").option("--cursor <cursor>", "continue from a previous response's cursor").action(async (opts) => {
4296
+ if (opts.status && !["open", "done", "all"].includes(opts.status)) {
4297
+ console.error("--status must be one of: open, done, all");
4298
+ process.exit(1);
4299
+ }
4268
4300
  const ctx = resolveContext(opts);
4269
4301
  const params = new URLSearchParams();
4270
4302
  if (opts.list)
4271
4303
  params.set("list", opts.list);
4272
4304
  if (opts.assignee)
4273
4305
  params.set("assignee", opts.assignee);
4306
+ if (opts.status)
4307
+ params.set("status", opts.status);
4308
+ if (opts.stale)
4309
+ params.set("staleDays", opts.stale);
4274
4310
  if (opts.fields)
4275
4311
  params.set("fields", opts.fields);
4276
4312
  if (opts.limit)
@@ -4289,7 +4325,9 @@ program2.command("cards").description(
4289
4325
  }
4290
4326
  for (const card of result.cards) {
4291
4327
  const listTitle = typeof card.listTitle === "string" ? `[${card.listTitle}] ` : "";
4292
- console.log(`${listTitle}${String(card.title ?? "")} (${String(card.id ?? "")})`);
4328
+ const last = typeof card.lastActivityAt === "string" ? card.lastActivityAt : null;
4329
+ const age = last ? ` ${last.slice(0, 10)} (${Math.floor((Date.now() - Date.parse(last)) / 864e5)}\u65E5\u524D)` : "";
4330
+ console.log(`${listTitle}${String(card.title ?? "")} (${String(card.id ?? "")})${age}`);
4293
4331
  }
4294
4332
  console.log(
4295
4333
  `
@@ -4564,6 +4602,13 @@ tokenCmd.command("revoke <boardId> <tokenId>").description("Revoke a write token
4564
4602
  );
4565
4603
  show(revoked, [`\u2713 token ${revoked.id} revoked at ${revoked.revokedAt}`]);
4566
4604
  });
4605
+ listCmd.command("done <listId>").description("Mark a list as done (cards in it are excluded by `smash cards --status open`)").option("--no-done", "unmark it instead").action(async (listId, opts) => {
4606
+ const list = await api(`/api/lists/${listId}`, {
4607
+ method: "PATCH",
4608
+ body: JSON.stringify({ isDone: opts.done })
4609
+ });
4610
+ show(list, [`\u2713 list ${list.id} (${list.title}) isDone=${list.isDone}`]);
4611
+ });
4567
4612
  var labelCmd = program2.command("label").description("Labels");
4568
4613
  labelCmd.command("create <boardId> <name>").description("Create a board label").option(
4569
4614
  "--color <color>",
@@ -15553,18 +15553,25 @@ var tools = [
15553
15553
  },
15554
15554
  {
15555
15555
  name: "smash_rename_list",
15556
- description: "Rename a list.",
15556
+ description: "Rename a list, and/or mark it as a done list. Cards in a list with isDone=true are excluded by smash_list_cards({status: 'open'}), so an agent can ask for open work without knowing the board's list names.",
15557
15557
  inputSchema: {
15558
15558
  type: "object",
15559
15559
  properties: {
15560
15560
  listId: { type: "string" },
15561
- title: { type: "string" }
15561
+ title: { type: "string" },
15562
+ isDone: {
15563
+ type: "boolean",
15564
+ description: "Treat cards in this list as done"
15565
+ }
15562
15566
  },
15563
- required: ["listId", "title"]
15567
+ required: ["listId"]
15564
15568
  },
15565
15569
  handler: async (args) => api(`/api/lists/${args.listId}`, {
15566
15570
  method: "PATCH",
15567
- body: JSON.stringify({ title: args.title })
15571
+ body: JSON.stringify({
15572
+ ...typeof args.title === "string" ? { title: args.title } : {},
15573
+ ...typeof args.isDone === "boolean" ? { isDone: args.isDone } : {}
15574
+ })
15568
15575
  })
15569
15576
  },
15570
15577
  {
@@ -15608,7 +15615,7 @@ var tools = [
15608
15615
  },
15609
15616
  {
15610
15617
  name: "smash_update_card",
15611
- description: "Update a card's fields. All fields except cardId are optional. Pass null to clear dueAt / coverAttachmentId / checklistTitle / description.",
15618
+ description: "Update a card, and optionally comment on it and move it IN THE SAME CALL. Advancing a card is usually 'write what happened, then move it' \u2014 pass `comment` plus `list` (or `complete: true`) instead of making three calls. All fields except cardId are optional. Pass null to clear dueAt / coverAttachmentId / checklistTitle / description.",
15612
15619
  inputSchema: {
15613
15620
  type: "object",
15614
15621
  properties: {
@@ -15629,6 +15636,33 @@ var tools = [
15629
15636
  checklistTitle: {
15630
15637
  type: ["string", "null"],
15631
15638
  description: "Custom title for the checklist section, or null to reset to default"
15639
+ },
15640
+ comment: {
15641
+ type: "object",
15642
+ description: "Add one comment in the same call",
15643
+ properties: {
15644
+ body: { type: "string" },
15645
+ authorName: { type: "string" },
15646
+ recipients: {
15647
+ type: "array",
15648
+ items: { type: "string" },
15649
+ description: "Assignee ids this comment is addressed to"
15650
+ }
15651
+ },
15652
+ required: ["body"]
15653
+ },
15654
+ list: {
15655
+ type: "string",
15656
+ description: "Move the card to this list (name, case-insensitive, or id)"
15657
+ },
15658
+ position: { type: "number", description: "Position within the destination list" },
15659
+ complete: {
15660
+ type: "boolean",
15661
+ description: "Move the card to the board's done list (wins over `list`). Fails with 409 when no list is marked done."
15662
+ },
15663
+ assignees: {
15664
+ type: ["array", "null"],
15665
+ description: "Replace the card's assignees (same shape as smash_set_assignees)"
15632
15666
  }
15633
15667
  },
15634
15668
  required: ["cardId"]
@@ -15645,6 +15679,16 @@ var tools = [
15645
15679
  patch.coverAttachmentId = args.coverAttachmentId;
15646
15680
  if (args.checklistTitle !== void 0)
15647
15681
  patch.checklistTitle = args.checklistTitle;
15682
+ if (args.comment !== void 0)
15683
+ patch.comment = args.comment;
15684
+ if (typeof args.list === "string")
15685
+ patch.list = args.list;
15686
+ if (typeof args.position === "number")
15687
+ patch.position = args.position;
15688
+ if (args.complete === true)
15689
+ patch.complete = true;
15690
+ if (args.assignees !== void 0)
15691
+ patch.assignees = args.assignees;
15648
15692
  return api(`/api/cards/${args.cardId}`, {
15649
15693
  method: "PATCH",
15650
15694
  body: JSON.stringify(patch)
@@ -15710,10 +15754,19 @@ var tools = [
15710
15754
  boardId: { type: "string" },
15711
15755
  list: { type: "string", description: "List name (case-insensitive) or list id" },
15712
15756
  assignee: { type: "string", description: "Assignee id or display name" },
15757
+ status: {
15758
+ type: "string",
15759
+ enum: ["open", "done", "all"],
15760
+ description: "open skips cards sitting in a list marked done (a flag on the list, not a name match); done keeps only those. Default all."
15761
+ },
15762
+ staleDays: {
15763
+ type: "number",
15764
+ description: "Only cards untouched for this many days, oldest first. Each row gets lastActivityAt, so you can tell a two-week stall from a three-month one. Use it to find work that stopped without anyone noticing."
15765
+ },
15713
15766
  fields: {
15714
15767
  type: "array",
15715
15768
  items: { type: "string" },
15716
- description: "Columns to return: id, boardId, title, description, listId, listTitle, position, dueAt, labelIds, checklistTitle, agentMeta, externalRefs, assignees, customData, createdAt, updatedAt"
15769
+ description: "Columns to return: id, boardId, title, description, listId, listTitle, position, dueAt, labelIds, checklistTitle, agentMeta, externalRefs, assignees, customData, source, createdAt, updatedAt, lastActivityAt"
15717
15770
  },
15718
15771
  limit: { type: "number", description: "Max cards per page (default 100, max 500)" },
15719
15772
  cursor: { type: "string", description: "Cursor from a previous response" }
@@ -15726,6 +15779,10 @@ var tools = [
15726
15779
  params.set("list", args.list);
15727
15780
  if (typeof args.assignee === "string" && args.assignee)
15728
15781
  params.set("assignee", args.assignee);
15782
+ if (typeof args.status === "string" && args.status)
15783
+ params.set("status", args.status);
15784
+ if (typeof args.staleDays === "number")
15785
+ params.set("staleDays", String(args.staleDays));
15729
15786
  if (Array.isArray(args.fields) && args.fields.length > 0)
15730
15787
  params.set("fields", args.fields.join(","));
15731
15788
  if (typeof args.limit === "number")
@@ -15786,9 +15843,19 @@ var tools = [
15786
15843
  })
15787
15844
  })
15788
15845
  },
15846
+ {
15847
+ name: "smash_complete_card",
15848
+ description: "Mark a card done: it moves to the list flagged as done on its board and STAYS on the board, where people can see it. Use this when work finished. smash_delete_card is different \u2014 that archives the card off the board. Fails with a message when no list is marked done yet (mark one with smash_rename_list({listId, isDone: true})).",
15849
+ inputSchema: {
15850
+ type: "object",
15851
+ properties: { cardId: { type: "string" } },
15852
+ required: ["cardId"]
15853
+ },
15854
+ handler: async (args) => api(`/api/cards/${args.cardId}/complete`, { method: "POST" })
15855
+ },
15789
15856
  {
15790
15857
  name: "smash_delete_card",
15791
- description: "Archive a card (soft delete). It leaves the board but is kept: it shows up in the space's archived column and smash_restore_card puts it back. Nothing is destroyed here \u2014 smash_permanently_delete_card is the irreversible one.",
15858
+ description: "Archive a card (soft delete) \u2014 NOT the same as marking it done. It leaves the board but is kept: it shows up in the space's archived column and smash_restore_card puts it back. Nothing is destroyed here \u2014 smash_permanently_delete_card is the irreversible one.",
15792
15859
  inputSchema: {
15793
15860
  type: "object",
15794
15861
  properties: { cardId: { type: "string" } },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smashspace",
3
- "version": "0.5.0",
3
+ "version": "0.8.0",
4
4
  "description": "SmashSpace CLI + MCP — operate SmashSpace boards from your terminal, Claude Code, Cursor, and Codex.",
5
5
  "type": "module",
6
6
  "bin": {