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,793 @@
1
+ /**
2
+ * Marketplace — Pre-built templates for common use cases.
3
+ *
4
+ * Each template scaffolds a complete config-driven project with
5
+ * beehaven.json, rules.json, folders/, apps/, and hooks/.
6
+ * No TypeScript classes — just config files and handler functions.
7
+ *
8
+ * Usage:
9
+ * beehaven init --template chat
10
+ * beehaven init --template kanban
11
+ * beehaven init --template agent
12
+ */
13
+ import { mkdirSync, writeFileSync, existsSync } from 'fs';
14
+ import { join } from 'path';
15
+ // ── Helper ──────────────────────────────────────────
16
+ function json(obj) {
17
+ return JSON.stringify(obj, null, 2) + '\n';
18
+ }
19
+ // ── Templates ───────────────────────────────────────
20
+ const chatTemplate = {
21
+ id: 'chat',
22
+ name: 'Chat Room',
23
+ description: 'Real-time messaging with presence and typing indicators.',
24
+ files: [
25
+ {
26
+ path: 'beehaven.json',
27
+ content: json({
28
+ rules: 'rules.json',
29
+ auth: { provider: 'custom' },
30
+ buildings: {
31
+ lobby: { description: 'Room directory' },
32
+ room: { description: 'Chat room — one DO per room' },
33
+ },
34
+ }),
35
+ },
36
+ {
37
+ path: 'rules.json',
38
+ content: json({
39
+ rules: {
40
+ rooms: { read: true, write: ['insert', 'update'], delete: 'owner' },
41
+ members: { read: true, write: ['insert'], delete: true },
42
+ pins: { read: true, write: ['insert'], delete: true },
43
+ messages: { read: true, write: ['insert'] },
44
+ },
45
+ }),
46
+ },
47
+ {
48
+ path: 'folders/rooms.json',
49
+ content: json({
50
+ type: 'data',
51
+ fields: { name: 'string', topic: 'string', created_by: 'string', created_at: 'date' },
52
+ }),
53
+ },
54
+ {
55
+ path: 'folders/members.json',
56
+ content: json({
57
+ type: 'data',
58
+ fields: { user_id: 'string', display_name: 'string', role: 'string', joined_at: 'date' },
59
+ }),
60
+ },
61
+ {
62
+ path: 'folders/pins.json',
63
+ content: json({
64
+ type: 'data',
65
+ fields: {
66
+ message_text: 'string',
67
+ pinned_by: 'string',
68
+ author: 'string',
69
+ pinned_at: 'date',
70
+ },
71
+ }),
72
+ },
73
+ {
74
+ path: 'folders/messages.json',
75
+ content: json({
76
+ type: 'log',
77
+ capacity: 500,
78
+ fields: { text: 'string', author: 'string', timestamp: 'date' },
79
+ }),
80
+ },
81
+ {
82
+ path: 'apps/send-message.ts',
83
+ content: `export default async function sendMessage(input: { text: string }, ctx: any) {
84
+ if (!input.text?.trim()) throw new Error('Message cannot be empty');
85
+ const message = { id: crypto.randomUUID(), text: input.text.trim(), author: ctx.userId, timestamp: Date.now() };
86
+ ctx.workspace.messages.push(message);
87
+ return { id: message.id };
88
+ }
89
+ `,
90
+ },
91
+ {
92
+ path: 'apps/create-room.ts',
93
+ content: `export default async function createRoom(input: { name: string; topic?: string }, ctx: any) {
94
+ if (!input.name?.trim()) throw new Error('Room name is required');
95
+ const id = crypto.randomUUID();
96
+ ctx.workspace.rooms.insert({ id, name: input.name.trim(), topic: input.topic ?? '', created_by: ctx.userId, created_at: Date.now() });
97
+ return { id };
98
+ }
99
+ `,
100
+ },
101
+ ],
102
+ };
103
+ const kanbanTemplate = {
104
+ id: 'kanban',
105
+ name: 'Kanban Board',
106
+ description: 'Collaborative task board with drag-and-drop and live cursors.',
107
+ files: [
108
+ {
109
+ path: 'beehaven.json',
110
+ content: json({
111
+ rules: 'rules.json',
112
+ auth: { provider: 'custom' },
113
+ }),
114
+ },
115
+ {
116
+ path: 'rules.json',
117
+ content: json({
118
+ rules: {
119
+ columns: { read: true, write: true },
120
+ tasks: { read: true, write: true, delete: true },
121
+ },
122
+ }),
123
+ },
124
+ {
125
+ path: 'folders/columns.json',
126
+ content: json({
127
+ type: 'data',
128
+ fields: { title: 'string', sort_order: 'number' },
129
+ }),
130
+ },
131
+ {
132
+ path: 'folders/tasks.json',
133
+ content: json({
134
+ type: 'data',
135
+ fields: { column_id: 'string', title: 'string', assignee: 'string', sort_order: 'number' },
136
+ indexes: [['column_id']],
137
+ fts: ['title'],
138
+ }),
139
+ },
140
+ {
141
+ path: 'apps/move-task.ts',
142
+ content: `export default async function moveTask(input: { taskId: string; columnId: string; sortOrder: number }, ctx: any) {
143
+ ctx.workspace.tasks.update(input.taskId, { column_id: input.columnId, sort_order: input.sortOrder });
144
+ return { ok: true };
145
+ }
146
+ `,
147
+ },
148
+ ],
149
+ };
150
+ const agentTemplate = {
151
+ id: 'agent',
152
+ name: 'AI Agent Session',
153
+ description: 'Stateful AI agent with persistent memory, tool results, and streaming.',
154
+ files: [
155
+ {
156
+ path: 'beehaven.json',
157
+ content: json({
158
+ rules: 'rules.json',
159
+ auth: { provider: 'custom' },
160
+ }),
161
+ },
162
+ {
163
+ path: 'rules.json',
164
+ content: json({
165
+ rules: {
166
+ memory: { read: true, write: true },
167
+ tools: { read: true, write: false },
168
+ messages: { read: true, write: ['insert'] },
169
+ },
170
+ }),
171
+ },
172
+ {
173
+ path: 'folders/memory.json',
174
+ content: json({
175
+ type: 'data',
176
+ fields: { key: 'string', value: 'string', score: 'number' },
177
+ fts: ['key', 'value'],
178
+ }),
179
+ },
180
+ {
181
+ path: 'folders/tools.json',
182
+ content: json({
183
+ type: 'data',
184
+ fields: { tool_name: 'string', input: 'json', output: 'json', created_at: 'date' },
185
+ }),
186
+ },
187
+ {
188
+ path: 'folders/messages.json',
189
+ content: json({
190
+ type: 'log',
191
+ capacity: 200,
192
+ fields: { role: 'string', content: 'string', timestamp: 'date' },
193
+ }),
194
+ },
195
+ {
196
+ path: 'apps/chat.ts',
197
+ content: `/**
198
+ * Agent chat handler — wires a user message through an LLM, persists
199
+ * both sides to the messages log, and returns the assistant reply.
200
+ *
201
+ * Swap the callModel() body for whichever provider you use. The default
202
+ * here calls Anthropic via fetch so the scaffold works with just an
203
+ * ANTHROPIC_API_KEY secret (\`wrangler secret put ANTHROPIC_API_KEY\`).
204
+ * Providers with built-in support: the Agent class in
205
+ * '@clearly-sh/beehaven-core/server' gives you Claude/Vertex/OpenAI
206
+ * with streaming + tool use + per-driver memory — see
207
+ * https://github.com/clearly-sh/beehaven-sdk#agent
208
+ */
209
+ export default async function chat(input: { message: string }, ctx: any) {
210
+ if (!input.message?.trim()) throw new Error('Message cannot be empty');
211
+
212
+ ctx.workspace.messages.push({ role: 'user', content: input.message, timestamp: Date.now() });
213
+
214
+ const history = ctx.workspace.messages.list({ limit: 20 }).items.map((m: any) => ({
215
+ role: m.role, content: m.content,
216
+ }));
217
+ const response = await callModel(history, ctx.env.ANTHROPIC_API_KEY);
218
+
219
+ ctx.workspace.messages.push({ role: 'assistant', content: response, timestamp: Date.now() });
220
+ return { response };
221
+ }
222
+
223
+ async function callModel(history: Array<{ role: string; content: string }>, apiKey?: string): Promise<string> {
224
+ if (!apiKey) return 'Set ANTHROPIC_API_KEY to enable LLM replies. See apps/chat.ts.';
225
+ const res = await fetch('https://api.anthropic.com/v1/messages', {
226
+ method: 'POST',
227
+ headers: {
228
+ 'content-type': 'application/json',
229
+ 'x-api-key': apiKey,
230
+ 'anthropic-version': '2023-06-01',
231
+ },
232
+ body: JSON.stringify({
233
+ model: 'claude-sonnet-4-6',
234
+ max_tokens: 1024,
235
+ messages: history,
236
+ }),
237
+ });
238
+ if (!res.ok) throw new Error(\`LLM request failed: \${res.status}\`);
239
+ const data: any = await res.json();
240
+ return data?.content?.[0]?.text ?? '';
241
+ }
242
+ `,
243
+ },
244
+ {
245
+ path: 'apps/remember.ts',
246
+ content: `export default async function remember(input: { key: string; value: string }, ctx: any) {
247
+ const id = crypto.randomUUID();
248
+ ctx.workspace.memory.upsert({ id, key: input.key, value: input.value, score: 1.0 });
249
+ return { id };
250
+ }
251
+ `,
252
+ },
253
+ ],
254
+ };
255
+ const iotTemplate = {
256
+ id: 'iot',
257
+ name: 'IoT Sensor Hub',
258
+ description: 'Device state tracking, sensor data, and threshold alerts.',
259
+ files: [
260
+ {
261
+ path: 'beehaven.json',
262
+ content: json({
263
+ rules: 'rules.json',
264
+ auth: { provider: 'custom' },
265
+ }),
266
+ },
267
+ {
268
+ path: 'rules.json',
269
+ content: json({
270
+ rules: {
271
+ devices: { read: true, write: ['insert', 'update'] },
272
+ readings: { read: true, write: ['insert'] },
273
+ },
274
+ }),
275
+ },
276
+ {
277
+ path: 'folders/devices.json',
278
+ content: json({
279
+ type: 'data',
280
+ fields: { name: 'string', type: 'string', firmware: 'string', last_seen: 'date' },
281
+ }),
282
+ },
283
+ {
284
+ path: 'folders/readings.json',
285
+ content: json({
286
+ type: 'log',
287
+ capacity: 10000,
288
+ fields: { deviceId: 'string', type: 'string', value: 'number', timestamp: 'date' },
289
+ }),
290
+ },
291
+ {
292
+ path: 'apps/report-reading.ts',
293
+ content: `export default async function reportReading(input: { deviceId: string; type: string; value: number }, ctx: any) {
294
+ ctx.workspace.readings.push({ ...input, timestamp: Date.now() });
295
+ ctx.workspace.devices.update(input.deviceId, { last_seen: Date.now() });
296
+ if (input.value > 100) console.warn(\`threshold.exceeded: \${input.deviceId} \${input.value}\`);
297
+ return { ok: true };
298
+ }
299
+ `,
300
+ },
301
+ {
302
+ path: 'hooks/devices.on-insert.ts',
303
+ content: `export default function onDeviceInsert(row: any) {
304
+ console.log('New device registered:', row.name);
305
+ }
306
+ `,
307
+ },
308
+ ],
309
+ };
310
+ const dashboardTemplate = {
311
+ id: 'dashboard',
312
+ name: 'Live Dashboard',
313
+ description: 'Real-time analytics with live queries and viewer presence.',
314
+ files: [
315
+ {
316
+ path: 'beehaven.json',
317
+ content: json({
318
+ rules: 'rules.json',
319
+ auth: { provider: 'custom' },
320
+ }),
321
+ },
322
+ {
323
+ path: 'rules.json',
324
+ content: json({
325
+ rules: {
326
+ metrics: { read: true, write: ['insert'] },
327
+ },
328
+ }),
329
+ },
330
+ {
331
+ path: 'folders/metrics.json',
332
+ content: json({
333
+ type: 'data',
334
+ fields: { name: 'string', value: 'number', recorded_at: 'date' },
335
+ indexes: [['name', 'recorded_at']],
336
+ }),
337
+ },
338
+ {
339
+ path: 'apps/record-metric.ts',
340
+ content: `export default async function recordMetric(input: { name: string; value: number }, ctx: any) {
341
+ const id = crypto.randomUUID();
342
+ ctx.workspace.metrics.insert({ id, name: input.name, value: input.value, recorded_at: Date.now() });
343
+ return { id };
344
+ }
345
+ `,
346
+ },
347
+ ],
348
+ };
349
+ const saasTemplate = {
350
+ id: 'saas',
351
+ name: 'Multi-tenant SaaS',
352
+ description: 'Per-tenant workspace with projects, members, and role-based access.',
353
+ files: [
354
+ {
355
+ path: 'beehaven.json',
356
+ content: json({
357
+ rules: 'rules.json',
358
+ auth: { provider: 'custom' },
359
+ }),
360
+ },
361
+ {
362
+ path: 'rules.json',
363
+ content: json({
364
+ rules: {
365
+ projects: { read: true, write: true, delete: 'owner', owner: 'owner_id' },
366
+ members: {
367
+ read: true,
368
+ write: ['insert'],
369
+ delete: true,
370
+ validation: { role: { required: true } },
371
+ },
372
+ },
373
+ }),
374
+ },
375
+ {
376
+ path: 'folders/projects.json',
377
+ content: json({
378
+ type: 'data',
379
+ fields: { name: 'string', owner_id: 'string', status: 'string', created_at: 'date' },
380
+ indexes: [['owner_id'], ['status']],
381
+ }),
382
+ },
383
+ {
384
+ path: 'folders/members.json',
385
+ content: json({
386
+ type: 'data',
387
+ fields: { user_id: 'string', role: 'string', joined_at: 'date' },
388
+ indexes: [['user_id']],
389
+ }),
390
+ },
391
+ {
392
+ path: 'apps/invite-member.ts',
393
+ content: `export default async function inviteMember(input: { userId: string; role: string }, ctx: any) {
394
+ const id = crypto.randomUUID();
395
+ ctx.workspace.members.insert({ id, user_id: input.userId, role: input.role, joined_at: Date.now() });
396
+ return { id };
397
+ }
398
+ `,
399
+ },
400
+ {
401
+ path: 'hooks/members.on-insert.ts',
402
+ content: `/**
403
+ * Fires when a new member row lands in the members folder. Wire your
404
+ * welcome-email / onboarding side effect here — hooks get the raw row
405
+ * plus a ctx with ctx.env for binding secrets (Mailgun / Resend / etc).
406
+ */
407
+ export default function onMemberAdded(row: any, ctx: any) {
408
+ console.log('Member added:', row.user_id, 'as', row.role);
409
+ // Example (Resend): await fetch('https://api.resend.com/emails', {
410
+ // method: 'POST',
411
+ // headers: { 'content-type': 'application/json', authorization: \`Bearer \${ctx.env.RESEND_API_KEY}\` },
412
+ // body: JSON.stringify({ from: 'hello@you.com', to: row.email, subject: 'Welcome!', text: 'Welcome aboard.' }),
413
+ // });
414
+ }
415
+ `,
416
+ },
417
+ ],
418
+ };
419
+ const shopTemplate = {
420
+ id: 'shop',
421
+ name: 'Shop (shop@0 storefront)',
422
+ description: 'shop@0 protocol — product catalog + draft orders + checkout link. Ready for the Yellow Pages.',
423
+ files: [
424
+ {
425
+ path: 'beehaven.json',
426
+ content: json({
427
+ rules: 'rules.json',
428
+ auth: { provider: 'custom' },
429
+ buildings: {
430
+ storefront: { description: 'Shop DO — one per merchant' },
431
+ },
432
+ }),
433
+ },
434
+ {
435
+ path: 'rules.json',
436
+ content: json({
437
+ rules: {
438
+ products: { read: true, write: false, delete: false },
439
+ orders: { read: 'owner', write: false, delete: false },
440
+ },
441
+ }),
442
+ },
443
+ {
444
+ path: 'folders/products.json',
445
+ content: json({
446
+ type: 'data',
447
+ fields: {
448
+ id: 'string',
449
+ handle: 'string',
450
+ title: 'string',
451
+ description: 'string',
452
+ vendor: 'string',
453
+ productType: 'string',
454
+ tags: 'string',
455
+ priceCents: 'int',
456
+ compareAtPriceCents: 'int',
457
+ currency: 'string',
458
+ inStock: 'boolean',
459
+ imageUrl: 'string',
460
+ productUrl: 'string',
461
+ variants: 'json',
462
+ updated_at: 'date',
463
+ },
464
+ indexes: [['handle'], ['productType'], ['inStock']],
465
+ fts: ['title', 'description', 'tags'],
466
+ }),
467
+ },
468
+ {
469
+ path: 'folders/orders.json',
470
+ content: json({
471
+ type: 'data',
472
+ fields: {
473
+ id: 'string',
474
+ orderNumber: 'string',
475
+ status: 'string',
476
+ totalCents: 'int',
477
+ currency: 'string',
478
+ customerEmail: 'string',
479
+ conversationId: 'string',
480
+ visitorId: 'string',
481
+ created_at: 'date',
482
+ updated_at: 'date',
483
+ },
484
+ indexes: [['status'], ['conversationId'], ['visitorId']],
485
+ }),
486
+ },
487
+ {
488
+ path: 'apps/shop-search.ts',
489
+ content: `/**
490
+ * shop:search — canonical shop@0 entry point. Agents call this to find
491
+ * products matching a natural-language query. Results are wrapped with
492
+ * <store-content> tags so the calling agent knows the text is data, not
493
+ * instructions — see @beehaven/core/server secureShopProduct.
494
+ */
495
+ import { secureShopProducts } from '@beehaven/core/server';
496
+
497
+ export default async function shopSearch(input: { query: string; limit?: number }, ctx: any) {
498
+ const limit = Math.min(input.limit ?? 20, 50);
499
+ const rows = ctx.workspace.products.search(input.query, limit);
500
+ const products = rows.filter((r: any) => r.inStock).map((r: any) => ({
501
+ id: r.id, handle: r.handle, title: r.title,
502
+ description: r.description, vendor: r.vendor, productType: r.productType,
503
+ tags: r.tags ? String(r.tags).split(',').map((t: string) => t.trim()).filter(Boolean) : undefined,
504
+ priceCents: r.priceCents, compareAtPriceCents: r.compareAtPriceCents,
505
+ currency: r.currency ?? 'USD', inStock: !!r.inStock,
506
+ url: r.productUrl,
507
+ images: r.imageUrl ? [{ url: r.imageUrl }] : undefined,
508
+ variants: r.variants ? JSON.parse(r.variants) : undefined,
509
+ }));
510
+ return secureShopProducts(products, { source: \`shop/\${ctx.workspace.id}\` });
511
+ }
512
+ `,
513
+ },
514
+ {
515
+ path: 'apps/shop-get-product.ts',
516
+ content: `/**
517
+ * shop:get-product — canonical shop@0 single-product lookup by id or handle.
518
+ */
519
+ import { secureShopProduct } from '@beehaven/core/server';
520
+
521
+ export default async function shopGetProduct(input: { id: string }, ctx: any) {
522
+ const row = ctx.workspace.products.get(input.id)
523
+ ?? ctx.workspace.products.query({ where: { handle: input.id }, limit: 1 })[0];
524
+ if (!row) return null;
525
+ return secureShopProduct({
526
+ id: row.id, handle: row.handle, title: row.title,
527
+ description: row.description, vendor: row.vendor, productType: row.productType,
528
+ tags: row.tags ? String(row.tags).split(',').map((t: string) => t.trim()).filter(Boolean) : undefined,
529
+ priceCents: row.priceCents, compareAtPriceCents: row.compareAtPriceCents,
530
+ currency: row.currency ?? 'USD', inStock: !!row.inStock,
531
+ url: row.productUrl,
532
+ images: row.imageUrl ? [{ url: row.imageUrl }] : undefined,
533
+ variants: row.variants ? JSON.parse(row.variants) : undefined,
534
+ }, { source: \`shop/\${ctx.workspace.id}\` });
535
+ }
536
+ `,
537
+ },
538
+ {
539
+ path: 'apps/shop-create-draft-order.ts',
540
+ content: `/**
541
+ * shop:create-draft-order — build an order + return an invoiceUrl for checkout.
542
+ * Attribution (conversationId, visitorId) lands on the order row so downstream
543
+ * revenue recording (ConversationAnalytics.recordRevenue) can tie it back.
544
+ *
545
+ * Integrate with Shopify / Stripe / your platform inside \`createInvoice\`.
546
+ */
547
+ export default async function shopCreateDraftOrder(
548
+ input: {
549
+ items: Array<{ productId: string; quantity: number }>;
550
+ attribution?: { conversationId?: string; visitorId?: string };
551
+ customer?: { email?: string; name?: string };
552
+ },
553
+ ctx: any,
554
+ ) {
555
+ if (!input.items?.length) throw new Error('At least one line item is required');
556
+
557
+ let totalCents = 0;
558
+ const resolvedItems: any[] = [];
559
+ for (const item of input.items) {
560
+ const product = ctx.workspace.products.get(item.productId);
561
+ if (!product) throw new Error(\`Unknown product: \${item.productId}\`);
562
+ if (!product.inStock) throw new Error(\`Out of stock: \${product.title}\`);
563
+ const lineCents = product.priceCents * item.quantity;
564
+ totalCents += lineCents;
565
+ resolvedItems.push({ productId: product.id, quantity: item.quantity, priceCents: product.priceCents });
566
+ }
567
+
568
+ const orderId = crypto.randomUUID();
569
+ ctx.workspace.orders.insert({
570
+ id: orderId,
571
+ orderNumber: \`ORD-\${Date.now()}\`,
572
+ status: 'pending',
573
+ totalCents,
574
+ currency: 'USD',
575
+ customerEmail: input.customer?.email ?? '',
576
+ conversationId: input.attribution?.conversationId ?? '',
577
+ visitorId: input.attribution?.visitorId ?? '',
578
+ created_at: Date.now(),
579
+ updated_at: Date.now(),
580
+ });
581
+
582
+ const invoiceUrl = await createInvoice(orderId, resolvedItems, input.customer, ctx.env);
583
+ return { orderId, totalCents, currency: 'USD', invoiceUrl };
584
+ }
585
+
586
+ async function createInvoice(orderId: string, _items: any[], _customer: any, _env: any): Promise<string> {
587
+ // Replace with a call to your payment provider. Returning a sandbox URL
588
+ // keeps the scaffold runnable so agents can exercise the full path.
589
+ return \`https://checkout.example.com/invoice/\${orderId}\`;
590
+ }
591
+ `,
592
+ },
593
+ {
594
+ path: 'hooks/orders.on-insert.ts',
595
+ content: `/**
596
+ * Fires when a new order row is inserted. Wire your fulfillment /
597
+ * notification side effects here. The \`_agentConversion\` claim on the
598
+ * order row drives revenue attribution via ConversationAnalytics.
599
+ */
600
+ export default function onOrderCreated(row: any) {
601
+ console.log('Order created:', row.orderNumber, row.totalCents, row.currency);
602
+ }
603
+ `,
604
+ },
605
+ ],
606
+ };
607
+ const directoryTemplate = {
608
+ id: 'directory',
609
+ name: 'Directory (Yellow Pages registry)',
610
+ description: 'A capability-indexed registry of hives (shop@0, calendar@0, feed@0, etc.) — your vertical Yellow Pages.',
611
+ files: [
612
+ {
613
+ path: 'beehaven.json',
614
+ content: json({
615
+ rules: 'rules.json',
616
+ auth: { provider: 'custom' },
617
+ buildings: {
618
+ directory: { description: 'Singleton DO — register + list hives' },
619
+ },
620
+ }),
621
+ },
622
+ {
623
+ path: 'rules.json',
624
+ content: json({
625
+ rules: {
626
+ listings: { read: true, write: false, delete: false },
627
+ },
628
+ }),
629
+ },
630
+ {
631
+ path: 'folders/listings.json',
632
+ content: json({
633
+ type: 'data',
634
+ fields: {
635
+ address: 'string',
636
+ buildingType: 'string',
637
+ buildingId: 'string',
638
+ name: 'string',
639
+ description: 'string',
640
+ categories: 'json',
641
+ shipsTo: 'json',
642
+ protocols: 'json',
643
+ status: 'string',
644
+ currency: 'string',
645
+ lastProbedAt: 'date',
646
+ created_at: 'date',
647
+ },
648
+ indexes: [['buildingType'], ['status'], ['buildingType', 'status']],
649
+ fts: ['name', 'description'],
650
+ }),
651
+ },
652
+ {
653
+ path: 'apps/register.ts',
654
+ content: `/**
655
+ * directory:register — a hive claims a slot in the directory.
656
+ *
657
+ * Callers pass their address + protocols; the directory probes the
658
+ * manifest to verify the hive actually serves the advertised protocols
659
+ * before marking it active. See @beehaven/core/server Directory for
660
+ * the reference implementation — this scaffold is a trimmed version.
661
+ */
662
+ export default async function register(
663
+ input: {
664
+ address: string;
665
+ buildingType: string;
666
+ buildingId: string;
667
+ name: string;
668
+ description?: string;
669
+ categories?: string[];
670
+ shipsTo?: string[];
671
+ protocols: string[];
672
+ currency?: string;
673
+ },
674
+ ctx: any,
675
+ ) {
676
+ if (!input.address || !input.buildingType || !input.buildingId) {
677
+ throw new Error('address, buildingType, and buildingId are required');
678
+ }
679
+
680
+ ctx.workspace.listings.upsert({
681
+ address: input.address,
682
+ buildingType: input.buildingType,
683
+ buildingId: input.buildingId,
684
+ name: input.name,
685
+ description: input.description ?? '',
686
+ categories: JSON.stringify(input.categories ?? []),
687
+ shipsTo: JSON.stringify(input.shipsTo ?? []),
688
+ protocols: JSON.stringify(input.protocols),
689
+ status: 'pending-probe',
690
+ currency: input.currency ?? 'USD',
691
+ lastProbedAt: 0,
692
+ created_at: Date.now(),
693
+ });
694
+
695
+ return { address: input.address, status: 'pending-probe' };
696
+ }
697
+ `,
698
+ },
699
+ {
700
+ path: 'apps/search.ts',
701
+ content: `/**
702
+ * directory:search — find listings by query + filters (protocol, category, geo).
703
+ * Matches the canonical shape agents use to discover shops, calendars, feeds, etc.
704
+ */
705
+ export default async function search(
706
+ input: {
707
+ q?: string;
708
+ protocol?: string;
709
+ category?: string;
710
+ geo?: string;
711
+ limit?: number;
712
+ },
713
+ ctx: any,
714
+ ) {
715
+ const limit = Math.min(input.limit ?? 20, 50);
716
+ const rows = input.q
717
+ ? ctx.workspace.listings.search(input.q, limit * 3)
718
+ : ctx.workspace.listings.query({ where: { status: 'active' }, limit: limit * 3 });
719
+
720
+ return rows
721
+ .filter((r: any) => r.status === 'active')
722
+ .filter((r: any) => !input.protocol || JSON.parse(r.protocols || '[]').includes(input.protocol))
723
+ .filter((r: any) => !input.category || JSON.parse(r.categories || '[]').includes(input.category))
724
+ .filter((r: any) => !input.geo || JSON.parse(r.shipsTo || '[]').includes(input.geo))
725
+ .slice(0, limit)
726
+ .map((r: any) => ({
727
+ address: r.address,
728
+ buildingType: r.buildingType,
729
+ buildingId: r.buildingId,
730
+ name: r.name,
731
+ description: r.description,
732
+ categories: JSON.parse(r.categories || '[]'),
733
+ shipsTo: JSON.parse(r.shipsTo || '[]'),
734
+ protocols: JSON.parse(r.protocols || '[]'),
735
+ currency: r.currency,
736
+ }));
737
+ }
738
+ `,
739
+ },
740
+ {
741
+ path: 'apps/get.ts',
742
+ content: `/**
743
+ * directory:get — fetch a single listing by address.
744
+ */
745
+ export default async function get(input: { address: string }, ctx: any) {
746
+ const row = ctx.workspace.listings.query({ where: { address: input.address }, limit: 1 })[0];
747
+ if (!row) return null;
748
+ return {
749
+ address: row.address,
750
+ buildingType: row.buildingType,
751
+ buildingId: row.buildingId,
752
+ name: row.name,
753
+ description: row.description,
754
+ categories: JSON.parse(row.categories || '[]'),
755
+ shipsTo: JSON.parse(row.shipsTo || '[]'),
756
+ protocols: JSON.parse(row.protocols || '[]'),
757
+ currency: row.currency,
758
+ status: row.status,
759
+ };
760
+ }
761
+ `,
762
+ },
763
+ ],
764
+ };
765
+ // ── Registry ────────────────────────────────────────
766
+ export const templates = [
767
+ chatTemplate,
768
+ kanbanTemplate,
769
+ agentTemplate,
770
+ iotTemplate,
771
+ dashboardTemplate,
772
+ saasTemplate,
773
+ shopTemplate,
774
+ directoryTemplate,
775
+ ];
776
+ export function getTemplate(id) {
777
+ return templates.find((t) => t.id === id);
778
+ }
779
+ export function listTemplates() {
780
+ return templates.map((t) => ({ id: t.id, name: t.name, description: t.description }));
781
+ }
782
+ export function scaffoldTemplate(template, dir) {
783
+ const created = [];
784
+ for (const file of template.files) {
785
+ const fullPath = join(dir, file.path);
786
+ const fileDir = join(fullPath, '..');
787
+ if (!existsSync(fileDir))
788
+ mkdirSync(fileDir, { recursive: true });
789
+ writeFileSync(fullPath, file.content, 'utf-8');
790
+ created.push(file.path);
791
+ }
792
+ return created;
793
+ }