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.
- package/README.md +14 -3
- package/dist/smash.mjs +85 -38
- 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>` (
|
|
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 "やること" #
|
|
88
|
-
smash
|
|
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
|
-
|
|
3845
|
-
|
|
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 (
|
|
4095
|
-
patch.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.
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
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
|
-
|
|
4411
|
-
parsed =
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
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 (
|
|
4417
|
-
|
|
4418
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
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(
|