@usableapp/cardds 0.7.71 → 0.7.72

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/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  > For: a project that consumes `@usableapp/cardds` and its AI — what changed between versions, written as before → after, so a screen can be brought up to date without reading the source. Newest first. A consumer never edits cardds; if something here leaves you without a way to do what you did before, report the gap.
4
4
 
5
+ ## 0.7.72 — 2026-10-08
6
+
7
+ ### Added
8
+ - **The catalog — for an AI session that builds with cardds** (Lh 2026-10-08) — the package now carries every component, its props and slots, and every variation's code and rendered HTML (`dist/catalog.json`), with one set of commands over it. Before → after: a session read `src/` and the gallery's stories to learn a component → it asks. `npx cardds list` (every name, by group) · `npx cardds get Menu [--html] [--all]` (props, slots, each variation's code) · `npx cardds find stamp picker` (the screens already drawn first, then components, then what they are used with — ask in English words) · `npx cardds scaffold [id] [--html [--css <href>]]` (a file to start a screen from: the React code, or a static HTML page that links `cardds.css`) · `npx cardds check` as before. Each takes `--json`.
9
+ - **`npx cardds mcp`** — the same five as an MCP server over stdio (`list_components`, `get_component`, `find_components`, `scaffold_screen`, `check_source`): `{"mcpServers":{"cardds":{"command":"npx","args":["cardds","mcp"]}}}`. It answers for the version the project has installed. README "For an AI session: the catalog".
10
+
5
11
  ## 0.7.71 — 2026-10-08
6
12
 
7
13
  ### Fixed
package/README.md CHANGED
@@ -136,6 +136,28 @@ back here. It is deliberately thin — **this repo stays the source of truth**,
136
136
  so new components and rule changes go here, and the skill only gets updated
137
137
  if the contract itself changes.
138
138
 
139
+ ### For an AI session: the catalog
140
+
141
+ A session asked for a prototype need not read `src/`: the package ships a catalog (`dist/catalog.json`, built by
142
+ `npm run build`) of every component — props, slots — and every story with its code and its rendered HTML. Ask it first:
143
+
144
+ ```bash
145
+ npx cardds find <words…> # the screens already drawn, then components, then what is used with them (no match → every screen)
146
+ npx cardds scaffold <id> # a whole file from a screen: tsx (the code) or --html (a static document linking cardds.css)
147
+ npx cardds get <Name> [--html] # one component: props (a ReactNode prop is a slot) + variations with code
148
+ npx cardds list # every name and group, nothing more
149
+ npx cardds check <dir> # the rules, before a screen is called done
150
+ ```
151
+
152
+ All take `--json` (the function's result) and `--root <path>` (a cardds checkout: rebuilt first, so never stale). The same
153
+ five answers over MCP — `list_components` · `get_component` · `find_components` · `scaffold_screen` · `check_source`:
154
+
155
+ ```json
156
+ { "mcpServers": { "cardds": { "command": "npx", "args": ["cardds", "mcp"] } } }
157
+ ```
158
+
159
+ A scaffolded tsx may import `./_story` or `./_*Art`: story glue, not in the package — replace or drop it.
160
+
139
161
  ### React adapter — how a component is written
140
162
 
141
163
  `src/<group>/<Name>.tsx`, one export per class family; sub-parts are their own
package/bin/cardds.mjs CHANGED
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  // `npx cardds init` — set a project up the ONE way (Lh 2026-09-29): the project's theme.css, the check script, the ESLint rules.
3
3
  // `npx cardds check [dir…]` — the rules of lint/rules.mjs over the project's source; exit 1 on an error.
4
+ // `npx cardds list | get <Name> | find <words…> | scaffold [id]` — the component catalog (lib/catalog.mjs) for an AI session; `npx cardds mcp` serves the same over MCP.
4
5
  import { existsSync, readFileSync, writeFileSync, readdirSync, statSync, mkdirSync, copyFileSync } from 'node:fs';
5
6
  import { dirname, join, relative } from 'node:path';
6
7
  import { fileURLToPath } from 'node:url';
7
8
  import { checkSource } from '../lint/rules.mjs';
9
+ import * as catalog from '../lib/catalog.mjs';
8
10
 
9
11
  const PKG = join(dirname(fileURLToPath(import.meta.url)), '..');
10
12
  const [cmd, ...args] = process.argv.slice(2);
@@ -68,5 +70,43 @@ import them, never copy their markup: the behaviour lives in the component. Gall
68
70
  return 0;
69
71
  }
70
72
 
71
- const code = cmd === 'check' ? check(args) : cmd === 'init' ? init() : (console.log('usage: cardds init | cardds check [dir…]'), cmd ? 2 : 0);
72
- process.exit(code);
73
+ const one = (text) => text.replace(/\s+/g, ' ').trim();
74
+ const indent = (text, by = ' ') => text.replace(/^(?=.)/gm, by);
75
+
76
+ // each catalog command: the function's result as compact text (--json prints the result itself)
77
+ const asText = {
78
+ list: (groups) => Object.entries(Object.groupBy(groups, (c) => c.group)).map(([g, cs]) => `${g}: ${cs.map((c) => c.name).join(' ')}`).join('\n'),
79
+ get: (c, { html }) => [
80
+ `${c.name} (${c.group})${c.element ? ` — forwards the <${c.element}> attributes` : ''}`,
81
+ one(c.description),
82
+ '\nprops',
83
+ ...c.props.map((p) => ` ${p.name}${p.optional ? '?' : ''}: ${p.type}${p.slot ? ' [slot]' : ''}${p.description ? ` — ${one(p.description)}` : ''}`),
84
+ `\nvariations (${c.variations.length})`,
85
+ ...c.variations.map((v) => `\n ${v.id} — ${one(v.description)}\n${indent(v.tsx)}${html ? `\n html:\n${indent(v.html, ' ')}` : ''}`),
86
+ ...(c.alsoUsedIn ? [`\nalso used in: ${c.alsoUsedIn.join(' ')}\n(--all prints their code too)`] : []),
87
+ ].join('\n'),
88
+ find: (r) => [
89
+ ...(r.fallback ? [`nothing matches "${r.query}" — ${r.hint}. Every screen already drawn:\n${r.results.map((x) => x.id).join(' ')}`] : r.results.map((x) => `${x.kind === 'screen' ? 'screen ' : 'component '}${x.id ?? x.name} — ${x.summary}`)),
90
+ ...(r.usedTogether?.length ? [`\nused together: ${r.usedTogether.map((u) => `${u.name} (${u.stories})`).join(' ')}`] : []),
91
+ ].join('\n'),
92
+ scaffold: (r) => typeof r === 'string' ? r.trimEnd() : r.map((x) => `${x.id} — ${x.summary}`).join('\n'),
93
+ };
94
+
95
+ function ask(cmd, argv) {
96
+ const flag = (name) => { const i = argv.indexOf(name); return i >= 0 && argv.splice(i, 1).length > 0; };
97
+ const at = argv.indexOf('--root');
98
+ const root = at >= 0 ? join(cwd, argv.splice(at, 2)[1] ?? '') : undefined;
99
+ const cssAt = argv.indexOf('--css'), css = cssAt >= 0 ? argv.splice(cssAt, 2)[1] : undefined;
100
+ const json = flag('--json'), html = flag('--html'), all = flag('--all');
101
+ if (cmd === 'mcp') return import('../lib/mcp.mjs').then((m) => m.serve({ root }));
102
+ const cat = catalog.load({ root });
103
+ const q = argv.join(' ');
104
+ const result = cmd === 'list' ? catalog.list(cat) : cmd === 'get' ? catalog.get(cat, q, { html, all }) : cmd === 'find' ? catalog.find(cat, q) : catalog.scaffold(cat, q || undefined, { format: html ? 'html' : 'tsx', css });
105
+ if (result.error) { console.error(`cardds ${cmd}: ${result.error}${result.closest ? ` — closest: ${result.closest.join(', ')}` : ''}`); return 1; }
106
+ console.log(json ? JSON.stringify(result) : asText[cmd](result, { html }));
107
+ return 0;
108
+ }
109
+
110
+ const code = cmd === 'check' ? check(args) : cmd === 'init' ? init() : ['list', 'get', 'find', 'scaffold', 'mcp'].includes(cmd) ? await ask(cmd, args)
111
+ : (console.log('usage: cardds init | check [dir…] | list | get <Name> [--html] | find <words…> | scaffold [id] [--html [--css <href>]] | mcp (list get find scaffold mcp: --root <path> --json)'), cmd ? 2 : 0);
112
+ process.exitCode = code; // not process.exit: a big answer piped to a reader is cut off by an exit before it is flushed