beehaven 0.8.16

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 (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +201 -0
  3. package/dist/__tests__/_cli-switch.js +105 -0
  4. package/dist/__tests__/agent-wrap-probe.js +136 -0
  5. package/dist/agent-attribution.js +192 -0
  6. package/dist/agent-identity-daemon.js +135 -0
  7. package/dist/agent-onboard.js +156 -0
  8. package/dist/agent-scope.js +79 -0
  9. package/dist/agent-sessions.js +370 -0
  10. package/dist/agent-state.js +482 -0
  11. package/dist/app-manifest.js +221 -0
  12. package/dist/app-signer.js +54 -0
  13. package/dist/app-signing.js +127 -0
  14. package/dist/audio-play.js +98 -0
  15. package/dist/avatar-protocol.js +25 -0
  16. package/dist/avatar-route.js +79 -0
  17. package/dist/bee/cloud-proxy.js +130 -0
  18. package/dist/blocked-actions.js +120 -0
  19. package/dist/build-channel.js +101 -0
  20. package/dist/buildings/daemon-workspace.js +475 -0
  21. package/dist/claude-bridge.js +310 -0
  22. package/dist/claude-credential-store.js +633 -0
  23. package/dist/clearly-sync.js +518 -0
  24. package/dist/clearly.js +616 -0
  25. package/dist/cli-state.js +334 -0
  26. package/dist/cli.js +6973 -0
  27. package/dist/commands/agent-skills.js +302 -0
  28. package/dist/commands/agent.js +1310 -0
  29. package/dist/commands/app.js +1123 -0
  30. package/dist/commands/auth.js +171 -0
  31. package/dist/commands/building-shell.js +392 -0
  32. package/dist/commands/canvas.js +932 -0
  33. package/dist/commands/claude.js +218 -0
  34. package/dist/commands/compliance.js +387 -0
  35. package/dist/commands/compose.js +507 -0
  36. package/dist/commands/composition.js +95 -0
  37. package/dist/commands/connect-session.js +409 -0
  38. package/dist/commands/conversations.js +160 -0
  39. package/dist/commands/costs.js +443 -0
  40. package/dist/commands/d1.js +253 -0
  41. package/dist/commands/daemon-cmd.js +162 -0
  42. package/dist/commands/deploy.js +164 -0
  43. package/dist/commands/design.js +617 -0
  44. package/dist/commands/dev-generate.js +125 -0
  45. package/dist/commands/dev.js +109 -0
  46. package/dist/commands/devices.js +135 -0
  47. package/dist/commands/explain-router.js +24 -0
  48. package/dist/commands/explain.js +882 -0
  49. package/dist/commands/explore.js +264 -0
  50. package/dist/commands/help.js +2418 -0
  51. package/dist/commands/init.js +408 -0
  52. package/dist/commands/install.js +129 -0
  53. package/dist/commands/library.js +229 -0
  54. package/dist/commands/marketplace.js +138 -0
  55. package/dist/commands/mcp-install.js +476 -0
  56. package/dist/commands/mention.js +257 -0
  57. package/dist/commands/omni.js +529 -0
  58. package/dist/commands/secrets.js +323 -0
  59. package/dist/commands/self-update.js +206 -0
  60. package/dist/commands/sheet.js +264 -0
  61. package/dist/commands/shell-output.js +309 -0
  62. package/dist/commands/shell.js +1048 -0
  63. package/dist/commands/shop.js +368 -0
  64. package/dist/commands/skills.js +311 -0
  65. package/dist/commands/slides.js +418 -0
  66. package/dist/commands/swarm.js +341 -0
  67. package/dist/commands/ticket.js +1301 -0
  68. package/dist/commands/tool.js +312 -0
  69. package/dist/commands/trash.js +106 -0
  70. package/dist/commands/treehouse.js +120 -0
  71. package/dist/commands/user.js +240 -0
  72. package/dist/commands/voice-tts.js +319 -0
  73. package/dist/commands/voice.js +166 -0
  74. package/dist/commands/watch-git.js +401 -0
  75. package/dist/commands/webhook.js +159 -0
  76. package/dist/commands/workspace.js +3354 -0
  77. package/dist/composition-repos.js +91 -0
  78. package/dist/daemon-avatar.js +130 -0
  79. package/dist/daemon-claude-code-auto-install.js +52 -0
  80. package/dist/daemon-client.js +363 -0
  81. package/dist/daemon-codex.js +349 -0
  82. package/dist/daemon-entry.js +101 -0
  83. package/dist/daemon-fetch-allowlist.js +194 -0
  84. package/dist/daemon-gemini.js +224 -0
  85. package/dist/daemon-mcp-clearly.js +353 -0
  86. package/dist/daemon-mcp-sandbox.js +127 -0
  87. package/dist/daemon-omni-company.js +338 -0
  88. package/dist/daemon-omni-hooks.js +375 -0
  89. package/dist/daemon-omni-identity.js +229 -0
  90. package/dist/daemon-omni-pipeline.js +251 -0
  91. package/dist/daemon-omni-policies.js +346 -0
  92. package/dist/daemon-omni-principal.js +580 -0
  93. package/dist/daemon-omni-tool-gate.js +116 -0
  94. package/dist/daemon-omni.js +1442 -0
  95. package/dist/daemon-path-transform.js +120 -0
  96. package/dist/daemon-write-sandbox.js +191 -0
  97. package/dist/daemon.js +4471 -0
  98. package/dist/dashboard.js +846 -0
  99. package/dist/dep-installer.js +286 -0
  100. package/dist/dev-mode.js +67 -0
  101. package/dist/doc-indexer.js +169 -0
  102. package/dist/e2e-crypto.js +94 -0
  103. package/dist/feature-flags.js +154 -0
  104. package/dist/file-browser.js +88 -0
  105. package/dist/first-party-hives.js +211 -0
  106. package/dist/folder-sync.js +226 -0
  107. package/dist/health.js +56 -0
  108. package/dist/ipc-auth.js +299 -0
  109. package/dist/ipc-client.js +495 -0
  110. package/dist/ipc-server.js +684 -0
  111. package/dist/keychain.js +224 -0
  112. package/dist/lib/admin-client.js +73 -0
  113. package/dist/lib/mobile-build-parser.js +156 -0
  114. package/dist/local-app-manager.js +660 -0
  115. package/dist/log-redaction.js +89 -0
  116. package/dist/logger.js +108 -0
  117. package/dist/manifest.js +143 -0
  118. package/dist/marketplace.js +793 -0
  119. package/dist/min-version-copy.js +51 -0
  120. package/dist/min-version.js +214 -0
  121. package/dist/public-surface.js +23 -0
  122. package/dist/relay-presets.js +89 -0
  123. package/dist/relay.js +2723 -0
  124. package/dist/repl.js +619 -0
  125. package/dist/setup-relay.js +363 -0
  126. package/dist/setup-service.js +536 -0
  127. package/dist/shared/bee-memory/index.js +6 -0
  128. package/dist/shared/bee-memory/types.js +682 -0
  129. package/dist/shared/claude-code/daemon-types.js +112 -0
  130. package/dist/shared/claude-code/index.js +2 -0
  131. package/dist/shared/claude-code/types.js +1 -0
  132. package/dist/shared/status/schema.js +133 -0
  133. package/dist/shell-session.js +198 -0
  134. package/dist/speak-lock.js +97 -0
  135. package/dist/spinner.js +57 -0
  136. package/dist/telemetry.js +166 -0
  137. package/dist/terminal/block-parser.js +207 -0
  138. package/dist/terminal/shell-integration.js +108 -0
  139. package/dist/transcript-tailer.js +215 -0
  140. package/dist/tts-qwen3.js +547 -0
  141. package/dist/types.js +4 -0
  142. package/dist/utm.js +38 -0
  143. package/dist/verb-surface.js +331 -0
  144. package/dist/version-utils.js +85 -0
  145. package/dist/watcher.js +207 -0
  146. package/package.json +88 -0
  147. package/python/qwen3_tts.py +230 -0
  148. package/scripts/stamp-build-channel.mjs +115 -0
  149. package/scripts/sync-installed.mjs +159 -0
  150. package/scripts/sync-skills.mjs +112 -0
  151. package/skills/README.md +24 -0
  152. package/skills/agent-doodle-space/SKILL.md +118 -0
  153. package/skills/agent-home/SKILL.md +98 -0
  154. package/skills/beehaven-cli/SKILL.md +144 -0
  155. package/skills/claude-shopper/README.md +90 -0
  156. package/skills/claude-shopper/SKILL.md +151 -0
  157. package/skills/clearly-agent/SKILL.md +183 -0
  158. package/skills/clearly-canvas/SKILL.md +222 -0
  159. package/skills/clearly-canvas/authoring.md +160 -0
  160. package/skills/clearly-canvas/communications.md +32 -0
  161. package/skills/clearly-canvas/composition-patterns.md +84 -0
  162. package/skills/clearly-canvas/example.mjs +78 -0
  163. package/skills/clearly-canvas/project-plans.md +156 -0
  164. package/skills/clearly-canvas/review.mjs +68 -0
  165. package/skills/clearly-workspace/SKILL.md +79 -0
  166. package/skills/clearly-workspace/survey.mjs +101 -0
  167. package/skills/codebase-map/SKILL.md +142 -0
  168. package/skills/design-craft/SKILL.md +300 -0
  169. package/skills/index.json +79 -0
  170. package/skills/layout-systems/SKILL.md +184 -0
  171. package/skills/pair-on-canvas/SKILL.md +92 -0
@@ -0,0 +1,617 @@
1
+ /**
2
+ * `beehaven brand` and `beehaven component` — the workspace design system from a terminal.
3
+ *
4
+ * ⚠⚠ WHY VERBS WHEN `call brand` ALREADY WORKED. Same argument `sheet.ts` and `ticket.ts` make:
5
+ * the brand and the component library are first-class workspace artifacts, and an artifact whose
6
+ * only door is `beehaven call <action> '{…}'` is one nobody thinks to look for. These verbs are
7
+ * thin — every one maps to ONE action — and they earn their place by making the common traps
8
+ * untypeable:
9
+ *
10
+ * · `brand set <brand> color.accent '#FF5500'` builds the ROLE-MERGE patch `{ colors: { accent } }`.
11
+ * Sending `colors` as an array REPLACES the whole palette — the easiest way to lose ten colours
12
+ * while changing one. The verb never sends the array form.
13
+ * · `component place` needs a composition; it is taken as `--composition`, and `--x/--y` are
14
+ * numbers, not strings the worker has to coerce.
15
+ * · `component render -o button.svg` writes the file — a preview is something you look at.
16
+ *
17
+ * ⚠ `agentRpc`, NEVER bare `rpcOnce` — a bare call runs as the ACCOUNT OWNER rather than as the
18
+ * signed-in agent, skipping `agent-invoke`, the per-agent capability gate and the activity log.
19
+ * A refusal here is the gate working: it names the permission (`agent-denied-<verb>`).
20
+ */
21
+ import { readFileSync, writeFileSync } from 'node:fs';
22
+ import { agentRpc as rpcOnce } from './workspace.js';
23
+ const c = {
24
+ dim: (s) => `\x1b[2m${s}\x1b[0m`,
25
+ cyan: (s) => `\x1b[36m${s}\x1b[0m`,
26
+ bold: (s) => `\x1b[1m${s}\x1b[0m`,
27
+ yellow: (s) => `\x1b[33m${s}\x1b[0m`,
28
+ red: (s) => `\x1b[31m${s}\x1b[0m`,
29
+ };
30
+ function getTimeout(values) {
31
+ const t = Number(values?.timeout);
32
+ return Number.isFinite(t) && t > 0 ? t : 30_000;
33
+ }
34
+ /** ⚠ `rpcOnce` returns `{ error }` as a VALUE and never throws — see `rpcFailure` in workspace.ts. */
35
+ function failure(res) {
36
+ if (!res || typeof res !== 'object')
37
+ return null;
38
+ if (typeof res.error === 'string' && res.error)
39
+ return res.error + (res.code ? c.dim(` (${res.code})`) : '');
40
+ if (res.ok === false)
41
+ return String(res.message ?? res.code ?? 'the workspace refused the call');
42
+ return null;
43
+ }
44
+ function die(msg, hint) {
45
+ console.error(` ${msg}`);
46
+ if (hint)
47
+ console.error(c.dim(` ${hint}`));
48
+ process.exit(1);
49
+ }
50
+ /** `--patch '{…}'` or `--patch @file.json`. */
51
+ function readJson(raw, flag) {
52
+ if (raw === undefined || raw === null || raw === '')
53
+ return undefined;
54
+ const s = String(raw);
55
+ const text = s.startsWith('@') ? readFileSync(s.slice(1), 'utf8') : s;
56
+ try {
57
+ return JSON.parse(text);
58
+ }
59
+ catch (e) {
60
+ die(`${flag} is not valid JSON: ${e?.message ?? e}`);
61
+ }
62
+ }
63
+ function num(v) {
64
+ if (v === undefined || v === null || v === '')
65
+ return undefined;
66
+ const n = Number(v);
67
+ return Number.isFinite(n) ? n : undefined;
68
+ }
69
+ const swatch = (hex) => {
70
+ const m = /^#?([0-9a-f]{6})/i.exec(hex);
71
+ if (!m)
72
+ return ' ';
73
+ const r = parseInt(m[1].slice(0, 2), 16), g = parseInt(m[1].slice(2, 4), 16), b = parseInt(m[1].slice(4, 6), 16);
74
+ return `\x1b[48;2;${r};${g};${b}m \x1b[0m`;
75
+ };
76
+ // ── brand ──────────────────────────────────────────────────────────────────────────────────
77
+ /**
78
+ * `brand set <brand> <field> <value>` → a patch. ⚠ Every colour goes through the role MAP, never the
79
+ * array, so one colour changes and the other roles are untouched.
80
+ */
81
+ export function brandFieldPatch(field, value) {
82
+ const f = field.trim();
83
+ let m;
84
+ if ((m = /^(?:color|colour|colors)\.([a-z0-9-]+)$/i.exec(f)))
85
+ return { colors: { [m[1].toLowerCase()]: value === 'none' ? null : value } };
86
+ if ((m = /^(?:dark|colorsDark)\.([a-z0-9-]+)$/i.exec(f)))
87
+ return { colorsDark: { [m[1].toLowerCase()]: value === 'none' ? null : value } };
88
+ if ((m = /^(?:type|font|typography)\.(display|heading|body|label|mono)(?:\.(family|weight|size|lineHeight|letterSpacing))?$/i.exec(f))) {
89
+ const role = m[1].toLowerCase(), prop = m[2];
90
+ if (!prop || prop === 'family')
91
+ return { typography: { [role]: value } };
92
+ const n = Number(value);
93
+ if (!Number.isFinite(n))
94
+ return `${f} needs a number`;
95
+ return { typography: { [role]: { [prop]: n } } };
96
+ }
97
+ if ((m = /^(?:shape\.)?(radius|spacing|borderWidth)$/i.exec(f))) {
98
+ const n = Number(value);
99
+ return Number.isFinite(n) ? { shape: { [m[1]]: n } } : `${f} needs a number`;
100
+ }
101
+ if (/^voice(\.summary)?$/i.test(f))
102
+ return { voice: { summary: value } };
103
+ if (/^voice\.tone$/i.test(f))
104
+ return { voice: { tone: value.split(',').map((s) => s.trim()).filter(Boolean) } };
105
+ if (/^voice\.dos$/i.test(f))
106
+ return { voice: { dos: value.split('|').map((s) => s.trim()).filter(Boolean) } };
107
+ if (/^voice\.donts$/i.test(f))
108
+ return { voice: { donts: value.split('|').map((s) => s.trim()).filter(Boolean) } };
109
+ if (/^(name|description|imagery)$/i.test(f))
110
+ return { [f.toLowerCase()]: value };
111
+ return `unknown field "${f}" — use color.<role>, dark.<role>, type.<role>[.weight|.size|.lineHeight|.letterSpacing], radius, spacing, borderWidth, voice, voice.tone, voice.dos, voice.donts, name, description, imagery`;
112
+ }
113
+ export async function cmdBrand(sub, rest, values) {
114
+ const t = getTimeout(values);
115
+ const json = (res) => console.log(JSON.stringify(res, null, 2));
116
+ if (sub === 'help' || sub === '--help') {
117
+ console.log(`
118
+ ${c.cyan('beehaven brand')} — the workspace brand (colours, type, logos, voice, shape)
119
+
120
+ brand [list] Every brand; the default is marked
121
+ brand show [<brand>] One brand in full + its checks (default brand when omitted)
122
+ brand tokens [<brand>] Design tokens (--format css|json · --mode dark · --composition <id>)
123
+ brand new "<name>" Create one (--from <brand> duplicates)
124
+ brand set <brand> <field> <value> Change ONE field, e.g. color.accent '#FF5500' · type.heading Fraunces
125
+ brand edit <brand> --patch <json> Any patch (--patch @file.json)
126
+ brand default <brand> Make it the workspace default
127
+ brand bind <brand> --composition <id> Design that canvas with this brand
128
+ brand board [<brand>] Lay it out as an editable brand board (--composition <id> · --name "<title>")
129
+ brand history <brand> Versions, newest first
130
+ brand revert <brand> <version> Restore a version (adds a new one)
131
+ brand rm <brand> Delete — recoverable
132
+ brand restore <brandId> Bring a deleted brand back
133
+
134
+ ${c.dim('<brand> is an id or a name. Colour roles: primary, secondary, accent, neutral, background, surface,')}
135
+ ${c.dim('text, muted, success, warning, danger — or any custom name. `none` removes a role.')}
136
+ ${c.dim('Agents: `brand` / clearly_brand does the same with { op }. Edits are bounded by your permissions.')}
137
+ `);
138
+ return;
139
+ }
140
+ if (!sub || sub === 'list' || sub === 'ls') {
141
+ const res = await rpcOnce('brand-list', {}, t, values);
142
+ const f = failure(res);
143
+ if (f)
144
+ die(f);
145
+ if (values.json)
146
+ return json(res);
147
+ const rows = res?.brands ?? [];
148
+ if (!rows.length) {
149
+ console.log(` No brand yet. ${c.dim('beehaven brand new "Acme"')}`);
150
+ return;
151
+ }
152
+ console.log('');
153
+ for (const b of rows) {
154
+ const sw = (b.colors ?? []).slice(0, 6).map((x) => swatch(String(x.hex))).join('');
155
+ console.log(` ${sw} ${c.bold(String(b.name))}${b.isDefault ? c.dim(' (default)') : ''} ${c.dim(String(b.id))}`);
156
+ }
157
+ console.log('');
158
+ return;
159
+ }
160
+ if (sub === 'show' || sub === 'get') {
161
+ const ref = rest.join(' ').trim();
162
+ const res = await rpcOnce('brand-get', { ...(ref ? { id: ref } : {}), ...(values.composition ? { compositionId: String(values.composition) } : {}) }, t, values);
163
+ const f = failure(res);
164
+ if (f)
165
+ die(f);
166
+ if (values.json)
167
+ return json(res);
168
+ console.log('');
169
+ console.log(` ${c.bold(String(res.name))}${res.isDefault ? c.dim(' (default)') : ''} ${c.dim(`v${res.version} · ${res.id}`)}`);
170
+ if (res.description)
171
+ console.log(` ${res.description}`);
172
+ console.log(`\n ${c.dim('COLOUR')}`);
173
+ for (const x of res.colors ?? [])
174
+ console.log(` ${swatch(String(x.hex))} ${String(x.role).padEnd(12)} ${String(x.hex).padEnd(9)} ${c.dim(String(x.name ?? ''))}`);
175
+ if ((res.colorsDark ?? []).length)
176
+ console.log(` ${c.dim(`dark: ${res.colorsDark.map((x) => `${x.role} ${x.hex}`).join(', ')}`)}`);
177
+ console.log(`\n ${c.dim('TYPE')}`);
178
+ for (const r of ['display', 'heading', 'body', 'label', 'mono']) {
179
+ const s = res.typography?.[r];
180
+ if (!s)
181
+ continue;
182
+ console.log(` ${r.padEnd(8)} ${s.family} ${s.weight} · ${s.size}px / ${s.lineHeight}`);
183
+ }
184
+ console.log(`\n ${c.dim('SHAPE')} radius ${res.shape?.radius}px · spacing ${res.shape?.spacing}px · stroke ${res.shape?.borderWidth}px`);
185
+ if ((res.logos ?? []).length) {
186
+ console.log(`\n ${c.dim('LOGOS')}`);
187
+ for (const l of res.logos)
188
+ console.log(` ${String(l.variant).padEnd(9)} ${l.url.length > 90 ? `${l.url.slice(0, 87)}…` : l.url}`);
189
+ }
190
+ if (res.voice?.summary || res.voice?.tone?.length) {
191
+ console.log(`\n ${c.dim('VOICE')} ${res.voice.summary ?? ''}`);
192
+ if (res.voice.tone?.length)
193
+ console.log(` tone: ${res.voice.tone.join(', ')}`);
194
+ }
195
+ if ((res.audit ?? []).length) {
196
+ console.log(`\n ${c.dim('CHECKS')}`);
197
+ for (const a of res.audit)
198
+ console.log(` ${a.level === 'error' ? c.red('✗') : c.yellow('!')} ${a.message}`);
199
+ }
200
+ console.log('');
201
+ return;
202
+ }
203
+ if (sub === 'tokens' || sub === 'css') {
204
+ const ref = rest.join(' ').trim();
205
+ const res = await rpcOnce('brand-tokens', {
206
+ ...(ref ? { id: ref } : {}),
207
+ ...(values.mode ? { mode: String(values.mode) } : {}),
208
+ ...(values.composition ? { compositionId: String(values.composition) } : {}),
209
+ }, t, values);
210
+ const f = failure(res);
211
+ if (f)
212
+ die(f);
213
+ if (values.json)
214
+ return json(res);
215
+ const out = sub === 'css' || values.format === 'css' ? res.css : JSON.stringify(res.tokens, null, 2);
216
+ if (values.output) {
217
+ writeFileSync(String(values.output), out);
218
+ console.log(` Wrote ${values.output}`);
219
+ return;
220
+ }
221
+ process.stdout.write(out.endsWith('\n') ? out : `${out}\n`);
222
+ return;
223
+ }
224
+ if (sub === 'new' || sub === 'create') {
225
+ const name = (rest.join(' ').trim() || String(values.name ?? '')).trim();
226
+ if (!name)
227
+ die('a name is required', 'beehaven brand new "Harbor"');
228
+ const res = await rpcOnce('brand-create', { name, ...(values.from ? { fromBrandId: String(values.from) } : {}) }, t, values);
229
+ const f = failure(res);
230
+ if (f)
231
+ die(f);
232
+ if (values.json)
233
+ return json(res);
234
+ console.log(` Created ${c.bold(name)} ${c.dim(String(res.id))}`);
235
+ console.log(c.dim(` Next: beehaven brand set "${name}" color.primary '#123456'`));
236
+ return;
237
+ }
238
+ if (sub === 'set') {
239
+ const [ref, field, ...valueParts] = rest;
240
+ const value = valueParts.join(' ');
241
+ if (!ref || !field || !valueParts.length)
242
+ die('usage: brand set <brand> <field> <value>', "beehaven brand set Harbor color.accent '#FF5500'");
243
+ const patch = brandFieldPatch(field, value);
244
+ if (typeof patch === 'string')
245
+ die(patch);
246
+ const res = await rpcOnce('brand-update', { id: ref, patch, summary: `CLI: ${field} = ${value}`.slice(0, 200) }, t, values);
247
+ const f = failure(res);
248
+ if (f)
249
+ die(f);
250
+ if (values.json)
251
+ return json(res);
252
+ console.log(` ${c.bold(String(res.brand?.name ?? ref))} ${field} → ${value} ${c.dim(`v${res.version}`)}`);
253
+ return;
254
+ }
255
+ if (sub === 'edit' || sub === 'update') {
256
+ const ref = rest.join(' ').trim();
257
+ const patch = readJson(values.patch, '--patch');
258
+ if (!ref || !patch)
259
+ die('usage: brand edit <brand> --patch <json|@file>', `beehaven brand edit Harbor --patch '{"colors":{"accent":"#FF5500"}}'`);
260
+ const res = await rpcOnce('brand-update', { id: ref, patch }, t, values);
261
+ const f = failure(res);
262
+ if (f)
263
+ die(f);
264
+ if (values.json)
265
+ return json(res);
266
+ console.log(` Updated ${c.bold(String(res.brand?.name ?? ref))}: ${(res.changed ?? []).join(', ')} ${c.dim(`v${res.version}`)}`);
267
+ return;
268
+ }
269
+ if (sub === 'default') {
270
+ const ref = rest.join(' ').trim();
271
+ if (!ref)
272
+ die('usage: brand default <brand>');
273
+ const res = await rpcOnce('brand-set-default', { id: ref }, t, values);
274
+ const f = failure(res);
275
+ if (f)
276
+ die(f);
277
+ if (values.json)
278
+ return json(res);
279
+ console.log(` ${c.bold(String(res.brand?.name ?? ref))} is now the workspace default`);
280
+ return;
281
+ }
282
+ if (sub === 'bind') {
283
+ const ref = rest.join(' ').trim();
284
+ if (!ref || !values.composition)
285
+ die('usage: brand bind <brand> --composition <id>');
286
+ const res = await rpcOnce('brand', { op: 'set-active', id: ref, compositionId: String(values.composition) }, t, values);
287
+ const f = failure(res);
288
+ if (f)
289
+ die(f);
290
+ if (values.json)
291
+ return json(res);
292
+ console.log(` Composition ${values.composition} now designs with ${ref}`);
293
+ return;
294
+ }
295
+ if (sub === 'board' || sub === 'compose') {
296
+ const ref = rest.join(' ').trim();
297
+ const res = await rpcOnce('brand-compose', {
298
+ ...(ref ? { id: ref } : {}),
299
+ ...(values.composition ? { compositionId: String(values.composition) } : {}),
300
+ ...(values.name ? { title: String(values.name) } : {}),
301
+ ...(values.project ? { projectId: String(values.project) } : {}),
302
+ }, t, values);
303
+ const f = failure(res);
304
+ if (f)
305
+ die(f);
306
+ if (values.json)
307
+ return json(res);
308
+ console.log(` Brand board ${res.created ? 'created' : 'added'} — ${res.nodes} layers in ${c.cyan(String(res.compositionId))}`);
309
+ console.log(c.dim(` Open it: beehaven open ${res.compositionId}`));
310
+ return;
311
+ }
312
+ if (sub === 'history' || sub === 'versions') {
313
+ const ref = rest.join(' ').trim();
314
+ const got = await rpcOnce('brand-get', ref ? { id: ref } : {}, t, values);
315
+ const f0 = failure(got);
316
+ if (f0)
317
+ die(f0);
318
+ const res = await rpcOnce('brand-version-list', { brandId: got.id }, t, values);
319
+ const f = failure(res);
320
+ if (f)
321
+ die(f);
322
+ if (values.json)
323
+ return json(res);
324
+ console.log('');
325
+ for (const v of res.versions ?? [])
326
+ console.log(` v${String(v.version).padEnd(4)} ${String(v.change_summary ?? '').padEnd(48).slice(0, 48)} ${c.dim(`${v.created_by ?? v.source} · ${new Date(v.created_at).toLocaleString()}`)}`);
327
+ console.log('');
328
+ return;
329
+ }
330
+ if (sub === 'revert') {
331
+ const [ref, ver] = rest;
332
+ const target = num(ver ?? values.rev);
333
+ if (!ref || target === undefined)
334
+ die('usage: brand revert <brand> <version>');
335
+ const got = await rpcOnce('brand-get', { id: ref }, t, values);
336
+ const f0 = failure(got);
337
+ if (f0)
338
+ die(f0);
339
+ const res = await rpcOnce('brand-revert', { brandId: got.id, targetVersion: target }, t, values);
340
+ const f = failure(res);
341
+ if (f)
342
+ die(f);
343
+ if (values.json)
344
+ return json(res);
345
+ console.log(` Reverted ${got.name} to v${target} ${c.dim(`(now v${res.newCurrentVersion})`)}`);
346
+ return;
347
+ }
348
+ if (sub === 'rm' || sub === 'delete') {
349
+ const ref = rest.join(' ').trim();
350
+ if (!ref)
351
+ die('usage: brand rm <brand>');
352
+ const res = await rpcOnce('brand-delete', { id: ref }, t, values);
353
+ const f = failure(res);
354
+ if (f)
355
+ die(f);
356
+ if (values.json)
357
+ return json(res);
358
+ console.log(` Deleted. ${c.dim(`Undo: beehaven brand restore ${res.id}`)}`);
359
+ return;
360
+ }
361
+ if (sub === 'restore') {
362
+ const id = rest[0];
363
+ if (!id)
364
+ die('usage: brand restore <brandId>');
365
+ const res = await rpcOnce('brand-restore', { brandId: id }, t, values);
366
+ const f = failure(res);
367
+ if (f)
368
+ die(f);
369
+ if (values.json)
370
+ return json(res);
371
+ console.log(` Restored ${c.bold(String(res.name))}`);
372
+ return;
373
+ }
374
+ die(`unknown subcommand "${sub}"`, 'beehaven brand help');
375
+ }
376
+ // ── component ──────────────────────────────────────────────────────────────────────────────
377
+ export async function cmdComponent(sub, rest, values) {
378
+ const t = getTimeout(values);
379
+ const json = (res) => console.log(JSON.stringify(res, null, 2));
380
+ if (sub === 'help' || sub === '--help') {
381
+ console.log(`
382
+ ${c.cyan('beehaven component')} — the workspace component library (brand-aware, reusable blocks)
383
+
384
+ component [list] [<query>] Browse (--category actions|inputs|display|navigation|feedback|layout|marketing|brand)
385
+ component show <component> Props, variants, size, brand tokens it lacks
386
+ component render <component> SVG preview (-o file.svg · --variant V · --props <json> · --brand <brand>)
387
+ component place <component> --composition <id> Put it on a canvas (--x --y --parent <frame> --variant --props --brand)
388
+ component save --composition <id> <node> Save a canvas node + its layers as a component (--name --category)
389
+ component new --patch <spec.json> Author one from a spec (--patch @file.json)
390
+ component edit <component> --patch <json> Change fields: name, description, category, status, tags, props, variants, nodes…
391
+ component dup <component> Duplicate (--name "<new name>")
392
+ component history <component> Versions
393
+ component revert <component> <version>
394
+ component rm <component> Delete — recoverable
395
+ component restore <component>
396
+
397
+ ${c.dim('<component> is an id, a slug (button, pricing-card) or a name.')}
398
+ ${c.dim('Colours and faces in a component are brand tokens ({brand.color.primary}) — place and render use the')}
399
+ ${c.dim("composition's brand (or --brand). Agents: `component` / clearly_component does the same with { op }.")}
400
+ `);
401
+ return;
402
+ }
403
+ if (!sub || sub === 'list' || sub === 'ls' || sub === 'search') {
404
+ const query = rest.join(' ').trim();
405
+ const res = await rpcOnce('component-list', { ...(query ? { query } : {}), ...(values.category ? { category: String(values.category) } : {}) }, t, values);
406
+ const f = failure(res);
407
+ if (f)
408
+ die(f);
409
+ if (values.json)
410
+ return json(res);
411
+ const rows = res?.components ?? [];
412
+ if (!rows.length) {
413
+ console.log(query ? ` Nothing matches "${query}".` : ' The library is empty.');
414
+ return;
415
+ }
416
+ console.log('');
417
+ let cat = '';
418
+ for (const r of [...rows].sort((a, b) => String(a.category).localeCompare(String(b.category)) || String(a.name).localeCompare(String(b.name)))) {
419
+ if (r.category !== cat) {
420
+ cat = r.category;
421
+ console.log(` ${c.dim(String(cat).toUpperCase())}`);
422
+ }
423
+ const variants = (r.variants ?? []).length > 1 ? c.dim(` · ${r.variants.join(' / ')}`) : '';
424
+ console.log(` ${c.cyan(String(r.slug).padEnd(18))} ${String(r.name)}${variants}`);
425
+ }
426
+ if (res.migrated)
427
+ console.log(c.dim(`\n ${res.migrated} older canvas component(s) were migrated into the library.`));
428
+ console.log('');
429
+ return;
430
+ }
431
+ if (sub === 'show' || sub === 'get') {
432
+ const ref = rest.join(' ').trim();
433
+ if (!ref)
434
+ die('usage: component show <component>');
435
+ const res = await rpcOnce('component-get', { id: ref, ...(values.brand ? { brandId: String(values.brand) } : {}) }, t, values);
436
+ const f = failure(res);
437
+ if (f)
438
+ die(f);
439
+ if (values.json)
440
+ return json(res);
441
+ const s = res.component;
442
+ console.log('');
443
+ console.log(` ${c.bold(s.name)} ${c.dim(`${s.slug} · ${s.category} · ${s.width}×${s.height} · v${s.version} · ${s.source}`)}`);
444
+ if (s.description)
445
+ console.log(` ${s.description}`);
446
+ if (s.props?.length) {
447
+ console.log(`\n ${c.dim('PROPS')}`);
448
+ for (const p of s.props)
449
+ console.log(` ${String(p.name).padEnd(14)} ${String(p.type === 'enum' ? (p.options ?? []).join('|') : p.type).padEnd(10)} ${c.dim(`= ${JSON.stringify(p.default)}`)}`);
450
+ }
451
+ if (s.variants?.length)
452
+ console.log(`\n ${c.dim('VARIANTS')} ${s.variants.map((v) => v.name).join(' · ')}`);
453
+ console.log(`\n ${c.dim('LAYERS')} ${s.nodes.length} ${c.dim(`rendered in ${res.render?.brandName ? `"${res.render.brandName}"` : 'fallback values'}`)}`);
454
+ if (res.render?.missingTokens?.length)
455
+ console.log(` ${c.yellow('!')} brand lacks: ${res.render.missingTokens.join(', ')}`);
456
+ console.log(c.dim(`\n Place it: beehaven component place ${s.slug} --composition <id> --x 100 --y 100`));
457
+ console.log('');
458
+ return;
459
+ }
460
+ if (sub === 'render' || sub === 'preview') {
461
+ const ref = rest.join(' ').trim();
462
+ if (!ref)
463
+ die('usage: component render <component> [-o file.svg]');
464
+ const res = await rpcOnce('component-render', {
465
+ id: ref,
466
+ ...(values.variant ? { variant: String(values.variant) } : {}),
467
+ ...(values.props ? { props: readJson(values.props, '--props') } : {}),
468
+ ...(values.brand ? { brandId: String(values.brand) } : {}),
469
+ ...(values.format === 'nodes' ? { format: 'nodes' } : {}),
470
+ }, t, values);
471
+ const f = failure(res);
472
+ if (f)
473
+ die(f);
474
+ if (values.json || values.format === 'nodes')
475
+ return json(res);
476
+ if (values.output) {
477
+ writeFileSync(String(values.output), res.svg);
478
+ console.log(` Wrote ${values.output} ${c.dim(`(${res.variant || 'default'} variant)`)}`);
479
+ return;
480
+ }
481
+ process.stdout.write(`${res.svg}\n`);
482
+ return;
483
+ }
484
+ if (sub === 'place' || sub === 'instantiate' || sub === 'add') {
485
+ const ref = rest.join(' ').trim();
486
+ if (!ref || !values.composition)
487
+ die('usage: component place <component> --composition <id> [--x N --y N --parent <frame> --variant V]', 'beehaven component place button --composition 7f3a… --x 120 --y 80 --variant Secondary');
488
+ const res = await rpcOnce('component-instantiate', {
489
+ id: ref, compositionId: String(values.composition),
490
+ ...(num(values.x) !== undefined ? { x: num(values.x) } : {}),
491
+ ...(num(values.y) !== undefined ? { y: num(values.y) } : {}),
492
+ ...(values.parent ? { parentId: String(values.parent) } : {}),
493
+ ...(values.variant ? { variant: String(values.variant) } : {}),
494
+ ...(values.props ? { props: readJson(values.props, '--props') } : {}),
495
+ ...(values.brand ? { brandId: String(values.brand) } : {}),
496
+ }, t, values);
497
+ const f = failure(res);
498
+ if (f)
499
+ die(f);
500
+ if (values.json)
501
+ return json(res);
502
+ console.log(` Placed ${c.bold(ref)}${res.variant ? ` · ${res.variant}` : ''} — ${res.ids?.length ?? 0} layers, root ${c.cyan(String(res.rootId))}`);
503
+ if (res.missingTokens?.length)
504
+ console.log(` ${c.yellow('!')} brand lacks: ${res.missingTokens.join(', ')} — fallbacks used`);
505
+ return;
506
+ }
507
+ if (sub === 'save') {
508
+ const node = rest.join(' ').trim();
509
+ if (!values.composition || !node)
510
+ die('usage: component save --composition <id> <node id or name> [--name "…"] [--category …]', 'beehaven component save --composition 7f3a… "Hero frame" --name "Marketing hero"');
511
+ const res = await rpcOnce('component-create', {
512
+ fromCompositionId: String(values.composition), nodeId: node,
513
+ ...(values.name ? { name: String(values.name) } : {}),
514
+ ...(values.category ? { category: String(values.category) } : {}),
515
+ }, t, values);
516
+ const f = failure(res);
517
+ if (f)
518
+ die(f);
519
+ if (values.json)
520
+ return json(res);
521
+ console.log(` Saved ${c.bold(String(res.component?.name ?? node))} as ${c.cyan(String(res.slug))} — brand colours and faces are linked to tokens`);
522
+ return;
523
+ }
524
+ if (sub === 'new' || sub === 'create') {
525
+ const spec = readJson(values.patch, '--patch');
526
+ if (!spec)
527
+ die('usage: component new --patch <spec.json|@file>', 'beehaven component new --patch @chip.json (see: beehaven explain component-create)');
528
+ const res = await rpcOnce('component-create', { ...spec, ...(values.name ? { name: String(values.name) } : {}) }, t, values);
529
+ const f = failure(res);
530
+ if (f)
531
+ die(f);
532
+ if (values.json)
533
+ return json(res);
534
+ console.log(` Created ${c.bold(String(res.component?.name))} ${c.dim(String(res.slug))}`);
535
+ return;
536
+ }
537
+ if (sub === 'edit' || sub === 'update') {
538
+ const ref = rest.join(' ').trim();
539
+ const patch = readJson(values.patch, '--patch');
540
+ if (!ref || !patch)
541
+ die('usage: component edit <component> --patch <json|@file>');
542
+ const res = await rpcOnce('component-update', { id: ref, patch }, t, values);
543
+ const f = failure(res);
544
+ if (f)
545
+ die(f);
546
+ if (values.json)
547
+ return json(res);
548
+ console.log(` Updated ${c.bold(String(res.component?.name ?? ref))}: ${(res.changed ?? []).join(', ')} ${c.dim(`v${res.version}`)}`);
549
+ return;
550
+ }
551
+ if (sub === 'dup' || sub === 'duplicate') {
552
+ const ref = rest.join(' ').trim();
553
+ if (!ref)
554
+ die('usage: component dup <component> [--name "…"]');
555
+ const res = await rpcOnce('component-create', { fromComponentId: ref, ...(values.name ? { name: String(values.name) } : {}) }, t, values);
556
+ const f = failure(res);
557
+ if (f)
558
+ die(f);
559
+ if (values.json)
560
+ return json(res);
561
+ console.log(` Duplicated as ${c.cyan(String(res.slug))}`);
562
+ return;
563
+ }
564
+ if (sub === 'history' || sub === 'versions') {
565
+ const ref = rest.join(' ').trim();
566
+ const res = await rpcOnce('component-versions', { id: ref }, t, values);
567
+ const f = failure(res);
568
+ if (f)
569
+ die(f);
570
+ if (values.json)
571
+ return json(res);
572
+ for (const v of res.versions ?? [])
573
+ console.log(` v${String(v.version).padEnd(4)} ${String(v.change_summary ?? '')} ${c.dim(String(v.created_by ?? ''))}`);
574
+ return;
575
+ }
576
+ if (sub === 'revert') {
577
+ const [ref, ver] = rest;
578
+ const version = num(ver ?? values.rev);
579
+ if (!ref || version === undefined)
580
+ die('usage: component revert <component> <version>');
581
+ const res = await rpcOnce('component-revert', { id: ref, version }, t, values);
582
+ const f = failure(res);
583
+ if (f)
584
+ die(f);
585
+ if (values.json)
586
+ return json(res);
587
+ console.log(` Reverted to v${version} ${c.dim(`(now v${res.version})`)}`);
588
+ return;
589
+ }
590
+ if (sub === 'rm' || sub === 'delete') {
591
+ const ref = rest.join(' ').trim();
592
+ if (!ref)
593
+ die('usage: component rm <component>');
594
+ const res = await rpcOnce('component-delete', { id: ref }, t, values);
595
+ const f = failure(res);
596
+ if (f)
597
+ die(f);
598
+ if (values.json)
599
+ return json(res);
600
+ console.log(` Deleted. ${c.dim(`Undo: beehaven component restore ${res.id}`)}`);
601
+ return;
602
+ }
603
+ if (sub === 'restore') {
604
+ const ref = rest.join(' ').trim();
605
+ if (!ref)
606
+ die('usage: component restore <component>');
607
+ const res = await rpcOnce('component-restore', { id: ref }, t, values);
608
+ const f = failure(res);
609
+ if (f)
610
+ die(f);
611
+ if (values.json)
612
+ return json(res);
613
+ console.log(` ${res.restored ? 'Restored' : 'Not deleted — nothing to do'}`);
614
+ return;
615
+ }
616
+ die(`unknown subcommand "${sub}"`, 'beehaven component help');
617
+ }