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,125 @@
1
+ /**
2
+ * Code generation for beehaven dev and deploy.
3
+ *
4
+ * Reads the app entry point (src/hive.ts), parses imports to discover
5
+ * Workspace classes, and generates wrangler.toml + worker.ts (entry point).
6
+ *
7
+ * The developer writes their Workspace class directly in TypeScript.
8
+ */
9
+ import { existsSync, readFileSync } from 'fs';
10
+ import { join } from 'path';
11
+ // ── Discovery ────────────────────────────────────────
12
+ /**
13
+ * Parse the app entry point to discover building names and their Workspace classes.
14
+ *
15
+ * Looks for a `buildings:` block in defineHive() and extracts key → class mappings.
16
+ * Also scans imports to verify classes extend Workspace.
17
+ */
18
+ export function discoverBuildings(cwd) {
19
+ const appEntryPath = join(cwd, 'src', 'hive.ts');
20
+ if (!existsSync(appEntryPath)) {
21
+ throw new Error('No src/hive.ts found. Run `beehaven init` first.');
22
+ }
23
+ const source = readFileSync(appEntryPath, 'utf8');
24
+ // Find all classes that extend Workspace (in the same file or imported)
25
+ // Pattern: class Foo extends Workspace
26
+ const classPattern = /class\s+(\w+)\s+extends\s+Workspace\b/g;
27
+ const workspaceClasses = new Set();
28
+ let classMatch;
29
+ while ((classMatch = classPattern.exec(source)) !== null) {
30
+ workspaceClasses.add(classMatch[1]);
31
+ }
32
+ // Also look for imported Workspace subclasses from other files
33
+ // Pattern: import { FooWorkspace } from './workspaces/foo';
34
+ const importPattern = /import\s+\{([^}]+)\}\s+from\s+['"][^'"]+['"]/g;
35
+ let importMatch;
36
+ const importedNames = [];
37
+ while ((importMatch = importPattern.exec(source)) !== null) {
38
+ const names = importMatch[1].split(',').map((s) => s
39
+ .trim()
40
+ .split(/\s+as\s+/)
41
+ .pop()
42
+ .trim());
43
+ importedNames.push(...names);
44
+ }
45
+ // Find the buildings: { ... } block
46
+ const buildingsPattern = /buildings\s*:\s*\{([^}]+)\}/;
47
+ const buildingsMatch = buildingsPattern.exec(source);
48
+ if (!buildingsMatch) {
49
+ throw new Error('No buildings: { ... } block found in src/hive.ts');
50
+ }
51
+ const buildingsBlock = buildingsMatch[1];
52
+ const buildings = [];
53
+ // Parse key: ClassName pairs from the buildings block
54
+ // Handles both `main: MyWorkspace` and `main: MyWorkspace,`
55
+ const entryPattern = /(\w+)\s*:\s*(\w+)/g;
56
+ let entryMatch;
57
+ while ((entryMatch = entryPattern.exec(buildingsBlock)) !== null) {
58
+ buildings.push({
59
+ name: entryMatch[1],
60
+ className: entryMatch[2],
61
+ });
62
+ }
63
+ if (buildings.length === 0) {
64
+ throw new Error('No buildings found in src/hive.ts buildings: { } block');
65
+ }
66
+ return buildings;
67
+ }
68
+ // ── Code Generation ──────────────────────────────────
69
+ /**
70
+ * Generate wrangler.toml with DO bindings for each building.
71
+ */
72
+ export function generateWrangler(cwd, buildings, port) {
73
+ const pkgPath = join(cwd, 'package.json');
74
+ const name = existsSync(pkgPath)
75
+ ? JSON.parse(readFileSync(pkgPath, 'utf8')).name || 'beehaven-app'
76
+ : 'beehaven-app';
77
+ const bindings = buildings
78
+ .map((b) => {
79
+ const bindingName = toScreamingSnake(b.name) + '_DO';
80
+ return ` { name = "${bindingName}", class_name = "${b.className}" }`;
81
+ })
82
+ .join(',\n');
83
+ const classNames = buildings.map((b) => b.className);
84
+ const sqliteClassesStr = JSON.stringify(classNames);
85
+ return `# Auto-generated by beehaven — do not edit
86
+ name = "${name}"
87
+ main = "worker.ts"
88
+ compatibility_date = "2024-12-01"
89
+ compatibility_flags = ["nodejs_compat"]
90
+
91
+ [durable_objects]
92
+ bindings = [
93
+ ${bindings}
94
+ ]
95
+
96
+ [[migrations]]
97
+ tag = "v1"
98
+ new_sqlite_classes = ${sqliteClassesStr}
99
+ `;
100
+ }
101
+ /**
102
+ * Generate worker.ts entry point that imports from the developer's app entry.
103
+ */
104
+ export function generateWorker(buildings) {
105
+ const exports = buildings
106
+ .map((b) => `export { ${b.className} } from '../../src/hive';`)
107
+ .join('\n');
108
+ return `// Auto-generated by beehaven CLI — do not edit
109
+ import { buildWorkerFromDefinition } from '@clearly-sh/beehaven-core/server';
110
+ import appDefinition from '../../src/hive';
111
+
112
+ ${exports}
113
+
114
+ const worker = buildWorkerFromDefinition(appDefinition);
115
+
116
+ export default {
117
+ fetch: worker.fetch,
118
+ scheduled: worker.scheduled,
119
+ };
120
+ `;
121
+ }
122
+ // ── Helpers ──────────────────────────────────────────
123
+ function toScreamingSnake(str) {
124
+ return str.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase();
125
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * beehaven dev — Start local development server.
3
+ *
4
+ * Reads the app entry point (src/hive.ts), discovers Workspace classes,
5
+ * generates wrangler.toml and worker.ts, then starts wrangler dev. The
6
+ * developer writes TypeScript directly — wrangler watches src/ for changes
7
+ * natively.
8
+ */
9
+ import { existsSync, mkdirSync, writeFileSync } from 'fs';
10
+ import { join } from 'path';
11
+ import { spawn } from 'child_process';
12
+ import { discoverBuildings, generateWrangler, generateWorker } from './dev-generate.js';
13
+ import { spinner, color } from '../spinner.js';
14
+ export async function dev(cwd, opts = {}) {
15
+ const port = opts.port || 8787;
16
+ const appEntryPath = join(cwd, 'src', 'hive.ts');
17
+ if (!existsSync(appEntryPath)) {
18
+ console.error(' No src/hive.ts found. Run `beehaven init` first.');
19
+ process.exit(1);
20
+ }
21
+ const s = spinner('Starting dev server...').start();
22
+ // Discover buildings from app entry
23
+ const buildings = discoverBuildings(cwd);
24
+ s.update(`Generating config for ${buildings.length} building(s)...`);
25
+ // Generate wrangler.toml + worker.ts
26
+ const genDir = join(cwd, '.beehaven', 'generated');
27
+ if (!existsSync(genDir))
28
+ mkdirSync(genDir, { recursive: true });
29
+ writeFileSync(join(genDir, 'wrangler.toml'), generateWrangler(cwd, buildings, port));
30
+ writeFileSync(join(genDir, 'worker.ts'), generateWorker(buildings));
31
+ s.succeed(`Dev server starting on port ${color.bold(String(port))}`);
32
+ console.log(` Buildings: ${buildings.map((b) => `${b.name} (${b.className})`).join(', ')}`);
33
+ console.log(` Generated ${color.dim('.beehaven/generated/wrangler.toml')}`);
34
+ console.log(` Generated ${color.dim('.beehaven/generated/worker.ts')}`);
35
+ console.log('');
36
+ // Start wrangler dev — it watches src/ for changes natively
37
+ // Validate port to prevent shell injection
38
+ const safePort = parseInt(String(port), 10);
39
+ if (isNaN(safePort) || safePort < 1 || safePort > 65535) {
40
+ console.error(` Invalid port: ${port}. Must be 1-65535.`);
41
+ process.exit(1);
42
+ }
43
+ const wranglerArgs = [
44
+ 'wrangler',
45
+ 'dev',
46
+ '--port',
47
+ String(safePort),
48
+ '--config',
49
+ join(genDir, 'wrangler.toml'),
50
+ ];
51
+ if (opts.env)
52
+ wranglerArgs.push('--env', opts.env);
53
+ const wrangler = spawn('npx', wranglerArgs, {
54
+ cwd,
55
+ stdio: 'inherit',
56
+ shell: false,
57
+ env: { ...process.env },
58
+ });
59
+ wrangler.on('error', (err) => {
60
+ console.error(' Failed to start wrangler:', err.message);
61
+ console.error(' Make sure wrangler is installed: npm install -D wrangler');
62
+ process.exit(1);
63
+ });
64
+ // ── Tunnel ──────────────────────────────────────────
65
+ // Expose local dev server to the internet via Cloudflare Quick Tunnel.
66
+ // Free, no signup, ephemeral URL. Useful for mobile testing + webhooks.
67
+ let tunnelProc = null;
68
+ if (opts.tunnel) {
69
+ // Wait for wrangler to be ready before starting tunnel
70
+ setTimeout(() => {
71
+ console.log(`\n ${color.cyan('Tunnel')} starting...`);
72
+ tunnelProc = spawn('npx', ['cloudflared', 'tunnel', '--url', `http://localhost:${safePort}`], {
73
+ cwd,
74
+ stdio: ['ignore', 'pipe', 'pipe'],
75
+ shell: false,
76
+ env: { ...process.env },
77
+ });
78
+ // Parse the tunnel URL from cloudflared output
79
+ const parseUrl = (data) => {
80
+ const output = data.toString();
81
+ // cloudflared prints: INF | https://random-name.trycloudflare.com
82
+ const match = output.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/);
83
+ if (match) {
84
+ console.log(`\n ${color.green('Tunnel ready')}: ${color.bold(match[0])}`);
85
+ console.log(` ${color.dim('Share this URL for mobile testing or webhooks')}\n`);
86
+ }
87
+ };
88
+ tunnelProc.stdout?.on('data', parseUrl);
89
+ tunnelProc.stderr?.on('data', parseUrl);
90
+ tunnelProc.on('error', (err) => {
91
+ console.error(`\n ${color.yellow('Tunnel failed')}: ${err.message}`);
92
+ console.error(` Install cloudflared: ${color.dim('brew install cloudflared')}`);
93
+ console.error(` Or: ${color.dim('npm install -g cloudflared')}\n`);
94
+ // Don't exit — dev server still works without tunnel
95
+ });
96
+ }, 3000); // Give wrangler 3s to start
97
+ }
98
+ const cleanup = () => {
99
+ if (tunnelProc)
100
+ tunnelProc.kill('SIGTERM');
101
+ wrangler.kill('SIGTERM');
102
+ process.exit(0);
103
+ };
104
+ process.on('SIGINT', cleanup);
105
+ process.on('SIGTERM', cleanup);
106
+ wrangler.on('exit', (code) => {
107
+ process.exit(code || 0);
108
+ });
109
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * `beehaven devices` — list connected clients for the current user and
3
+ * pin/unpin one as the default target for canvas-invoke calls.
4
+ *
5
+ * beehaven devices # list
6
+ * beehaven devices pin <clientId> # persist target in ~/.beehaven/target.json
7
+ * beehaven devices unpin # clear the pin
8
+ * beehaven devices target # show what's pinned
9
+ *
10
+ * The pinned deviceId is auto-appended to `beehaven call canvas-invoke ...`
11
+ * and to the `beehaven canvas ...` subcommands.
12
+ */
13
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync } from 'node:fs';
14
+ import { homedir } from 'node:os';
15
+ import { join } from 'node:path';
16
+ import { rpcOnce } from '../ipc-client.js';
17
+ const DEVICE_TARGET_PATH = join(homedir(), '.beehaven', 'canvas-device.json');
18
+ function readPinned() {
19
+ try {
20
+ if (!existsSync(DEVICE_TARGET_PATH))
21
+ return null;
22
+ return JSON.parse(readFileSync(DEVICE_TARGET_PATH, 'utf-8'));
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ function writePinned(pin) {
29
+ mkdirSync(join(homedir(), '.beehaven'), { recursive: true });
30
+ writeFileSync(DEVICE_TARGET_PATH, JSON.stringify(pin, null, 2), 'utf-8');
31
+ }
32
+ function clearPinned() {
33
+ try {
34
+ unlinkSync(DEVICE_TARGET_PATH);
35
+ }
36
+ catch { }
37
+ }
38
+ export function getPinnedDevice() {
39
+ return readPinned();
40
+ }
41
+ function ago(ts) {
42
+ if (!ts)
43
+ return '—';
44
+ const s = Math.floor((Date.now() - ts) / 1000);
45
+ if (s < 60)
46
+ return `${s}s ago`;
47
+ if (s < 3600)
48
+ return `${Math.floor(s / 60)}m ago`;
49
+ if (s < 86400)
50
+ return `${Math.floor(s / 3600)}h ago`;
51
+ return `${Math.floor(s / 86400)}d ago`;
52
+ }
53
+ function pad(s, n) {
54
+ if (s.length >= n)
55
+ return s.slice(0, n);
56
+ return s + ' '.repeat(n - s.length);
57
+ }
58
+ export async function cmdDevices(sub, arg, values) {
59
+ if (sub === 'pin' || sub === 'target-set') {
60
+ if (!arg) {
61
+ console.error(' Usage: beehaven devices pin <clientId>');
62
+ process.exit(1);
63
+ }
64
+ // Resolve clientId to a device by listing first — nice UX, enables friendly labels.
65
+ const list = await fetchDevices();
66
+ const match = list.find((d) => d.clientId === arg || d.deviceId === arg);
67
+ if (!match) {
68
+ console.error(` No connected device matches '${arg}'. Run 'beehaven devices' to list.`);
69
+ process.exit(1);
70
+ }
71
+ writePinned({
72
+ clientId: match.clientId,
73
+ deviceId: match.deviceId,
74
+ deviceLabel: match.deviceLabel,
75
+ clientType: match.clientType,
76
+ pinnedAt: Date.now(),
77
+ });
78
+ console.log(`Pinned: ${match.deviceLabel || match.clientId} (${match.clientType || 'web'})`);
79
+ return;
80
+ }
81
+ if (sub === 'unpin' || sub === 'target-clear') {
82
+ clearPinned();
83
+ console.log('Pin cleared.');
84
+ return;
85
+ }
86
+ if (sub === 'target' || sub === 'pinned') {
87
+ const p = readPinned();
88
+ if (!p) {
89
+ console.log('No device pinned.');
90
+ return;
91
+ }
92
+ console.log(`Pinned: ${p.deviceLabel || p.clientId}`);
93
+ console.log(` clientId: ${p.clientId}`);
94
+ console.log(` clientType: ${p.clientType ?? '—'}`);
95
+ console.log(` deviceId: ${p.deviceId ?? '—'}`);
96
+ console.log(` since: ${new Date(p.pinnedAt).toLocaleString()}`);
97
+ return;
98
+ }
99
+ // Default: list
100
+ const list = await fetchDevices();
101
+ if (values.json) {
102
+ console.log(JSON.stringify({ devices: list, pinned: readPinned() }, null, 2));
103
+ return;
104
+ }
105
+ if (list.length === 0) {
106
+ console.log('No devices currently connected.');
107
+ console.log('Open the Clearly web app or the Clearly Dev Mac app on a composition to register one.');
108
+ return;
109
+ }
110
+ const pinned = readPinned();
111
+ console.log(pad('CLIENT ID', 38) +
112
+ pad('TYPE', 10) +
113
+ pad('LABEL', 32) +
114
+ pad('APP', 10) +
115
+ pad('LAST ACTIVE', 12));
116
+ console.log('─'.repeat(102));
117
+ for (const d of list) {
118
+ const isPinned = pinned && pinned.clientId === d.clientId;
119
+ const marker = isPinned ? '★ ' : ' ';
120
+ const id = d.clientId.length > 32 ? d.clientId.slice(0, 29) + '...' : d.clientId;
121
+ console.log(marker +
122
+ pad(id, 36) +
123
+ pad(d.clientType ?? '—', 10) +
124
+ pad((d.deviceLabel ?? '—').slice(0, 30), 32) +
125
+ pad(d.appContext ?? '—', 10) +
126
+ pad(ago(d.lastActivityAt || d.connectedAt), 12));
127
+ }
128
+ if (!pinned) {
129
+ console.log('\nPin one as the canvas-invoke default: beehaven devices pin <clientId>');
130
+ }
131
+ }
132
+ async function fetchDevices() {
133
+ const out = (await rpcOnce('list-devices', {}, 15_000));
134
+ return out?.devices ?? [];
135
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Thin routing helper extracted from workspace.ts's `resolveBuilding`
3
+ * so `explain.ts` doesn't import the larger command file.
4
+ */
5
+ export function resolveBuildingForExplain(action, input, building) {
6
+ if (!building || building === 'home')
7
+ return { action, input };
8
+ const colonIdx = building.indexOf(':');
9
+ if (colonIdx === -1)
10
+ return { action, input };
11
+ const buildingType = building.slice(0, colonIdx);
12
+ const buildingId = building.slice(colonIdx + 1);
13
+ // Same type alias as workspace.ts:resolveBuilding — keeps routing consistent.
14
+ const typeMap = {
15
+ store: 'store',
16
+ shopifyStore: 'store',
17
+ team: 'team',
18
+ };
19
+ const resolvedType = typeMap[buildingType] ?? buildingType;
20
+ return {
21
+ action: `admin:${resolvedType}-rpc`,
22
+ input: { storeId: buildingId, action, input },
23
+ };
24
+ }