@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,130 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Cloud Bee Proxy — Lightweight state holder for cloud-sourced bee status
|
|
3
|
+
//
|
|
4
|
+
// Replaces the local BeeAgent. Receives state updates from the BeeHive
|
|
5
|
+
// Durable Object via WebSocket 'agent-status' messages and exposes a
|
|
6
|
+
// getStatus() shape compatible with the DaemonRepl.
|
|
7
|
+
// ============================================================================
|
|
8
|
+
import { EventEmitter } from 'events';
|
|
9
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { homedir } from 'os';
|
|
12
|
+
const THOUGHTS_DIR = join(homedir(), 'beehaven', 'thoughts');
|
|
13
|
+
// ── CloudBeeProxy ───────────────────────────────────────────────────────────
|
|
14
|
+
export class CloudBeeProxy extends EventEmitter {
|
|
15
|
+
relay;
|
|
16
|
+
state = {
|
|
17
|
+
emotion: 'sleep',
|
|
18
|
+
mood: 'fine',
|
|
19
|
+
moodScore: 50,
|
|
20
|
+
phase: 'rest',
|
|
21
|
+
activeSession: null,
|
|
22
|
+
soulVersion: 0,
|
|
23
|
+
learnings: 0,
|
|
24
|
+
patterns: 0,
|
|
25
|
+
rhythmRunning: false,
|
|
26
|
+
energy: -1,
|
|
27
|
+
hunger: -1,
|
|
28
|
+
fun: -1,
|
|
29
|
+
social: -1,
|
|
30
|
+
};
|
|
31
|
+
constructor(relay) {
|
|
32
|
+
super();
|
|
33
|
+
this.relay = relay;
|
|
34
|
+
}
|
|
35
|
+
/** Update from an incoming agent-status message from the DO */
|
|
36
|
+
updateFromCloud(msg) {
|
|
37
|
+
const prevEmotion = this.state.emotion;
|
|
38
|
+
if (msg.emotion !== undefined)
|
|
39
|
+
this.state.emotion = msg.emotion;
|
|
40
|
+
if (msg.mood !== undefined)
|
|
41
|
+
this.state.mood = String(msg.mood);
|
|
42
|
+
if (msg.moodScore !== undefined)
|
|
43
|
+
this.state.moodScore = msg.moodScore;
|
|
44
|
+
if (msg.phase !== undefined)
|
|
45
|
+
this.state.phase = msg.phase;
|
|
46
|
+
if (msg.activeSession !== undefined)
|
|
47
|
+
this.state.activeSession = msg.activeSession;
|
|
48
|
+
if (msg.soulVersion !== undefined)
|
|
49
|
+
this.state.soulVersion = msg.soulVersion;
|
|
50
|
+
if (msg.learnings !== undefined)
|
|
51
|
+
this.state.learnings = msg.learnings;
|
|
52
|
+
if (msg.patterns !== undefined)
|
|
53
|
+
this.state.patterns = msg.patterns;
|
|
54
|
+
if (msg.rhythmRunning !== undefined)
|
|
55
|
+
this.state.rhythmRunning = msg.rhythmRunning;
|
|
56
|
+
if (msg.energy !== undefined)
|
|
57
|
+
this.state.energy = msg.energy;
|
|
58
|
+
if (msg.hunger !== undefined)
|
|
59
|
+
this.state.hunger = msg.hunger;
|
|
60
|
+
if (msg.fun !== undefined)
|
|
61
|
+
this.state.fun = msg.fun;
|
|
62
|
+
if (msg.social !== undefined)
|
|
63
|
+
this.state.social = msg.social;
|
|
64
|
+
if (msg.emotion && msg.emotion !== prevEmotion) {
|
|
65
|
+
this.emit('emotion', msg.emotion);
|
|
66
|
+
}
|
|
67
|
+
this.emit('status', this.state);
|
|
68
|
+
}
|
|
69
|
+
/** Shape-compatible with BeeAgent.getStatus() for the REPL */
|
|
70
|
+
getStatus() {
|
|
71
|
+
return {
|
|
72
|
+
emotion: this.state.emotion,
|
|
73
|
+
mood: this.state.mood,
|
|
74
|
+
moodScore: this.state.moodScore,
|
|
75
|
+
energy: this.state.energy,
|
|
76
|
+
hunger: this.state.hunger,
|
|
77
|
+
fun: this.state.fun,
|
|
78
|
+
social: this.state.social,
|
|
79
|
+
rhythmPhase: this.state.phase,
|
|
80
|
+
learnings: this.state.learnings,
|
|
81
|
+
patterns: this.state.patterns,
|
|
82
|
+
soulVersion: this.state.soulVersion,
|
|
83
|
+
sessionActive: this.state.activeSession !== null,
|
|
84
|
+
wakeTimerActive: false,
|
|
85
|
+
llmConfigured: true,
|
|
86
|
+
llmProvider: 'cloud',
|
|
87
|
+
llmDailyCost: 0,
|
|
88
|
+
llmCallsThisHour: 0,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/** Send a reflect command to the cloud via relay */
|
|
92
|
+
async forceReflect() {
|
|
93
|
+
if (this.relay?.wsSend) {
|
|
94
|
+
this.relay.wsSend({ type: 'command', text: '$ bee think' });
|
|
95
|
+
this.emit('thought', { type: 'observation', content: 'Reflection requested from cloud...' });
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
this.emit('thought', { type: 'observation', content: 'Not connected to cloud.' });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** No-op — lifecycle managed by cloud DO */
|
|
102
|
+
stop() {
|
|
103
|
+
/* no-op */
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// ── Standalone utility (extracted from bee/llm.ts) ──────────────────────────
|
|
107
|
+
/** Read recent thought files from ~/beehaven/thoughts/ */
|
|
108
|
+
export function getRecentThoughts(count = 5) {
|
|
109
|
+
try {
|
|
110
|
+
if (!existsSync(THOUGHTS_DIR))
|
|
111
|
+
return [];
|
|
112
|
+
const files = readdirSync(THOUGHTS_DIR)
|
|
113
|
+
.filter((f) => f.endsWith('.md'))
|
|
114
|
+
.sort()
|
|
115
|
+
.slice(-count);
|
|
116
|
+
return files
|
|
117
|
+
.map((f) => {
|
|
118
|
+
try {
|
|
119
|
+
return readFileSync(join(THOUGHTS_DIR, f), 'utf8');
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return '';
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
.filter(Boolean);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spend confirmation for actions that cost real money.
|
|
3
|
+
*
|
|
4
|
+
* `beehaven call <action>` can reach ANY action mounted on the workspace DO — the action
|
|
5
|
+
* registry is looked up by name (`_actions.get`), so mounting an RPC for the web app also
|
|
6
|
+
* exposes it to the shell. For most actions that is the point. For actions that bill a paid
|
|
7
|
+
* model per invocation it is a footgun: a shell one-liner — or an agent driving this CLI —
|
|
8
|
+
* can run it in a loop with nothing between the keystroke and the charge.
|
|
9
|
+
*
|
|
10
|
+
* This used to be a HARD BLOCK on `studio-image-generate`, which was the wrong shape. It made
|
|
11
|
+
* the CLI — and every agent driving it — unable to do the product's core job (generate and
|
|
12
|
+
* vectorize) in order to prevent a cost problem that a confirmation prompt solves. Generation
|
|
13
|
+
* is now ALLOWED and CONFIRMED: interactive callers get a y/N prompt carrying the estimate,
|
|
14
|
+
* scripted and agent callers pass `--yes`, and free paths are never gated at all.
|
|
15
|
+
*
|
|
16
|
+
* Cost is assessed per-CALL, not per-action, because the same RPC is free or paid depending on
|
|
17
|
+
* its input: a hand-rolled SVG (`engine:'handroll'`, or `svgContent` supplied) runs no model,
|
|
18
|
+
* while the same action with a prompt bills a raster model plus a trace. Gating on the action
|
|
19
|
+
* name alone would prompt on the free path and teach people to reflex-pass `--yes` — which
|
|
20
|
+
* would cost more than it saved.
|
|
21
|
+
*
|
|
22
|
+
* This is a GUARDRAIL, not a security boundary. It lives in the CLI, so it stops accidental and
|
|
23
|
+
* automated spend through the supported path; it does not stop someone who edits this file or
|
|
24
|
+
* speaks the daemon protocol directly. The real ceiling is server-side — the composition
|
|
25
|
+
* pre-flight checks quota BEFORE dispatching, so an exhausted workspace is refused, not billed.
|
|
26
|
+
*/
|
|
27
|
+
export const COSTLY_ACTIONS = [
|
|
28
|
+
{
|
|
29
|
+
action: 'studio-image-generate',
|
|
30
|
+
estimate: '~$0.03–0.04',
|
|
31
|
+
what: 'a paid raster model, and/or a Recraft vectorize pass',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
action: 'create',
|
|
35
|
+
estimate: '~$0.03–0.04',
|
|
36
|
+
what: 'a paid raster/video model, or a Recraft vectorize pass',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
action: 'studio-video-generate',
|
|
40
|
+
estimate: '~$0.10+',
|
|
41
|
+
what: 'a paid video model',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
action: 'recraft-vectorize',
|
|
45
|
+
estimate: '~$0.01',
|
|
46
|
+
what: 'a Recraft vectorize pass',
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
const BY_NAME = new Map(COSTLY_ACTIONS.map((b) => [b.action, b]));
|
|
50
|
+
/** The cost entry for `action`, or null when the action never spends. */
|
|
51
|
+
export function costlyAction(action) {
|
|
52
|
+
return BY_NAME.get(String(action || '').trim()) ?? null;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Does THIS call spend money? Input-aware, because the free and paid paths share an RPC.
|
|
56
|
+
*
|
|
57
|
+
* Free (returns null):
|
|
58
|
+
* - `engine:'handroll'`, or any call supplying `svgContent` — the caller wrote the SVG, so
|
|
59
|
+
* no model runs. This is the path agents should prefer and it must never prompt.
|
|
60
|
+
* - `create` with a non-generating `kind` (document / spreadsheet), which is just a write.
|
|
61
|
+
*/
|
|
62
|
+
export function costOfCall(action, input) {
|
|
63
|
+
const entry = costlyAction(action);
|
|
64
|
+
if (!entry)
|
|
65
|
+
return null;
|
|
66
|
+
const i = input && typeof input === 'object' ? input : {};
|
|
67
|
+
// Hand-rolled vectors run no model, on either RPC.
|
|
68
|
+
if (i.engine === 'handroll' || (typeof i.svgContent === 'string' && i.svgContent.trim()))
|
|
69
|
+
return null;
|
|
70
|
+
if (entry.action === 'create') {
|
|
71
|
+
const kind = String(i.kind ?? 'vector').toLowerCase();
|
|
72
|
+
const PAID = new Set([
|
|
73
|
+
'image', 'video', 'clip', 'animation', 'movie',
|
|
74
|
+
'vectorize', 'trace', 'vector-trace', 'sticker-sheet',
|
|
75
|
+
]);
|
|
76
|
+
// 'vector' is paid ONLY when the model has to build it — a call carrying svgContent
|
|
77
|
+
// already returned above. Every other kind (document, spreadsheet, …) is a plain write.
|
|
78
|
+
if (kind === 'vector')
|
|
79
|
+
return entry;
|
|
80
|
+
return PAID.has(kind) ? entry : null;
|
|
81
|
+
}
|
|
82
|
+
return entry;
|
|
83
|
+
}
|
|
84
|
+
/** Read a single y/N answer from the TTY. Resolves false on EOF or anything but y/yes. */
|
|
85
|
+
async function askYesNo(question) {
|
|
86
|
+
const { createInterface } = await import('readline');
|
|
87
|
+
// Prompt on STDERR so a `--json` consumer piping stdout still gets clean JSON.
|
|
88
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
89
|
+
try {
|
|
90
|
+
const answer = await new Promise((resolve) => rl.question(question, resolve));
|
|
91
|
+
return /^y(es)?$/i.test(String(answer).trim());
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
rl.close();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Confirm a spending call before it is dispatched; no-op for free calls.
|
|
99
|
+
*
|
|
100
|
+
* Non-interactive callers (agents, CI, pipes) MUST pass `yes` — we refuse rather than prompt
|
|
101
|
+
* into a void, and the refusal names the flag so the fix is one edit away.
|
|
102
|
+
*/
|
|
103
|
+
export async function assertActionAllowed(action, input, opts) {
|
|
104
|
+
const cost = costOfCall(action, input);
|
|
105
|
+
if (!cost)
|
|
106
|
+
return;
|
|
107
|
+
if (opts?.yes)
|
|
108
|
+
return;
|
|
109
|
+
if (!process.stdin.isTTY) {
|
|
110
|
+
console.error(`\n \`${action}\` spends money (${cost.estimate} per call — ${cost.what}).`);
|
|
111
|
+
console.error(` This shell is not interactive, so it cannot be confirmed here.`);
|
|
112
|
+
console.error(` Re-run with --yes to authorize the spend.\n`);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
const ok = await askYesNo(`\n \`${action}\` will spend ${cost.estimate} (${cost.what}).\n Continue? [y/N] `);
|
|
116
|
+
if (!ok) {
|
|
117
|
+
console.error(' Cancelled — nothing was spent.\n');
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which BUILD this is, and therefore whether Developer Mode may be turned on at all.
|
|
3
|
+
*
|
|
4
|
+
* ⚠⚠ DEVELOPER SCOPE IS NOT SOMETHING A PUBLIC INSTALL MAY OPT INTO. `HIVE_DEV_MODE=1` mints a
|
|
5
|
+
* second IPC token that unlocks `get-token` (exfiltrates the user's Firebase ID token),
|
|
6
|
+
* `daemon-fetch`, `shell`, `sql`, `marketplace:install` and the `admin:*` proxies — and the relay
|
|
7
|
+
* path additionally unlocks `bash` / `workspace-exec`, which its own comment calls "arbitrary local
|
|
8
|
+
* command execution" on a channel that is "unauthenticated per message". That is an internal
|
|
9
|
+
* operator surface. Shipping it to `curl … | sh` users behind one environment variable means the
|
|
10
|
+
* capability is one blog post away from being a supported feature.
|
|
11
|
+
*
|
|
12
|
+
* ⚠ THE DEFAULT IS THE *SOURCE* BUILD, so a checkout keeps working exactly as it does today and
|
|
13
|
+
* nobody has to remember a flag to develop. The PUBLISH path opts INTO being public — see
|
|
14
|
+
* `scripts/stamp-build-channel.mjs`, run by `npm run build` and stamped by
|
|
15
|
+
* `scripts/publish-beehaven-cli.sh`. Defaulting the other way would ship a dev build the first time
|
|
16
|
+
* somebody published without the flag, and that failure is silent.
|
|
17
|
+
*
|
|
18
|
+
* ⚠⚠ THIS IS A DISTRIBUTION BOUNDARY, NOT AN ACCESS-CONTROL ONE, and the difference matters. The
|
|
19
|
+
* file is JavaScript on the user's own disk and they can edit it; what it prevents is the
|
|
20
|
+
* capability being PRESENT and documented in a build we hand out. Real authorization is server-side
|
|
21
|
+
* — every `admin:*` RPC is gated on `ADMIN_USER_IDS` — and nothing here weakens or replaces that.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* `true` in a tarball produced by the publish script; `false` in a source checkout.
|
|
25
|
+
*
|
|
26
|
+
* ⚠ REWRITTEN IN `dist/` AT BUILD TIME. Read it through `devModeAvailable()` rather than importing
|
|
27
|
+
* it directly, so there is one place that decides and one place to look.
|
|
28
|
+
*/
|
|
29
|
+
export const PUBLIC_BUILD = true;
|
|
30
|
+
/**
|
|
31
|
+
* WHICH BUILD THIS IS — a commit and a build time, stamped into `dist/` — CBH2-145.
|
|
32
|
+
*
|
|
33
|
+
* ⚠⚠ THE VERSION NUMBER CANNOT ANSWER THIS AND NEVER COULD. On 2026-09-06 two copies of the CLI
|
|
34
|
+
* on one machine both reported `0.8.2` and were DIFFERENT PROGRAMS: the Mac app's bundled
|
|
35
|
+
* `cli.js` (351,057 bytes) and the repo build (360,010). The bundled one answered "Unknown
|
|
36
|
+
* command: sprint" while the repo build listed the sprints, and nothing in `--version` said so.
|
|
37
|
+
* That is the same hazard the publish script already guards for the tarball — *"different bytes
|
|
38
|
+
* under one version is an error, not a silent default"* — on the copy that actually owns
|
|
39
|
+
* `~/.local/bin/beehaven`.
|
|
40
|
+
*
|
|
41
|
+
* ⚠⚠ NOT FIXED BY BUMPING THE PACKAGE VERSION, deliberately. That would make the number
|
|
42
|
+
* meaningless for releases AND still not help here: a dev build can be made at any commit, so two
|
|
43
|
+
* builds of one version are the NORMAL case on a developer's machine, not an anomaly.
|
|
44
|
+
*
|
|
45
|
+
* ⚠ TWO FIELDS BECAUSE THEY ANSWER TWO QUESTIONS. The sha says WHICH SOURCE; the timestamp says
|
|
46
|
+
* WHICH BUILD, and it is the half that still works when the sha cannot distinguish them — a build
|
|
47
|
+
* from a dirty tree, or from a tarball with no `.git` at all. Neither alone is sufficient.
|
|
48
|
+
*
|
|
49
|
+
* ⚠ `-dirty` IS SCOPED TO THIS PACKAGE, not to the monorepo — a repo this size is almost always
|
|
50
|
+
* dirty somewhere, and a marker that is always on says nothing. Scoped, it means exactly *"the sha
|
|
51
|
+
* above does not identify these bytes"*, which is the claim a reader needs.
|
|
52
|
+
*
|
|
53
|
+
* ⚠ REWRITTEN IN `dist/` AT BUILD TIME, like `PUBLIC_BUILD`. `unstamped` is what a source checkout
|
|
54
|
+
* carries and what `tsx`/`vitest` see, and it is honest: nothing has stamped them.
|
|
55
|
+
*/
|
|
56
|
+
export const BUILD_SHA = '6cab82f4d7e2';
|
|
57
|
+
/** ISO-8601 minute precision, UTC. Rewritten in `dist/` by `scripts/stamp-build-channel.mjs`. */
|
|
58
|
+
export const BUILD_TIME = '2026-09-27T06:05Z';
|
|
59
|
+
/**
|
|
60
|
+
* The build identity as one short string, for `--version`.
|
|
61
|
+
*
|
|
62
|
+
* ⚠ ONE FORMATTER, so the version line and any guard read the same thing. Returns `''` when
|
|
63
|
+
* nothing is stamped rather than the word "unstamped" twice — a `tsx` run has no build to name,
|
|
64
|
+
* and printing placeholder noise on the one command people use to check a build is how the field
|
|
65
|
+
* gets ignored.
|
|
66
|
+
*/
|
|
67
|
+
export function buildLabel() {
|
|
68
|
+
const parts = [];
|
|
69
|
+
if (BUILD_SHA && BUILD_SHA !== 'unstamped')
|
|
70
|
+
parts.push(BUILD_SHA);
|
|
71
|
+
if (BUILD_TIME && BUILD_TIME !== 'unstamped')
|
|
72
|
+
parts.push(`built ${BUILD_TIME}`);
|
|
73
|
+
return parts.join(' ');
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Is Developer Mode reachable in this build, and asked for?
|
|
77
|
+
*
|
|
78
|
+
* ⚠⚠ ONE PREDICATE, BECAUSE THERE WERE FOUR CHECKS AND THEY DID NOT AGREE. `daemon.ts` accepted
|
|
79
|
+
* `'1'` or `'true'`; `relay.ts` accepted only `'1'`; `cli.ts` accepted both again for the help
|
|
80
|
+
* text. So `HIVE_DEV_MODE=true` produced a daemon in developer mode whose relay path still refused
|
|
81
|
+
* `bash` — a state nothing reports and which reads as the gate being broken. Anything that gates on
|
|
82
|
+
* developer mode calls this.
|
|
83
|
+
*/
|
|
84
|
+
export function devModeAvailable(env = process.env) {
|
|
85
|
+
if (PUBLIC_BUILD)
|
|
86
|
+
return false;
|
|
87
|
+
const v = String(env.HIVE_DEV_MODE ?? '').trim().toLowerCase();
|
|
88
|
+
return v === '1' || v === 'true' || v === 'yes' || v === 'on';
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Why Developer Mode is unavailable, for a message a person can act on.
|
|
92
|
+
*
|
|
93
|
+
* ⚠ THE TWO REASONS NEED DIFFERENT ANSWERS. "You did not switch it on" is fixed by setting the
|
|
94
|
+
* variable; "this build cannot" is not fixed by anything the user types, and telling them to set a
|
|
95
|
+
* variable that will be ignored is how a support thread starts.
|
|
96
|
+
*/
|
|
97
|
+
export function devModeRefusal(env = process.env) {
|
|
98
|
+
return PUBLIC_BUILD
|
|
99
|
+
? 'Developer Mode is not available in a released build of the beehaven CLI.'
|
|
100
|
+
: 'Developer Mode is off. Start the daemon with HIVE_DEV_MODE=1 to enable it.';
|
|
101
|
+
}
|