smashspace 0.1.0 → 0.1.2

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 CHANGED
@@ -45,6 +45,7 @@ Drop this at your repo root so `smash` knows which board to operate on:
45
45
  ## Everyday commands
46
46
 
47
47
  ```bash
48
+ smash status # what context is this directory in? (see below)
48
49
  smash read # show the board (lists + cards)
49
50
  smash scan --list "やること" # actionable cards as JSON (for agents)
50
51
  smash create "Fix signup bug" # add a card (to the first list, or --list)
@@ -56,6 +57,28 @@ smash --help # full command list (board/list/card/…)
56
57
  Pick a board with `-b <label>` (defaults to `main`), or bypass config with
57
58
  `--board-id <id>`.
58
59
 
60
+ ## Multiple repos on one machine
61
+
62
+ `smash` is **directory-aware**: it walks up from your current directory to the
63
+ nearest `.smashspace.json`, so each repo operates on its own instance and board
64
+ automatically — no global switch to flip.
65
+
66
+ ```bash
67
+ smash status # config file, instance, board, and signed-in account for CWD
68
+ ```
69
+
70
+ ```text
71
+ config .smashspace.json
72
+ base_url https://smashspace.app (.smashspace.json)
73
+ board main → b_xxxxxxxx
74
+ account <userId> ✓ token ok (23 boards) [file]
75
+ ```
76
+
77
+ Run it whenever you're unsure which repo/board a command will hit. Add
78
+ `--no-check` to skip the network round-trip, or `--json` for scripts. The login
79
+ token itself is stored once per instance in `~/.config/smash/credentials.json`
80
+ and shared across repos that point at the same `base_url`.
81
+
59
82
  > `smash space …` is an alias of `smash board …`, and the HTTP API mirrors
60
83
  > `/api/boards` at `/api/spaces`. In `.smashspace.json`, `space_id` / `spaces`
61
84
  > also work as aliases for `board_id` / `boards`.
package/dist/smash.mjs CHANGED
@@ -3395,6 +3395,7 @@ import { randomBytes } from "node:crypto";
3395
3395
  import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
3396
3396
  import { platform as platform2 } from "node:os";
3397
3397
  import { createInterface } from "node:readline/promises";
3398
+ import { relative } from "node:path";
3398
3399
  import { stdin, stdout } from "node:process";
3399
3400
 
3400
3401
  // ../../shared/board-registry.ts
@@ -4389,6 +4390,95 @@ program2.command("whoami").description("Show the user identified by the saved/en
4389
4390
  [`\u2713 ${me.userId} (${me.boardCount} boards) on ${baseUrl}`]
4390
4391
  );
4391
4392
  });
4393
+ program2.command("status").aliases(["context", "where"]).description(
4394
+ "Show the context resolved from the current directory: config file, instance, board, and signed-in account"
4395
+ ).option("--no-check", "Skip the live token check (offline)").action(async (opts) => {
4396
+ const loaded = loadProjectConfig();
4397
+ const baseUrl = resolveBaseUrl(loaded);
4398
+ const baseSource = program2.getOptionValueSource("baseUrl") === "cli" ? "--base-url" : loaded?.config.base_url ? CONFIG_FILENAME : process.env.SMASH_BASE_URL ? "env:SMASH_BASE_URL" : "default";
4399
+ let board = null;
4400
+ let boardError = null;
4401
+ if (loaded) {
4402
+ try {
4403
+ const entry = resolveBoardEntry(loaded.config, void 0);
4404
+ board = { label: entry.label, boardId: entry.board_id };
4405
+ } catch (e) {
4406
+ boardError = e instanceof Error ? e.message : String(e);
4407
+ }
4408
+ }
4409
+ const cred = await findCredential(baseUrl);
4410
+ const token = await resolveToken(baseUrl);
4411
+ const tokenSource = process.env.SMASH_TOKEN ? "env:SMASH_TOKEN" : cred ? "file" : null;
4412
+ let live = null;
4413
+ if (token && opts.check) {
4414
+ try {
4415
+ const ctrl = new AbortController();
4416
+ const timer = setTimeout(() => ctrl.abort(), 5e3);
4417
+ const res = await fetch(new URL("/api/usage/me", baseUrl), {
4418
+ headers: { Authorization: `Bearer ${token}` },
4419
+ signal: ctrl.signal
4420
+ }).finally(() => clearTimeout(timer));
4421
+ if (res.ok) {
4422
+ const me = await res.json();
4423
+ live = { ok: true, userId: me.userId, boardCount: me.boardCount };
4424
+ } else {
4425
+ live = { ok: false, error: `${res.status} ${res.statusText}` };
4426
+ }
4427
+ } catch (e) {
4428
+ live = { ok: false, error: e instanceof Error ? e.message : String(e) };
4429
+ }
4430
+ }
4431
+ const account = (live?.ok ? live.userId : void 0) ?? cred?.userId ?? null;
4432
+ if (getOpts().json) {
4433
+ show({
4434
+ cwd: process.cwd(),
4435
+ config: loaded?.path ?? null,
4436
+ baseUrl,
4437
+ baseUrlSource: baseSource,
4438
+ board,
4439
+ boardError,
4440
+ account,
4441
+ tokenPresent: !!token,
4442
+ tokenSource,
4443
+ live,
4444
+ boards: loaded?.config.boards.map((b) => ({
4445
+ label: b.label,
4446
+ boardId: b.board_id
4447
+ })) ?? []
4448
+ });
4449
+ return;
4450
+ }
4451
+ const cfgDisplay = loaded ? relative(process.cwd(), loaded.path) || loaded.path : `(none \u2014 searched up from ${process.cwd()})`;
4452
+ const lines = [
4453
+ `config ${cfgDisplay}`,
4454
+ `base_url ${baseUrl} (${baseSource})`
4455
+ ];
4456
+ if (board)
4457
+ lines.push(`board ${board.label} \u2192 ${board.boardId}`);
4458
+ else if (boardError)
4459
+ lines.push(`board (unresolved: ${boardError})`);
4460
+ else
4461
+ lines.push(`board (no ${CONFIG_FILENAME}; pass --board-id)`);
4462
+ if (!token) {
4463
+ lines.push(`account \u2717 not signed in \u2014 run 'smash login'`);
4464
+ } else if (live) {
4465
+ lines.push(
4466
+ live.ok ? `account ${live.userId} \u2713 token ok (${live.boardCount} boards) [${tokenSource}]` : `account ${account ?? "?"} \u2717 token check failed: ${live.error} [${tokenSource}]`
4467
+ );
4468
+ } else {
4469
+ lines.push(
4470
+ `account ${account ?? "(token set)"} [${tokenSource}] (not checked)`
4471
+ );
4472
+ }
4473
+ if (loaded && loaded.config.boards.length > 1) {
4474
+ const active = board?.label ?? loaded.config.default ?? "main";
4475
+ lines.push("", "boards:");
4476
+ for (const b of loaded.config.boards) {
4477
+ lines.push(` ${b.label === active ? "*" : " "} ${b.label} \u2192 ${b.board_id}`);
4478
+ }
4479
+ }
4480
+ show(void 0, lines);
4481
+ });
4392
4482
  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) => {
4393
4483
  const ctx = resolveContext(opts);
4394
4484
  const detail = await fetchBoard(ctx.boardId, ctx.baseUrl);
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "smashspace",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "SmashSpace CLI + MCP — operate SmashSpace boards from your terminal, Claude Code, Cursor, and Codex.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "smash": "dist/smash.mjs",
8
+ "smashspace": "dist/smash.mjs",
8
9
  "smashspace-mcp": "dist/smashspace-mcp.mjs"
9
10
  },
10
11
  "files": [