@clearlysh/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.
- package/LICENSE +21 -0
- package/README.md +201 -0
- package/dist/__tests__/_cli-switch.js +105 -0
- package/dist/__tests__/agent-wrap-probe.js +136 -0
- package/dist/agent-attribution.js +192 -0
- package/dist/agent-identity-daemon.js +135 -0
- package/dist/agent-onboard.js +156 -0
- package/dist/agent-scope.js +79 -0
- package/dist/agent-sessions.js +370 -0
- package/dist/agent-state.js +482 -0
- package/dist/app-manifest.js +221 -0
- package/dist/app-signer.js +54 -0
- package/dist/app-signing.js +127 -0
- package/dist/audio-play.js +98 -0
- package/dist/avatar-protocol.js +25 -0
- package/dist/avatar-route.js +79 -0
- package/dist/bee/cloud-proxy.js +130 -0
- package/dist/blocked-actions.js +120 -0
- package/dist/build-channel.js +101 -0
- package/dist/buildings/daemon-workspace.js +475 -0
- package/dist/claude-bridge.js +310 -0
- package/dist/claude-credential-store.js +633 -0
- package/dist/clearly-sync.js +518 -0
- package/dist/clearly.js +616 -0
- package/dist/cli-state.js +334 -0
- package/dist/cli.js +6973 -0
- package/dist/commands/agent-skills.js +302 -0
- package/dist/commands/agent.js +1310 -0
- package/dist/commands/app.js +1123 -0
- package/dist/commands/auth.js +171 -0
- package/dist/commands/building-shell.js +392 -0
- package/dist/commands/canvas.js +932 -0
- package/dist/commands/claude.js +218 -0
- package/dist/commands/compliance.js +387 -0
- package/dist/commands/compose.js +507 -0
- package/dist/commands/composition.js +95 -0
- package/dist/commands/connect-session.js +409 -0
- package/dist/commands/conversations.js +160 -0
- package/dist/commands/costs.js +443 -0
- package/dist/commands/d1.js +253 -0
- package/dist/commands/daemon-cmd.js +162 -0
- package/dist/commands/deploy.js +164 -0
- package/dist/commands/design.js +617 -0
- package/dist/commands/dev-generate.js +125 -0
- package/dist/commands/dev.js +109 -0
- package/dist/commands/devices.js +135 -0
- package/dist/commands/explain-router.js +24 -0
- package/dist/commands/explain.js +882 -0
- package/dist/commands/explore.js +264 -0
- package/dist/commands/help.js +2418 -0
- package/dist/commands/init.js +408 -0
- package/dist/commands/install.js +129 -0
- package/dist/commands/library.js +229 -0
- package/dist/commands/marketplace.js +138 -0
- package/dist/commands/mcp-install.js +476 -0
- package/dist/commands/mention.js +257 -0
- package/dist/commands/omni.js +529 -0
- package/dist/commands/secrets.js +323 -0
- package/dist/commands/self-update.js +206 -0
- package/dist/commands/sheet.js +264 -0
- package/dist/commands/shell-output.js +309 -0
- package/dist/commands/shell.js +1048 -0
- package/dist/commands/shop.js +368 -0
- package/dist/commands/skills.js +311 -0
- package/dist/commands/slides.js +418 -0
- package/dist/commands/swarm.js +341 -0
- package/dist/commands/ticket.js +1301 -0
- package/dist/commands/tool.js +312 -0
- package/dist/commands/trash.js +106 -0
- package/dist/commands/treehouse.js +120 -0
- package/dist/commands/user.js +240 -0
- package/dist/commands/voice-tts.js +319 -0
- package/dist/commands/voice.js +166 -0
- package/dist/commands/watch-git.js +401 -0
- package/dist/commands/webhook.js +159 -0
- package/dist/commands/workspace.js +3354 -0
- package/dist/composition-repos.js +91 -0
- package/dist/daemon-avatar.js +130 -0
- package/dist/daemon-claude-code-auto-install.js +52 -0
- package/dist/daemon-client.js +363 -0
- package/dist/daemon-codex.js +349 -0
- package/dist/daemon-entry.js +101 -0
- package/dist/daemon-fetch-allowlist.js +194 -0
- package/dist/daemon-gemini.js +224 -0
- package/dist/daemon-mcp-clearly.js +353 -0
- package/dist/daemon-mcp-sandbox.js +127 -0
- package/dist/daemon-omni-company.js +338 -0
- package/dist/daemon-omni-hooks.js +375 -0
- package/dist/daemon-omni-identity.js +229 -0
- package/dist/daemon-omni-pipeline.js +251 -0
- package/dist/daemon-omni-policies.js +346 -0
- package/dist/daemon-omni-principal.js +580 -0
- package/dist/daemon-omni-tool-gate.js +116 -0
- package/dist/daemon-omni.js +1442 -0
- package/dist/daemon-path-transform.js +120 -0
- package/dist/daemon-write-sandbox.js +191 -0
- package/dist/daemon.js +4471 -0
- package/dist/dashboard.js +846 -0
- package/dist/dep-installer.js +286 -0
- package/dist/dev-mode.js +67 -0
- package/dist/doc-indexer.js +169 -0
- package/dist/e2e-crypto.js +94 -0
- package/dist/feature-flags.js +154 -0
- package/dist/file-browser.js +88 -0
- package/dist/first-party-hives.js +211 -0
- package/dist/folder-sync.js +226 -0
- package/dist/health.js +56 -0
- package/dist/ipc-auth.js +299 -0
- package/dist/ipc-client.js +495 -0
- package/dist/ipc-server.js +684 -0
- package/dist/keychain.js +224 -0
- package/dist/lib/admin-client.js +73 -0
- package/dist/lib/mobile-build-parser.js +156 -0
- package/dist/local-app-manager.js +660 -0
- package/dist/log-redaction.js +89 -0
- package/dist/logger.js +108 -0
- package/dist/manifest.js +143 -0
- package/dist/marketplace.js +793 -0
- package/dist/min-version-copy.js +51 -0
- package/dist/min-version.js +214 -0
- package/dist/public-surface.js +23 -0
- package/dist/relay-presets.js +89 -0
- package/dist/relay.js +2723 -0
- package/dist/repl.js +619 -0
- package/dist/setup-relay.js +363 -0
- package/dist/setup-service.js +536 -0
- package/dist/shared/bee-memory/index.js +6 -0
- package/dist/shared/bee-memory/types.js +682 -0
- package/dist/shared/claude-code/daemon-types.js +112 -0
- package/dist/shared/claude-code/index.js +2 -0
- package/dist/shared/claude-code/types.js +1 -0
- package/dist/shared/status/schema.js +133 -0
- package/dist/shell-session.js +198 -0
- package/dist/speak-lock.js +97 -0
- package/dist/spinner.js +57 -0
- package/dist/telemetry.js +166 -0
- package/dist/terminal/block-parser.js +207 -0
- package/dist/terminal/shell-integration.js +108 -0
- package/dist/transcript-tailer.js +215 -0
- package/dist/tts-qwen3.js +547 -0
- package/dist/types.js +4 -0
- package/dist/utm.js +38 -0
- package/dist/verb-surface.js +331 -0
- package/dist/version-utils.js +85 -0
- package/dist/watcher.js +207 -0
- package/package.json +88 -0
- package/python/qwen3_tts.py +230 -0
- package/scripts/stamp-build-channel.mjs +115 -0
- package/scripts/sync-installed.mjs +159 -0
- package/scripts/sync-skills.mjs +112 -0
- package/skills/README.md +24 -0
- package/skills/agent-doodle-space/SKILL.md +118 -0
- package/skills/agent-home/SKILL.md +98 -0
- package/skills/beehaven-cli/SKILL.md +144 -0
- package/skills/claude-shopper/README.md +90 -0
- package/skills/claude-shopper/SKILL.md +151 -0
- package/skills/clearly-agent/SKILL.md +183 -0
- package/skills/clearly-canvas/SKILL.md +222 -0
- package/skills/clearly-canvas/authoring.md +160 -0
- package/skills/clearly-canvas/communications.md +32 -0
- package/skills/clearly-canvas/composition-patterns.md +84 -0
- package/skills/clearly-canvas/example.mjs +78 -0
- package/skills/clearly-canvas/project-plans.md +156 -0
- package/skills/clearly-canvas/review.mjs +68 -0
- package/skills/clearly-workspace/SKILL.md +79 -0
- package/skills/clearly-workspace/survey.mjs +101 -0
- package/skills/codebase-map/SKILL.md +142 -0
- package/skills/design-craft/SKILL.md +300 -0
- package/skills/index.json +79 -0
- package/skills/layout-systems/SKILL.md +184 -0
- 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
|
+
}
|