@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,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
|
+
}
|