@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,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* beehaven secrets — Cloudflare Workers secrets wrapper
|
|
3
|
+
*
|
|
4
|
+
* Defaults to setting on EVERY declared env (the parity-enforcement the root
|
|
5
|
+
* CLAUDE.md explicitly calls out as a recurring bug source). Detects envs from
|
|
6
|
+
* `[env.<name>]` blocks in wrangler.toml/wrangler.jsonc.
|
|
7
|
+
*
|
|
8
|
+
* Subcommands:
|
|
9
|
+
* list [--env <name>|all] List secret names per env
|
|
10
|
+
* set <NAME> [--env <name>|all] Set on all envs by default (parity)
|
|
11
|
+
* delete <NAME> [--env <name>|all] Delete on all envs by default
|
|
12
|
+
* diff Show secrets present on one env but not the other
|
|
13
|
+
* check Verify required secrets (from beehaven.json) are set everywhere
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync, readFileSync } from 'fs';
|
|
16
|
+
import { dirname, join, resolve } from 'path';
|
|
17
|
+
import { spawn } from 'child_process';
|
|
18
|
+
import { createInterface } from 'readline';
|
|
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, envs: parseEnvs(p) };
|
|
27
|
+
}
|
|
28
|
+
const parent = dirname(dir);
|
|
29
|
+
if (parent === dir)
|
|
30
|
+
return null;
|
|
31
|
+
dir = parent;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function parseEnvs(configPath) {
|
|
35
|
+
const text = readFileSync(configPath, 'utf-8');
|
|
36
|
+
if (configPath.endsWith('.toml')) {
|
|
37
|
+
const envs = new Set();
|
|
38
|
+
const re = /^\[env\.([a-zA-Z0-9_-]+)/gm;
|
|
39
|
+
let m;
|
|
40
|
+
while ((m = re.exec(text)) !== null)
|
|
41
|
+
envs.add(m[1]);
|
|
42
|
+
return Array.from(envs);
|
|
43
|
+
}
|
|
44
|
+
const stripped = text.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
45
|
+
const cfg = JSON.parse(stripped);
|
|
46
|
+
return Object.keys(cfg.env ?? {});
|
|
47
|
+
}
|
|
48
|
+
function readRequiredSecrets(cwd) {
|
|
49
|
+
const candidates = [join(cwd, 'beehaven.json'), join(dirname(cwd), 'beehaven.json')];
|
|
50
|
+
for (const p of candidates) {
|
|
51
|
+
if (!existsSync(p))
|
|
52
|
+
continue;
|
|
53
|
+
try {
|
|
54
|
+
const cfg = JSON.parse(readFileSync(p, 'utf-8'));
|
|
55
|
+
if (Array.isArray(cfg.requiredSecrets))
|
|
56
|
+
return cfg.requiredSecrets;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
/* ignore */
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
function runWranglerCapture(args, cwd) {
|
|
65
|
+
return new Promise((resolveP) => {
|
|
66
|
+
const proc = spawn('npx', ['wrangler', ...args], {
|
|
67
|
+
cwd,
|
|
68
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
69
|
+
env: { ...process.env },
|
|
70
|
+
});
|
|
71
|
+
let stdout = '';
|
|
72
|
+
let stderr = '';
|
|
73
|
+
proc.stdout.on('data', (d) => (stdout += d.toString()));
|
|
74
|
+
proc.stderr.on('data', (d) => (stderr += d.toString()));
|
|
75
|
+
proc.on('close', (code) => resolveP({ stdout, stderr, code: code ?? 1 }));
|
|
76
|
+
proc.on('error', () => resolveP({ stdout, stderr, code: 1 }));
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function runWranglerWithStdin(args, cwd, stdinValue) {
|
|
80
|
+
return new Promise((resolveP) => {
|
|
81
|
+
const proc = spawn('npx', ['wrangler', ...args], {
|
|
82
|
+
cwd,
|
|
83
|
+
stdio: ['pipe', 'inherit', 'pipe'],
|
|
84
|
+
env: { ...process.env },
|
|
85
|
+
});
|
|
86
|
+
let stderr = '';
|
|
87
|
+
proc.stderr?.on('data', (d) => (stderr += d.toString()));
|
|
88
|
+
proc.stdin.write(stdinValue);
|
|
89
|
+
proc.stdin.end();
|
|
90
|
+
proc.on('close', (code) => resolveP({ code: code ?? 1, stderr }));
|
|
91
|
+
proc.on('error', () => resolveP({ code: 1, stderr }));
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function extractJson(stdout) {
|
|
95
|
+
const idx = stdout.indexOf('[');
|
|
96
|
+
if (idx === -1) {
|
|
97
|
+
const objIdx = stdout.indexOf('{');
|
|
98
|
+
if (objIdx === -1)
|
|
99
|
+
return null;
|
|
100
|
+
try {
|
|
101
|
+
return JSON.parse(stdout.slice(objIdx));
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
return JSON.parse(stdout.slice(idx));
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function listSecrets(cwd, env) {
|
|
115
|
+
const args = ['secret', 'list'];
|
|
116
|
+
if (env)
|
|
117
|
+
args.push('--env', env);
|
|
118
|
+
const { stdout, code } = await runWranglerCapture(args, cwd);
|
|
119
|
+
if (code !== 0)
|
|
120
|
+
return [];
|
|
121
|
+
const parsed = extractJson(stdout);
|
|
122
|
+
if (!Array.isArray(parsed))
|
|
123
|
+
return [];
|
|
124
|
+
return parsed
|
|
125
|
+
.map((s) => s.name)
|
|
126
|
+
.filter(Boolean)
|
|
127
|
+
.sort();
|
|
128
|
+
}
|
|
129
|
+
function targetEnvs(cfg, requested) {
|
|
130
|
+
// null = the unnamed default env (top-level wrangler.toml)
|
|
131
|
+
if (requested && requested !== 'all')
|
|
132
|
+
return [requested];
|
|
133
|
+
if (cfg.envs.length === 0)
|
|
134
|
+
return [null];
|
|
135
|
+
return [null, ...cfg.envs];
|
|
136
|
+
}
|
|
137
|
+
function envLabel(env) {
|
|
138
|
+
return env ?? 'prod';
|
|
139
|
+
}
|
|
140
|
+
function promptHidden(prompt) {
|
|
141
|
+
return new Promise((resolveP) => {
|
|
142
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
143
|
+
process.stdout.write(prompt);
|
|
144
|
+
const stdin = process.stdin;
|
|
145
|
+
const wasRaw = stdin.isRaw;
|
|
146
|
+
if (stdin.setRawMode)
|
|
147
|
+
stdin.setRawMode(true);
|
|
148
|
+
let value = '';
|
|
149
|
+
const onData = (buf) => {
|
|
150
|
+
const ch = buf.toString();
|
|
151
|
+
if (ch === '\n' || ch === '\r' || ch === '') {
|
|
152
|
+
if (stdin.setRawMode)
|
|
153
|
+
stdin.setRawMode(!!wasRaw);
|
|
154
|
+
stdin.removeListener('data', onData);
|
|
155
|
+
process.stdout.write('\n');
|
|
156
|
+
rl.close();
|
|
157
|
+
resolveP(value);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (ch === '') {
|
|
161
|
+
process.exit(130);
|
|
162
|
+
}
|
|
163
|
+
if (ch === '\b' || ch === '\x7f') {
|
|
164
|
+
value = value.slice(0, -1);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
value += ch;
|
|
168
|
+
};
|
|
169
|
+
stdin.on('data', onData);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
export async function secrets(opts) {
|
|
173
|
+
const cfg = findWranglerConfig();
|
|
174
|
+
if (!cfg) {
|
|
175
|
+
console.error(color.red('No wrangler.toml/wrangler.jsonc found in cwd or any parent.'));
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
const cwd = dirname(cfg.path);
|
|
179
|
+
const sub = opts.subcommand ?? 'list';
|
|
180
|
+
switch (sub) {
|
|
181
|
+
case 'list': {
|
|
182
|
+
const envs = targetEnvs(cfg, opts.env);
|
|
183
|
+
const byEnv = {};
|
|
184
|
+
for (const env of envs) {
|
|
185
|
+
const s = spinner(`Listing secrets on ${envLabel(env)}…`).start();
|
|
186
|
+
const names = await listSecrets(cwd, env);
|
|
187
|
+
s.succeed(`${envLabel(env)}: ${names.length} secrets`);
|
|
188
|
+
byEnv[envLabel(env)] = names;
|
|
189
|
+
}
|
|
190
|
+
if (opts.json) {
|
|
191
|
+
console.log(JSON.stringify(byEnv, null, 2));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
console.log('');
|
|
195
|
+
const allNames = Array.from(new Set(Object.values(byEnv).flat())).sort();
|
|
196
|
+
const envNames = Object.keys(byEnv);
|
|
197
|
+
const rows = allNames.map((name) => [
|
|
198
|
+
color.cyan(name),
|
|
199
|
+
...envNames.map((e) => (byEnv[e].includes(name) ? color.green('✓') : color.dim('—'))),
|
|
200
|
+
]);
|
|
201
|
+
console.log(table(['Secret', ...envNames], rows));
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
case 'set': {
|
|
205
|
+
const name = opts.positionals[0];
|
|
206
|
+
if (!name) {
|
|
207
|
+
console.error('Usage: beehaven secrets set <NAME> [--env <name>|all] [--yes]');
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
const envs = targetEnvs(cfg, opts.env);
|
|
211
|
+
const value = await promptHidden(`Value for ${name}: `);
|
|
212
|
+
if (!value) {
|
|
213
|
+
console.error('Empty value — aborting.');
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
for (const env of envs) {
|
|
217
|
+
const args = ['secret', 'put', name];
|
|
218
|
+
if (env)
|
|
219
|
+
args.push('--env', env);
|
|
220
|
+
const s = spinner(`Setting ${name} on ${envLabel(env)}…`).start();
|
|
221
|
+
const { code, stderr } = await runWranglerWithStdin(args, cwd, value);
|
|
222
|
+
if (code !== 0) {
|
|
223
|
+
s.fail(`Failed on ${envLabel(env)} (exit ${code})`);
|
|
224
|
+
if (stderr)
|
|
225
|
+
console.error(color.dim(stderr));
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
s.succeed(`${name} → ${envLabel(env)}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
case 'delete':
|
|
234
|
+
case 'rm': {
|
|
235
|
+
const name = opts.positionals[0];
|
|
236
|
+
if (!name) {
|
|
237
|
+
console.error('Usage: beehaven secrets delete <NAME> [--env <name>|all] [--yes]');
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
const envs = targetEnvs(cfg, opts.env);
|
|
241
|
+
if (!opts.yes) {
|
|
242
|
+
console.error(color.yellow(`About to delete ${name} from: ${envs.map(envLabel).join(', ')}`));
|
|
243
|
+
console.error(color.dim('Re-run with --yes to confirm.'));
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
for (const env of envs) {
|
|
247
|
+
const args = ['secret', 'delete', name];
|
|
248
|
+
if (env)
|
|
249
|
+
args.push('--env', env);
|
|
250
|
+
const s = spinner(`Deleting ${name} on ${envLabel(env)}…`).start();
|
|
251
|
+
const { code } = await runWranglerCapture([...args, '--force'], cwd);
|
|
252
|
+
if (code !== 0)
|
|
253
|
+
s.fail(`Failed on ${envLabel(env)}`);
|
|
254
|
+
else
|
|
255
|
+
s.succeed(`${name} ✕ ${envLabel(env)}`);
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
case 'diff': {
|
|
260
|
+
if (cfg.envs.length === 0) {
|
|
261
|
+
console.log(color.dim('Only one env declared in wrangler.toml — nothing to diff.'));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const envs = targetEnvs(cfg, 'all');
|
|
265
|
+
const byEnv = {};
|
|
266
|
+
for (const env of envs) {
|
|
267
|
+
const s = spinner(`Listing ${envLabel(env)}…`).start();
|
|
268
|
+
byEnv[envLabel(env)] = new Set(await listSecrets(cwd, env));
|
|
269
|
+
s.succeed(envLabel(env));
|
|
270
|
+
}
|
|
271
|
+
const envNames = Object.keys(byEnv);
|
|
272
|
+
const allNames = Array.from(new Set(envNames.flatMap((e) => Array.from(byEnv[e])))).sort();
|
|
273
|
+
const missing = allNames.filter((n) => envNames.some((e) => !byEnv[e].has(n)));
|
|
274
|
+
console.log('');
|
|
275
|
+
if (missing.length === 0) {
|
|
276
|
+
console.log(color.green('✓ All secrets present on every env.'));
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
console.log(color.yellow(`${missing.length} secrets out of parity:`));
|
|
280
|
+
const rows = missing.map((name) => [
|
|
281
|
+
color.cyan(name),
|
|
282
|
+
...envNames.map((e) => (byEnv[e].has(name) ? color.green('✓') : color.red('✗'))),
|
|
283
|
+
]);
|
|
284
|
+
console.log(table(['Secret', ...envNames], rows));
|
|
285
|
+
console.log('');
|
|
286
|
+
console.log(color.dim('Fix with: beehaven secrets set <NAME> (defaults to all envs)'));
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
case 'check': {
|
|
290
|
+
const required = readRequiredSecrets(cwd);
|
|
291
|
+
if (required.length === 0) {
|
|
292
|
+
console.log(color.yellow('No requiredSecrets declared in beehaven.json.'));
|
|
293
|
+
console.log(color.dim('Add e.g.: { "requiredSecrets": ["GCP_SERVICE_ACCOUNT_JSON", "..."] }'));
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const envs = targetEnvs(cfg, opts.env);
|
|
297
|
+
const byEnv = {};
|
|
298
|
+
for (const env of envs) {
|
|
299
|
+
byEnv[envLabel(env)] = new Set(await listSecrets(cwd, env));
|
|
300
|
+
}
|
|
301
|
+
const envNames = Object.keys(byEnv);
|
|
302
|
+
const rows = required.map((name) => [
|
|
303
|
+
color.cyan(name),
|
|
304
|
+
...envNames.map((e) => (byEnv[e].has(name) ? color.green('✓') : color.red('MISSING'))),
|
|
305
|
+
]);
|
|
306
|
+
console.log('');
|
|
307
|
+
console.log(table(['Required Secret', ...envNames], rows));
|
|
308
|
+
const missing = required.filter((n) => envNames.some((e) => !byEnv[e].has(n)));
|
|
309
|
+
if (missing.length > 0) {
|
|
310
|
+
console.log('');
|
|
311
|
+
console.log(color.red(`✗ ${missing.length} required secrets missing on at least one env.`));
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
console.log('');
|
|
315
|
+
console.log(color.green('✓ All required secrets set on every env.'));
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
default:
|
|
319
|
+
console.error(`Unknown secrets subcommand: ${sub}`);
|
|
320
|
+
console.error('Use: list | set | delete | diff | check');
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* self-update — where this `beehaven` came from, and how (or whether) it updates.
|
|
3
|
+
*
|
|
4
|
+
* ## ⚠⚠ THE OLD NOTIFIER GAVE ADVICE THAT BREAKS THE MAC APP'S USERS
|
|
5
|
+
*
|
|
6
|
+
* `checkForUpdates` printed, unconditionally:
|
|
7
|
+
*
|
|
8
|
+
* Update available: 0.7.6 → 0.7.8
|
|
9
|
+
* Run curl -fsSL https://clearly.sh/install.sh | sh to update
|
|
10
|
+
*
|
|
11
|
+
* For anyone whose `beehaven` comes from the Mac app — which is EVERY ordinary user, since the
|
|
12
|
+
* app bundles the CLI and installs the wrapper — following that instruction used to be actively
|
|
13
|
+
* harmful. The wrapper resolved `$BEEHAVEN_CLI` → `~/.beehaven/cli/dist/cli.js` → the bundled
|
|
14
|
+
* copy, in that fixed order, so a curl install created a SECOND CLI that permanently shadowed the
|
|
15
|
+
* app's.
|
|
16
|
+
*
|
|
17
|
+
* ⚠⚠ AND IT WAS PERMANENT IN BOTH DIRECTIONS, WHICH IS WORSE THAN THIS PARAGRAPH ORIGINALLY SAID.
|
|
18
|
+
* The shadowed user copy had no updater either: the app spawns its daemon with
|
|
19
|
+
* `BEEHAVEN_MANAGED_BY`, so `checkAndAutoUpdate` (daemon.ts) SKIPS — correctly, since the app owns
|
|
20
|
+
* the bundle's version. So on a machine with both, the app auto-updated a bundle nothing could
|
|
21
|
+
* reach while `~/.beehaven/cli` froze at whatever was curl-installed, and the terminal was served
|
|
22
|
+
* by neither channel.
|
|
23
|
+
*
|
|
24
|
+
* ✅ FIXED 2026-09-09 IN THE WRAPPER, NOT HERE: `buildWrapperContent` now execs whichever copy
|
|
25
|
+
* reports the higher version, so an app update and a `beehaven update` both reach the shell. The
|
|
26
|
+
* hint below is still per-install, because "how do I update this" still has three answers.
|
|
27
|
+
*
|
|
28
|
+
* ⚠ IT WAS ALSO THE REASON A DEVELOPER NEVER SAW THE BUNDLED CLI GO STALE — their curl install
|
|
29
|
+
* won unconditionally, so they were testing a different binary from the one users get. Under the
|
|
30
|
+
* version comparison they still win whenever their build is newer, which is the normal case.
|
|
31
|
+
*
|
|
32
|
+
* ## The three installs, and what "update" means for each
|
|
33
|
+
*
|
|
34
|
+
* bundled — inside `Clearly*.app/Contents/Resources/beehaven/`. The APP owns it and the app
|
|
35
|
+
* auto-updates (electron-updater), so this CLI already updates; it just does not
|
|
36
|
+
* update ITSELF. Telling this user to curl is the bug above.
|
|
37
|
+
* user — `~/.beehaven/cli/`, from install.sh. A CLI-owned standalone daemon checks hourly;
|
|
38
|
+
* without that daemon, `beehaven update` performs the update on demand.
|
|
39
|
+
* dev-bundled — Clearly Dev.app has no release updater; rebuild and reinstall it.
|
|
40
|
+
* linked — a dev checkout via `npm link`. Never touch it; the developer's build IS the point.
|
|
41
|
+
*/
|
|
42
|
+
import { execFileSync } from 'node:child_process';
|
|
43
|
+
import { createHash } from 'node:crypto';
|
|
44
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
|
45
|
+
import { homedir, tmpdir } from 'node:os';
|
|
46
|
+
import { join, sep } from 'node:path';
|
|
47
|
+
import { fileURLToPath } from 'node:url';
|
|
48
|
+
import { usableUpdateDigest } from '../version-utils.js';
|
|
49
|
+
/**
|
|
50
|
+
* Which install this process is.
|
|
51
|
+
*
|
|
52
|
+
* ⚠ DECIDED FROM THIS RUNNING MODULE'S RESOLVED PATH, not the binary symlink or from what
|
|
53
|
+
* happens to exist on disk. npm global binaries are often symlinks outside node_modules;
|
|
54
|
+
* process.argv[1] would misclassify them as linked development builds. Both a bundled
|
|
55
|
+
* CLI and a user install can be present at once — that is exactly the shadowing case above — so
|
|
56
|
+
* "does `~/.beehaven/cli` exist" answers the wrong question. `process.argv[1]` is the module that
|
|
57
|
+
* was actually loaded.
|
|
58
|
+
*/
|
|
59
|
+
export function installKind(entry = fileURLToPath(import.meta.url)) {
|
|
60
|
+
if (/(?:Clearly|Hive) Dev\.app[\\/]Contents[\\/]Resources[\\/]/.test(entry))
|
|
61
|
+
return 'dev-bundled';
|
|
62
|
+
if (entry.includes(`.app${sep}Contents${sep}Resources${sep}`))
|
|
63
|
+
return 'bundled';
|
|
64
|
+
const prefixes = [join(homedir(), '.beehaven'), process.env.BEEHAVEN_PREFIX].filter((p) => Boolean(p));
|
|
65
|
+
if (prefixes.some((prefix) => entry.startsWith(join(prefix, 'cli') + sep)))
|
|
66
|
+
return 'user';
|
|
67
|
+
if (entry.includes(`${sep}_npx${sep}`) && entry.includes(`${sep}node_modules${sep}`))
|
|
68
|
+
return 'npx';
|
|
69
|
+
if (entry.includes(`${sep}node_modules${sep}`))
|
|
70
|
+
return 'npm';
|
|
71
|
+
return 'linked';
|
|
72
|
+
}
|
|
73
|
+
/** The one-line instruction to print when a newer version exists. Per install, because the right
|
|
74
|
+
* answer genuinely differs and a single string is what caused the defect above. */
|
|
75
|
+
export function updateHint(kind = installKind()) {
|
|
76
|
+
if (kind === 'dev-bundled')
|
|
77
|
+
return 'This is a development app build — rebuild and reinstall Clearly Dev.app from the repo.';
|
|
78
|
+
if (kind === 'bundled') {
|
|
79
|
+
/* ⚠ NOT "run the installer". The app updates this copy; a curl install would shadow it. */
|
|
80
|
+
return 'The Clearly app ships this CLI and updates it — open the app, or Tray → Check for updates.';
|
|
81
|
+
}
|
|
82
|
+
if (kind === 'user')
|
|
83
|
+
return 'Run \x1b[1mbeehaven update\x1b[0m';
|
|
84
|
+
if (kind === 'npx')
|
|
85
|
+
return 'Run `npx @clearlysh/beehaven@latest` for the current npm release.';
|
|
86
|
+
if (kind === 'npm')
|
|
87
|
+
return 'Run `npm install -g @clearlysh/beehaven@latest` to update this npm install.';
|
|
88
|
+
return 'This is a linked dev build — `npm run build` in beehaven_sdk/packages/cli.';
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Perform the update, for a user install only.
|
|
92
|
+
*
|
|
93
|
+
* ⚠⚠ IT RE-RUNS THE PUBLISHED INSTALLER RATHER THAN REIMPLEMENTING IT. `install.sh` already
|
|
94
|
+
* stages into a temp dir, runs `npm install` to resolve the NATIVE dependencies (better-sqlite3,
|
|
95
|
+
* node-pty — per-platform, per-ABI prebuilds), and only then swaps the directory into place. A
|
|
96
|
+
* hand-rolled updater here would have to reproduce all of that, and would be the copy that rots.
|
|
97
|
+
*
|
|
98
|
+
* ⚠ REPLACING A RUNNING PROGRAM IS SAFE HERE ONLY BECAUSE OF THAT STAGING. The swap is a
|
|
99
|
+
* directory rename after the download and build have both succeeded; this process is already
|
|
100
|
+
* resident in memory and exits normally. An in-place overwrite would not be safe.
|
|
101
|
+
*
|
|
102
|
+
* ⚠ IT REFUSES THE OTHER TWO KINDS RATHER THAN DOING SOMETHING PLAUSIBLE. A bundled install that
|
|
103
|
+
* "updated" itself would create the shadowing copy this whole module exists to prevent, and a
|
|
104
|
+
* linked one would overwrite a developer's working tree.
|
|
105
|
+
*/
|
|
106
|
+
export function runSelfUpdate(kind = installKind()) {
|
|
107
|
+
if (kind === 'dev-bundled') {
|
|
108
|
+
return { ok: false, message: 'This CLI is bundled with Clearly Dev.app. Rebuild and reinstall the development app from the repo.' };
|
|
109
|
+
}
|
|
110
|
+
if (kind === 'bundled') {
|
|
111
|
+
return { ok: false, message: 'This CLI is part of the Clearly app and updates with it.\n Open the app, or use Tray → Check for updates.' };
|
|
112
|
+
}
|
|
113
|
+
if (kind === 'linked') {
|
|
114
|
+
return { ok: false, message: 'This is a linked development build — refusing to overwrite it.\n cd beehaven_sdk/packages/cli && npm run build' };
|
|
115
|
+
}
|
|
116
|
+
if (kind === 'npm' || kind === 'npx') {
|
|
117
|
+
return { ok: false, message: updateHint(kind) };
|
|
118
|
+
}
|
|
119
|
+
const feed = (process.env.BEEHAVEN_CLI_FEED || 'https://downloads.clearly.sh/cli/').replace(/\/+$/, '');
|
|
120
|
+
const installerUrl = process.env.BEEHAVEN_INSTALL_URL || 'https://clearly.sh/install.sh';
|
|
121
|
+
const temp = mkdtempSync(join(tmpdir(), 'beehaven-self-update-'));
|
|
122
|
+
try {
|
|
123
|
+
const manifestPath = join(temp, 'latest.json');
|
|
124
|
+
execFileSync('curl', ['-fsSL', '--max-time', '30', '-o', manifestPath, `${feed}/latest.json`]);
|
|
125
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
126
|
+
const version = manifest.version;
|
|
127
|
+
const digest = usableUpdateDigest(manifest);
|
|
128
|
+
if (typeof version !== 'string' || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version) || !digest) {
|
|
129
|
+
return { ok: false, message: 'Update failed: the release manifest has no valid version and sha256.' };
|
|
130
|
+
}
|
|
131
|
+
const tarball = join(temp, 'beehaven.tgz');
|
|
132
|
+
execFileSync('curl', ['-fsSL', '--max-time', '120', '-o', tarball, `${feed}/beehaven-${version}.tgz`]);
|
|
133
|
+
const actual = createHash('sha256').update(readFileSync(tarball)).digest('hex');
|
|
134
|
+
if (actual !== digest)
|
|
135
|
+
return { ok: false, message: 'Update failed: release sha256 mismatch.' };
|
|
136
|
+
// Download the installer as a file. A `curl | sh` pipeline reports sh's success even when
|
|
137
|
+
// curl fails, so it can say "Updated" without ever running an installer.
|
|
138
|
+
const installer = join(temp, 'install.sh');
|
|
139
|
+
execFileSync('curl', ['-fsSL', '--max-time', '30', '-o', installer, installerUrl]);
|
|
140
|
+
execFileSync('/bin/sh', [installer], {
|
|
141
|
+
stdio: 'inherit',
|
|
142
|
+
timeout: 10 * 60_000,
|
|
143
|
+
env: { ...process.env, BEEHAVEN_TARBALL: `file://${tarball}` },
|
|
144
|
+
});
|
|
145
|
+
const installed = JSON.parse(readFileSync(join(process.env.BEEHAVEN_PREFIX || join(homedir(), '.beehaven'), 'cli', 'package.json'), 'utf8'));
|
|
146
|
+
if (installed.version !== version)
|
|
147
|
+
return { ok: false, message: `Update failed: installed ${installed.version ?? 'unknown'}, expected ${version}.` };
|
|
148
|
+
return { ok: true, message: `Updated to ${version}.` };
|
|
149
|
+
}
|
|
150
|
+
catch (e) {
|
|
151
|
+
return { ok: false, message: `Update failed: ${String(e?.message ?? e).slice(0, 200)}` };
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
rmSync(temp, { recursive: true, force: true });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** True when this install can update itself at all — what the notifier uses to pick its wording. */
|
|
158
|
+
export function canSelfUpdate(kind = installKind()) {
|
|
159
|
+
return kind === 'user';
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Is a bundled CLI present that the shell can NEVER reach, whatever its version?
|
|
163
|
+
*
|
|
164
|
+
* ⚠⚠ THIS REPLACES `bundledCliShadowed`, WHICH BECAME WRONG ADVICE (2026-09-09). That function
|
|
165
|
+
* answered "is a bundle present alongside a user install", and the `update` command turned a
|
|
166
|
+
* `true` into *"this install shadows it — remove ~/.beehaven/cli to go back to the app-managed
|
|
167
|
+
* copy."* Since the app's wrapper started resolving by VERSION rather than by fixed order
|
|
168
|
+
* (`cli-installer.ts:buildWrapperContent`), that is advice to go BACKWARDS: it fired immediately
|
|
169
|
+
* after `beehaven update`, at the exact moment the freshly-installed copy is the newer one and
|
|
170
|
+
* SHOULD win, and told the user to delete it.
|
|
171
|
+
*
|
|
172
|
+
* ⚠ RENAMED RATHER THAN RE-POINTED, because the question it answers is genuinely different. A
|
|
173
|
+
* name that still says "shadowed" over a body that means "unreachable" is how the next reader
|
|
174
|
+
* restores the old warning.
|
|
175
|
+
*
|
|
176
|
+
* The one configuration that is still a real problem: a wrapper on PATH that the app does NOT
|
|
177
|
+
* own. `install.sh` writes an unmarked shim (`exec node <user cli>`) with no chain in it at all,
|
|
178
|
+
* and `cli-installer` correctly refuses to take a path it did not write — so on that machine the
|
|
179
|
+
* bundle is unreachable REGARDLESS of which copy is newer, and an app update can never arrive.
|
|
180
|
+
* That is worth saying; "both are installed" is not.
|
|
181
|
+
*
|
|
182
|
+
* ⚠ THE MAGIC HEADER IS THE TEST, not the file's existence. It is the app's own claim of
|
|
183
|
+
* ownership and the only thing that distinguishes a wrapper that compares versions from one that
|
|
184
|
+
* hardcodes a single path. Kept in step with `WRAPPER_MAGIC` in `cli-installer.ts`.
|
|
185
|
+
*/
|
|
186
|
+
export function bundledCliUnreachable(kind = installKind()) {
|
|
187
|
+
if (kind !== 'user')
|
|
188
|
+
return false;
|
|
189
|
+
const bundlePresent = ['/Applications/Clearly.app', '/Applications/Clearly Dev.app', '/Applications/Clearly STG.app']
|
|
190
|
+
.some((a) => existsSync(join(a, 'Contents', 'Resources', 'beehaven', 'cli.js')));
|
|
191
|
+
if (!bundlePresent)
|
|
192
|
+
return false;
|
|
193
|
+
/* No wrapper at all ⇒ the app has not installed one yet; its next launch will, and that one
|
|
194
|
+
compares versions. Nothing to warn about. */
|
|
195
|
+
const wrapper = join(homedir(), '.local', 'bin', 'beehaven');
|
|
196
|
+
if (!existsSync(wrapper))
|
|
197
|
+
return false;
|
|
198
|
+
try {
|
|
199
|
+
return !readFileSync(wrapper, 'utf8').includes('managed-by: clearly-mac-app');
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
/* Unreadable ⇒ we cannot prove there is a problem, and a false alarm here tells someone to
|
|
203
|
+
delete a working install. Say nothing. */
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
}
|