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,253 @@
1
+ /**
2
+ * beehaven d1 — Cloudflare D1 wrapper
3
+ *
4
+ * Reads D1 bindings from wrangler.toml/wrangler.jsonc so devs don't have
5
+ * to pass --database-id every call. Shells out to wrangler under the hood
6
+ * for the actual operations.
7
+ *
8
+ * Subcommands:
9
+ * list List configured D1 bindings
10
+ * exec <db> "SQL" [--local] Run a SQL statement (defaults to --remote)
11
+ * dump <db> <table> Dump a table to JSON/CSV
12
+ * migrate <db> apply [--local] wrangler d1 migrations apply
13
+ * migrate <db> list wrangler d1 migrations list
14
+ * migrate <db> create <name> wrangler d1 migrations create
15
+ */
16
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
17
+ import { dirname, join, resolve } from 'path';
18
+ import { spawn } from 'child_process';
19
+ import { color, spinner, table } from '../spinner.js';
20
+ function findWranglerConfig(start = process.cwd()) {
21
+ let dir = resolve(start);
22
+ while (true) {
23
+ for (const name of ['wrangler.toml', 'wrangler.jsonc', 'wrangler.json']) {
24
+ const p = join(dir, name);
25
+ if (existsSync(p))
26
+ return { path: p, d1: parseD1Bindings(p) };
27
+ }
28
+ const parent = dirname(dir);
29
+ if (parent === dir)
30
+ return null;
31
+ dir = parent;
32
+ }
33
+ }
34
+ function parseD1Bindings(configPath) {
35
+ const text = readFileSync(configPath, 'utf-8');
36
+ if (configPath.endsWith('.toml'))
37
+ return parseD1Toml(text);
38
+ return parseD1Json(text);
39
+ }
40
+ function parseD1Toml(text) {
41
+ const bindings = [];
42
+ const re = /\[\[d1_databases\]\][\s\S]*?(?=\n\[\[|\n\[|$)/g;
43
+ let match;
44
+ while ((match = re.exec(text)) !== null) {
45
+ const block = match[0];
46
+ const get = (k) => block.match(new RegExp(`${k}\\s*=\\s*"([^"]+)"`))?.[1];
47
+ const binding = get('binding');
48
+ const database_name = get('database_name');
49
+ const database_id = get('database_id');
50
+ if (binding && database_name)
51
+ bindings.push({ binding, database_name, database_id });
52
+ }
53
+ return bindings;
54
+ }
55
+ function parseD1Json(text) {
56
+ const stripped = text.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
57
+ const cfg = JSON.parse(stripped);
58
+ const raw = cfg.d1_databases ?? cfg.d1 ?? [];
59
+ return raw.map((b) => ({
60
+ binding: b.binding,
61
+ database_name: b.database_name,
62
+ database_id: b.database_id,
63
+ }));
64
+ }
65
+ function resolveDbName(cfg, input) {
66
+ return cfg.d1.find((b) => b.database_name === input || b.binding === input) ?? null;
67
+ }
68
+ function runWrangler(args, opts = {}) {
69
+ return new Promise((resolveP) => {
70
+ const cwd = opts.cwd ?? dirname(findWranglerConfig()?.path ?? process.cwd());
71
+ const proc = spawn('npx', ['wrangler', ...args], {
72
+ cwd,
73
+ stdio: opts.capture ? ['inherit', 'pipe', 'pipe'] : 'inherit',
74
+ env: { ...process.env },
75
+ });
76
+ let stdout = '';
77
+ let stderr = '';
78
+ if (opts.capture) {
79
+ proc.stdout?.on('data', (d) => (stdout += d.toString()));
80
+ proc.stderr?.on('data', (d) => (stderr += d.toString()));
81
+ }
82
+ proc.on('close', (code) => resolveP({ stdout, stderr, code: code ?? 1 }));
83
+ proc.on('error', () => resolveP({ stdout, stderr, code: 1 }));
84
+ });
85
+ }
86
+ function extractJson(stdout) {
87
+ const idx = stdout.indexOf('[');
88
+ if (idx === -1) {
89
+ const objIdx = stdout.indexOf('{');
90
+ if (objIdx === -1)
91
+ return null;
92
+ try {
93
+ return JSON.parse(stdout.slice(objIdx));
94
+ }
95
+ catch {
96
+ return null;
97
+ }
98
+ }
99
+ try {
100
+ return JSON.parse(stdout.slice(idx));
101
+ }
102
+ catch {
103
+ return null;
104
+ }
105
+ }
106
+ export async function d1(opts) {
107
+ const cfg = findWranglerConfig();
108
+ if (!cfg) {
109
+ console.error(color.red('No wrangler.toml/wrangler.jsonc found in cwd or any parent.'));
110
+ console.error(color.dim('Run from your worker project directory.'));
111
+ process.exit(1);
112
+ }
113
+ const sub = opts.subcommand ?? 'list';
114
+ const remoteFlag = opts.local ? '--local' : '--remote';
115
+ const envFlag = opts.env ? ['--env', opts.env] : [];
116
+ switch (sub) {
117
+ case 'list': {
118
+ if (cfg.d1.length === 0) {
119
+ console.log(color.dim(`No D1 bindings declared in ${cfg.path}.`));
120
+ return;
121
+ }
122
+ console.log('');
123
+ console.log(` D1 databases ${color.dim('— ' + cfg.path)}`);
124
+ console.log('');
125
+ const rows = cfg.d1.map((b) => [
126
+ color.cyan(b.binding),
127
+ b.database_name,
128
+ b.database_id ? color.dim(b.database_id.slice(0, 8) + '…') : color.yellow('(unset)'),
129
+ ]);
130
+ console.log(table(['Binding', 'Name', 'ID'], rows));
131
+ console.log('');
132
+ console.log(color.dim(' beehaven d1 exec <name> "SELECT …"'));
133
+ return;
134
+ }
135
+ case 'exec': {
136
+ const dbInput = opts.positionals[0];
137
+ const query = opts.positionals[1];
138
+ if (!dbInput || !query) {
139
+ console.error('Usage: beehaven d1 exec <db> "<sql>" [--local] [--json]');
140
+ process.exit(1);
141
+ }
142
+ const db = resolveDbName(cfg, dbInput);
143
+ if (!db) {
144
+ console.error(color.red(`Unknown D1 db "${dbInput}". Known: ${cfg.d1.map((b) => b.database_name).join(', ')}`));
145
+ process.exit(1);
146
+ }
147
+ const s = spinner(`Running on ${db.database_name} (${opts.local ? 'local' : 'remote'})…`).start();
148
+ const { stdout, stderr, code } = await runWrangler(['d1', 'execute', db.database_name, remoteFlag, '--json', '--command', query, ...envFlag], { capture: true });
149
+ if (code !== 0) {
150
+ s.fail(`wrangler exited ${code}`);
151
+ if (stderr)
152
+ console.error(color.dim(stderr));
153
+ if (stdout)
154
+ console.error(color.dim(stdout));
155
+ process.exit(code);
156
+ }
157
+ const parsed = extractJson(stdout);
158
+ const results = Array.isArray(parsed) ? parsed[0]?.results : parsed?.results;
159
+ const meta = Array.isArray(parsed) ? parsed[0]?.meta : parsed?.meta;
160
+ s.succeed(`${results?.length ?? 0} rows${meta?.duration ? ` in ${meta.duration.toFixed(1)}ms` : ''}`);
161
+ if (!results || results.length === 0) {
162
+ console.log(color.dim('(no rows)'));
163
+ return;
164
+ }
165
+ if (opts.json) {
166
+ console.log(JSON.stringify(results, null, 2));
167
+ return;
168
+ }
169
+ const cols = Object.keys(results[0]);
170
+ console.log(table(cols, results.map((r) => cols.map((c) => String(r[c] ?? '')))));
171
+ return;
172
+ }
173
+ case 'dump': {
174
+ const dbInput = opts.positionals[0];
175
+ const tableName = opts.positionals[1];
176
+ if (!dbInput || !tableName) {
177
+ console.error('Usage: beehaven d1 dump <db> <table> [--out file.json] [--json]');
178
+ process.exit(1);
179
+ }
180
+ const db = resolveDbName(cfg, dbInput);
181
+ if (!db) {
182
+ console.error(color.red(`Unknown D1 db "${dbInput}". Known: ${cfg.d1.map((b) => b.database_name).join(', ')}`));
183
+ process.exit(1);
184
+ }
185
+ const s = spinner(`Dumping ${tableName} from ${db.database_name}…`).start();
186
+ const { stdout, code } = await runWrangler([
187
+ 'd1',
188
+ 'execute',
189
+ db.database_name,
190
+ remoteFlag,
191
+ '--json',
192
+ '--command',
193
+ `SELECT * FROM ${tableName}`,
194
+ ...envFlag,
195
+ ], { capture: true });
196
+ if (code !== 0) {
197
+ s.fail(`wrangler exited ${code}`);
198
+ process.exit(code);
199
+ }
200
+ const parsed = extractJson(stdout);
201
+ const results = Array.isArray(parsed) ? parsed[0]?.results : parsed?.results;
202
+ s.succeed(`${results?.length ?? 0} rows`);
203
+ const payload = JSON.stringify(results ?? [], null, 2);
204
+ if (opts.out) {
205
+ writeFileSync(opts.out, payload);
206
+ console.log(color.green(`✓ Wrote ${opts.out}`));
207
+ }
208
+ else {
209
+ console.log(payload);
210
+ }
211
+ return;
212
+ }
213
+ case 'migrate': {
214
+ const dbInput = opts.positionals[0];
215
+ const action = opts.positionals[1] ?? 'list';
216
+ const name = opts.positionals[2];
217
+ if (!dbInput) {
218
+ console.error('Usage: beehaven d1 migrate <db> <apply|list|create> [name]');
219
+ process.exit(1);
220
+ }
221
+ const db = resolveDbName(cfg, dbInput);
222
+ if (!db) {
223
+ console.error(color.red(`Unknown D1 db "${dbInput}"`));
224
+ process.exit(1);
225
+ }
226
+ const wranglerArgs = ['d1', 'migrations'];
227
+ switch (action) {
228
+ case 'apply':
229
+ wranglerArgs.push('apply', db.database_name, remoteFlag, ...envFlag);
230
+ break;
231
+ case 'list':
232
+ wranglerArgs.push('list', db.database_name, remoteFlag, ...envFlag);
233
+ break;
234
+ case 'create':
235
+ if (!name) {
236
+ console.error('Usage: beehaven d1 migrate <db> create <name>');
237
+ process.exit(1);
238
+ }
239
+ wranglerArgs.push('create', db.database_name, name);
240
+ break;
241
+ default:
242
+ console.error(`Unknown migrate action: ${action}. Use apply | list | create.`);
243
+ process.exit(1);
244
+ }
245
+ const { code } = await runWrangler(wranglerArgs);
246
+ process.exit(code);
247
+ }
248
+ default:
249
+ console.error(`Unknown d1 subcommand: ${sub}`);
250
+ console.error('Use: list | exec | dump | migrate');
251
+ process.exit(1);
252
+ }
253
+ }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * `beehaven daemon <start|stop|status>` and `beehaven start --headless` —
3
+ * run the daemon WITHOUT the Hive Mac app (opt-in standalone mode).
4
+ *
5
+ * The daemon entry (`daemon-entry.js`) ships inside this package, so we can fork
6
+ * it directly. This is deliberately OPT-IN: the default `beehaven start` still
7
+ * opens the Mac tray app (Hive 1.0 single-spawn rule). Headless mode is for CI,
8
+ * servers, and agents on machines without the desktop app.
9
+ *
10
+ * Auth: the forked daemon runs with BEEHAVEN_NO_REPL=1 (no prompt), so a token
11
+ * must already be on disk — run `beehaven login` first. Voice/avatar/mood are
12
+ * desktop-only and stay unavailable headless; cloud + canvas control + DO ops work.
13
+ */
14
+ import { spawn } from 'node:child_process';
15
+ import { fileURLToPath } from 'node:url';
16
+ import { existsSync } from 'node:fs';
17
+ import { dirname, join } from 'node:path';
18
+ import { findLiveDaemons, resolveRelayUrlForDaemon } from '../daemon.js';
19
+ /** dist/daemon-entry.js — sibling of dist/cli.js (this file is dist/commands/). */
20
+ function daemonEntryPath() {
21
+ return join(dirname(fileURLToPath(import.meta.url)), '..', 'daemon-entry.js');
22
+ }
23
+ /**
24
+ * Fork the standalone daemon. The ONE spawn path — `beehaven start --headless`,
25
+ * `beehaven daemon start` and `beehaven restart` all route here.
26
+ *
27
+ * They did not always. `restart` used to inline its own `spawn()` that set
28
+ * BEEHAVEN_NO_REPL but NOT BEEHAVEN_HEADLESS, so every `beehaven restart`
29
+ * produced a daemon that printed a cheerful PID and then silently suicided ~3s
30
+ * later when the parent-death watchdog fired. The user saw "Daemon restarted
31
+ * (PID 13766)" followed by "Cannot connect to daemon" on the very next command.
32
+ * One spawn path means that divergence cannot come back.
33
+ */
34
+ export function spawnStandaloneDaemon(values) {
35
+ const entry = daemonEntryPath();
36
+ if (!existsSync(entry)) {
37
+ console.error(` daemon-entry.js not found at ${entry} — reinstall/rebuild the CLI (npm run build).`);
38
+ process.exit(1);
39
+ }
40
+ const devMode = !!(values.dev || values['dev-mode'] || process.env.HIVE_DEV_MODE);
41
+ const relayUrl = resolveRelayUrlForDaemon();
42
+ const child = spawn(process.execPath, [entry], {
43
+ detached: true,
44
+ stdio: 'ignore',
45
+ // BEEHAVEN_HEADLESS=1 is REQUIRED, not optional: daemon-entry.ts runs a
46
+ // parent-death watchdog (polls process.ppid, self-exits ~3s after the parent
47
+ // is gone) to enforce the tray-coupled lifecycle. A detached CLI-spawned
48
+ // daemon's parent (this process) exits immediately, so without HEADLESS the
49
+ // daemon would suicide ~3s later — "headless" never actually stayed up.
50
+ // Setting it disables that watchdog so the standalone daemon persists.
51
+ env: {
52
+ ...process.env,
53
+ BEEHAVEN_NO_REPL: '1',
54
+ BEEHAVEN_HEADLESS: '1',
55
+ BEEHAVEN_CLI_OWNED_DAEMON: '1',
56
+ BEEHAVEN_RELAY_URL: relayUrl,
57
+ ...(devMode ? { HIVE_DEV_MODE: '1' } : {}),
58
+ },
59
+ });
60
+ child.unref();
61
+ return child.pid;
62
+ }
63
+ /**
64
+ * How long `start --headless` waits for the new daemon's socket. The old code printed "confirms in
65
+ * ~2s", so ~2s is the observed startup; 12s is generous enough for a loaded machine (a deploy
66
+ * running alongside it, which is exactly when this bit) without hanging a script.
67
+ */
68
+ const DAEMON_READY_TIMEOUT_MS = 12_000;
69
+ const DAEMON_POLL_MS = 250;
70
+ export async function startHeadlessDaemon(values) {
71
+ const existing = findLiveDaemons();
72
+ if (existing.length > 0) {
73
+ // A live PID is NOT proof the daemon is usable. If its socket was removed or
74
+ // it wedged, `start` says "already running" while every `call` says "cannot
75
+ // connect" — two commands contradicting each other with no way out named in
76
+ // either. Probe the socket and, when it's dead, say the one thing that fixes
77
+ // it. (This exact standoff cost a real session ~20 minutes.)
78
+ const { isDaemonRunning } = await import('../daemon-client.js');
79
+ if (await isDaemonRunning()) {
80
+ const { daemonRpc } = await import('../daemon-client.js');
81
+ let activeRelay = null;
82
+ try {
83
+ const status = await daemonRpc('daemon:status', {});
84
+ activeRelay = status?.relay?.endpoint ?? null;
85
+ }
86
+ catch { /* status is advisory; the socket probe already succeeded */ }
87
+ const configuredRelay = resolveRelayUrlForDaemon();
88
+ if (activeRelay && activeRelay.replace(/\/$/, '') !== configuredRelay.replace(/\/$/, '')) {
89
+ console.error(` Daemon is running on ${activeRelay}, but the configured environment is ${configuredRelay}.\n` +
90
+ ` The environment switch is NOT active. Apply it with:\n\n` +
91
+ ` beehaven restart\n`);
92
+ process.exitCode = 2;
93
+ return;
94
+ }
95
+ console.log(` Daemon already running (PID ${existing.join(', ')}). 'beehaven status' shows state.`);
96
+ }
97
+ else {
98
+ console.error(` Daemon process is alive (PID ${existing.join(', ')}) but its IPC socket is NOT responding —\n` +
99
+ ` every 'beehaven call' will fail until it is replaced. Fix it with:\n\n` +
100
+ ` beehaven stop && beehaven start --headless\n`);
101
+ process.exitCode = 1;
102
+ }
103
+ return;
104
+ }
105
+ const devMode = !!(values.dev || values['dev-mode'] || process.env.HIVE_DEV_MODE);
106
+ const pid = spawnStandaloneDaemon(values);
107
+ /**
108
+ * ⚠⚠ WAIT FOR IT TO BE USABLE — RETURNING EARLY IS A RACE THAT LOOKS LIKE A BROKEN INSTALL.
109
+ *
110
+ * `stop` scrubs the IPC token on shutdown (`clearIpcAuthToken`, daemon.ts) and this spawn is
111
+ * DETACHED, so `beehaven stop && beehaven start --headless && beehaven call …` had a window in
112
+ * which no token existed on disk: the old one deleted, the new one not yet minted. Every command
113
+ * in that window failed with **"Cannot find IPC auth token"**, whose own text then blamed the
114
+ * daemon and advised a restart — which deletes the token again and re-enters the race.
115
+ *
116
+ * ⚠ THE OLD LINE ADMITTED IT: *"'beehaven status' confirms in ~2s"*. That is this function
117
+ * telling the caller to do the waiting, in a sentence, on a path every script uses.
118
+ *
119
+ * ⚠ IT POLLS FOR A USABLE SOCKET, NOT FOR THE TOKEN FILE. The token appearing is necessary and
120
+ * not sufficient — `isDaemonRunning` is the same probe `startHeadlessDaemon` already trusts above
121
+ * to tell a live PID from a wedged one.
122
+ *
123
+ * ⚠ AND IT NEVER FAILS THE COMMAND. A daemon that is slow to come up has still been started; the
124
+ * caller is told what state it is in rather than handed an error for something that is working.
125
+ */
126
+ const { isDaemonRunning } = await import('../daemon-client.js');
127
+ const deadline = Date.now() + DAEMON_READY_TIMEOUT_MS;
128
+ let ready = false;
129
+ while (Date.now() < deadline) {
130
+ await new Promise((r) => setTimeout(r, DAEMON_POLL_MS));
131
+ if (await isDaemonRunning()) {
132
+ ready = true;
133
+ break;
134
+ }
135
+ }
136
+ console.log(` 🐝 Headless daemon started (PID ${pid})${devMode ? ' [dev scope]' : ''}.`);
137
+ console.log(ready
138
+ ? ` Ready — socket answering.`
139
+ : ` Started, but not answering yet after ${Math.round(DAEMON_READY_TIMEOUT_MS / 1000)}s. 'beehaven status' shows state.`);
140
+ }
141
+ export async function cmdDaemon(sub, values) {
142
+ switch (sub) {
143
+ case 'start':
144
+ return startHeadlessDaemon(values);
145
+ case 'status': {
146
+ const live = findLiveDaemons();
147
+ console.log(live.length
148
+ ? ` Daemon running (PID ${live.join(', ')}).`
149
+ : ' No daemon running. Start one: beehaven daemon start');
150
+ return;
151
+ }
152
+ case 'stop':
153
+ case 'restart':
154
+ // Defer to the existing top-level stop/restart machinery.
155
+ console.log(` Run: beehaven ${sub}`);
156
+ return;
157
+ default:
158
+ console.log('Usage: beehaven daemon <start|stop|status> (start = headless, no Mac app)');
159
+ console.log(' Standalone daemon for CI / servers / agents. Run `beehaven login` first.');
160
+ return;
161
+ }
162
+ }
@@ -0,0 +1,164 @@
1
+ /**
2
+ * beehaven deploy — Deploy to Cloudflare.
3
+ *
4
+ * Discovers buildings from the app entry point (src/hive.ts), generates
5
+ * wrangler.toml + worker.ts, then runs wrangler deploy.
6
+ *
7
+ * Features:
8
+ * - Pre-deploy checks: tsc --noEmit + vitest run (DEPLOY-03)
9
+ * - Production confirmation gate (DEPLOY-02)
10
+ * - Rollback and version listing (DEPLOY-01)
11
+ * - Dry-run mode
12
+ */
13
+ import { existsSync, mkdirSync, writeFileSync } from 'fs';
14
+ import { join } from 'path';
15
+ import { spawn, execSync } from 'child_process';
16
+ import { discoverBuildings, generateWrangler, generateWorker } from './dev-generate.js';
17
+ import { spinner, color } from '../spinner.js';
18
+ /**
19
+ * Run pre-deploy checks: TypeScript type checking and tests.
20
+ * Returns true if all checks pass, false otherwise.
21
+ */
22
+ function runPreDeployChecks(cwd) {
23
+ console.log(' Running pre-deploy checks...\n');
24
+ // 1. TypeScript type check
25
+ const tscSpinner = spinner('Running type check...').start();
26
+ try {
27
+ execSync('npx tsc --noEmit', { cwd, stdio: 'pipe' });
28
+ tscSpinner.succeed('Type check passed');
29
+ }
30
+ catch (err) {
31
+ tscSpinner.fail('Type check failed');
32
+ if (err.stdout)
33
+ console.error(err.stdout.toString());
34
+ if (err.stderr)
35
+ console.error(err.stderr.toString());
36
+ console.error(`\n Deploy aborted. Fix type errors or use ${color.bold('--skip-checks')} to bypass.\n`);
37
+ return false;
38
+ }
39
+ // 2. Run tests
40
+ const testSpinner = spinner('Running tests...').start();
41
+ try {
42
+ execSync('npx vitest run', { cwd, stdio: 'pipe', timeout: 120_000 });
43
+ testSpinner.succeed('Tests passed');
44
+ }
45
+ catch (err) {
46
+ testSpinner.fail('Tests failed');
47
+ if (err.stdout)
48
+ console.error(err.stdout.toString().slice(-2000));
49
+ if (err.stderr)
50
+ console.error(err.stderr.toString().slice(-2000));
51
+ console.error(`\n Deploy aborted. Fix test failures or use ${color.bold('--skip-checks')} to bypass.\n`);
52
+ return false;
53
+ }
54
+ console.log(`\n ${color.green('All pre-deploy checks passed.')}\n`);
55
+ return true;
56
+ }
57
+ export async function deploy(cwd, opts = {}) {
58
+ const appEntryPath = join(cwd, 'src', 'hive.ts');
59
+ if (!existsSync(appEntryPath)) {
60
+ console.error(' No src/hive.ts found. Run `beehaven init` first.');
61
+ process.exit(1);
62
+ }
63
+ if (opts.managed) {
64
+ console.log('\n Managed deployment coming soon.');
65
+ console.log(' Use `beehaven deploy` (self-host via wrangler) for now.\n');
66
+ return;
67
+ }
68
+ // DEPLOY-02: Production confirmation gate
69
+ if (opts.env === 'production' && !opts.force && !opts.confirm) {
70
+ console.error('\n \x1b[33m⚠ Production deployment requires confirmation.\x1b[0m');
71
+ console.error(` Add --force or --confirm to deploy to production.`);
72
+ console.error(` Example: beehaven deploy --env production --force\n`);
73
+ process.exit(1);
74
+ }
75
+ // DEPLOY-03: Pre-deploy checks
76
+ if (!opts.skipChecks && !opts.dryRun) {
77
+ const checksPass = runPreDeployChecks(cwd);
78
+ if (!checksPass) {
79
+ process.exit(1);
80
+ }
81
+ }
82
+ const deploySpinner = spinner('Deploying to Cloudflare...').start();
83
+ // Discover buildings from app entry
84
+ const buildings = discoverBuildings(cwd);
85
+ deploySpinner.update(`Deploying ${buildings.length} building(s) to Cloudflare...`);
86
+ console.log(` Buildings: ${buildings.map((b) => `${b.name} (${b.className})`).join(', ')}`);
87
+ // Generate wrangler.toml + worker.ts
88
+ const genDir = join(cwd, '.beehaven', 'generated');
89
+ if (!existsSync(genDir))
90
+ mkdirSync(genDir, { recursive: true });
91
+ writeFileSync(join(genDir, 'wrangler.toml'), generateWrangler(cwd, buildings, 8787));
92
+ writeFileSync(join(genDir, 'worker.ts'), generateWorker(buildings));
93
+ // DEPLOY-02: Dry-run mode
94
+ if (opts.dryRun) {
95
+ console.log('\n \x1b[36m[DRY RUN]\x1b[0m Would deploy with:');
96
+ console.log(` wrangler.toml: ${join(genDir, 'wrangler.toml')}`);
97
+ console.log(` worker.ts: ${join(genDir, 'worker.ts')}`);
98
+ console.log(` environment: ${opts.env ?? 'default'}`);
99
+ console.log(` buildings: ${buildings.map((b) => b.name).join(', ')}`);
100
+ console.log('\n No deployment executed.\n');
101
+ return;
102
+ }
103
+ deploySpinner.stop();
104
+ console.log(` Buildings: ${buildings.map((b) => `${b.name} (${b.className})`).join(', ')}`);
105
+ const args = ['wrangler', 'deploy', '--config', join(genDir, 'wrangler.toml')];
106
+ if (opts.env)
107
+ args.push('--env', opts.env);
108
+ const wrangler = spawn('npx', args, {
109
+ cwd,
110
+ stdio: 'inherit',
111
+ shell: true,
112
+ });
113
+ wrangler.on('error', (err) => {
114
+ console.error(` ${color.red('✗')} Failed to deploy: ${err.message}`);
115
+ process.exit(1);
116
+ });
117
+ wrangler.on('exit', (code) => {
118
+ if (code === 0) {
119
+ console.log(`\n ${color.green('✓')} Deployed successfully!\n`);
120
+ }
121
+ process.exit(code || 0);
122
+ });
123
+ }
124
+ // DEPLOY-01: Rollback command
125
+ export async function rollback(cwd, opts = {}) {
126
+ console.log('\n Rolling back to previous deployment version...\n');
127
+ const args = ['wrangler', 'rollback'];
128
+ if (opts.env)
129
+ args.push('--env', opts.env);
130
+ const wrangler = spawn('npx', args, {
131
+ cwd,
132
+ stdio: 'inherit',
133
+ shell: true,
134
+ });
135
+ wrangler.on('error', (err) => {
136
+ console.error(' Rollback failed:', err.message);
137
+ process.exit(1);
138
+ });
139
+ wrangler.on('exit', (code) => {
140
+ if (code === 0) {
141
+ console.log('\n Rollback complete.\n');
142
+ }
143
+ process.exit(code || 0);
144
+ });
145
+ }
146
+ // DEPLOY-01: Versions command
147
+ export async function versions(cwd, opts = {}) {
148
+ console.log('\n Listing deployment versions...\n');
149
+ const args = ['wrangler', 'versions', 'list'];
150
+ if (opts.env)
151
+ args.push('--env', opts.env);
152
+ const wrangler = spawn('npx', args, {
153
+ cwd,
154
+ stdio: 'inherit',
155
+ shell: true,
156
+ });
157
+ wrangler.on('error', (err) => {
158
+ console.error(' Failed to list versions:', err.message);
159
+ process.exit(1);
160
+ });
161
+ wrangler.on('exit', (code) => {
162
+ process.exit(code || 0);
163
+ });
164
+ }