@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,536 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Beehaven Service Installer — launchd (macOS) / systemd (Linux)
|
|
3
|
+
//
|
|
4
|
+
// Registers the daemon as a user-level service with auto-restart on crash.
|
|
5
|
+
// No root/sudo required — uses ~/Library/LaunchAgents or ~/.config/systemd/user.
|
|
6
|
+
// ============================================================================
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
import { existsSync, mkdirSync, writeFileSync, unlinkSync, readFileSync, chmodSync } from 'fs';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
import { join, dirname } from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const LABEL = 'sh.clearly.beehaven';
|
|
14
|
+
const LOG_DIR = join(homedir(), '.beehaven', 'logs');
|
|
15
|
+
// macOS
|
|
16
|
+
const LAUNCH_AGENTS_DIR = join(homedir(), 'Library', 'LaunchAgents');
|
|
17
|
+
const PLIST_PATH = join(LAUNCH_AGENTS_DIR, `${LABEL}.plist`);
|
|
18
|
+
// Linux
|
|
19
|
+
const SYSTEMD_USER_DIR = join(homedir(), '.config', 'systemd', 'user');
|
|
20
|
+
const UNIT_PATH = join(SYSTEMD_USER_DIR, 'beehaven.service');
|
|
21
|
+
// ── Resolve paths ──────────────────────────────────────────────────────────
|
|
22
|
+
function resolveNodePath() {
|
|
23
|
+
return process.execPath;
|
|
24
|
+
}
|
|
25
|
+
function resolveCliPath() {
|
|
26
|
+
// Prefer the compiled dist/cli.js adjacent to this file
|
|
27
|
+
const distCli = join(__dirname, '..', 'dist', 'cli.js');
|
|
28
|
+
if (existsSync(distCli))
|
|
29
|
+
return distCli;
|
|
30
|
+
// Fallback: adjacent cli.js (we're already in dist/)
|
|
31
|
+
const adjacentCli = join(__dirname, 'cli.js');
|
|
32
|
+
if (existsSync(adjacentCli))
|
|
33
|
+
return adjacentCli;
|
|
34
|
+
// Last resort: resolve from npm global
|
|
35
|
+
try {
|
|
36
|
+
const bin = execSync('which beehaven', { encoding: 'utf8' }).trim();
|
|
37
|
+
// Read the shebang to find the actual script
|
|
38
|
+
const content = readFileSync(bin, 'utf8');
|
|
39
|
+
if (content.startsWith('#!/')) {
|
|
40
|
+
// npm bin wrapper — the real script is this file itself
|
|
41
|
+
return bin;
|
|
42
|
+
}
|
|
43
|
+
return bin;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
throw new Error('Could not resolve beehaven CLI path. Is it installed globally?');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function getCurrentPath() {
|
|
50
|
+
return process.env.PATH || '/usr/local/bin:/usr/bin:/bin';
|
|
51
|
+
}
|
|
52
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
53
|
+
/** Escape strings for safe inclusion in XML/plist files */
|
|
54
|
+
function escapeXml(str) {
|
|
55
|
+
return str
|
|
56
|
+
.replace(/&/g, '&')
|
|
57
|
+
.replace(/</g, '<')
|
|
58
|
+
.replace(/>/g, '>')
|
|
59
|
+
.replace(/"/g, '"')
|
|
60
|
+
.replace(/'/g, ''');
|
|
61
|
+
}
|
|
62
|
+
// ── Templates ──────────────────────────────────────────────────────────────
|
|
63
|
+
function generatePlist(nodePath, cliPath) {
|
|
64
|
+
const envPath = getCurrentPath();
|
|
65
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
66
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
67
|
+
<plist version="1.0">
|
|
68
|
+
<dict>
|
|
69
|
+
<key>Label</key>
|
|
70
|
+
<string>${escapeXml(LABEL)}</string>
|
|
71
|
+
<key>ProgramArguments</key>
|
|
72
|
+
<array>
|
|
73
|
+
<string>${escapeXml(nodePath)}</string>
|
|
74
|
+
<string>${escapeXml(cliPath)}</string>
|
|
75
|
+
<string>start</string>
|
|
76
|
+
</array>
|
|
77
|
+
<key>WorkingDirectory</key>
|
|
78
|
+
<string>${escapeXml(homedir())}</string>
|
|
79
|
+
<key>RunAtLoad</key>
|
|
80
|
+
<true/>
|
|
81
|
+
<key>KeepAlive</key>
|
|
82
|
+
<dict>
|
|
83
|
+
<key>SuccessfulExit</key>
|
|
84
|
+
<false/>
|
|
85
|
+
</dict>
|
|
86
|
+
<key>ThrottleInterval</key>
|
|
87
|
+
<integer>10</integer>
|
|
88
|
+
<key>StandardOutPath</key>
|
|
89
|
+
<string>${escapeXml(LOG_DIR)}/stdout.log</string>
|
|
90
|
+
<key>StandardErrorPath</key>
|
|
91
|
+
<string>${escapeXml(LOG_DIR)}/stderr.log</string>
|
|
92
|
+
<key>EnvironmentVariables</key>
|
|
93
|
+
<dict>
|
|
94
|
+
<key>PATH</key>
|
|
95
|
+
<string>${escapeXml(envPath)}</string>
|
|
96
|
+
<key>HOME</key>
|
|
97
|
+
<string>${escapeXml(homedir())}</string>
|
|
98
|
+
</dict>
|
|
99
|
+
</dict>
|
|
100
|
+
</plist>`;
|
|
101
|
+
}
|
|
102
|
+
function generateUnit(nodePath, cliPath) {
|
|
103
|
+
const envPath = getCurrentPath();
|
|
104
|
+
return `[Unit]
|
|
105
|
+
Description=Beehaven Daemon — Claude Code activity relay
|
|
106
|
+
After=network-online.target
|
|
107
|
+
|
|
108
|
+
[Service]
|
|
109
|
+
Type=simple
|
|
110
|
+
ExecStart="${nodePath}" "${cliPath}" start
|
|
111
|
+
WorkingDirectory=${homedir()}
|
|
112
|
+
Restart=on-failure
|
|
113
|
+
RestartSec=10
|
|
114
|
+
StandardOutput=append:${LOG_DIR}/stdout.log
|
|
115
|
+
StandardError=append:${LOG_DIR}/stderr.log
|
|
116
|
+
Environment=PATH=${envPath}
|
|
117
|
+
Environment=HOME=${homedir()}
|
|
118
|
+
|
|
119
|
+
[Install]
|
|
120
|
+
WantedBy=default.target`;
|
|
121
|
+
}
|
|
122
|
+
// ── Install ────────────────────────────────────────────────────────────────
|
|
123
|
+
export async function installService() {
|
|
124
|
+
const platform = process.platform;
|
|
125
|
+
console.log('');
|
|
126
|
+
console.log(' \uD83D\uDC1D Beehaven — Install Service');
|
|
127
|
+
console.log(' \u2501'.repeat(30));
|
|
128
|
+
console.log('');
|
|
129
|
+
// Resolve paths
|
|
130
|
+
const nodePath = resolveNodePath();
|
|
131
|
+
const cliPath = resolveCliPath();
|
|
132
|
+
console.log(` Node: ${nodePath}`);
|
|
133
|
+
console.log(` CLI: ${cliPath}`);
|
|
134
|
+
console.log('');
|
|
135
|
+
// Create log directory with restricted permissions
|
|
136
|
+
mkdirSync(LOG_DIR, { recursive: true, mode: 0o700 });
|
|
137
|
+
if (platform === 'darwin') {
|
|
138
|
+
await installLaunchd(nodePath, cliPath);
|
|
139
|
+
}
|
|
140
|
+
else if (platform === 'linux') {
|
|
141
|
+
await installSystemd(nodePath, cliPath);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
console.error(` Unsupported platform: ${platform}`);
|
|
145
|
+
console.error(' Supported: macOS (launchd), Linux (systemd)');
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async function installLaunchd(nodePath, cliPath) {
|
|
150
|
+
// Unload existing service if present
|
|
151
|
+
if (existsSync(PLIST_PATH)) {
|
|
152
|
+
try {
|
|
153
|
+
execSync(`launchctl unload "${PLIST_PATH}"`, { stdio: 'ignore' });
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
/* may not be loaded */
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// Write plist
|
|
160
|
+
mkdirSync(LAUNCH_AGENTS_DIR, { recursive: true });
|
|
161
|
+
const plist = generatePlist(nodePath, cliPath);
|
|
162
|
+
writeFileSync(PLIST_PATH, plist, { mode: 0o600 });
|
|
163
|
+
chmodSync(PLIST_PATH, 0o600);
|
|
164
|
+
console.log(` \u2713 Created ${PLIST_PATH} (mode 0600)`);
|
|
165
|
+
// Load service
|
|
166
|
+
try {
|
|
167
|
+
execSync(`launchctl load -w "${PLIST_PATH}"`, { stdio: 'pipe' });
|
|
168
|
+
console.log(' \u2713 Service loaded (launchd)');
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
console.error(` \u2717 Failed to load service: ${err.message}`);
|
|
172
|
+
console.error(' Try: launchctl load -w ~/Library/LaunchAgents/sh.clearly.beehaven.plist');
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
// Verify
|
|
176
|
+
await sleep(1000);
|
|
177
|
+
try {
|
|
178
|
+
const output = execSync(`launchctl list | grep ${LABEL}`, { encoding: 'utf8' });
|
|
179
|
+
const parts = output.trim().split(/\s+/);
|
|
180
|
+
const pid = parts[0];
|
|
181
|
+
if (pid && pid !== '-') {
|
|
182
|
+
console.log(` \u2713 Running (PID ${pid})`);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
console.log(' \u26A0 Service loaded but not yet running (will start on next login)');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
console.log(' \u26A0 Could not verify — check: launchctl list | grep beehaven');
|
|
190
|
+
}
|
|
191
|
+
printSuccess('launchd');
|
|
192
|
+
}
|
|
193
|
+
async function installSystemd(nodePath, cliPath) {
|
|
194
|
+
// Write unit file
|
|
195
|
+
mkdirSync(SYSTEMD_USER_DIR, { recursive: true });
|
|
196
|
+
const unit = generateUnit(nodePath, cliPath);
|
|
197
|
+
writeFileSync(UNIT_PATH, unit, { mode: 0o600 });
|
|
198
|
+
chmodSync(UNIT_PATH, 0o600);
|
|
199
|
+
console.log(` \u2713 Created ${UNIT_PATH} (mode 0600)`);
|
|
200
|
+
// Reload + enable + start
|
|
201
|
+
try {
|
|
202
|
+
execSync('systemctl --user daemon-reload', { stdio: 'pipe' });
|
|
203
|
+
execSync('systemctl --user enable --now beehaven', { stdio: 'pipe' });
|
|
204
|
+
console.log(' \u2713 Service enabled and started (systemd)');
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
console.error(` \u2717 Failed to enable service: ${err.message}`);
|
|
208
|
+
console.error(' Try: systemctl --user enable --now beehaven');
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
// Verify
|
|
212
|
+
await sleep(1000);
|
|
213
|
+
try {
|
|
214
|
+
const output = execSync('systemctl --user is-active beehaven', { encoding: 'utf8' }).trim();
|
|
215
|
+
if (output === 'active') {
|
|
216
|
+
console.log(' \u2713 Running');
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
console.log(` \u26A0 Status: ${output}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
console.log(' \u26A0 Could not verify — check: systemctl --user status beehaven');
|
|
224
|
+
}
|
|
225
|
+
printSuccess('systemd');
|
|
226
|
+
}
|
|
227
|
+
function printSuccess(backend) {
|
|
228
|
+
console.log('');
|
|
229
|
+
console.log(' Service installed. Beehaven will:');
|
|
230
|
+
console.log(' • Start automatically on login');
|
|
231
|
+
console.log(' • Restart automatically on crash (10s delay)');
|
|
232
|
+
console.log(' • Stay stopped on clean shutdown');
|
|
233
|
+
console.log('');
|
|
234
|
+
console.log(` Logs: ${LOG_DIR}/`);
|
|
235
|
+
console.log('');
|
|
236
|
+
if (backend === 'launchd') {
|
|
237
|
+
console.log(' Manage: use the Clearly Dev Mac app tray.');
|
|
238
|
+
console.log(' Daemon lifecycle (start/stop/uninstall) is tray-only.');
|
|
239
|
+
console.log(' Diagnostic: launchctl list | grep beehaven');
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
console.log(' Manage:');
|
|
243
|
+
console.log(' systemctl --user stop beehaven # Stop');
|
|
244
|
+
console.log(' systemctl --user start beehaven # Start');
|
|
245
|
+
console.log(' systemctl --user status beehaven # Status');
|
|
246
|
+
}
|
|
247
|
+
console.log('');
|
|
248
|
+
}
|
|
249
|
+
// ── Uninstall ──────────────────────────────────────────────────────────────
|
|
250
|
+
export async function uninstallService() {
|
|
251
|
+
const platform = process.platform;
|
|
252
|
+
if (platform === 'darwin') {
|
|
253
|
+
await uninstallLaunchd();
|
|
254
|
+
}
|
|
255
|
+
else if (platform === 'linux') {
|
|
256
|
+
await uninstallSystemd();
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
console.log(' No service to uninstall (unsupported platform)');
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
async function uninstallLaunchd() {
|
|
263
|
+
if (!existsSync(PLIST_PATH)) {
|
|
264
|
+
console.log(' No launchd service installed');
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
execSync(`launchctl unload "${PLIST_PATH}"`, { stdio: 'ignore' });
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
/* may not be loaded */
|
|
272
|
+
}
|
|
273
|
+
unlinkSync(PLIST_PATH);
|
|
274
|
+
console.log(' \u2713 Removed launchd service');
|
|
275
|
+
}
|
|
276
|
+
async function uninstallSystemd() {
|
|
277
|
+
if (!existsSync(UNIT_PATH)) {
|
|
278
|
+
console.log(' No systemd service installed');
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
try {
|
|
282
|
+
execSync('systemctl --user disable --now beehaven', { stdio: 'ignore' });
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
/* may not be enabled */
|
|
286
|
+
}
|
|
287
|
+
unlinkSync(UNIT_PATH);
|
|
288
|
+
try {
|
|
289
|
+
execSync('systemctl --user daemon-reload', { stdio: 'ignore' });
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
/* best effort */
|
|
293
|
+
}
|
|
294
|
+
console.log(' \u2713 Removed systemd service');
|
|
295
|
+
}
|
|
296
|
+
// ── Check if service is installed ──────────────────────────────────────────
|
|
297
|
+
export function isServiceInstalled() {
|
|
298
|
+
if (process.platform === 'darwin')
|
|
299
|
+
return existsSync(PLIST_PATH);
|
|
300
|
+
if (process.platform === 'linux')
|
|
301
|
+
return existsSync(UNIT_PATH);
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
// ── Start / Stop (without install/uninstall) ──────────────────────────────
|
|
305
|
+
/** Start the service if installed. Returns true if started via service manager. */
|
|
306
|
+
export function startService() {
|
|
307
|
+
if (process.platform === 'darwin' && existsSync(PLIST_PATH)) {
|
|
308
|
+
try {
|
|
309
|
+
execSync(`launchctl load -w "${PLIST_PATH}"`, { stdio: 'pipe' });
|
|
310
|
+
console.log(' Service started (launchd).');
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
// May already be loaded — try kickstart
|
|
315
|
+
try {
|
|
316
|
+
execSync(`launchctl start ${LABEL}`, { stdio: 'pipe' });
|
|
317
|
+
console.log(' Service started (launchd).');
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
catch { }
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (process.platform === 'linux' && existsSync(UNIT_PATH)) {
|
|
324
|
+
try {
|
|
325
|
+
execSync('systemctl --user start beehaven', { stdio: 'pipe' });
|
|
326
|
+
console.log(' Service started (systemd).');
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
catch { }
|
|
330
|
+
}
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
/** Stop the service if installed. Returns true if stopped via service manager. */
|
|
334
|
+
export function stopService() {
|
|
335
|
+
if (process.platform === 'darwin' && existsSync(PLIST_PATH)) {
|
|
336
|
+
try {
|
|
337
|
+
execSync(`launchctl unload "${PLIST_PATH}"`, { stdio: 'pipe' });
|
|
338
|
+
console.log(' Service stopped (launchd unloaded — will not auto-restart).');
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
catch { }
|
|
342
|
+
}
|
|
343
|
+
if (process.platform === 'linux' && existsSync(UNIT_PATH)) {
|
|
344
|
+
try {
|
|
345
|
+
execSync('systemctl --user stop beehaven', { stdio: 'pipe' });
|
|
346
|
+
console.log(' Service stopped (systemd).');
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
catch { }
|
|
350
|
+
}
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
// ── Full uninstall (for Mac app uninstall hook) ───────────────────────────
|
|
354
|
+
//
|
|
355
|
+
// Single entry point the Clearly Dev Mac app invokes when the user removes
|
|
356
|
+
// the app. Tears down the LaunchAgent, revokes the server session, purges
|
|
357
|
+
// all local state, and removes the socket + event log. Safe to call
|
|
358
|
+
// repeatedly — every step swallows its own errors.
|
|
359
|
+
//
|
|
360
|
+
// The CLI no longer exposes this as a `beehaven uninstall` command by
|
|
361
|
+
// design; daemon lifecycle is owned by the tray. The Mac app imports this
|
|
362
|
+
// function directly and calls it from its own teardown routine.
|
|
363
|
+
export async function cleanupAll() {
|
|
364
|
+
const steps = [];
|
|
365
|
+
const errors = [];
|
|
366
|
+
// 1. LaunchAgent / systemd unit
|
|
367
|
+
try {
|
|
368
|
+
await uninstallService();
|
|
369
|
+
steps.push('service-unregistered');
|
|
370
|
+
}
|
|
371
|
+
catch (err) {
|
|
372
|
+
errors.push(`service-unregister: ${err?.message ?? err}`);
|
|
373
|
+
}
|
|
374
|
+
// 2. Revoke server session + clear keychain token
|
|
375
|
+
try {
|
|
376
|
+
const { Relay } = await import('./relay.js');
|
|
377
|
+
const r = new Relay();
|
|
378
|
+
await r.deleteSession();
|
|
379
|
+
steps.push('session-deleted');
|
|
380
|
+
}
|
|
381
|
+
catch (err) {
|
|
382
|
+
errors.push(`session-delete: ${err?.message ?? err}`);
|
|
383
|
+
}
|
|
384
|
+
try {
|
|
385
|
+
const { logout } = await import('./setup-relay.js');
|
|
386
|
+
logout();
|
|
387
|
+
steps.push('logged-out');
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
errors.push(`logout: ${err?.message ?? err}`);
|
|
391
|
+
}
|
|
392
|
+
// 3. Socket + event log
|
|
393
|
+
const { existsSync, unlinkSync, readdirSync, rmSync, readFileSync, writeFileSync } = await import('node:fs');
|
|
394
|
+
const { join } = await import('node:path');
|
|
395
|
+
for (const p of ['/tmp/beehaven.sock', '/tmp/beehaven-ipc.sock', '/tmp/beehaven-events.jsonl']) {
|
|
396
|
+
try {
|
|
397
|
+
if (existsSync(p)) {
|
|
398
|
+
unlinkSync(p);
|
|
399
|
+
steps.push(`removed:${p}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
catch { }
|
|
403
|
+
}
|
|
404
|
+
try {
|
|
405
|
+
for (const f of readdirSync('/tmp')) {
|
|
406
|
+
if (f.startsWith('beehaven-events.jsonl.') && f.endsWith('.old')) {
|
|
407
|
+
try {
|
|
408
|
+
unlinkSync(join('/tmp', f));
|
|
409
|
+
}
|
|
410
|
+
catch { }
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
catch { }
|
|
415
|
+
// 4. ~/.beehaven/ (config, sessions, cache, apps, logs)
|
|
416
|
+
const configDir = join(homedir(), '.beehaven');
|
|
417
|
+
try {
|
|
418
|
+
if (existsSync(configDir)) {
|
|
419
|
+
rmSync(configDir, { recursive: true, force: true });
|
|
420
|
+
steps.push('config-dir-removed');
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
catch (err) {
|
|
424
|
+
errors.push(`config-dir: ${err?.message ?? err}`);
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* 5–6. The `~/.local/bin` wrappers the Mac app writes.
|
|
428
|
+
*
|
|
429
|
+
* ⚠⚠ ONLY OURS. These two steps used to `unlinkSync` unconditionally, and that is not a
|
|
430
|
+
* theoretical hazard — it was observed deleting a developer's own `beehaven`: `cli-installer`'s
|
|
431
|
+
* `uninstallBeehavenCli` inspected the file, correctly reported *"wrapper is not managed by this
|
|
432
|
+
* app"*, declined — and then this ran and removed it anyway. Two uninstallers with opposite
|
|
433
|
+
* policies on the same path, and the destructive one went second.
|
|
434
|
+
*
|
|
435
|
+
* ⚠ THE HEADER IS THE OWNERSHIP TEST, and it is the same one the installer uses in the other
|
|
436
|
+
* direction: it refuses to overwrite a wrapper it did not write. An uninstall has to honour the
|
|
437
|
+
* same boundary or the pair is incoherent — you can be unable to install over a file you are
|
|
438
|
+
* nonetheless willing to delete.
|
|
439
|
+
*/
|
|
440
|
+
const WRAPPER_MAGIC = '# managed-by: clearly-mac-app';
|
|
441
|
+
const removeOurWrapper = (name) => {
|
|
442
|
+
try {
|
|
443
|
+
const wrapper = join(homedir(), '.local', 'bin', name);
|
|
444
|
+
if (!existsSync(wrapper))
|
|
445
|
+
return;
|
|
446
|
+
if (!readFileSync(wrapper, 'utf8').includes(WRAPPER_MAGIC)) {
|
|
447
|
+
steps.push(`kept:~/.local/bin/${name} (not ours)`);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
unlinkSync(wrapper);
|
|
451
|
+
steps.push(`removed:~/.local/bin/${name}`);
|
|
452
|
+
}
|
|
453
|
+
catch { }
|
|
454
|
+
};
|
|
455
|
+
removeOurWrapper('beehaven');
|
|
456
|
+
// 6. mwah CLI wrappers — the Mac app installs these under CLEARLY_AVATARS
|
|
457
|
+
// (cleanupAll only handled `beehaven` before, so they were orphaned).
|
|
458
|
+
// ⚠ Same ownership test as above; see the note there.
|
|
459
|
+
for (const name of ['mwah', 'mwah-say'])
|
|
460
|
+
removeOurWrapper(name);
|
|
461
|
+
// 7. Claude Code integration — the avatar/mwah hooks, skills, commands, and
|
|
462
|
+
// caches the Mac app installs (installClaudeAssets), plus the hook entries
|
|
463
|
+
// in ~/.claude/settings.json. Leaves ~/.mwah/pets (the user's generated art).
|
|
464
|
+
const claudeDir = join(homedir(), '.claude');
|
|
465
|
+
for (const rel of [
|
|
466
|
+
'hooks/mwah-hook', 'hooks/mwah-narrate', 'hooks/mwah-stream-bubble', 'hooks/mwah-stream-watcher',
|
|
467
|
+
'skills/mwah-avatar', 'skills/mwah-pet', 'commands/mwah.md',
|
|
468
|
+
]) {
|
|
469
|
+
try {
|
|
470
|
+
const full = join(claudeDir, rel);
|
|
471
|
+
if (existsSync(full)) {
|
|
472
|
+
rmSync(full, { recursive: true, force: true });
|
|
473
|
+
steps.push(`removed:~/.claude/${rel}`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
catch { }
|
|
477
|
+
}
|
|
478
|
+
for (const cache of ['mwah-scene', 'mwah-stream', 'mwah-narrate']) {
|
|
479
|
+
try {
|
|
480
|
+
const full = join(homedir(), '.cache', cache);
|
|
481
|
+
if (existsSync(full))
|
|
482
|
+
rmSync(full, { recursive: true, force: true });
|
|
483
|
+
}
|
|
484
|
+
catch { }
|
|
485
|
+
}
|
|
486
|
+
try {
|
|
487
|
+
const settingsPath = join(claudeDir, 'settings.json');
|
|
488
|
+
if (existsSync(settingsPath)) {
|
|
489
|
+
const cfg = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
490
|
+
let removed = 0;
|
|
491
|
+
if (cfg && cfg.hooks && typeof cfg.hooks === 'object') {
|
|
492
|
+
for (const ev of Object.keys(cfg.hooks)) {
|
|
493
|
+
const list = cfg.hooks[ev];
|
|
494
|
+
if (!Array.isArray(list))
|
|
495
|
+
continue;
|
|
496
|
+
const kept = list.filter((entry) => !((entry.hooks || []).some((h) => typeof h.command === 'string' && /mwah|spawn-pet/i.test(h.command))));
|
|
497
|
+
removed += list.length - kept.length;
|
|
498
|
+
if (kept.length)
|
|
499
|
+
cfg.hooks[ev] = kept;
|
|
500
|
+
else
|
|
501
|
+
delete cfg.hooks[ev];
|
|
502
|
+
}
|
|
503
|
+
if (removed) {
|
|
504
|
+
writeFileSync(settingsPath, JSON.stringify(cfg, null, 2) + '\n');
|
|
505
|
+
steps.push(`stripped ${removed} mwah hook(s) from settings.json`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
catch (err) {
|
|
511
|
+
errors.push(`claude-hooks: ${err?.message ?? err}`);
|
|
512
|
+
}
|
|
513
|
+
// 8. On-device voice model caches OUTSIDE ~/.beehaven — the Parakeet-MLX weights
|
|
514
|
+
// live in the HuggingFace hub cache, so removing ~/.beehaven alone leaves them
|
|
515
|
+
// behind. (The venv + Kokoro model live inside ~/.beehaven, already removed.)
|
|
516
|
+
try {
|
|
517
|
+
const hubDir = join(homedir(), '.cache', 'huggingface', 'hub');
|
|
518
|
+
if (existsSync(hubDir)) {
|
|
519
|
+
for (const d of readdirSync(hubDir)) {
|
|
520
|
+
if (/^models--(mlx-community|nvidia)--parakeet/i.test(d)) {
|
|
521
|
+
try {
|
|
522
|
+
rmSync(join(hubDir, d), { recursive: true, force: true });
|
|
523
|
+
steps.push(`removed:hf-cache/${d}`);
|
|
524
|
+
}
|
|
525
|
+
catch { }
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
catch { }
|
|
531
|
+
return { steps, errors };
|
|
532
|
+
}
|
|
533
|
+
// ── Utilities ──────────────────────────────────────────────────────────────
|
|
534
|
+
function sleep(ms) {
|
|
535
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
536
|
+
}
|