smashspace 0.1.9 → 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.
Files changed (3) hide show
  1. package/README.md +14 -3
  2. package/dist/smash.mjs +85 -38
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -60,7 +60,8 @@ space (`--new-space` / `--space-id`) and exits without writing anything. Same,
60
60
  predictable behaviour in a terminal, an agent, or CI — nothing hangs, nothing is
61
61
  written half-way. (`--skills-only` installs just the skill, no space needed.)
62
62
 
63
- Flags: `--new-space <name>`, `--space-id <id>` (`--board-id` is a legacy alias),
63
+ Flags: `--new-space <name>`, `--space-id <id-or-url>` (accepts a full
64
+ `https://…/space/s_xxx` URL too; `--board-id` is a legacy alias),
64
65
  `--skills-only` (just the operator skill), `--no-mcp`, `--force`. Sign in once
65
66
  with `smash login` first (`--new-space` needs it).
66
67
 
@@ -84,13 +85,23 @@ prefer, so `smash` knows which board to operate on:
84
85
  ```bash
85
86
  smash status # what context is this directory in? (see below)
86
87
  smash read # show the board (lists + cards)
87
- smash scan --list "やること" # actionable cards as JSON (for agents)
88
- smash create "Fix signup bug" # add a card (to the first list, or --list)
88
+ smash scan --list "やること" # cards as JSON (id/title/list only)
89
+ smash scan --list "やること" --full # + description/agentMeta/labels — pick up work in ONE request
90
+ smash create "Fix signup bug" # add a card (to the first list, or --list; --desc for a body)
89
91
  smash move <cardId> "レビュー" # move a card to a list by name
90
92
  smash archive <cardId> # archive (soft-delete) a card
91
93
  smash --help # full command list (board/list/card/…)
92
94
  ```
93
95
 
96
+ Output is a concise line by default; add `--json` for machine-readable output on
97
+ any command. For agents:
98
+
99
+ ```bash
100
+ smash scan --full # or --fields description,agentMeta
101
+ smash agent-meta set <cardId> --set status=working --set progress=0.4 --merge
102
+ smash card update <cardId> --desc-file spec.md # multi-line body from a file / - (stdin)
103
+ ```
104
+
94
105
  Pick a board with `-b <label>` (defaults to `main`), or bypass config with
95
106
  `--board-id <id>`.
96
107
 
package/dist/smash.mjs CHANGED
@@ -3811,7 +3811,10 @@ function mcpServerEntry(baseUrl, boardId) {
3811
3811
  // ../../cli/index.ts
3812
3812
  var DEFAULT_BASE_URL = process.env.SMASH_BASE_URL ?? "https://smashspace.app";
3813
3813
  var program2 = new Command();
3814
- program2.name("smash").description("SmashSpace CLI \u2014 Trello as a spreadsheet for the AI agent era").option("--base-url <url>", "API base URL", DEFAULT_BASE_URL).option("--json", "Output raw JSON", false);
3814
+ program2.name("smash").description("SmashSpace CLI \u2014 Trello as a spreadsheet for the AI agent era").option("--base-url <url>", "API base URL", DEFAULT_BASE_URL).option("--json", "Output raw JSON", false).showSuggestionAfterError().addHelpText(
3815
+ "after",
3816
+ '\nTop-level verbs (create/read/move/scan/archive) act on the space resolved\nfrom .smashspace.json (default "main"). The card/board/list groups take\nexplicit ids. Default output is a concise line; add --json for machine-readable.'
3817
+ );
3815
3818
  function getOpts() {
3816
3819
  const o = program2.opts();
3817
3820
  return { baseUrl: o.baseUrl ?? DEFAULT_BASE_URL, json: !!o.json };
@@ -3841,8 +3844,11 @@ function show(data, headerLines = []) {
3841
3844
  console.log(JSON.stringify(data, null, 2));
3842
3845
  return;
3843
3846
  }
3844
- for (const l of headerLines)
3845
- console.log(l);
3847
+ if (headerLines.length > 0) {
3848
+ for (const l of headerLines)
3849
+ console.log(l);
3850
+ return;
3851
+ }
3846
3852
  if (data !== void 0)
3847
3853
  console.log(JSON.stringify(data, null, 2));
3848
3854
  }
@@ -4060,7 +4066,7 @@ cardCmd.command("create <listId> <title>").description("Create a card in a list"
4060
4066
  });
4061
4067
  show(card, [`\u2713 card ${card.id} created in ${listId}`]);
4062
4068
  });
4063
- cardCmd.command("show <cardId>").description("Show card detail (description, checklist, comments, activity)").action(async (cardId) => {
4069
+ cardCmd.command("show <cardId>").alias("get").description("Show card detail (description, checklist, comments, activity)").action(async (cardId) => {
4064
4070
  const detail = await api(`/api/cards/${cardId}`);
4065
4071
  if (getOpts().json) {
4066
4072
  console.log(JSON.stringify(detail, null, 2));
@@ -4084,15 +4090,16 @@ ${detail.card.description}`);
4084
4090
  }
4085
4091
  }
4086
4092
  });
4087
- cardCmd.command("update <cardId>").description("Update card fields (title, description, due date, cover, checklist title)").option("--title <title>", "New title").option("--description <description>", "New description (markdown)").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(
4093
+ 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(
4088
4094
  async (cardId, opts) => {
4089
4095
  const patch = {};
4090
4096
  if (opts.title !== void 0)
4091
4097
  patch.title = opts.title;
4098
+ const desc = opts.description ?? opts.desc;
4092
4099
  if (opts.descFile !== void 0)
4093
4100
  patch.description = await readTextArg(opts.descFile);
4094
- else if (opts.description !== void 0)
4095
- patch.description = opts.description;
4101
+ else if (desc !== void 0)
4102
+ patch.description = desc;
4096
4103
  if (opts.due !== void 0)
4097
4104
  patch.dueAt = opts.due === "" ? null : opts.due;
4098
4105
  if (opts.cover !== void 0)
@@ -4393,29 +4400,57 @@ labelCmd.command("delete <labelId>").description("Delete a label from the board"
4393
4400
  console.log(`\u2713 label ${labelId} deleted`);
4394
4401
  });
4395
4402
  var agentMetaCmd = program2.command("agent-meta").description("Set or clear a card's agent_meta (structured field for external agents)");
4396
- agentMetaCmd.command("set <cardId>").description("Set agent_meta on a card. Provide JSON via --data or --file.").option("--data <json>", "Inline JSON string").option("--file <path>", "Read JSON from a file path (use - for stdin)").action(async (cardId, opts) => {
4397
- let raw;
4398
- if (opts.data && opts.file) {
4399
- console.error("Provide --data or --file, not both");
4400
- process.exit(1);
4401
- } else if (opts.data !== void 0) {
4402
- raw = opts.data;
4403
- } else if (opts.file !== void 0) {
4404
- raw = opts.file === "-" ? await new Response(process.stdin).text() : await readFile2(opts.file, "utf8");
4405
- } else {
4406
- console.error("Provide --data '<...>' or --file <path>");
4407
- process.exit(1);
4408
- }
4403
+ agentMetaCmd.command("set <cardId>").description("Set agent_meta on a card. Use --set key=value (repeatable), or --data/--file for full JSON.").option("--set <pair>", "key=value (repeatable; value parsed as JSON if valid, else string)", (v, acc) => {
4404
+ acc.push(v);
4405
+ return acc;
4406
+ }, []).option("--merge", "merge into the existing agent_meta instead of replacing it").option("--data <json>", "Inline JSON string (full object)").option("--file <path>", "Read JSON from a file path (use - for stdin)").action(async (cardId, opts) => {
4409
4407
  let parsed;
4410
- try {
4411
- parsed = JSON.parse(raw);
4412
- } catch (e) {
4413
- console.error(`Invalid JSON: ${e instanceof Error ? e.message : String(e)}`);
4414
- process.exit(1);
4408
+ if (opts.set.length > 0) {
4409
+ parsed = {};
4410
+ for (const pair of opts.set) {
4411
+ const i = pair.indexOf("=");
4412
+ if (i < 0) {
4413
+ console.error(`--set expects key=value, got: ${pair}`);
4414
+ process.exit(1);
4415
+ }
4416
+ const key = pair.slice(0, i);
4417
+ const rawVal = pair.slice(i + 1);
4418
+ try {
4419
+ parsed[key] = JSON.parse(rawVal);
4420
+ } catch {
4421
+ parsed[key] = rawVal;
4422
+ }
4423
+ }
4424
+ } else {
4425
+ let raw;
4426
+ if (opts.data && opts.file) {
4427
+ console.error("Provide --set, --data, or --file (not more than one source)");
4428
+ process.exit(1);
4429
+ } else if (opts.data !== void 0) {
4430
+ raw = opts.data;
4431
+ } else if (opts.file !== void 0) {
4432
+ raw = opts.file === "-" ? await new Response(process.stdin).text() : await readFile2(opts.file, "utf8");
4433
+ } else {
4434
+ console.error("Provide --set key=value, --data '<json>', or --file <path>");
4435
+ process.exit(1);
4436
+ }
4437
+ let p;
4438
+ try {
4439
+ p = JSON.parse(raw);
4440
+ } catch (e) {
4441
+ console.error(`Invalid JSON: ${e instanceof Error ? e.message : String(e)}`);
4442
+ process.exit(1);
4443
+ }
4444
+ if (typeof p !== "object" || p === null || Array.isArray(p)) {
4445
+ console.error("agent_meta must be a JSON object");
4446
+ process.exit(1);
4447
+ }
4448
+ parsed = p;
4415
4449
  }
4416
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
4417
- console.error("agent_meta must be a JSON object");
4418
- process.exit(1);
4450
+ if (opts.merge) {
4451
+ const detail = await api(`/api/cards/${cardId}`);
4452
+ const existing = detail.card.agentMeta ?? {};
4453
+ parsed = { ...existing, ...parsed };
4419
4454
  }
4420
4455
  const card = await api(`/api/cards/${cardId}`, {
4421
4456
  method: "PATCH",
@@ -4811,7 +4846,9 @@ program2.command("init").description(
4811
4846
  boardId = space.id;
4812
4847
  actions.push(`\u2713 created space "${space.title || opts.newSpace}" (${space.id})`);
4813
4848
  } else {
4814
- boardId = opts.spaceId ?? opts.boardId;
4849
+ const raw = opts.spaceId ?? opts.boardId;
4850
+ const m = raw.match(/\/(?:board|space)\/((?:board_|b_|s_)[a-z0-9]+)/i);
4851
+ boardId = m ? m[1] : raw;
4815
4852
  }
4816
4853
  }
4817
4854
  if (!opts.skillsOnly) {
@@ -4907,19 +4944,29 @@ program2.command("move <cardId> <listName>").description("Move a card to a list
4907
4944
  );
4908
4945
  show(card, [`\u2713 moved ${cardId} \u2192 ${list.title} (board ${ctx.entry.label})`]);
4909
4946
  });
4910
- program2.command("scan").description("Emit the board's cards as JSON (agent-facing: pick up work programmatically)").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").action(async (opts) => {
4947
+ program2.command("scan").description("Emit the board's cards as JSON (agent-facing: pick up work programmatically)").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").option("--full", "include all card fields (description, agentMeta, labelIds, \u2026) \u2014 no extra requests").option("--fields <list>", "comma-separated extra fields to include, e.g. description,agentMeta").action(async (opts) => {
4911
4948
  const ctx = resolveContext(opts);
4912
4949
  const detail = await fetchBoard(ctx.boardId, ctx.baseUrl);
4913
4950
  const lists = opts.list ? [findListByName(detail, opts.list)] : detail.lists;
4951
+ const extraFields = opts.fields ? opts.fields.split(",").map((s) => s.trim()).filter(Boolean) : [];
4914
4952
  const cards = lists.flatMap(
4915
- (l) => l.cards.map((c) => ({
4916
- id: c.id,
4917
- title: c.title,
4918
- list: l.title,
4919
- listId: l.id,
4920
- position: c.position,
4921
- dueAt: c.dueAt ?? null
4922
- }))
4953
+ (l) => l.cards.map((c) => {
4954
+ if (opts.full)
4955
+ return { list: l.title, ...c };
4956
+ const base = {
4957
+ id: c.id,
4958
+ title: c.title,
4959
+ list: l.title,
4960
+ listId: l.id,
4961
+ position: c.position,
4962
+ dueAt: c.dueAt ?? null
4963
+ };
4964
+ for (const f of extraFields) {
4965
+ if (f in c)
4966
+ base[f] = c[f];
4967
+ }
4968
+ return base;
4969
+ })
4923
4970
  );
4924
4971
  console.log(
4925
4972
  JSON.stringify(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smashspace",
3
- "version": "0.1.9",
3
+ "version": "0.2.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": {