smashspace 0.1.6 → 0.1.8
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 +17 -7
- package/dist/smash.mjs +80 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,11 +34,17 @@ server below. (Headless? use `smash login --token <PAT>`.)
|
|
|
34
34
|
|
|
35
35
|
## Onboard a project for agents: `smash init`
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
**One project = one `smash init` = one space.** Run it once inside each repo, so
|
|
38
|
+
that project's agents (Claude Code / Codex) operate their own space:
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
|
|
41
|
+
cd your/dir/project
|
|
42
|
+
|
|
43
|
+
# create a new space and pin it to this project
|
|
44
|
+
smash init --new-space "Your Project"
|
|
45
|
+
|
|
46
|
+
# …or pin an existing space
|
|
47
|
+
smash init --space-id s_xxxxxxxx
|
|
42
48
|
```
|
|
43
49
|
|
|
44
50
|
It writes (and is safe to re-run — existing config is kept unless `--force`):
|
|
@@ -46,12 +52,16 @@ It writes (and is safe to re-run — existing config is kept unless `--force`):
|
|
|
46
52
|
- `.claude/skills/smash/SKILL.md` + `.codex/skills/smash/SKILL.md` — the
|
|
47
53
|
SmashSpace Operator playbook (CLI/MCP usage + Inbox→Todo→Doing→Review→Done
|
|
48
54
|
workflow + safety rules). Always refreshed to the installed CLI version.
|
|
49
|
-
- `.smashspace.json` — pins the instance +
|
|
55
|
+
- `.smashspace.json` — pins the instance + space (which space this dir talks to).
|
|
50
56
|
- `.mcp.json` — wires the `smashspace` MCP server (merged, other servers kept).
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
Plain `smash init` (no space flag) is safe: it installs the skill and writes a
|
|
59
|
+
`.smashspace.json` with a **placeholder** space id, then prints how to set a real
|
|
60
|
+
one (`--new-space` / `--space-id`). It never guesses or deletes anything.
|
|
61
|
+
|
|
62
|
+
Flags: `--new-space <name>`, `--space-id <id>` (`--board-id` is a legacy alias),
|
|
63
|
+
`--skills-only` (just the operator skill), `--no-mcp`, `--force`. Sign in once
|
|
64
|
+
with `smash login` first (`--new-space` needs it).
|
|
55
65
|
|
|
56
66
|
## Project config: `.smashspace.json`
|
|
57
67
|
|
package/dist/smash.mjs
CHANGED
|
@@ -3599,7 +3599,11 @@ the target board for you.
|
|
|
3599
3599
|
shim that fails with "Volta error: Could not execute command".
|
|
3600
3600
|
- MCP server: \`smashspace-mcp\` (exposes \`smash_*\` tools, same token as the CLI)
|
|
3601
3601
|
- Sign in once per machine: \`smash login\` (browser approval; token saved to
|
|
3602
|
-
\`~/.config/smash/credentials.json\` and reused by MCP)
|
|
3602
|
+
\`~/.config/smash/credentials.json\` and reused by MCP). It is idempotent \u2014 if a
|
|
3603
|
+
valid token is already saved it does nothing, so don't re-run it in a loop.
|
|
3604
|
+
- If \`smash\` is on PATH, **call it directly** \u2014 do not prefix
|
|
3605
|
+
\`PATH=\u2026/node/\u2026/bin\`. A "Volta error: Could not execute command" means a stale
|
|
3606
|
+
install; fix it with \`volta install smashspace\` rather than PATH-hacking.
|
|
3603
3607
|
|
|
3604
3608
|
### Which board am I on?
|
|
3605
3609
|
|
|
@@ -3671,7 +3675,20 @@ Keep the card's agent state current while you work:
|
|
|
3671
3675
|
}
|
|
3672
3676
|
\`\`\`
|
|
3673
3677
|
|
|
3674
|
-
Set \`status: "done"\` only after verification passes.
|
|
3678
|
+
Set \`status: "done"\` only after verification passes. Keep it current on
|
|
3679
|
+
meaningful moves/edits too (not just during todo-dev) so a human can see whose
|
|
3680
|
+
turn it is at a glance.
|
|
3681
|
+
|
|
3682
|
+
## Batching (stay inside the CLI)
|
|
3683
|
+
|
|
3684
|
+
Prefer the CLI's own batch flags over dropping to a shell/node loop:
|
|
3685
|
+
|
|
3686
|
+
\`\`\`bash
|
|
3687
|
+
smash create "Title" --desc-file spec.md # multi-line description, no quoting pain
|
|
3688
|
+
smash card update <cardId> --desc-file - # description from stdin
|
|
3689
|
+
smash checklist add <cardId> --file steps.txt # one item per line (or many args)
|
|
3690
|
+
smash checklist add <cardId> "Step 1" "Step 2" "Step 3"
|
|
3691
|
+
\`\`\`
|
|
3675
3692
|
|
|
3676
3693
|
## Command: /smash list
|
|
3677
3694
|
|
|
@@ -3829,6 +3846,15 @@ function show(data, headerLines = []) {
|
|
|
3829
3846
|
if (data !== void 0)
|
|
3830
3847
|
console.log(JSON.stringify(data, null, 2));
|
|
3831
3848
|
}
|
|
3849
|
+
async function readTextArg(pathOrDash) {
|
|
3850
|
+
if (pathOrDash === "-") {
|
|
3851
|
+
const chunks = [];
|
|
3852
|
+
for await (const chunk of stdin)
|
|
3853
|
+
chunks.push(chunk);
|
|
3854
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
3855
|
+
}
|
|
3856
|
+
return readFile2(pathOrDash, "utf8");
|
|
3857
|
+
}
|
|
3832
3858
|
function resolveBaseUrl(loaded = loadProjectConfig()) {
|
|
3833
3859
|
if (program2.getOptionValueSource("baseUrl") === "cli")
|
|
3834
3860
|
return getOpts().baseUrl;
|
|
@@ -4058,12 +4084,14 @@ ${detail.card.description}`);
|
|
|
4058
4084
|
}
|
|
4059
4085
|
}
|
|
4060
4086
|
});
|
|
4061
|
-
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("--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(
|
|
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(
|
|
4062
4088
|
async (cardId, opts) => {
|
|
4063
4089
|
const patch = {};
|
|
4064
4090
|
if (opts.title !== void 0)
|
|
4065
4091
|
patch.title = opts.title;
|
|
4066
|
-
if (opts.
|
|
4092
|
+
if (opts.descFile !== void 0)
|
|
4093
|
+
patch.description = await readTextArg(opts.descFile);
|
|
4094
|
+
else if (opts.description !== void 0)
|
|
4067
4095
|
patch.description = opts.description;
|
|
4068
4096
|
if (opts.due !== void 0)
|
|
4069
4097
|
patch.dueAt = opts.due === "" ? null : opts.due;
|
|
@@ -4072,7 +4100,7 @@ cardCmd.command("update <cardId>").description("Update card fields (title, descr
|
|
|
4072
4100
|
if (opts.checklistTitle !== void 0)
|
|
4073
4101
|
patch.checklistTitle = opts.checklistTitle === "" ? null : opts.checklistTitle;
|
|
4074
4102
|
if (Object.keys(patch).length === 0) {
|
|
4075
|
-
console.error("Provide at least one of: --title, --description, --due, --cover, --checklist-title");
|
|
4103
|
+
console.error("Provide at least one of: --title, --description, --desc-file, --due, --cover, --checklist-title");
|
|
4076
4104
|
process.exit(1);
|
|
4077
4105
|
}
|
|
4078
4106
|
const card = await api(`/api/cards/${cardId}`, {
|
|
@@ -4198,15 +4226,28 @@ archiveCmd.command("purge <cardId>").description("Permanently delete an archived
|
|
|
4198
4226
|
console.log(`\u2713 card ${cardId} permanently deleted`);
|
|
4199
4227
|
});
|
|
4200
4228
|
var checklistCmd = program2.command("checklist").description("Card checklist items");
|
|
4201
|
-
checklistCmd.command("add <cardId>
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4229
|
+
checklistCmd.command("add <cardId> [titles...]").description("Add one or more checklist items (repeat titles, or --file with one per line)").option("--file <path>", "read items from a file, one per line (or - for stdin)").action(async (cardId, titles, opts) => {
|
|
4230
|
+
let items = titles ?? [];
|
|
4231
|
+
if (opts.file) {
|
|
4232
|
+
const text = await readTextArg(opts.file);
|
|
4233
|
+
items = items.concat(
|
|
4234
|
+
text.split(/\r?\n/).map((s) => s.trim()).filter(Boolean)
|
|
4235
|
+
);
|
|
4236
|
+
}
|
|
4237
|
+
if (items.length === 0) {
|
|
4238
|
+
console.error("Provide at least one item title, or --file <path>");
|
|
4239
|
+
process.exit(1);
|
|
4240
|
+
}
|
|
4241
|
+
const created = [];
|
|
4242
|
+
for (const title of items) {
|
|
4243
|
+
created.push(
|
|
4244
|
+
await api(`/api/cards/${cardId}/checklist-items`, {
|
|
4245
|
+
method: "POST",
|
|
4246
|
+
body: JSON.stringify({ title })
|
|
4247
|
+
})
|
|
4248
|
+
);
|
|
4249
|
+
}
|
|
4250
|
+
show(created, [`\u2713 ${created.length} checklist item(s) added`]);
|
|
4210
4251
|
});
|
|
4211
4252
|
checklistCmd.command("toggle <itemId>").description("Toggle a checklist item to checked/unchecked").option("--check", "Mark as checked", false).option("--uncheck", "Mark as unchecked", false).action(
|
|
4212
4253
|
async (itemId, opts) => {
|
|
@@ -4722,7 +4763,7 @@ program2.command("status").aliases(["context", "where"]).description(
|
|
|
4722
4763
|
});
|
|
4723
4764
|
program2.command("init").description(
|
|
4724
4765
|
"Scaffold this project for agents: operator skill + .smashspace.json + .mcp.json"
|
|
4725
|
-
).option("--
|
|
4766
|
+
).option("--new-space <name>", "create a new space with this name, then pin it (needs login)").option("--space-id <id>", "existing space id to pin, e.g. s_xxxxxxxx").option("--board-id <id>", "alias for --space-id (legacy)").option("--skills-only", "Only (re)install the operator skill files").option("--no-mcp", "Skip writing .mcp.json").option("-f, --force", "Overwrite .smashspace.json / .mcp.json if they exist").action(
|
|
4726
4767
|
async (opts) => {
|
|
4727
4768
|
const cwd = process.cwd();
|
|
4728
4769
|
const actions = [];
|
|
@@ -4747,15 +4788,26 @@ program2.command("init").description(
|
|
|
4747
4788
|
"\u2713 operator skill \u2192 .claude/skills/smash/ + .codex/skills/smash/"
|
|
4748
4789
|
);
|
|
4749
4790
|
const baseUrl = resolveBaseUrl();
|
|
4750
|
-
|
|
4751
|
-
|
|
4791
|
+
let boardId;
|
|
4792
|
+
if (opts.newSpace) {
|
|
4793
|
+
const space = await api(
|
|
4794
|
+
"/api/boards",
|
|
4795
|
+
{ method: "POST", body: JSON.stringify({ title: opts.newSpace }) },
|
|
4796
|
+
baseUrl
|
|
4797
|
+
);
|
|
4798
|
+
boardId = space.id;
|
|
4799
|
+
actions.push(`\u2713 created space "${space.title || opts.newSpace}" (${space.id})`);
|
|
4800
|
+
} else {
|
|
4801
|
+
boardId = opts.spaceId ?? opts.boardId ?? "s_xxxxxxxx";
|
|
4802
|
+
}
|
|
4803
|
+
const placeholder = boardId === "s_xxxxxxxx";
|
|
4752
4804
|
if (!opts.skillsOnly) {
|
|
4753
4805
|
if (await exists(CONFIG_FILENAME) && !opts.force) {
|
|
4754
4806
|
actions.push(`\u2022 ${CONFIG_FILENAME} exists \u2014 kept (use --force)`);
|
|
4755
4807
|
} else {
|
|
4756
4808
|
await put(CONFIG_FILENAME, smashspaceJson(baseUrl, boardId));
|
|
4757
4809
|
actions.push(
|
|
4758
|
-
`\u2713 ${CONFIG_FILENAME}${placeholder ? " (placeholder space_id \u2014
|
|
4810
|
+
`\u2713 ${CONFIG_FILENAME}${placeholder ? " (placeholder space_id \u2014 set a real one below)" : ` \u2192 ${boardId}`}`
|
|
4759
4811
|
);
|
|
4760
4812
|
}
|
|
4761
4813
|
if (opts.mcp !== false) {
|
|
@@ -4790,9 +4842,14 @@ program2.command("init").description(
|
|
|
4790
4842
|
}
|
|
4791
4843
|
}
|
|
4792
4844
|
}
|
|
4793
|
-
const next = ["", "Next:"
|
|
4845
|
+
const next = ["", "Next:"];
|
|
4794
4846
|
if (!opts.skillsOnly && placeholder) {
|
|
4795
|
-
next.push("
|
|
4847
|
+
next.push(" This project isn't pinned to a space yet. Pick one:");
|
|
4848
|
+
next.push(' smash init --new-space "My Project" --force # create + pin a new space');
|
|
4849
|
+
next.push(" smash init --space-id s_xxxxxxxx --force # pin an existing space");
|
|
4850
|
+
next.push(" (find ids with: smash space list)");
|
|
4851
|
+
} else if (!opts.skillsOnly) {
|
|
4852
|
+
next.push(" smash login # if you haven't yet \u2014 then you're ready.");
|
|
4796
4853
|
}
|
|
4797
4854
|
next.push(
|
|
4798
4855
|
" \u2192 Claude Code / Codex pick up .claude|.codex/skills/smash automatically"
|
|
@@ -4804,24 +4861,18 @@ program2.command("init").description(
|
|
|
4804
4861
|
show(void 0, [...actions, ...next]);
|
|
4805
4862
|
}
|
|
4806
4863
|
);
|
|
4807
|
-
program2.command("create <title>").description(`Create a card on the resolved board (${CONFIG_FILENAME}, default "main")`).option("-b, --board <label>", "board label from config").option("--board-id <id>", "explicit board id (bypass config)").option("-l, --list <name>", "target list name (default: first list)").option("-d, --desc <text>", "card description (Markdown)").action(async (title, opts) => {
|
|
4864
|
+
program2.command("create <title>").description(`Create a card on the resolved board (${CONFIG_FILENAME}, default "main")`).option("-b, --board <label>", "board label from config").option("--board-id <id>", "explicit board id (bypass config)").option("-l, --list <name>", "target list name (default: first list)").option("-d, --desc <text>", "card description (Markdown)").option("--desc-file <path>", "read description from a file (or - for stdin)").action(async (title, opts) => {
|
|
4808
4865
|
const ctx = resolveContext(opts);
|
|
4809
4866
|
const detail = await fetchBoard(ctx.boardId, ctx.baseUrl);
|
|
4810
4867
|
const list = opts.list ? findListByName(detail, opts.list) : detail.lists[0];
|
|
4811
4868
|
if (!list)
|
|
4812
4869
|
throw new Error(`Board ${ctx.boardId} has no lists`);
|
|
4870
|
+
const description = opts.descFile ? await readTextArg(opts.descFile) : opts.desc;
|
|
4813
4871
|
const card = await api(
|
|
4814
4872
|
`/api/lists/${list.id}/cards`,
|
|
4815
|
-
{ method: "POST", body: JSON.stringify({ title }) },
|
|
4873
|
+
{ method: "POST", body: JSON.stringify(description ? { title, description } : { title }) },
|
|
4816
4874
|
ctx.baseUrl
|
|
4817
4875
|
);
|
|
4818
|
-
if (opts.desc) {
|
|
4819
|
-
await api(
|
|
4820
|
-
`/api/cards/${card.id}`,
|
|
4821
|
-
{ method: "PATCH", body: JSON.stringify({ description: opts.desc }) },
|
|
4822
|
-
ctx.baseUrl
|
|
4823
|
-
);
|
|
4824
|
-
}
|
|
4825
4876
|
show(card, [`\u2713 created ${card.id} "${title}" in ${list.title} (board ${ctx.entry.label})`]);
|
|
4826
4877
|
});
|
|
4827
4878
|
program2.command("read").description(`Show the resolved board \u2014 lists + cards (${CONFIG_FILENAME}, default "main")`).option("-b, --board <label>", "board label from config").option("--board-id <id>", "explicit board id (bypass config)").action(async (opts) => {
|