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,408 @@
1
+ /**
2
+ * beehaven init — Scaffold a new beehaven project.
3
+ *
4
+ * Creates:
5
+ * src/hive.ts defineHive + example Workspace
6
+ * .dev.vars Local secrets file
7
+ * package.json
8
+ * tsconfig.json
9
+ * .gitignore
10
+ *
11
+ * The CLI auto-generates wrangler.toml + worker.ts when you run
12
+ * `beehaven dev` or `beehaven deploy` — no need to write those by hand.
13
+ */
14
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
15
+ import { join, basename } from 'path';
16
+ import { randomUUID } from 'crypto';
17
+ import { color } from '../spinner.js';
18
+ export async function init(cwd, options) {
19
+ // Validate Node version
20
+ const nodeVersion = parseInt(process.version.slice(1));
21
+ if (nodeVersion < 20) {
22
+ console.error(color.red('✗') + ` Node 20+ required (found ${process.version})`);
23
+ process.exit(1);
24
+ }
25
+ const template = (options?.template ?? 'api');
26
+ const validTemplates = ['api', 'notes', 'chat', 'game'];
27
+ if (!validTemplates.includes(template)) {
28
+ console.error(color.red('✗') + ` Unknown template "${template}". Choose: ${validTemplates.join(', ')}`);
29
+ process.exit(1);
30
+ }
31
+ const projectName = basename(cwd);
32
+ const projectId = `bh_${randomUUID().slice(0, 8)}`;
33
+ console.log(`\n Initializing beehaven in ${projectName}/ (template: ${color.cyan(template)})\n`);
34
+ // .beehaven/config.json
35
+ const configDir = join(cwd, '.beehaven');
36
+ if (!existsSync(configDir))
37
+ mkdirSync(configDir, { recursive: true });
38
+ writeFileSync(join(configDir, 'config.json'), JSON.stringify({
39
+ projectId,
40
+ projectName,
41
+ region: 'auto',
42
+ createdAt: new Date().toISOString(),
43
+ }, null, 2));
44
+ console.log(' Created .beehaven/config.json');
45
+ // src/ directory
46
+ const srcDir = join(cwd, 'src');
47
+ if (!existsSync(srcDir))
48
+ mkdirSync(srcDir, { recursive: true });
49
+ // src/hive.ts — main entry point with example Workspace
50
+ if (!existsSync(join(srcDir, 'hive.ts'))) {
51
+ writeFileSync(join(srcDir, 'hive.ts'), APP_TEMPLATE(projectName, template));
52
+ console.log(' Created src/hive.ts');
53
+ }
54
+ // package.json
55
+ const pkgPath = join(cwd, 'package.json');
56
+ if (existsSync(pkgPath)) {
57
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
58
+ if (!pkg.dependencies?.['@clearly-sh/beehaven-core']) {
59
+ pkg.dependencies = pkg.dependencies || {};
60
+ pkg.dependencies['@clearly-sh/beehaven-core'] = '^0.3.0';
61
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
62
+ console.log(' Added @clearly-sh/beehaven-core to package.json');
63
+ }
64
+ }
65
+ else {
66
+ writeFileSync(pkgPath, JSON.stringify({
67
+ name: projectName,
68
+ private: true,
69
+ type: 'module',
70
+ scripts: {
71
+ dev: 'beehaven dev',
72
+ deploy: 'beehaven deploy',
73
+ },
74
+ dependencies: {
75
+ '@clearly-sh/beehaven-core': '^0.3.0',
76
+ hono: '^4.0.0',
77
+ },
78
+ devDependencies: {
79
+ '@cloudflare/workers-types': '^4.0.0',
80
+ typescript: '^5.6.0',
81
+ wrangler: '^3.0.0',
82
+ },
83
+ }, null, 2));
84
+ console.log(' Created package.json');
85
+ }
86
+ // tsconfig.json
87
+ if (!existsSync(join(cwd, 'tsconfig.json'))) {
88
+ writeFileSync(join(cwd, 'tsconfig.json'), JSON.stringify({
89
+ compilerOptions: {
90
+ target: 'ES2022',
91
+ module: 'ESNext',
92
+ moduleResolution: 'bundler',
93
+ lib: ['ES2022'],
94
+ strict: true,
95
+ esModuleInterop: true,
96
+ skipLibCheck: true,
97
+ outDir: 'dist',
98
+ types: ['@cloudflare/workers-types'],
99
+ },
100
+ include: ['src/**/*.ts'],
101
+ exclude: ['node_modules', 'dist', '.beehaven'],
102
+ }, null, 2));
103
+ console.log(' Created tsconfig.json');
104
+ }
105
+ // .dev.vars — local secrets (never committed)
106
+ const devVarsPath = join(cwd, '.dev.vars');
107
+ if (!existsSync(devVarsPath)) {
108
+ writeFileSync(devVarsPath, `# Local secrets — loaded by wrangler dev automatically
109
+ # These are NOT committed to git.
110
+ #
111
+ # ADMIN_SECRET=change-me
112
+ # FIREBASE_PROJECT_ID=my-project
113
+ `);
114
+ console.log(' Created .dev.vars');
115
+ }
116
+ // .gitignore
117
+ const gitignorePath = join(cwd, '.gitignore');
118
+ const gitignoreContent = `node_modules/
119
+ dist/
120
+ .beehaven/
121
+ .dev.vars
122
+ .wrangler/
123
+ `;
124
+ if (existsSync(gitignorePath)) {
125
+ const content = readFileSync(gitignorePath, 'utf8');
126
+ if (!content.includes('.beehaven/')) {
127
+ writeFileSync(gitignorePath, content + '\n# Beehaven\n.beehaven/\n.dev.vars\n');
128
+ console.log(' Updated .gitignore');
129
+ }
130
+ }
131
+ else {
132
+ writeFileSync(gitignorePath, gitignoreContent);
133
+ console.log(' Created .gitignore');
134
+ }
135
+ // ── Template-specific folder configs ────────────────
136
+ const foldersDir = join(cwd, 'folders');
137
+ if (!existsSync(foldersDir))
138
+ mkdirSync(foldersDir, { recursive: true });
139
+ const templateFolders = getTemplateFolders(template);
140
+ for (const [name, config] of Object.entries(templateFolders)) {
141
+ const folderPath = join(foldersDir, `${name}.json`);
142
+ if (!existsSync(folderPath)) {
143
+ writeFileSync(folderPath, JSON.stringify(config, null, 2));
144
+ console.log(` Created folders/${name}.json`);
145
+ }
146
+ }
147
+ console.log(`
148
+ Done! Next steps:
149
+
150
+ npm install
151
+ beehaven dev
152
+
153
+ Your project:
154
+
155
+ src/hive.ts Your Workspace classes + defineHive config
156
+ folders/ Data schemas (one .json per collection)
157
+ .dev.vars Local secrets (auto-loaded by wrangler dev)
158
+ .beehaven/ Auto-generated files (wrangler.toml, worker.ts)
159
+
160
+ The CLI generates wrangler.toml and worker.ts for you when you
161
+ run \`beehaven dev\` or \`beehaven deploy\`. Just write your Workspace
162
+ classes in src/hive.ts and the SDK handles everything else.
163
+
164
+ Docs: https://beehaven.dev/docs
165
+ `);
166
+ }
167
+ // ── Template Folder Configs ─────────────────────────────
168
+ function getTemplateFolders(template) {
169
+ switch (template) {
170
+ case 'notes':
171
+ return {
172
+ notes: {
173
+ type: 'data',
174
+ fields: {
175
+ title: 'string',
176
+ content: 'string',
177
+ pinned: 'boolean',
178
+ created_at: 'date',
179
+ updated_at: 'date',
180
+ },
181
+ indexes: [['pinned'], ['created_at']],
182
+ fts: ['title', 'content'],
183
+ },
184
+ tags: {
185
+ type: 'data',
186
+ fields: { name: 'string', color: 'string', note_id: 'string' },
187
+ indexes: [['note_id']],
188
+ },
189
+ };
190
+ case 'chat':
191
+ return {
192
+ messages: {
193
+ type: 'data',
194
+ fields: { text: 'string', sender_id: 'string', room_id: 'string', created_at: 'date' },
195
+ indexes: [['room_id', 'created_at']],
196
+ sync: 'full',
197
+ },
198
+ rooms: {
199
+ type: 'data',
200
+ fields: {
201
+ name: 'string',
202
+ description: 'string',
203
+ created_by: 'string',
204
+ created_at: 'date',
205
+ },
206
+ indexes: [['created_by']],
207
+ presence: { enabled: true, channels: ['typing', 'online'] },
208
+ },
209
+ };
210
+ case 'game':
211
+ return {
212
+ players: {
213
+ type: 'data',
214
+ fields: {
215
+ name: 'string',
216
+ x: 'number',
217
+ y: 'number',
218
+ health: 'int',
219
+ score: 'int',
220
+ team: 'string',
221
+ },
222
+ indexes: [['team']],
223
+ sync: 'full',
224
+ resolver: { hz: 30 },
225
+ },
226
+ };
227
+ case 'api':
228
+ default:
229
+ return {
230
+ items: {
231
+ type: 'data',
232
+ fields: { title: 'string', done: 'boolean', created_at: 'date' },
233
+ indexes: [['done'], ['created_at']],
234
+ },
235
+ };
236
+ }
237
+ }
238
+ // ── Templates ──────────────────────────────────────────
239
+ function APP_TEMPLATE(projectName, template = 'api') {
240
+ const className = toPascalCase(projectName);
241
+ const body = template === 'chat' ? CHAT_WORKSPACE_BODY(className) : DEFAULT_WORKSPACE_BODY(className);
242
+ return `import { defineHive, Workspace } from '@clearly-sh/beehaven-core/server';
243
+
244
+ // ── Your Workspace ─────────────────────────────────────
245
+ // Each Workspace is a Durable Object — a mini computer with
246
+ // folders (data), apps (RPCs), hooks (triggers), and more.
247
+
248
+ ${body}
249
+
250
+ // ── App config ─────────────────────────────────────────
251
+ // buildings: map of named DOs. Each gets its own SQLite database.
252
+ // auth: verify tokens from your auth provider (Firebase, Clerk, etc.)
253
+
254
+ export default defineHive({
255
+ buildings: { main: ${toPascalCase(projectName)}Workspace },
256
+ auth: {
257
+ verify: async (token: string, env: any) => {
258
+ // Replace with your auth verification logic.
259
+ // Return { userId } on success, null on failure.
260
+ //
261
+ // Firebase example:
262
+ // import { Passport } from '@clearly-sh/beehaven-core/server';
263
+ // const passport = Passport.firebase({ projectId: env.FIREBASE_PROJECT_ID });
264
+ // return passport.verify(token);
265
+ //
266
+ // Simple dev auth (remove in production!):
267
+ if (!token) return null;
268
+ return { userId: token };
269
+ },
270
+ },
271
+ });
272
+
273
+ export { ${toPascalCase(projectName)}Workspace };
274
+ `;
275
+ }
276
+ // ── Per-template Workspace bodies ──────────────────────
277
+ /** Default body — items list. Used by api/notes/game/default templates. */
278
+ function DEFAULT_WORKSPACE_BODY(className) {
279
+ return `class ${className}Workspace extends Workspace {
280
+ // ── Data folders ────────────────────────────────────
281
+ // Like Firestore collections. Each row is auto-synced to clients.
282
+ items = this.folder('items', {
283
+ title: 'string',
284
+ done: 'boolean',
285
+ created_at: 'date',
286
+ });
287
+
288
+ // ── Hooks (triggers) ───────────────────────────────
289
+ // Runs after an item is inserted. The SDK auto-broadcasts
290
+ // deltas to subscribed clients — no manual broadcastDelta needed.
291
+ onItemInsert = this.hook('items', 'insert', async (row) => {
292
+ console.log('New item:', row.title);
293
+ });
294
+
295
+ // ── Apps (RPCs) ────────────────────────────────────
296
+ // Callable from clients via cell.rpc('add-item', { title: '...' })
297
+ addItem = this.install('add-item', {
298
+ run: async (input: { title: string }) => {
299
+ const id = crypto.randomUUID();
300
+ this.items.insert({ id, title: input.title, done: false, created_at: Date.now() });
301
+ return { id };
302
+ },
303
+ });
304
+
305
+ toggleItem = this.install('toggle-item', {
306
+ run: async (input: { id: string }) => {
307
+ const item = this.items.get(input.id);
308
+ if (!item) throw new Error('Item not found');
309
+ this.items.update(input.id, { done: !item.done });
310
+ return { ok: true };
311
+ },
312
+ });
313
+
314
+ deleteItem = this.install('delete-item', {
315
+ run: async (input: { id: string }) => {
316
+ this.items.delete(input.id);
317
+ return { ok: true };
318
+ },
319
+ });
320
+ }`;
321
+ }
322
+ /** Chat template body — uses this.messages() for full lifecycle tracking.
323
+ * See docs/MESSAGING.md for the surface this exposes. */
324
+ function CHAT_WORKSPACE_BODY(className) {
325
+ return `class ${className}Workspace extends Workspace {
326
+ // ── Messages shelf (per RFC #13 / docs/MESSAGING.md) ──
327
+ // this.messages() gives you the full lifecycle:
328
+ // queued → sending → sent → delivered_inbox → delivered_device → read
329
+ // plus per-recipient per-device receipts, TTL, retry, opt-out.
330
+ // For an ephemeral ringbuffer-style chat without delivery tracking,
331
+ // use this.chat() instead.
332
+ messages = this.messages('messages', {
333
+ fields: {
334
+ thread_id: 'string',
335
+ attachments: 'json',
336
+ },
337
+ // 7-day TTL: messages stuck at delivered_inbox past 7 days expire.
338
+ deliveryTtl: 7 * 86400_000,
339
+ });
340
+
341
+ rooms = this.folder('rooms', {
342
+ name: 'string',
343
+ description: 'string',
344
+ created_by: 'string',
345
+ created_at: 'date',
346
+ });
347
+
348
+ // ── Apps (RPCs) ────────────────────────────────────
349
+ // Send a new message. Clients call this via the OptimisticSender
350
+ // pattern (see useMessages React hook) — local state moves to
351
+ // 'queued' immediately, the server response transitions to 'sent'.
352
+ sendMessage = this.install('messages:insert', {
353
+ run: async (input: { recipients: string[]; body: string; thread_id?: string }, ctx: any) => {
354
+ const id = crypto.randomUUID();
355
+ this.messages.insert({
356
+ id,
357
+ senderId: ctx.userId,
358
+ recipients: input.recipients,
359
+ body: input.body,
360
+ thread_id: input.thread_id ?? null,
361
+ });
362
+ return { id };
363
+ },
364
+ });
365
+
366
+ // Receipt batching (paired with client MessageAckClient).
367
+ // The hook batches consecutive acks into one RPC to amortize ops.
368
+ ackBatch = this.install('messages:ack-batch', {
369
+ run: async (input: { userId: string; deviceId: string; acks: Array<{ type: 'delivered_device' | 'read'; messageId: string }> }) => {
370
+ for (const ack of input.acks) {
371
+ if (ack.type === 'delivered_device') {
372
+ this.messages.markDeliveredDevice(ack.messageId, input.userId, input.deviceId);
373
+ } else if (ack.type === 'read') {
374
+ this.messages.markRead(ack.messageId, input.userId, input.deviceId);
375
+ }
376
+ }
377
+ return { count: input.acks.length };
378
+ },
379
+ });
380
+
381
+ // ── TTL sweep ─────────────────────────────────────
382
+ // Messages stuck at delivered_inbox past deliveryTtl transition
383
+ // to 'expired'. Hourly sweep is fine for most products.
384
+ sweepExpired = this.cron('0 * * * *', async () => {
385
+ const expired = this.messages.sweepExpired();
386
+ if (expired > 0) console.log(\`[chat] expired \${expired} messages\`);
387
+ });
388
+
389
+ // ── Client side (React) ────────────────────────────
390
+ // import { useMessages } from '@clearly-sh/beehaven-core/react';
391
+ //
392
+ // const { messages, send, markRead, status } = useMessages(client, {
393
+ // shelf: 'messages',
394
+ // userId: currentUser.id,
395
+ // deviceId: 'phone-' + deviceFingerprint,
396
+ // });
397
+ //
398
+ // See docs/MESSAGING.md for the full client-side guide.
399
+ }`;
400
+ }
401
+ function toPascalCase(str) {
402
+ return str
403
+ .replace(/[^a-zA-Z0-9]+/g, ' ')
404
+ .split(' ')
405
+ .filter(Boolean)
406
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
407
+ .join('');
408
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * beehaven install — Install reusable apps, hooks, and folder schemas from packages.
3
+ *
4
+ * Packages are git repos or local directories containing beehaven config files.
5
+ * Installing a package copies its folders/, apps/, and hooks/ into your project.
6
+ *
7
+ * Usage:
8
+ * beehaven install ./my-auth-hooks # Local directory
9
+ * beehaven install github:user/repo # GitHub repo (future)
10
+ * beehaven install @beehaven/stripe-billing # Registry (future)
11
+ *
12
+ * A package is just a directory with the standard beehaven structure:
13
+ * folders/*.json — Merged into your folders/
14
+ * apps/*.ts — Copied into your apps/
15
+ * hooks/*.ts — Copied into your hooks/
16
+ * rules.json — Merged into your rules.json
17
+ */
18
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, copyFileSync } from 'fs';
19
+ import { join } from 'path';
20
+ export async function install(cwd, packagePath) {
21
+ if (!packagePath) {
22
+ console.error(' Usage: beehaven install <path>');
23
+ console.error(' Example: beehaven install ./my-auth-hooks');
24
+ process.exit(1);
25
+ }
26
+ // Resolve package path
27
+ let srcDir = packagePath;
28
+ if (packagePath.startsWith('github:')) {
29
+ console.error(' GitHub packages coming soon. Use a local path for now.');
30
+ process.exit(1);
31
+ }
32
+ if (packagePath.startsWith('@')) {
33
+ console.error(' Registry packages coming soon. Use a local path for now.');
34
+ process.exit(1);
35
+ }
36
+ if (!existsSync(srcDir)) {
37
+ console.error(` Package not found: ${srcDir}`);
38
+ process.exit(1);
39
+ }
40
+ console.log(`\n Installing from ${srcDir}...\n`);
41
+ let installed = 0;
42
+ // Copy folders/*.json
43
+ const srcFolders = join(srcDir, 'folders');
44
+ if (existsSync(srcFolders)) {
45
+ const dstFolders = join(cwd, 'folders');
46
+ if (!existsSync(dstFolders))
47
+ mkdirSync(dstFolders, { recursive: true });
48
+ for (const file of readdirSync(srcFolders)) {
49
+ if (!file.endsWith('.json'))
50
+ continue;
51
+ const dst = join(dstFolders, file);
52
+ if (existsSync(dst)) {
53
+ console.log(` Skip folders/${file} (already exists)`);
54
+ continue;
55
+ }
56
+ copyFileSync(join(srcFolders, file), dst);
57
+ console.log(` Added folders/${file}`);
58
+ installed++;
59
+ }
60
+ }
61
+ // Copy apps/*.ts
62
+ const srcApps = join(srcDir, 'apps');
63
+ if (existsSync(srcApps)) {
64
+ const dstApps = join(cwd, 'apps');
65
+ if (!existsSync(dstApps))
66
+ mkdirSync(dstApps, { recursive: true });
67
+ for (const file of readdirSync(srcApps)) {
68
+ if (!file.endsWith('.ts') && !file.endsWith('.js'))
69
+ continue;
70
+ const dst = join(dstApps, file);
71
+ if (existsSync(dst)) {
72
+ console.log(` Skip apps/${file} (already exists)`);
73
+ continue;
74
+ }
75
+ copyFileSync(join(srcApps, file), dst);
76
+ console.log(` Added apps/${file}`);
77
+ installed++;
78
+ }
79
+ }
80
+ // Copy hooks/*.ts
81
+ const srcHooks = join(srcDir, 'hooks');
82
+ if (existsSync(srcHooks)) {
83
+ const dstHooks = join(cwd, 'hooks');
84
+ if (!existsSync(dstHooks))
85
+ mkdirSync(dstHooks, { recursive: true });
86
+ for (const file of readdirSync(srcHooks)) {
87
+ if (!file.endsWith('.ts') && !file.endsWith('.js'))
88
+ continue;
89
+ const dst = join(dstHooks, file);
90
+ if (existsSync(dst)) {
91
+ console.log(` Skip hooks/${file} (already exists)`);
92
+ continue;
93
+ }
94
+ copyFileSync(join(srcHooks, file), dst);
95
+ console.log(` Added hooks/${file}`);
96
+ installed++;
97
+ }
98
+ }
99
+ // Merge rules.json
100
+ const srcRules = join(srcDir, 'rules.json');
101
+ if (existsSync(srcRules)) {
102
+ const dstRules = join(cwd, 'rules.json');
103
+ const srcRulesData = JSON.parse(readFileSync(srcRules, 'utf8'));
104
+ if (existsSync(dstRules)) {
105
+ const dstRulesData = JSON.parse(readFileSync(dstRules, 'utf8'));
106
+ const merged = {
107
+ ...dstRulesData,
108
+ rules: { ...dstRulesData.rules, ...srcRulesData.rules },
109
+ };
110
+ writeFileSync(dstRules, JSON.stringify(merged, null, 2) + '\n');
111
+ const newRules = Object.keys(srcRulesData.rules || {}).filter((k) => !dstRulesData.rules?.[k]);
112
+ if (newRules.length > 0) {
113
+ console.log(` Merged ${newRules.length} rule(s) into rules.json`);
114
+ installed += newRules.length;
115
+ }
116
+ }
117
+ else {
118
+ writeFileSync(dstRules, JSON.stringify(srcRulesData, null, 2) + '\n');
119
+ console.log(` Created rules.json`);
120
+ installed++;
121
+ }
122
+ }
123
+ if (installed === 0) {
124
+ console.log(' Nothing to install (all files already exist).');
125
+ }
126
+ else {
127
+ console.log(`\n Installed ${installed} file(s). Run \`beehaven dev\` to apply.\n`);
128
+ }
129
+ }