@beeos-ai/cli 0.1.2 → 1.0.2

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 (4) hide show
  1. package/README.md +56 -26
  2. package/bin/beeos.js +3 -159
  3. package/dist/index.js +2124 -0
  4. package/package.json +39 -24
package/dist/index.js ADDED
@@ -0,0 +1,2124 @@
1
+ // src/index.ts
2
+ import { Command } from "commander";
3
+
4
+ // ../core/dist/platform-adapter.js
5
+ var _adapter = null;
6
+ function setPlatformAdapter(adapter) {
7
+ _adapter = adapter;
8
+ }
9
+ function getPlatformAdapter() {
10
+ if (!_adapter) {
11
+ throw new Error("PlatformAdapter not initialized. Call setPlatformAdapter() before using @beeos/core.");
12
+ }
13
+ return _adapter;
14
+ }
15
+
16
+ // ../core/dist/progress.js
17
+ var noopReporter = {
18
+ onStatus() {
19
+ },
20
+ onComplete() {
21
+ }
22
+ };
23
+
24
+ // ../core/dist/config.js
25
+ import * as TOML from "smol-toml";
26
+ var DEFAULT_API_URL = "https://api.beeos.ai";
27
+ var DEFAULT_AGENT_GATEWAY_URL = "https://agent-gw.beeos.ai";
28
+ var DEFAULT_DASHBOARD_URL = "https://beeos.ai";
29
+ var DEFAULT_BRIDGE_URL = "wss://bridge-sg.beeos.ai";
30
+ function defaultPlatformConfig() {
31
+ return {
32
+ api_url: DEFAULT_API_URL,
33
+ agent_gateway_url: DEFAULT_AGENT_GATEWAY_URL,
34
+ dashboard_base_url: DEFAULT_DASHBOARD_URL,
35
+ bridge_url: DEFAULT_BRIDGE_URL
36
+ };
37
+ }
38
+ function defaultDeviceConfig() {
39
+ return {
40
+ http_enabled: true,
41
+ http_port: 9090
42
+ };
43
+ }
44
+ function defaultConfig() {
45
+ return {
46
+ platform: defaultPlatformConfig(),
47
+ device: defaultDeviceConfig()
48
+ };
49
+ }
50
+ function beeoHome() {
51
+ const p = getPlatformAdapter();
52
+ return p.joinPath(p.homeDir(), ".beeos");
53
+ }
54
+ async function ensureDirs() {
55
+ const p = getPlatformAdapter();
56
+ const root = beeoHome();
57
+ for (const sub of ["", "identity", "agents"]) {
58
+ await p.mkdir(p.joinPath(root, sub));
59
+ }
60
+ return root;
61
+ }
62
+ function configPath() {
63
+ const p = getPlatformAdapter();
64
+ return p.joinPath(beeoHome(), "config.toml");
65
+ }
66
+ function bindingPath() {
67
+ const p = getPlatformAdapter();
68
+ return p.joinPath(beeoHome(), "binding.json");
69
+ }
70
+ function agentDir(agentType) {
71
+ const p = getPlatformAdapter();
72
+ return p.joinPath(beeoHome(), "agents", agentType);
73
+ }
74
+ function agentHome(agentType) {
75
+ const p = getPlatformAdapter();
76
+ return p.joinPath(agentDir(agentType), "home");
77
+ }
78
+ function pidFile(agentType) {
79
+ const p = getPlatformAdapter();
80
+ return p.joinPath(beeoHome(), `${agentType}.pid`);
81
+ }
82
+ async function loadOrCreateConfig() {
83
+ const p = getPlatformAdapter();
84
+ const path2 = configPath();
85
+ if (await p.exists(path2)) {
86
+ const raw = await p.readFile(path2);
87
+ const parsed = TOML.parse(raw);
88
+ const cfg2 = mergeWithDefaults(parsed);
89
+ return cfg2;
90
+ }
91
+ const cfg = defaultConfig();
92
+ await saveConfig(cfg);
93
+ return cfg;
94
+ }
95
+ async function saveConfig(cfg) {
96
+ const p = getPlatformAdapter();
97
+ const path2 = configPath();
98
+ await p.mkdir(p.dirname(path2));
99
+ const serializable = {
100
+ platform: {
101
+ api_url: cfg.platform.api_url,
102
+ agent_gateway_url: cfg.platform.agent_gateway_url,
103
+ dashboard_base_url: cfg.platform.dashboard_base_url,
104
+ bridge_url: cfg.platform.bridge_url
105
+ },
106
+ device: {
107
+ http_enabled: cfg.device.http_enabled,
108
+ http_port: cfg.device.http_port
109
+ }
110
+ };
111
+ const raw = TOML.stringify(serializable);
112
+ await p.writeFile(path2, raw);
113
+ }
114
+ function mergeWithDefaults(parsed) {
115
+ const platform = parsed.platform ?? {};
116
+ const device = parsed.device ?? {};
117
+ return {
118
+ platform: {
119
+ api_url: platform.api_url || DEFAULT_API_URL,
120
+ agent_gateway_url: platform.agent_gateway_url || DEFAULT_AGENT_GATEWAY_URL,
121
+ dashboard_base_url: platform.dashboard_base_url || DEFAULT_DASHBOARD_URL,
122
+ bridge_url: platform.bridge_url || DEFAULT_BRIDGE_URL,
123
+ canvas_relay_url: platform.canvas_relay_url || void 0
124
+ },
125
+ device: {
126
+ mqtt_host: device.mqtt_host || void 0,
127
+ mqtt_port: device.mqtt_port || void 0,
128
+ mqtt_use_tls: device.mqtt_use_tls || void 0,
129
+ http_enabled: device.http_enabled !== void 0 ? device.http_enabled : true,
130
+ http_port: device.http_port || 9090
131
+ }
132
+ };
133
+ }
134
+ async function loadBindingInfo() {
135
+ const p = getPlatformAdapter();
136
+ const path2 = bindingPath();
137
+ if (!await p.exists(path2)) {
138
+ return migratePairedToBinding();
139
+ }
140
+ const raw = await p.readFile(path2);
141
+ return JSON.parse(raw);
142
+ }
143
+ async function saveBindingInfo(info) {
144
+ const p = getPlatformAdapter();
145
+ const path2 = bindingPath();
146
+ await p.writeFile(path2, JSON.stringify(info, null, 2));
147
+ }
148
+ async function removeBindingInfo() {
149
+ const p = getPlatformAdapter();
150
+ const path2 = bindingPath();
151
+ if (await p.exists(path2)) {
152
+ await p.rm(path2);
153
+ }
154
+ }
155
+ async function migratePairedToBinding() {
156
+ const p = getPlatformAdapter();
157
+ const legacy = p.joinPath(beeoHome(), "paired.json");
158
+ if (!await p.exists(legacy))
159
+ return null;
160
+ try {
161
+ const raw = await p.readFile(legacy);
162
+ const old = JSON.parse(raw);
163
+ const info = {
164
+ fingerprint: old.fingerprint,
165
+ instance_id: old.instance_id,
166
+ bound_at: old.paired_at
167
+ };
168
+ await saveBindingInfo(info);
169
+ await p.rm(legacy);
170
+ return info;
171
+ } catch {
172
+ return null;
173
+ }
174
+ }
175
+ async function loadOrCreateGatewayToken() {
176
+ const p = getPlatformAdapter();
177
+ const path2 = p.joinPath(beeoHome(), "gateway_token");
178
+ if (await p.exists(path2)) {
179
+ const token2 = (await p.readFile(path2)).trim();
180
+ if (token2)
181
+ return token2;
182
+ }
183
+ const token = crypto.randomUUID();
184
+ await p.mkdir(p.dirname(path2));
185
+ await p.writeFile(path2, token);
186
+ if (p.platform() !== "win32") {
187
+ await p.chmod(path2, 384);
188
+ }
189
+ return token;
190
+ }
191
+ async function readPid(agentType) {
192
+ const p = getPlatformAdapter();
193
+ const path2 = pidFile(agentType);
194
+ if (!await p.exists(path2))
195
+ return null;
196
+ const raw = (await p.readFile(path2)).trim();
197
+ const pid = parseInt(raw, 10);
198
+ return isNaN(pid) ? null : pid;
199
+ }
200
+ async function writePid(agentType, pid) {
201
+ const p = getPlatformAdapter();
202
+ await p.writeFile(pidFile(agentType), String(pid));
203
+ }
204
+ async function removePid(agentType) {
205
+ const p = getPlatformAdapter();
206
+ const path2 = pidFile(agentType);
207
+ if (await p.exists(path2)) {
208
+ await p.rm(path2);
209
+ }
210
+ }
211
+
212
+ // ../core/dist/identity/keypair.js
213
+ import { createHash } from "crypto";
214
+ import * as ed from "@noble/ed25519";
215
+ import { sha512 } from "@noble/hashes/sha512";
216
+ ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));
217
+ function publicKeyB64(id) {
218
+ return Buffer.from(id.publicKey).toString("base64");
219
+ }
220
+ function fingerprint(id) {
221
+ return createHash("sha256").update(id.publicKey).digest("hex");
222
+ }
223
+ function fingerprintFromB64(pubkeyB64) {
224
+ const raw = Buffer.from(pubkeyB64, "base64");
225
+ return createHash("sha256").update(raw).digest("hex");
226
+ }
227
+ async function loadOrCreateIdentity() {
228
+ const p = getPlatformAdapter();
229
+ const path2 = keypairPath();
230
+ if (await p.exists(path2)) {
231
+ return loadFromFile(path2);
232
+ }
233
+ const id = generate();
234
+ await save(id);
235
+ return id;
236
+ }
237
+ async function ensureDeviceKey(keyPath) {
238
+ const p = getPlatformAdapter();
239
+ if (await p.exists(keyPath))
240
+ return;
241
+ const id = generate();
242
+ const stored = {
243
+ publicKey: Buffer.from(id.publicKey).toString("base64"),
244
+ privateKey: Buffer.from(id.privateKey).toString("base64")
245
+ };
246
+ await p.mkdir(p.dirname(keyPath));
247
+ await p.writeFile(keyPath, JSON.stringify(stored, null, 2));
248
+ if (p.platform() !== "win32") {
249
+ await p.chmod(keyPath, 384);
250
+ }
251
+ }
252
+ async function readPubkeyFromKeyFile(keyPath) {
253
+ const p = getPlatformAdapter();
254
+ const raw = await p.readFile(keyPath);
255
+ const parsed = JSON.parse(raw);
256
+ if (!parsed.publicKey)
257
+ throw new Error("publicKey not found in key file");
258
+ return parsed.publicKey;
259
+ }
260
+ function keypairPath() {
261
+ const p = getPlatformAdapter();
262
+ return p.joinPath(beeoHome(), "identity", "keypair.json");
263
+ }
264
+ function fingerprintFilePath() {
265
+ const p = getPlatformAdapter();
266
+ return p.joinPath(beeoHome(), "identity", "fingerprint");
267
+ }
268
+ async function loadFromFile(path2) {
269
+ const p = getPlatformAdapter();
270
+ const raw = await p.readFile(path2);
271
+ const stored = JSON.parse(raw);
272
+ const privateKey = new Uint8Array(Buffer.from(stored.privateKey, "base64"));
273
+ if (privateKey.length !== 32) {
274
+ throw new Error(`private key seed must be 32 bytes, got ${privateKey.length}`);
275
+ }
276
+ const publicKey = ed.getPublicKey(privateKey);
277
+ return { privateKey, publicKey };
278
+ }
279
+ function generate() {
280
+ const privateKey = ed.utils.randomPrivateKey();
281
+ const publicKey = ed.getPublicKey(privateKey);
282
+ return { privateKey, publicKey };
283
+ }
284
+ async function save(id) {
285
+ const p = getPlatformAdapter();
286
+ const stored = {
287
+ publicKey: Buffer.from(id.publicKey).toString("base64"),
288
+ privateKey: Buffer.from(id.privateKey).toString("base64")
289
+ };
290
+ const kpPath = keypairPath();
291
+ await p.mkdir(p.dirname(kpPath));
292
+ await p.writeFile(kpPath, JSON.stringify(stored, null, 2));
293
+ if (p.platform() !== "win32") {
294
+ await p.chmod(kpPath, 384);
295
+ }
296
+ const fpPath = fingerprintFilePath();
297
+ await p.writeFile(fpPath, fingerprint(id));
298
+ }
299
+
300
+ // ../core/dist/identity/qr.js
301
+ import QRCode from "qrcode";
302
+ async function qrModules(url) {
303
+ const segments = QRCode.create(url, { errorCorrectionLevel: "M" });
304
+ const size = segments.modules.size;
305
+ const modules = [];
306
+ for (let y = 0; y < size; y++) {
307
+ for (let x = 0; x < size; x++) {
308
+ modules.push(segments.modules.get(x, y) !== 0);
309
+ }
310
+ }
311
+ return { width: size, modules };
312
+ }
313
+ async function qrToTerminal(url) {
314
+ const { width, modules } = await qrModules(url);
315
+ const rows = [];
316
+ for (let i = 0; i < modules.length; i += width) {
317
+ rows.push(modules.slice(i, i + width));
318
+ }
319
+ const quiet = 2;
320
+ const fullWidth = width + quiet * 2;
321
+ const blankLine = " ".repeat(fullWidth);
322
+ const lines = [];
323
+ for (let i = 0; i < quiet; i++)
324
+ lines.push(blankLine);
325
+ let y = 0;
326
+ while (y < rows.length) {
327
+ const topRow = rows[y];
328
+ const botRow = y + 1 < rows.length ? rows[y + 1] : null;
329
+ let line = " ".repeat(quiet);
330
+ for (let x = 0; x < width; x++) {
331
+ const top = topRow[x];
332
+ const bot = botRow ? botRow[x] : false;
333
+ if (top && bot)
334
+ line += "\u2588";
335
+ else if (top && !bot)
336
+ line += "\u2580";
337
+ else if (!top && bot)
338
+ line += "\u2584";
339
+ else
340
+ line += " ";
341
+ }
342
+ line += " ".repeat(quiet);
343
+ lines.push(line);
344
+ y += 2;
345
+ }
346
+ for (let i = 0; i < quiet; i++)
347
+ lines.push(blankLine);
348
+ return lines.join("\n");
349
+ }
350
+
351
+ // ../core/dist/platform/client.js
352
+ function buildBindUrl(dashboardBaseUrl, bindId) {
353
+ const base = dashboardBaseUrl.replace(/\/+$/, "");
354
+ return `${base}/bind/${bindId}`;
355
+ }
356
+ async function agentBind(apiUrl, publicKey, fingerprint2, agentType, hostname) {
357
+ const p = getPlatformAdapter();
358
+ const url = `${apiUrl}/api/v1/agent/bind`;
359
+ const resp = await p.fetch(url, {
360
+ method: "POST",
361
+ headers: { "Content-Type": "application/json" },
362
+ body: JSON.stringify({
363
+ public_key: publicKey,
364
+ fingerprint: fingerprint2,
365
+ agent_type: agentType,
366
+ hostname
367
+ })
368
+ });
369
+ if (!resp.ok) {
370
+ const text = await resp.text().catch(() => "");
371
+ throw new Error(`agent bind failed (${resp.status}): ${text}`);
372
+ }
373
+ return await resp.json();
374
+ }
375
+ async function pollBind(apiUrl, bindId) {
376
+ const p = getPlatformAdapter();
377
+ const url = `${apiUrl}/api/v1/agent/bind/${bindId}`;
378
+ const resp = await p.fetch(url, {
379
+ signal: AbortSignal.timeout(35e3)
380
+ });
381
+ if (!resp.ok) {
382
+ throw new Error(`bind poll failed: ${resp.status}`);
383
+ }
384
+ return await resp.json();
385
+ }
386
+ async function markInstanceOffline(apiUrl, instanceId) {
387
+ const p = getPlatformAdapter();
388
+ const url = `${apiUrl}/api/v1/instances/${instanceId}/offline`;
389
+ const resp = await p.fetch(url, {
390
+ method: "POST",
391
+ signal: AbortSignal.timeout(5e3)
392
+ });
393
+ if (!resp.ok) {
394
+ throw new Error(`mark offline failed: ${resp.status}`);
395
+ }
396
+ }
397
+ async function getBindStatus(apiUrl, bindId) {
398
+ const p = getPlatformAdapter();
399
+ const url = `${apiUrl}/api/v1/agent/bind/${bindId}`;
400
+ const resp = await p.fetch(url, {
401
+ signal: AbortSignal.timeout(5e3)
402
+ });
403
+ if (!resp.ok) {
404
+ throw new Error(`bind status check failed: ${resp.status}`);
405
+ }
406
+ return await resp.json();
407
+ }
408
+
409
+ // ../core/dist/platform/npm.js
410
+ async function fetchNpmPackageInfo(packageName) {
411
+ const p = getPlatformAdapter();
412
+ const encoded = packageName.replace(/\//g, "%2f");
413
+ const url = `https://registry.npmjs.org/${encoded}`;
414
+ const resp = await p.fetch(url, {
415
+ headers: { Accept: "application/json" }
416
+ });
417
+ if (!resp.ok) {
418
+ throw new Error(`npm registry returned ${resp.status}`);
419
+ }
420
+ return await resp.json();
421
+ }
422
+ async function downloadTarball(url) {
423
+ const p = getPlatformAdapter();
424
+ const resp = await p.fetch(url);
425
+ if (!resp.ok) {
426
+ throw new Error(`tarball download failed: ${resp.status}`);
427
+ }
428
+ const buf = await resp.arrayBuffer();
429
+ return new Uint8Array(buf);
430
+ }
431
+
432
+ // ../core/dist/process.js
433
+ function killProcess(pid) {
434
+ const p = getPlatformAdapter();
435
+ if (p.platform() === "win32") {
436
+ p.exec("taskkill", ["/PID", String(pid), "/F"]).catch(() => {
437
+ });
438
+ } else {
439
+ p.kill(pid, "SIGTERM");
440
+ }
441
+ }
442
+ function isProcessAlive(pid) {
443
+ return getPlatformAdapter().isProcessAlive(pid);
444
+ }
445
+ function shouldOpenBrowser() {
446
+ const p = getPlatformAdapter();
447
+ if (p.env("SSH_CLIENT") || p.env("SSH_TTY"))
448
+ return false;
449
+ const plat = p.platform();
450
+ if (plat === "darwin" || plat === "win32")
451
+ return true;
452
+ return !!(p.env("DISPLAY") || p.env("WAYLAND_DISPLAY"));
453
+ }
454
+
455
+ // ../core/dist/bind.js
456
+ async function presentBindUrl(bindUrl, forceHeadless, log = console.log) {
457
+ const p = getPlatformAdapter();
458
+ const canBrowser = !forceHeadless && shouldOpenBrowser();
459
+ if (canBrowser) {
460
+ log(" Opening browser to connect to BeeOS...");
461
+ log(` ${bindUrl}`);
462
+ try {
463
+ await p.openUrl(bindUrl);
464
+ } catch (e) {
465
+ log(` (Could not open browser: ${e})`);
466
+ log("");
467
+ log(" Open this URL manually, or scan with BeeOS app:");
468
+ await printQr(bindUrl, log);
469
+ }
470
+ } else {
471
+ log(" -- Connect to BeeOS ---------------------------------");
472
+ log("");
473
+ log(" Open this URL in your browser, or scan with BeeOS app:");
474
+ log(` ${bindUrl}`);
475
+ log("");
476
+ await printQr(bindUrl, log);
477
+ log(" -----------------------------------------------------");
478
+ }
479
+ }
480
+ async function pollUntilBound(apiUrl, bindId, timeoutMs) {
481
+ const deadline = Date.now() + timeoutMs;
482
+ while (true) {
483
+ if (Date.now() > deadline) {
484
+ throw new Error("Bind approval timed out \u2014 please re-run the command");
485
+ }
486
+ try {
487
+ const resp = await pollBind(apiUrl, bindId);
488
+ if (resp.status === "bound") {
489
+ return resp.instance_id ?? "";
490
+ }
491
+ if (resp.status === "expired") {
492
+ throw new Error("Bind session expired \u2014 please re-run the command");
493
+ }
494
+ } catch (e) {
495
+ const msg = e instanceof Error ? e.message : String(e);
496
+ if (msg.includes("expired") || msg.includes("timed out"))
497
+ throw e;
498
+ await sleep(2e3);
499
+ }
500
+ }
501
+ }
502
+ async function printQr(url, log) {
503
+ try {
504
+ const rendered = await qrToTerminal(url);
505
+ log(rendered);
506
+ } catch (e) {
507
+ log(` (QR generation failed: ${e})`);
508
+ }
509
+ }
510
+ function sleep(ms) {
511
+ return new Promise((resolve) => setTimeout(resolve, ms));
512
+ }
513
+
514
+ // ../core/dist/device-setup.js
515
+ var MIN_PYTHON_VERSION = [3, 11];
516
+ var RECOMMENDED_AGENT_VERSION = "0.2.7";
517
+ function agentBinCommandAndArgs(bin) {
518
+ if (bin.type === "executable") {
519
+ return { cmd: bin.path, args: [] };
520
+ }
521
+ return { cmd: pythonCmdName(), args: ["-m", "device_agent"] };
522
+ }
523
+ async function ensureDeviceAgent(progress) {
524
+ const existing = await findExisting();
525
+ if (existing) {
526
+ if (existing.type === "executable") {
527
+ progress.onStatus(`device-agent found: ${existing.path}`);
528
+ await checkAgentVersion(existing.path, progress);
529
+ } else {
530
+ progress.onStatus(`device-agent found: ${pythonCmdName()} -m device_agent`);
531
+ }
532
+ return existing;
533
+ }
534
+ return install(progress);
535
+ }
536
+ async function upgradeDeviceAgent(progress) {
537
+ const p = getPlatformAdapter();
538
+ const venv = venvDir();
539
+ if (!await p.exists(venv)) {
540
+ throw new Error(`No managed device-agent venv at ${venv}. Run \`beeos device attach\` first.`);
541
+ }
542
+ const source = await installSource();
543
+ if (await hasUv()) {
544
+ progress.onStatus(`Upgrading ${source.displayName} with uv...`);
545
+ const venvPy = p.joinPath(venv, venvBinName(), pythonCmdName());
546
+ await runCmd("uv", [
547
+ "pip",
548
+ "install",
549
+ "--upgrade",
550
+ "--python",
551
+ venvPy,
552
+ ...source.specs
553
+ ]);
554
+ } else {
555
+ progress.onStatus(`Upgrading ${source.displayName} with pip...`);
556
+ const pip = p.joinPath(venv, venvBinName(), "pip");
557
+ await runCmd(pip, ["install", "--upgrade", ...source.specs]);
558
+ }
559
+ progress.onComplete("device-agent upgraded");
560
+ }
561
+ async function findExisting() {
562
+ const p = getPlatformAdapter();
563
+ try {
564
+ const result = await p.exec("which", ["device-agent"]);
565
+ if (result.code === 0 && result.stdout.trim()) {
566
+ return { type: "executable", path: result.stdout.trim() };
567
+ }
568
+ } catch {
569
+ }
570
+ if (p.platform() === "win32") {
571
+ try {
572
+ const result = await p.exec("where", ["device-agent"]);
573
+ if (result.code === 0 && result.stdout.trim()) {
574
+ return { type: "executable", path: result.stdout.trim().split("\n")[0].trim() };
575
+ }
576
+ } catch {
577
+ }
578
+ }
579
+ const managed = managedBinary();
580
+ if (await p.exists(managed)) {
581
+ return { type: "executable", path: managed };
582
+ }
583
+ try {
584
+ const result = await p.exec(pythonCmdName(), ["-c", "import device_agent; print('ok')"]);
585
+ if (result.code === 0) {
586
+ return { type: "pythonModule" };
587
+ }
588
+ } catch {
589
+ }
590
+ return null;
591
+ }
592
+ async function checkAgentVersion(binPath, progress) {
593
+ const p = getPlatformAdapter();
594
+ try {
595
+ const result = await p.exec(binPath, ["--version"]);
596
+ if (result.code !== 0)
597
+ return;
598
+ const ver = result.stdout.trim().split(" ").pop() ?? "";
599
+ if (!ver)
600
+ return;
601
+ if (versionLessThan(ver, RECOMMENDED_AGENT_VERSION)) {
602
+ progress.onStatus(` \x1B[33mdevice-agent v${ver} installed, v${RECOMMENDED_AGENT_VERSION} available. Run \`beeos device upgrade\` to update.\x1B[0m`);
603
+ }
604
+ } catch {
605
+ }
606
+ }
607
+ function versionLessThan(a, b) {
608
+ const parse2 = (s) => s.split(".").map((p) => parseInt(p, 10) || 0);
609
+ const pa = parse2(a);
610
+ const pb = parse2(b);
611
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
612
+ const va = pa[i] ?? 0;
613
+ const vb = pb[i] ?? 0;
614
+ if (va < vb)
615
+ return true;
616
+ if (va > vb)
617
+ return false;
618
+ }
619
+ return false;
620
+ }
621
+ async function install(progress) {
622
+ const p = getPlatformAdapter();
623
+ const venv = venvDir();
624
+ const source = await installSource();
625
+ if (await hasUv()) {
626
+ await installWithUv(progress, venv, source);
627
+ } else {
628
+ const python = await findPython();
629
+ if (python) {
630
+ await installWithPython(progress, venv, source, python);
631
+ } else {
632
+ throw new Error(`Cannot install device-agent: no Python >= ${MIN_PYTHON_VERSION[0]}.${MIN_PYTHON_VERSION[1]} found and \`uv\` is not installed.
633
+
634
+ Easiest fix \u2014 install uv (manages Python automatically):
635
+ ${uvInstallHint()}
636
+
637
+ Or install Python manually:
638
+ ${pythonInstallHint()}`);
639
+ }
640
+ }
641
+ const bin = managedBinary();
642
+ if (!await p.exists(bin)) {
643
+ throw new Error(`device-agent binary not found at ${bin} after installation`);
644
+ }
645
+ progress.onComplete("device-agent installed");
646
+ return { type: "executable", path: bin };
647
+ }
648
+ async function installWithUv(progress, venv, source) {
649
+ const pyVersion = `${MIN_PYTHON_VERSION[0]}.${MIN_PYTHON_VERSION[1]}`;
650
+ const p = getPlatformAdapter();
651
+ progress.onStatus("Creating venv with uv (will download Python if needed)...");
652
+ await runCmd("uv", ["venv", "--python", pyVersion, venv]);
653
+ progress.onStatus(`Installing ${source.displayName}...`);
654
+ const venvPy = p.joinPath(venv, venvBinName(), pythonCmdName());
655
+ await runCmd("uv", ["pip", "install", "--python", venvPy, ...source.specs]);
656
+ }
657
+ async function installWithPython(progress, venv, source, python) {
658
+ const p = getPlatformAdapter();
659
+ progress.onStatus("Creating Python virtual environment...");
660
+ await runCmd(python, ["-m", "venv", venv]);
661
+ progress.onStatus(`Installing ${source.displayName}...`);
662
+ const pip = p.joinPath(venv, venvBinName(), "pip");
663
+ await runCmd(pip, ["install", ...source.specs]);
664
+ }
665
+ async function installSource() {
666
+ const p = getPlatformAdapter();
667
+ try {
668
+ const cwd = p.env("PWD") || p.homeDir();
669
+ let dir = cwd;
670
+ for (let i = 0; i < 10; i++) {
671
+ const da = p.joinPath(dir, "agents", "device-agent", "pyproject.toml");
672
+ const bc = p.joinPath(dir, "sdks", "bridge-client-python", "pyproject.toml");
673
+ if (await p.exists(da) && await p.exists(bc)) {
674
+ return {
675
+ displayName: "beeos-device-agent (dev mode from repo)",
676
+ specs: [
677
+ "-e",
678
+ p.joinPath(dir, "sdks", "bridge-client-python"),
679
+ "-e",
680
+ p.joinPath(dir, "agents", "device-agent")
681
+ ]
682
+ };
683
+ }
684
+ const parent = p.dirname(dir);
685
+ if (parent === dir)
686
+ break;
687
+ dir = parent;
688
+ }
689
+ } catch {
690
+ }
691
+ return {
692
+ displayName: "beeos-device-agent",
693
+ specs: ["beeos-device-agent"]
694
+ };
695
+ }
696
+ async function hasUv() {
697
+ const p = getPlatformAdapter();
698
+ try {
699
+ const cmd = p.platform() === "win32" ? "where" : "which";
700
+ const result = await p.exec(cmd, ["uv"]);
701
+ return result.code === 0;
702
+ } catch {
703
+ return false;
704
+ }
705
+ }
706
+ async function findPython() {
707
+ const p = getPlatformAdapter();
708
+ const candidates = p.platform() === "win32" ? ["python3.13", "python3.12", "python3.11", "python3", "python"] : ["python3.13", "python3.12", "python3.11", "python3"];
709
+ for (const name of candidates) {
710
+ try {
711
+ const cmd = p.platform() === "win32" ? "where" : "which";
712
+ const whichResult = await p.exec(cmd, [name]);
713
+ if (whichResult.code !== 0)
714
+ continue;
715
+ const bin = whichResult.stdout.trim().split("\n")[0].trim();
716
+ const verResult = await p.exec(bin, ["--version"]);
717
+ const text = (verResult.stdout || verResult.stderr).trim();
718
+ const versionStr = text.replace(/^Python\s+/i, "");
719
+ const [major, minor] = versionStr.split(".").map((v) => parseInt(v, 10) || 0);
720
+ if (major > MIN_PYTHON_VERSION[0] || major === MIN_PYTHON_VERSION[0] && minor >= MIN_PYTHON_VERSION[1]) {
721
+ return bin;
722
+ }
723
+ } catch {
724
+ }
725
+ }
726
+ return null;
727
+ }
728
+ function pythonCmdName() {
729
+ return getPlatformAdapter().platform() === "win32" ? "python" : "python3";
730
+ }
731
+ function venvDir() {
732
+ const p = getPlatformAdapter();
733
+ return p.joinPath(beeoHome(), "device-agent-env");
734
+ }
735
+ function venvBinName() {
736
+ return getPlatformAdapter().platform() === "win32" ? "Scripts" : "bin";
737
+ }
738
+ function managedBinary() {
739
+ const p = getPlatformAdapter();
740
+ const name = p.platform() === "win32" ? "device-agent.exe" : "device-agent";
741
+ return p.joinPath(venvDir(), venvBinName(), name);
742
+ }
743
+ function uvInstallHint() {
744
+ const p = getPlatformAdapter();
745
+ if (p.platform() === "win32") {
746
+ return ' powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"';
747
+ }
748
+ return " curl -LsSf https://astral.sh/uv/install.sh | sh";
749
+ }
750
+ function pythonInstallHint() {
751
+ const p = getPlatformAdapter();
752
+ if (p.platform() === "win32") {
753
+ return " winget install Python.Python.3.13\n Or download from https://www.python.org/downloads/";
754
+ }
755
+ if (p.platform() === "darwin") {
756
+ return " brew install python@3.13\n Or download from https://www.python.org/downloads/";
757
+ }
758
+ return " sudo apt install python3 (Debian/Ubuntu)\n sudo dnf install python3 (Fedora)\n Or download from https://www.python.org/downloads/";
759
+ }
760
+ async function runCmd(cmd, args) {
761
+ const p = getPlatformAdapter();
762
+ const result = await p.exec(cmd, args);
763
+ if (result.code !== 0) {
764
+ const stderr = result.stderr.slice(0, 2e3);
765
+ throw new Error(`${cmd} ${args.join(" ")} failed (exit ${result.code}):
766
+ ${stderr}`);
767
+ }
768
+ }
769
+
770
+ // ../core/dist/runtime/device.js
771
+ var deviceRuntime = {
772
+ agentType() {
773
+ return "device";
774
+ },
775
+ displayName() {
776
+ return "Device Agent";
777
+ },
778
+ async detect() {
779
+ try {
780
+ const bin = await ensureDeviceAgent(noopReporter);
781
+ const { cmd } = agentBinCommandAndArgs(bin);
782
+ return { binary: cmd, version: "", home: beeoHome() };
783
+ } catch {
784
+ return null;
785
+ }
786
+ },
787
+ async ensure(_version, progress) {
788
+ const bin = await ensureDeviceAgent(progress);
789
+ const { cmd } = agentBinCommandAndArgs(bin);
790
+ return { binary: cmd, version: "", home: beeoHome() };
791
+ },
792
+ async launch(_ctx) {
793
+ throw new Error("DeviceRuntime.launch() should not be called directly \u2014 use spawnForDevice() instead");
794
+ },
795
+ async isHealthy() {
796
+ return false;
797
+ },
798
+ async stop() {
799
+ },
800
+ async update(progress) {
801
+ await upgradeDeviceAgent(progress);
802
+ },
803
+ // ── Device-specific extensions ──────────────────────────
804
+ async ensureAgent(progress) {
805
+ return ensureDeviceAgent(progress);
806
+ },
807
+ async upgradeAgent(progress) {
808
+ return upgradeDeviceAgent(progress);
809
+ },
810
+ async listAdbDevices() {
811
+ const p = getPlatformAdapter();
812
+ const result = await p.exec("adb", ["devices"]);
813
+ if (result.code !== 0) {
814
+ throw new Error("adb not found \u2014 install Android platform-tools");
815
+ }
816
+ const devices = [];
817
+ const lines = result.stdout.split("\n").slice(1);
818
+ for (const line of lines) {
819
+ const parts = line.trim().split(/\s+/);
820
+ if (parts.length >= 2 && parts[1] === "device") {
821
+ devices.push({ serial: parts[0] });
822
+ }
823
+ }
824
+ return devices;
825
+ },
826
+ async detectSingleDevice() {
827
+ const devices = await this.listAdbDevices();
828
+ if (devices.length === 0) {
829
+ throw new Error("No ADB devices found. Connect a device and try again.");
830
+ }
831
+ if (devices.length > 1) {
832
+ const serials = devices.map((d) => d.serial).join(", ");
833
+ throw new Error(`Multiple ADB devices found: ${serials}. Use --serial to specify one.`);
834
+ }
835
+ return devices[0].serial;
836
+ },
837
+ async spawnForDevice(serial, agentBin, bridgeUrl, httpEnabled, httpPort) {
838
+ const p = getPlatformAdapter();
839
+ const keyFile = this.deviceKeyPath(serial);
840
+ await ensureDeviceKey(keyFile);
841
+ if (!bridgeUrl) {
842
+ throw new Error("bridge_url is not configured in ~/.beeos/config.toml.\nSet [platform] bridge_url (e.g. wss://bridge.beeos.ai).");
843
+ }
844
+ const { cmd, args: baseArgs } = agentBinCommandAndArgs(agentBin);
845
+ const args = [
846
+ ...baseArgs,
847
+ "--key",
848
+ keyFile,
849
+ "--adb",
850
+ serial,
851
+ "--bridge",
852
+ bridgeUrl
853
+ ];
854
+ if (httpEnabled) {
855
+ args.push("--http", "--http-port", String(httpPort));
856
+ }
857
+ const logDir = p.joinPath(beeoHome(), "logs");
858
+ await p.mkdir(logDir);
859
+ const logFile = p.joinPath(logDir, `device-${serial}.log`);
860
+ const result = await p.spawn(cmd, args, {
861
+ detached: true,
862
+ stdoutFile: logFile,
863
+ stderrFile: logFile
864
+ });
865
+ return result.pid;
866
+ },
867
+ deviceKeyPath(serial) {
868
+ const p = getPlatformAdapter();
869
+ return p.joinPath(beeoHome(), "identity", `device-${serial}.key.json`);
870
+ },
871
+ async ensureKeyAndGetPubkey(serial) {
872
+ const keyFile = this.deviceKeyPath(serial);
873
+ await ensureDeviceKey(keyFile);
874
+ return readPubkeyFromKeyFile(keyFile);
875
+ }
876
+ };
877
+
878
+ // ../core/dist/agent/detector.js
879
+ var GATEWAY_PORT = 18789;
880
+ async function isGatewayRunning() {
881
+ return getPlatformAdapter().tcpProbe("127.0.0.1", GATEWAY_PORT, 500);
882
+ }
883
+ async function managedBinary2(agentType) {
884
+ const p = getPlatformAdapter();
885
+ const base = agentDir(agentType);
886
+ const currentLink = p.joinPath(base, "versions", "current");
887
+ if (!await p.exists(currentLink))
888
+ return null;
889
+ let resolved;
890
+ try {
891
+ resolved = await p.readlink(currentLink);
892
+ } catch {
893
+ resolved = currentLink;
894
+ }
895
+ const binShim = p.joinPath(resolved, "node_modules", ".bin", "openclaw");
896
+ if (await p.exists(binShim))
897
+ return binShim;
898
+ const mjs = p.joinPath(resolved, "node_modules", "openclaw", "openclaw.mjs");
899
+ if (await p.exists(mjs))
900
+ return mjs;
901
+ return null;
902
+ }
903
+ async function findAgent(driver) {
904
+ const managed = await managedBinary2(driver.agentType());
905
+ if (managed) {
906
+ return {
907
+ type: "managed",
908
+ binary: managed,
909
+ home: agentHome(driver.agentType())
910
+ };
911
+ }
912
+ const local = await driver.detectLocal();
913
+ if (local) {
914
+ return {
915
+ type: "system",
916
+ binary: local.binary,
917
+ version: local.version,
918
+ home: local.homeDir
919
+ };
920
+ }
921
+ return { type: "notFound" };
922
+ }
923
+
924
+ // ../core/dist/agent/downloader.js
925
+ async function downloadAgent(npmPackage, requestedVersion, agentType, progress) {
926
+ const p = getPlatformAdapter();
927
+ const info = await fetchNpmPackageInfo(npmPackage);
928
+ const version = requestedVersion ?? info["dist-tags"]["latest"];
929
+ if (!version)
930
+ throw new Error("no 'latest' tag in npm registry");
931
+ const verInfo = info.versions[version];
932
+ if (!verInfo)
933
+ throw new Error(`version ${version} not found in npm registry`);
934
+ const versionsDir = p.joinPath(agentDir(agentType), "versions");
935
+ const dest = p.joinPath(versionsDir, version);
936
+ if (await p.exists(dest)) {
937
+ progress.onStatus(`${npmPackage} v${version} already downloaded`);
938
+ } else {
939
+ progress.onStatus(`Downloading ${npmPackage} v${version} ...`);
940
+ const data = await downloadTarball(verInfo.dist.tarball);
941
+ progress.onStatus("extracting...");
942
+ await extractTarball(data, dest);
943
+ progress.onStatus("installing dependencies...");
944
+ await runNpmInstall(dest);
945
+ progress.onComplete(`${npmPackage} v${version} ready`);
946
+ }
947
+ const current = p.joinPath(versionsDir, "current");
948
+ if (await p.exists(current)) {
949
+ try {
950
+ await p.rm(current);
951
+ } catch {
952
+ }
953
+ }
954
+ try {
955
+ await p.symlink(dest, current);
956
+ } catch {
957
+ }
958
+ return dest;
959
+ }
960
+ async function downloadPlugin(pluginPackage, agentType, progress) {
961
+ const p = getPlatformAdapter();
962
+ const info = await fetchNpmPackageInfo(pluginPackage);
963
+ const version = info["dist-tags"]["latest"];
964
+ if (!version)
965
+ throw new Error("no 'latest' tag for plugin");
966
+ const verInfo = info.versions[version];
967
+ if (!verInfo)
968
+ throw new Error("plugin version not found");
969
+ const cacheDir = p.joinPath(agentDir(agentType), "beeos-claw");
970
+ const dest = p.joinPath(cacheDir, version);
971
+ if (await p.exists(dest))
972
+ return dest;
973
+ progress.onStatus(`Downloading ${pluginPackage} v${version} ...`);
974
+ const data = await downloadTarball(verInfo.dist.tarball);
975
+ await extractTarball(data, dest);
976
+ const pkgJson = p.joinPath(dest, "package.json");
977
+ if (await p.exists(pkgJson)) {
978
+ await runNpmInstall(dest);
979
+ }
980
+ return dest;
981
+ }
982
+ async function extractTarball(data, dest) {
983
+ const p = getPlatformAdapter();
984
+ await p.mkdir(dest);
985
+ const tmpFile = p.joinPath(dest, "__tmp.tgz");
986
+ await p.writeFileBytes(tmpFile, data);
987
+ try {
988
+ await p.exec("tar", ["-xzf", tmpFile, "--strip-components=1", "-C", dest]);
989
+ } finally {
990
+ try {
991
+ await p.rm(tmpFile);
992
+ } catch {
993
+ }
994
+ }
995
+ }
996
+ async function runNpmInstall(dir) {
997
+ const p = getPlatformAdapter();
998
+ const result = await p.exec("npm", ["install", "--omit=dev", "--no-audit", "--no-fund"], {
999
+ cwd: dir
1000
+ });
1001
+ if (result.code !== 0) {
1002
+ throw new Error(`npm install failed with exit code ${result.code}`);
1003
+ }
1004
+ }
1005
+
1006
+ // ../core/dist/agent/launcher.js
1007
+ import { createHash as createHash2 } from "crypto";
1008
+ var MAX_BACKUPS = 5;
1009
+ async function backupOpenclawConfig(agentHome2) {
1010
+ const p = getPlatformAdapter();
1011
+ const configPath2 = p.joinPath(agentHome2, "openclaw.json");
1012
+ if (!await p.exists(configPath2))
1013
+ return false;
1014
+ const ts = Math.floor(Date.now() / 1e3);
1015
+ const backupPath = p.joinPath(agentHome2, `openclaw.json.beeos-backup-${ts}`);
1016
+ await p.copyFile(configPath2, backupPath);
1017
+ const entries = await p.readdir(agentHome2);
1018
+ const backups = entries.filter((e) => e.startsWith("openclaw.json.beeos-backup-")).sort();
1019
+ if (backups.length > MAX_BACKUPS) {
1020
+ for (const old of backups.slice(0, backups.length - MAX_BACKUPS)) {
1021
+ try {
1022
+ await p.rm(p.joinPath(agentHome2, old));
1023
+ } catch {
1024
+ }
1025
+ }
1026
+ }
1027
+ return true;
1028
+ }
1029
+ async function readExistingGatewayToken(agentHome2) {
1030
+ const p = getPlatformAdapter();
1031
+ const configPath2 = p.joinPath(agentHome2, "openclaw.json");
1032
+ if (!await p.exists(configPath2))
1033
+ return null;
1034
+ try {
1035
+ const raw = await p.readFile(configPath2);
1036
+ const val = JSON.parse(raw);
1037
+ return val?.gateway?.auth?.token ?? null;
1038
+ } catch {
1039
+ return null;
1040
+ }
1041
+ }
1042
+ function beeosClawPluginConfig(ctx) {
1043
+ const p = getPlatformAdapter();
1044
+ const logDir = p.joinPath(ctx.agentHome, "beeos-logs");
1045
+ return {
1046
+ _generator: { name: "beeos-cli", version: "1.0.0", configProtocol: 1 },
1047
+ agentGatewayUrl: ctx.agentGatewayUrl,
1048
+ keyFile: ctx.keyFile,
1049
+ gateway: { token: ctx.gatewayToken },
1050
+ log: { dir: logDir }
1051
+ };
1052
+ }
1053
+ async function configurePluginViaCli(ctx) {
1054
+ const p = getPlatformAdapter();
1055
+ const env = {};
1056
+ if (!ctx.isSystemHome)
1057
+ env.OPENCLAW_HOME = ctx.agentHome;
1058
+ await runConfigSet(ctx.agentBinary, ctx.agentHome, "plugins.entries.beeos-claw.enabled", "true", false, ctx.isSystemHome);
1059
+ const configJson = JSON.stringify(beeosClawPluginConfig(ctx));
1060
+ const result = await p.exec(ctx.agentBinary, ["config", "set", "plugins.entries.beeos-claw.config", configJson, "--json"], { env });
1061
+ if (result.code !== 0) {
1062
+ throw new Error("`openclaw config set plugins.entries.beeos-claw.config` failed");
1063
+ }
1064
+ await writePairedJson(ctx.agentHome, ctx.keyFile);
1065
+ }
1066
+ async function generateOpenclawConfig(ctx) {
1067
+ const p = getPlatformAdapter();
1068
+ await p.mkdir(ctx.agentHome);
1069
+ const env = { OPENCLAW_HOME: ctx.agentHome };
1070
+ const setupResult = await p.exec(ctx.agentBinary, ["setup", "--non-interactive", "--mode", "local"], { env });
1071
+ if (setupResult.code === 0) {
1072
+ for (const [path2, value] of [
1073
+ ["gateway.auth.token", ctx.gatewayToken],
1074
+ ["gateway.remote.token", ctx.gatewayToken],
1075
+ ["gateway.bind", "lan"]
1076
+ ]) {
1077
+ await runConfigSet(ctx.agentBinary, ctx.agentHome, path2, value, false, false);
1078
+ }
1079
+ await configurePluginViaCli(ctx);
1080
+ } else {
1081
+ await generateConfigFallback(ctx);
1082
+ }
1083
+ }
1084
+ async function generateConfigFallback(ctx) {
1085
+ const p = getPlatformAdapter();
1086
+ for (const sub of ["extensions", "skills", "devices", "workspace"]) {
1087
+ await p.mkdir(p.joinPath(ctx.agentHome, sub));
1088
+ }
1089
+ const config = {
1090
+ gateway: {
1091
+ auth: { token: ctx.gatewayToken },
1092
+ remote: { token: ctx.gatewayToken },
1093
+ bind: "lan"
1094
+ },
1095
+ plugins: {
1096
+ entries: {
1097
+ "beeos-claw": {
1098
+ enabled: true,
1099
+ config: beeosClawPluginConfig(ctx)
1100
+ }
1101
+ }
1102
+ }
1103
+ };
1104
+ const configPath2 = p.joinPath(ctx.agentHome, "openclaw.json");
1105
+ await p.writeFile(configPath2, JSON.stringify(config, null, 2));
1106
+ await writePairedJson(ctx.agentHome, ctx.keyFile);
1107
+ }
1108
+ async function runConfigSet(bin, home, path2, value, json, isSystemHome) {
1109
+ const p = getPlatformAdapter();
1110
+ const args = ["config", "set", path2, value];
1111
+ if (json)
1112
+ args.push("--json");
1113
+ const env = {};
1114
+ if (!isSystemHome)
1115
+ env.OPENCLAW_HOME = home;
1116
+ const result = await p.exec(bin, args, { env });
1117
+ if (result.code !== 0) {
1118
+ throw new Error(`\`openclaw config set ${path2}\` exited with ${result.code}`);
1119
+ }
1120
+ }
1121
+ async function restartGatewayViaCli(ctx) {
1122
+ const p = getPlatformAdapter();
1123
+ const env = {};
1124
+ if (!ctx.isSystemHome)
1125
+ env.OPENCLAW_HOME = ctx.agentHome;
1126
+ const result = await p.exec(ctx.agentBinary, ["gateway", "restart"], { env });
1127
+ return result.code === 0;
1128
+ }
1129
+ async function spawnOpenclaw(ctx) {
1130
+ const p = getPlatformAdapter();
1131
+ const logDir = p.joinPath(ctx.agentHome, "beeos-logs");
1132
+ await p.mkdir(logDir);
1133
+ const stdoutFile = p.joinPath(logDir, "gateway-stdout.log");
1134
+ const stderrFile = p.joinPath(logDir, "gateway-stderr.log");
1135
+ const isMjs = ctx.agentBinary.endsWith(".mjs") || ctx.agentBinary.endsWith(".js");
1136
+ const cmd = isMjs ? "node" : ctx.agentBinary;
1137
+ const baseArgs = isMjs ? [ctx.agentBinary] : [];
1138
+ const env = {
1139
+ AGENT_GATEWAY_URL: ctx.agentGatewayUrl
1140
+ };
1141
+ if (!ctx.isSystemHome) {
1142
+ env.OPENCLAW_HOME = ctx.agentHome;
1143
+ env.OPENCLAW_GATEWAY_TOKEN = ctx.gatewayToken;
1144
+ }
1145
+ const result = await p.spawn(cmd, [...baseArgs, "gateway", "--allow-unconfigured", "--bind", "lan"], {
1146
+ detached: true,
1147
+ stdoutFile,
1148
+ stderrFile,
1149
+ env
1150
+ });
1151
+ return result.pid;
1152
+ }
1153
+ async function writePairedJson(home, keyFile) {
1154
+ const p = getPlatformAdapter();
1155
+ const devicesDir = p.joinPath(home, "devices");
1156
+ await p.mkdir(devicesDir);
1157
+ let pubKeyB64 = "";
1158
+ if (await p.exists(keyFile)) {
1159
+ try {
1160
+ const raw = await p.readFile(keyFile);
1161
+ const kp = JSON.parse(raw);
1162
+ pubKeyB64 = kp.publicKey ?? "";
1163
+ } catch {
1164
+ }
1165
+ }
1166
+ let deviceId = "beeos-claw-device";
1167
+ if (pubKeyB64) {
1168
+ const decoded = Buffer.from(pubKeyB64, "base64");
1169
+ const hash = createHash2("sha256").update(decoded).digest("hex");
1170
+ deviceId = `beeos-claw-${hash.slice(0, 12)}`;
1171
+ }
1172
+ const paired = {
1173
+ devices: {
1174
+ [deviceId]: {
1175
+ name: "beeos-claw",
1176
+ role: "operator",
1177
+ scopes: ["operator.admin", "operator.approvals", "operator.pairing"],
1178
+ publicKey: pubKeyB64,
1179
+ paired: true
1180
+ }
1181
+ }
1182
+ };
1183
+ const pairedPath = p.joinPath(devicesDir, "paired.json");
1184
+ await p.writeFile(pairedPath, JSON.stringify(paired, null, 2));
1185
+ }
1186
+
1187
+ // ../core/dist/agent/registry.js
1188
+ var openClawDriver = {
1189
+ agentType() {
1190
+ return "openclaw";
1191
+ },
1192
+ npmPackage() {
1193
+ return "openclaw";
1194
+ },
1195
+ pluginPackage() {
1196
+ return "beeos-claw";
1197
+ },
1198
+ async detectLocal() {
1199
+ const p = getPlatformAdapter();
1200
+ const cmd = p.platform() === "win32" ? "where" : "which";
1201
+ let binary;
1202
+ try {
1203
+ const result = await p.exec(cmd, ["openclaw"]);
1204
+ if (result.code !== 0)
1205
+ return null;
1206
+ binary = result.stdout.trim().split("\n")[0].trim();
1207
+ } catch {
1208
+ return null;
1209
+ }
1210
+ let version = "";
1211
+ try {
1212
+ const verResult = await p.exec(binary, ["--version"]);
1213
+ version = verResult.stdout.trim();
1214
+ } catch {
1215
+ }
1216
+ const homeDir = p.joinPath(p.homeDir(), ".openclaw");
1217
+ return { binary, version, homeDir };
1218
+ }
1219
+ };
1220
+
1221
+ // ../core/dist/runtime/openclaw.js
1222
+ var GATEWAY_PORT2 = 18789;
1223
+ var openclawRuntime = {
1224
+ agentType() {
1225
+ return "openclaw";
1226
+ },
1227
+ displayName() {
1228
+ return "OpenClaw";
1229
+ },
1230
+ async detect() {
1231
+ const location = await findAgent(openClawDriver);
1232
+ if (location.type === "notFound")
1233
+ return null;
1234
+ return {
1235
+ binary: location.binary,
1236
+ version: location.type === "system" ? location.version : "",
1237
+ home: location.home
1238
+ };
1239
+ },
1240
+ async ensure(version, progress) {
1241
+ const location = await findAgent(openClawDriver);
1242
+ if (location.type === "managed") {
1243
+ return { binary: location.binary, version: "", home: location.home };
1244
+ }
1245
+ if (location.type === "system") {
1246
+ return { binary: location.binary, version: location.version, home: location.home };
1247
+ }
1248
+ const dest = await downloadAgent(openClawDriver.npmPackage(), version ?? void 0, "openclaw", progress);
1249
+ const home = agentHome("openclaw");
1250
+ await getPlatformAdapter().mkdir(home);
1251
+ await downloadPlugin(openClawDriver.pluginPackage(), "openclaw", progress);
1252
+ return { binary: dest, version: "", home };
1253
+ },
1254
+ async launch(ctx) {
1255
+ return spawnOpenclaw(ctx);
1256
+ },
1257
+ async isHealthy() {
1258
+ return getPlatformAdapter().tcpProbe("127.0.0.1", GATEWAY_PORT2, 500);
1259
+ },
1260
+ async stop(agentType) {
1261
+ const pid = await readPid(agentType);
1262
+ if (pid != null) {
1263
+ killProcess(pid);
1264
+ await removePid(agentType);
1265
+ }
1266
+ },
1267
+ async update(progress) {
1268
+ await downloadAgent(openClawDriver.npmPackage(), void 0, "openclaw", progress);
1269
+ await downloadPlugin(openClawDriver.pluginPackage(), "openclaw", progress);
1270
+ }
1271
+ };
1272
+
1273
+ // src/node-adapter.ts
1274
+ import {
1275
+ execFile,
1276
+ spawn as nodeSpawn
1277
+ } from "child_process";
1278
+ import fs from "fs";
1279
+ import fsp from "fs/promises";
1280
+ import net from "net";
1281
+ import os from "os";
1282
+ import path from "path";
1283
+ var NodePlatformAdapter = class {
1284
+ // ── Filesystem ──────────────────────────────────────────
1285
+ async readFile(filePath) {
1286
+ return fsp.readFile(filePath, "utf-8");
1287
+ }
1288
+ async readFileBytes(filePath) {
1289
+ const buf = await fsp.readFile(filePath);
1290
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
1291
+ }
1292
+ async writeFile(filePath, content) {
1293
+ await fsp.writeFile(filePath, content, "utf-8");
1294
+ }
1295
+ async writeFileBytes(filePath, content) {
1296
+ await fsp.writeFile(filePath, content);
1297
+ }
1298
+ async exists(filePath) {
1299
+ try {
1300
+ await fsp.access(filePath);
1301
+ return true;
1302
+ } catch {
1303
+ return false;
1304
+ }
1305
+ }
1306
+ async mkdir(dirPath) {
1307
+ await fsp.mkdir(dirPath, { recursive: true });
1308
+ }
1309
+ async rm(filePath) {
1310
+ await fsp.rm(filePath, { force: true });
1311
+ }
1312
+ async rmdir(dirPath) {
1313
+ await fsp.rm(dirPath, { recursive: true, force: true });
1314
+ }
1315
+ async readdir(dirPath) {
1316
+ return fsp.readdir(dirPath);
1317
+ }
1318
+ async chmod(filePath, mode) {
1319
+ if (process.platform !== "win32") {
1320
+ await fsp.chmod(filePath, mode);
1321
+ }
1322
+ }
1323
+ async symlink(target, linkPath) {
1324
+ try {
1325
+ await fsp.symlink(target, linkPath);
1326
+ } catch (e) {
1327
+ if (process.platform === "win32") {
1328
+ await fsp.symlink(target, linkPath, "junction");
1329
+ } else {
1330
+ throw e;
1331
+ }
1332
+ }
1333
+ }
1334
+ async readlink(linkPath) {
1335
+ return fsp.readlink(linkPath);
1336
+ }
1337
+ async copyFile(src, dest) {
1338
+ await fsp.copyFile(src, dest);
1339
+ }
1340
+ async isDirectory(filePath) {
1341
+ try {
1342
+ const stat = await fsp.stat(filePath);
1343
+ return stat.isDirectory();
1344
+ } catch {
1345
+ return false;
1346
+ }
1347
+ }
1348
+ async stat(filePath) {
1349
+ const s = await fsp.stat(filePath);
1350
+ return { size: s.size, mtimeMs: s.mtimeMs };
1351
+ }
1352
+ // ── Process execution ───────────────────────────────────
1353
+ exec(cmd, args, options) {
1354
+ return new Promise((resolve) => {
1355
+ const env = options?.env ? { ...process.env, ...options.env } : process.env;
1356
+ execFile(
1357
+ cmd,
1358
+ args,
1359
+ {
1360
+ cwd: options?.cwd,
1361
+ env,
1362
+ timeout: options?.timeout,
1363
+ maxBuffer: 10 * 1024 * 1024,
1364
+ encoding: "utf-8"
1365
+ },
1366
+ (error, stdout, stderr) => {
1367
+ const code = error && "code" in error && typeof error.code === "number" ? error.code : error ? 1 : 0;
1368
+ resolve({
1369
+ stdout: stdout ?? "",
1370
+ stderr: stderr ?? "",
1371
+ code
1372
+ });
1373
+ }
1374
+ );
1375
+ });
1376
+ }
1377
+ async spawn(cmd, args, options) {
1378
+ const env = options?.env ? { ...process.env, ...options.env } : process.env;
1379
+ let stdoutFd;
1380
+ let stderrFd;
1381
+ if (options?.stdoutFile) {
1382
+ await fsp.mkdir(path.dirname(options.stdoutFile), { recursive: true });
1383
+ stdoutFd = fs.openSync(options.stdoutFile, "a");
1384
+ }
1385
+ if (options?.stderrFile) {
1386
+ await fsp.mkdir(path.dirname(options.stderrFile), { recursive: true });
1387
+ stderrFd = options.stderrFile === options.stdoutFile ? stdoutFd : fs.openSync(options.stderrFile, "a");
1388
+ }
1389
+ const child = nodeSpawn(cmd, args, {
1390
+ cwd: options?.cwd,
1391
+ env,
1392
+ detached: options?.detached ?? false,
1393
+ stdio: [
1394
+ "ignore",
1395
+ stdoutFd != null ? stdoutFd : "ignore",
1396
+ stderrFd != null ? stderrFd : "ignore"
1397
+ ]
1398
+ });
1399
+ if (options?.detached) {
1400
+ child.unref();
1401
+ }
1402
+ if (stdoutFd != null) fs.closeSync(stdoutFd);
1403
+ if (stderrFd != null && stderrFd !== stdoutFd) fs.closeSync(stderrFd);
1404
+ const pid = child.pid;
1405
+ if (pid == null) {
1406
+ throw new Error(`Failed to spawn process: ${cmd} ${args.join(" ")}`);
1407
+ }
1408
+ return { pid };
1409
+ }
1410
+ kill(pid, signal) {
1411
+ try {
1412
+ process.kill(pid, signal ?? "SIGTERM");
1413
+ return true;
1414
+ } catch {
1415
+ return false;
1416
+ }
1417
+ }
1418
+ isProcessAlive(pid) {
1419
+ try {
1420
+ process.kill(pid, 0);
1421
+ return true;
1422
+ } catch {
1423
+ return false;
1424
+ }
1425
+ }
1426
+ // ── Network ─────────────────────────────────────────────
1427
+ async fetch(url, init) {
1428
+ return globalThis.fetch(url, init);
1429
+ }
1430
+ tcpProbe(host, port, timeoutMs = 500) {
1431
+ return new Promise((resolve) => {
1432
+ const socket = net.createConnection({ host, port }, () => {
1433
+ socket.destroy();
1434
+ resolve(true);
1435
+ });
1436
+ socket.setTimeout(timeoutMs);
1437
+ socket.on("timeout", () => {
1438
+ socket.destroy();
1439
+ resolve(false);
1440
+ });
1441
+ socket.on("error", () => {
1442
+ socket.destroy();
1443
+ resolve(false);
1444
+ });
1445
+ });
1446
+ }
1447
+ // ── OS info ─────────────────────────────────────────────
1448
+ homeDir() {
1449
+ return os.homedir();
1450
+ }
1451
+ hostname() {
1452
+ return os.hostname();
1453
+ }
1454
+ platform() {
1455
+ return process.platform;
1456
+ }
1457
+ env(key) {
1458
+ return process.env[key];
1459
+ }
1460
+ // ── UI ──────────────────────────────────────────────────
1461
+ async openUrl(url) {
1462
+ const open = await import("open");
1463
+ await open.default(url);
1464
+ }
1465
+ // ── Path utilities ──────────────────────────────────────
1466
+ joinPath(...segments) {
1467
+ return path.join(...segments);
1468
+ }
1469
+ dirname(p) {
1470
+ return path.dirname(p);
1471
+ }
1472
+ basename(p) {
1473
+ return path.basename(p);
1474
+ }
1475
+ };
1476
+
1477
+ // src/commands/start.ts
1478
+ import os2 from "os";
1479
+
1480
+ // src/progress.ts
1481
+ import ora from "ora";
1482
+ var CliReporter = class {
1483
+ spinner;
1484
+ constructor() {
1485
+ this.spinner = ora({ stream: process.stderr });
1486
+ }
1487
+ onStatus(message) {
1488
+ if (this.spinner.isSpinning) {
1489
+ this.spinner.text = message;
1490
+ } else {
1491
+ this.spinner.start(message);
1492
+ }
1493
+ }
1494
+ onComplete(message) {
1495
+ if (this.spinner.isSpinning) {
1496
+ this.spinner.succeed(message);
1497
+ } else {
1498
+ console.log(`\u2713 ${message}`);
1499
+ }
1500
+ }
1501
+ stop() {
1502
+ if (this.spinner.isSpinning) {
1503
+ this.spinner.stop();
1504
+ }
1505
+ }
1506
+ };
1507
+
1508
+ // src/commands/start.ts
1509
+ async function run(agentType, options) {
1510
+ const p = getPlatformAdapter();
1511
+ await ensureDirs();
1512
+ const cfg = await loadOrCreateConfig();
1513
+ const reporter = new CliReporter();
1514
+ if (agentType !== "openclaw") {
1515
+ throw new Error(`Agent type '${agentType}' is not yet supported. Use 'openclaw'.`);
1516
+ }
1517
+ const identity = await loadOrCreateIdentity();
1518
+ const fp = fingerprint(identity);
1519
+ const pubkey = publicKeyB64(identity);
1520
+ const keyFile = p.joinPath(beeoHome(), "identity", "keypair.json");
1521
+ const gwToken = await loadOrCreateGatewayToken();
1522
+ const location = await findAgent(openClawDriver);
1523
+ let binary;
1524
+ let home;
1525
+ let isSystemHome = false;
1526
+ let effectiveToken = gwToken;
1527
+ if (location.type === "managed") {
1528
+ binary = location.binary;
1529
+ home = location.home;
1530
+ } else if (location.type === "system") {
1531
+ binary = location.binary;
1532
+ home = location.home;
1533
+ isSystemHome = true;
1534
+ try {
1535
+ const pluginDir = await downloadPlugin(openClawDriver.pluginPackage(), agentType, reporter);
1536
+ await installPluginToHome(home, pluginDir, binary);
1537
+ } catch {
1538
+ }
1539
+ const systemToken = await readExistingGatewayToken(home);
1540
+ effectiveToken = systemToken ?? gwToken;
1541
+ await backupOpenclawConfig(home);
1542
+ if (await isGatewayRunning()) {
1543
+ const ctx = {
1544
+ agentHome: home,
1545
+ agentBinary: binary,
1546
+ keyFile,
1547
+ bridgeUrl: cfg.platform.bridge_url,
1548
+ agentGatewayUrl: cfg.platform.api_url,
1549
+ gatewayToken: effectiveToken,
1550
+ isSystemHome: true
1551
+ };
1552
+ try {
1553
+ await configurePluginViaCli(ctx);
1554
+ } catch {
1555
+ }
1556
+ try {
1557
+ await restartGatewayViaCli(ctx);
1558
+ } catch {
1559
+ }
1560
+ }
1561
+ } else {
1562
+ reporter.onStatus(`Downloading ${openClawDriver.npmPackage()}...`);
1563
+ await downloadAgent(openClawDriver.npmPackage(), options.version ?? void 0, agentType, reporter);
1564
+ home = agentHome(agentType);
1565
+ await p.mkdir(home);
1566
+ await downloadPlugin(openClawDriver.pluginPackage(), agentType, reporter);
1567
+ const managedBin = await managedBinary2(agentType);
1568
+ if (!managedBin) throw new Error("agent binary not found after download");
1569
+ binary = managedBin;
1570
+ }
1571
+ reporter.stop();
1572
+ let gatewayPid;
1573
+ if (!await isGatewayRunning()) {
1574
+ const ctx = {
1575
+ agentHome: home,
1576
+ agentBinary: binary,
1577
+ keyFile,
1578
+ bridgeUrl: cfg.platform.bridge_url,
1579
+ agentGatewayUrl: cfg.platform.api_url,
1580
+ gatewayToken: effectiveToken,
1581
+ isSystemHome
1582
+ };
1583
+ if (isSystemHome) {
1584
+ await configurePluginViaCli(ctx);
1585
+ } else {
1586
+ await generateOpenclawConfig(ctx);
1587
+ }
1588
+ const pid = await spawnOpenclaw(ctx);
1589
+ await writePid(agentType, pid);
1590
+ gatewayPid = pid;
1591
+ let alive = false;
1592
+ for (let i = 0; i < 10; i++) {
1593
+ await sleep2(500);
1594
+ if (await isGatewayRunning()) {
1595
+ alive = true;
1596
+ break;
1597
+ }
1598
+ }
1599
+ if (!alive) {
1600
+ throw new Error("Agent gateway did not start within 5s");
1601
+ }
1602
+ } else {
1603
+ gatewayPid = await readPid(agentType) ?? void 0;
1604
+ }
1605
+ const hostname = buildHostname();
1606
+ try {
1607
+ const resp = await agentBind(cfg.platform.api_url, pubkey, fp, agentType, hostname);
1608
+ if (resp.status === "bound") {
1609
+ const instanceId = resp.instance_id ?? "";
1610
+ await saveBindingInfo({ fingerprint: fp, instance_id: instanceId, bound_at: nowUnix() });
1611
+ const result = {
1612
+ status: "bound",
1613
+ public_key: pubkey,
1614
+ fingerprint: fp,
1615
+ gateway_url: "http://127.0.0.1:18789",
1616
+ gateway_pid: gatewayPid,
1617
+ bridge_url: cfg.platform.bridge_url,
1618
+ instance_id: instanceId
1619
+ };
1620
+ if (options.json) {
1621
+ console.log(JSON.stringify(result, null, 2));
1622
+ } else {
1623
+ console.log(`Agent bound to instance: ${instanceId}`);
1624
+ }
1625
+ return;
1626
+ }
1627
+ if (resp.status === "pending") {
1628
+ const bindId = resp.bind_id ?? "";
1629
+ const bindUrl = buildBindUrl(cfg.platform.dashboard_base_url, bindId);
1630
+ await removeBindingInfo();
1631
+ if (options.json) {
1632
+ const result = {
1633
+ status: "pending",
1634
+ public_key: pubkey,
1635
+ fingerprint: fp,
1636
+ bind_id: bindId,
1637
+ bind_url: bindUrl,
1638
+ qr_data: bindUrl,
1639
+ gateway_url: "http://127.0.0.1:18789",
1640
+ gateway_pid: gatewayPid,
1641
+ bridge_url: cfg.platform.bridge_url
1642
+ };
1643
+ console.log(JSON.stringify(result, null, 2));
1644
+ return;
1645
+ }
1646
+ await presentBindUrl(bindUrl, options.browser === false);
1647
+ console.log("");
1648
+ console.log(" Waiting for bind approval (timeout: 10min)...");
1649
+ const instanceId = await pollUntilBound(cfg.platform.api_url, bindId, 6e5);
1650
+ await saveBindingInfo({ fingerprint: fp, instance_id: instanceId, bound_at: nowUnix() });
1651
+ console.log(` Bind confirmed! Instance: ${instanceId}`);
1652
+ }
1653
+ } catch (e) {
1654
+ const cached = await loadBindingInfo();
1655
+ if (cached && cached.fingerprint === fp) {
1656
+ if (options.json) {
1657
+ console.log(JSON.stringify({
1658
+ status: "bound_offline",
1659
+ public_key: pubkey,
1660
+ fingerprint: fp,
1661
+ gateway_url: "http://127.0.0.1:18789",
1662
+ gateway_pid: gatewayPid,
1663
+ bridge_url: cfg.platform.bridge_url,
1664
+ instance_id: cached.instance_id
1665
+ }, null, 2));
1666
+ } else {
1667
+ console.log(`Agent running (offline, cached instance: ${cached.instance_id})`);
1668
+ }
1669
+ return;
1670
+ }
1671
+ throw new Error(`Cannot reach platform to bind agent: ${e}`);
1672
+ }
1673
+ }
1674
+ async function installPluginToHome(home, pluginDir, binary) {
1675
+ const p = getPlatformAdapter();
1676
+ const dest = p.joinPath(home, "extensions", "beeos-claw");
1677
+ if (await p.exists(dest)) {
1678
+ await p.rmdir(dest);
1679
+ }
1680
+ await copyDirRecursive(pluginDir, dest);
1681
+ }
1682
+ async function copyDirRecursive(src, dst) {
1683
+ const p = getPlatformAdapter();
1684
+ await p.mkdir(dst);
1685
+ const entries = await p.readdir(src);
1686
+ for (const name of entries) {
1687
+ const srcPath = p.joinPath(src, name);
1688
+ const dstPath = p.joinPath(dst, name);
1689
+ if (await p.isDirectory(srcPath)) {
1690
+ await copyDirRecursive(srcPath, dstPath);
1691
+ } else {
1692
+ await p.copyFile(srcPath, dstPath);
1693
+ }
1694
+ }
1695
+ }
1696
+ function buildHostname() {
1697
+ const machine = os2.hostname();
1698
+ const user = process.env.USER || process.env.USERNAME || "";
1699
+ return user ? `${user}@${machine}` : machine;
1700
+ }
1701
+ function nowUnix() {
1702
+ return Math.floor(Date.now() / 1e3);
1703
+ }
1704
+ function sleep2(ms) {
1705
+ return new Promise((resolve) => setTimeout(resolve, ms));
1706
+ }
1707
+
1708
+ // src/commands/stop.ts
1709
+ async function run2(agentType) {
1710
+ const pid = await readPid(agentType);
1711
+ if (pid == null) {
1712
+ console.log(`No running ${agentType} agent found`);
1713
+ return;
1714
+ }
1715
+ killProcess(pid);
1716
+ await removePid(agentType);
1717
+ console.log(`Stopped ${agentType} agent (pid ${pid})`);
1718
+ }
1719
+
1720
+ // src/commands/status.ts
1721
+ var KNOWN_RUNTIMES = ["openclaw", "device"];
1722
+ async function run3() {
1723
+ const home = beeoHome();
1724
+ const cfg = await loadOrCreateConfig();
1725
+ console.log(`BeeOS Home: ${home}`);
1726
+ console.log(`API URL: ${cfg.platform.api_url}`);
1727
+ console.log(`Bridge URL: ${cfg.platform.bridge_url}`);
1728
+ console.log("");
1729
+ const p = getPlatformAdapter();
1730
+ const fpPath = p.joinPath(home, "identity", "fingerprint");
1731
+ if (await p.exists(fpPath)) {
1732
+ const fp = (await p.readFile(fpPath)).trim();
1733
+ console.log(`Identity fingerprint: ${fp}`);
1734
+ } else {
1735
+ console.log("Identity: not yet created");
1736
+ }
1737
+ console.log("");
1738
+ for (const rt of KNOWN_RUNTIMES) {
1739
+ const pid = await readPid(rt);
1740
+ if (pid != null && isProcessAlive(pid)) {
1741
+ console.log(`${rt}: running (pid ${pid})`);
1742
+ } else if (pid != null) {
1743
+ console.log(`${rt}: stale pid ${pid} (process not alive)`);
1744
+ } else {
1745
+ console.log(`${rt}: not running`);
1746
+ }
1747
+ }
1748
+ }
1749
+
1750
+ // src/commands/update.ts
1751
+ async function run4(agentType) {
1752
+ const reporter = new CliReporter();
1753
+ const pid = await readPid(agentType);
1754
+ const wasRunning = pid != null && isProcessAlive(pid);
1755
+ if (wasRunning) {
1756
+ await run2(agentType);
1757
+ }
1758
+ if (agentType === "openclaw") {
1759
+ await openclawRuntime.update(reporter);
1760
+ } else if (agentType === "device") {
1761
+ await deviceRuntime.update(reporter);
1762
+ } else {
1763
+ throw new Error(`Unknown agent type: ${agentType}`);
1764
+ }
1765
+ reporter.stop();
1766
+ console.log(`${agentType} updated`);
1767
+ if (wasRunning) {
1768
+ console.log("Restarting agent...");
1769
+ await run(agentType, { force: true });
1770
+ }
1771
+ }
1772
+
1773
+ // src/commands/bind-status.ts
1774
+ async function run5(bindId, options) {
1775
+ const cfg = await loadOrCreateConfig();
1776
+ const resp = await getBindStatus(cfg.platform.api_url, bindId);
1777
+ if (resp.status === "bound" && resp.instance_id) {
1778
+ const p = getPlatformAdapter();
1779
+ const fpPath = p.joinPath(beeoHome(), "identity", "fingerprint");
1780
+ try {
1781
+ if (await p.exists(fpPath)) {
1782
+ const fp = (await p.readFile(fpPath)).trim();
1783
+ if (fp) {
1784
+ await saveBindingInfo({
1785
+ fingerprint: fp,
1786
+ instance_id: resp.instance_id,
1787
+ bound_at: Math.floor(Date.now() / 1e3)
1788
+ });
1789
+ }
1790
+ }
1791
+ } catch {
1792
+ }
1793
+ }
1794
+ if (options.json) {
1795
+ console.log(JSON.stringify({
1796
+ status: resp.status,
1797
+ instance_id: resp.instance_id ?? null
1798
+ }, null, 2));
1799
+ } else {
1800
+ console.log(`Bind status: ${resp.status}`);
1801
+ if (resp.instance_id) {
1802
+ console.log(`Instance: ${resp.instance_id}`);
1803
+ }
1804
+ }
1805
+ }
1806
+
1807
+ // src/commands/device.ts
1808
+ import lockfile from "proper-lockfile";
1809
+ function devicesPath() {
1810
+ const p = getPlatformAdapter();
1811
+ return p.joinPath(beeoHome(), "devices.json");
1812
+ }
1813
+ async function loadDeviceState() {
1814
+ const p = getPlatformAdapter();
1815
+ const path2 = devicesPath();
1816
+ if (!await p.exists(path2)) return { devices: [] };
1817
+ try {
1818
+ const raw = await p.readFile(path2);
1819
+ return JSON.parse(raw);
1820
+ } catch {
1821
+ return { devices: [] };
1822
+ }
1823
+ }
1824
+ async function saveDeviceState(state) {
1825
+ const p = getPlatformAdapter();
1826
+ await p.writeFile(devicesPath(), JSON.stringify(state, null, 2));
1827
+ }
1828
+ async function withDeviceLock(fn) {
1829
+ const p = getPlatformAdapter();
1830
+ const path2 = devicesPath();
1831
+ await p.mkdir(p.dirname(path2));
1832
+ if (!await p.exists(path2)) {
1833
+ await p.writeFile(path2, JSON.stringify({ devices: [] }));
1834
+ }
1835
+ const release = await lockfile.lock(path2, { retries: 3 });
1836
+ try {
1837
+ return await fn();
1838
+ } finally {
1839
+ await release();
1840
+ }
1841
+ }
1842
+ async function attach(options) {
1843
+ const cfg = await loadOrCreateConfig();
1844
+ const reporter = new CliReporter();
1845
+ if (options.all) {
1846
+ return attachAll(cfg, reporter);
1847
+ }
1848
+ const serial = options.serial ?? await deviceRuntime.detectSingleDevice();
1849
+ const name = options.name ?? serial;
1850
+ const agentBin = await deviceRuntime.ensureAgent(reporter);
1851
+ reporter.stop();
1852
+ const pubkeyB64 = await deviceRuntime.ensureKeyAndGetPubkey(serial);
1853
+ const instanceId = await tryBindDevice(pubkeyB64, name, cfg);
1854
+ if (!instanceId) {
1855
+ console.error("Bind was not completed \u2014 device-agent will not start without a platform binding.");
1856
+ console.error("Run `beeos device attach` again to retry.");
1857
+ return;
1858
+ }
1859
+ await withDeviceLock(async () => {
1860
+ const state = await loadDeviceState();
1861
+ const idx = state.devices.findIndex((d) => d.serial === serial);
1862
+ if (idx >= 0) {
1863
+ if (isProcessAlive(state.devices[idx].pid)) {
1864
+ console.log(`Device ${serial} is already attached (pid ${state.devices[idx].pid})`);
1865
+ return;
1866
+ }
1867
+ console.error(` Cleaning stale entry for ${serial} (pid ${state.devices[idx].pid} no longer alive)`);
1868
+ state.devices.splice(idx, 1);
1869
+ }
1870
+ const httpPort = nextHttpPort(state, cfg.device.http_port);
1871
+ const pid = await deviceRuntime.spawnForDevice(
1872
+ serial,
1873
+ agentBin,
1874
+ cfg.platform.bridge_url,
1875
+ cfg.device.http_enabled,
1876
+ httpPort
1877
+ );
1878
+ await sleep3(2e3);
1879
+ if (!isProcessAlive(pid)) {
1880
+ const p2 = getPlatformAdapter();
1881
+ const logPath2 = p2.joinPath(beeoHome(), "logs", `device-${serial}.log`);
1882
+ const tail = await readLogTail(logPath2, 20);
1883
+ throw new Error(
1884
+ `device-agent for ${serial} exited immediately (pid ${pid}). Check logs:
1885
+ ${logPath2}
1886
+
1887
+ ${tail}`
1888
+ );
1889
+ }
1890
+ const keyFile = deviceRuntime.deviceKeyPath(serial);
1891
+ state.devices.push({
1892
+ serial,
1893
+ name,
1894
+ pid,
1895
+ instance_id: instanceId,
1896
+ key_file: keyFile,
1897
+ http_port: httpPort
1898
+ });
1899
+ await saveDeviceState(state);
1900
+ const p = getPlatformAdapter();
1901
+ const logPath = p.joinPath(beeoHome(), "logs", `device-${serial}.log`);
1902
+ console.log(` logs: ${logPath}`);
1903
+ console.log(
1904
+ `Attached device ${serial} (${name}) \u2014 pid ${pid} (http :${httpPort}) \u2014 instance ${instanceId}`
1905
+ );
1906
+ });
1907
+ }
1908
+ async function attachAll(cfg, reporter) {
1909
+ const devices = await deviceRuntime.listAdbDevices();
1910
+ if (devices.length === 0) {
1911
+ console.log("No ADB devices found");
1912
+ return;
1913
+ }
1914
+ const agentBin = await deviceRuntime.ensureAgent(reporter);
1915
+ reporter.stop();
1916
+ await withDeviceLock(async () => {
1917
+ const state = await loadDeviceState();
1918
+ state.devices = state.devices.filter((d) => isProcessAlive(d.pid));
1919
+ const alreadyAttached = new Set(state.devices.map((d) => d.serial));
1920
+ let count = 0;
1921
+ for (const device of devices) {
1922
+ if (alreadyAttached.has(device.serial)) continue;
1923
+ const httpPort = nextHttpPort(state, cfg.device.http_port);
1924
+ try {
1925
+ const pubkeyB64 = await deviceRuntime.ensureKeyAndGetPubkey(device.serial);
1926
+ const instanceId = await tryBindDevice(pubkeyB64, device.serial, cfg);
1927
+ if (!instanceId) {
1928
+ console.error(` Skipping ${device.serial} \u2014 bind not completed`);
1929
+ continue;
1930
+ }
1931
+ const pid = await deviceRuntime.spawnForDevice(
1932
+ device.serial,
1933
+ agentBin,
1934
+ cfg.platform.bridge_url,
1935
+ cfg.device.http_enabled,
1936
+ httpPort
1937
+ );
1938
+ await sleep3(2e3);
1939
+ if (!isProcessAlive(pid)) {
1940
+ const p = getPlatformAdapter();
1941
+ const logPath = p.joinPath(beeoHome(), "logs", `device-${device.serial}.log`);
1942
+ const tail = await readLogTail(logPath, 10);
1943
+ console.error(`device-agent for ${device.serial} exited immediately (pid ${pid}): ${tail}`);
1944
+ continue;
1945
+ }
1946
+ const keyFile = deviceRuntime.deviceKeyPath(device.serial);
1947
+ state.devices.push({
1948
+ serial: device.serial,
1949
+ name: device.serial,
1950
+ pid,
1951
+ instance_id: instanceId,
1952
+ key_file: keyFile,
1953
+ http_port: httpPort
1954
+ });
1955
+ console.log(`Attached device ${device.serial} \u2014 pid ${pid} (http :${httpPort})`);
1956
+ count++;
1957
+ } catch (e) {
1958
+ console.error(`Failed to attach ${device.serial}: ${e}`);
1959
+ }
1960
+ }
1961
+ await saveDeviceState(state);
1962
+ console.log(`Attached ${count} new device(s)`);
1963
+ });
1964
+ }
1965
+ async function detach(options) {
1966
+ const cfg = await loadOrCreateConfig();
1967
+ await withDeviceLock(async () => {
1968
+ const state = await loadDeviceState();
1969
+ if (options.all) {
1970
+ for (const entry2 of state.devices) {
1971
+ killProcess(entry2.pid);
1972
+ await notifyOffline(cfg.platform.api_url, entry2.instance_id);
1973
+ }
1974
+ const count = state.devices.length;
1975
+ state.devices = [];
1976
+ await saveDeviceState(state);
1977
+ console.log(`Detached ${count} device(s)`);
1978
+ return;
1979
+ }
1980
+ if (!options.serial) {
1981
+ throw new Error("Specify --serial or use --all");
1982
+ }
1983
+ const idx = state.devices.findIndex((d) => d.serial === options.serial);
1984
+ if (idx < 0) throw new Error(`Device ${options.serial} not found`);
1985
+ const entry = state.devices.splice(idx, 1)[0];
1986
+ killProcess(entry.pid);
1987
+ await notifyOffline(cfg.platform.api_url, entry.instance_id);
1988
+ await saveDeviceState(state);
1989
+ console.log(`Detached device ${options.serial}`);
1990
+ });
1991
+ }
1992
+ async function notifyOffline(apiUrl, instanceId) {
1993
+ if (!instanceId) return;
1994
+ try {
1995
+ await markInstanceOffline(apiUrl, instanceId);
1996
+ } catch {
1997
+ }
1998
+ }
1999
+ async function list(options) {
2000
+ if (options.local) {
2001
+ const state = await loadDeviceState();
2002
+ if (state.devices.length === 0) {
2003
+ console.log("No attached devices");
2004
+ return;
2005
+ }
2006
+ console.log(`${"SERIAL".padEnd(16)} ${"NAME".padEnd(20)} ${"STATUS".padEnd(12)} PID`);
2007
+ for (const entry of state.devices) {
2008
+ const status = isProcessAlive(entry.pid) ? "running" : "stopped";
2009
+ console.log(
2010
+ `${entry.serial.padEnd(16)} ${entry.name.padEnd(20)} ${status.padEnd(12)} ${entry.pid}`
2011
+ );
2012
+ }
2013
+ } else {
2014
+ console.log("Remote device listing requires platform API (use --local for local devices)");
2015
+ }
2016
+ }
2017
+ async function exec(prompt, options) {
2018
+ const state = await loadDeviceState();
2019
+ const entry = options.serial ? state.devices.find((d) => d.serial === options.serial) : state.devices[0];
2020
+ if (!entry) {
2021
+ throw new Error(options.serial ? `Device ${options.serial} not attached` : "No attached devices");
2022
+ }
2023
+ if (!entry.http_port) {
2024
+ throw new Error(
2025
+ `Device ${entry.serial} has no local HTTP port configured.
2026
+ Enable http_enabled in ~/.beeos/config.toml and re-attach.`
2027
+ );
2028
+ }
2029
+ const p = getPlatformAdapter();
2030
+ const url = `http://127.0.0.1:${entry.http_port}/prompt`;
2031
+ console.log(`-> ${entry.serial} (local :${entry.http_port}): ${prompt}`);
2032
+ const resp = await p.fetch(url, {
2033
+ method: "POST",
2034
+ headers: { "Content-Type": "application/json" },
2035
+ body: JSON.stringify({ prompt })
2036
+ });
2037
+ if (!resp.ok) {
2038
+ const body2 = await resp.text().catch(() => "");
2039
+ throw new Error(`device-agent returned ${resp.status} \u2014 ${body2}`);
2040
+ }
2041
+ const body = await resp.json();
2042
+ console.log(JSON.stringify(body, null, 2));
2043
+ }
2044
+ async function upgrade() {
2045
+ const reporter = new CliReporter();
2046
+ await deviceRuntime.upgradeAgent(reporter);
2047
+ }
2048
+ async function tryBindDevice(pubkeyB64, name, cfg) {
2049
+ const fp = fingerprintFromB64(pubkeyB64);
2050
+ try {
2051
+ const resp = await agentBind(
2052
+ cfg.platform.api_url,
2053
+ pubkeyB64,
2054
+ fp,
2055
+ "device",
2056
+ name
2057
+ );
2058
+ if (resp.status === "bound") {
2059
+ console.log(` Already bound \u2014 instance: ${resp.instance_id}`);
2060
+ return resp.instance_id ?? void 0;
2061
+ }
2062
+ if (resp.bind_id) {
2063
+ const bindUrl = buildBindUrl(cfg.platform.dashboard_base_url, resp.bind_id);
2064
+ await presentBindUrl(bindUrl, false);
2065
+ console.log("");
2066
+ console.log(" Waiting for bind approval (timeout: 10min)...");
2067
+ try {
2068
+ const instanceId = await pollUntilBound(
2069
+ cfg.platform.api_url,
2070
+ resp.bind_id,
2071
+ 6e5
2072
+ );
2073
+ console.log(` Bind confirmed! Instance: ${instanceId}`);
2074
+ return instanceId;
2075
+ } catch (e) {
2076
+ console.error(` Bind polling stopped: ${e}`);
2077
+ }
2078
+ }
2079
+ return void 0;
2080
+ } catch (e) {
2081
+ console.error(` Platform bind failed: ${e}`);
2082
+ return void 0;
2083
+ }
2084
+ }
2085
+ function nextHttpPort(state, base) {
2086
+ const used = new Set(state.devices.map((d) => d.http_port));
2087
+ let port = base;
2088
+ while (used.has(port)) port++;
2089
+ return port;
2090
+ }
2091
+ async function readLogTail(path2, lines) {
2092
+ const p = getPlatformAdapter();
2093
+ try {
2094
+ const content = await p.readFile(path2);
2095
+ const allLines = content.split("\n");
2096
+ const start = Math.max(0, allLines.length - lines);
2097
+ return allLines.slice(start).join("\n");
2098
+ } catch {
2099
+ return "(could not read log file)";
2100
+ }
2101
+ }
2102
+ function sleep3(ms) {
2103
+ return new Promise((resolve) => setTimeout(resolve, ms));
2104
+ }
2105
+
2106
+ // src/index.ts
2107
+ setPlatformAdapter(new NodePlatformAdapter());
2108
+ var program = new Command();
2109
+ program.name("beeos").version("1.0.0").description("BeeOS \u2014 run AI agents from your desktop");
2110
+ program.command("start").description("Install and start an agent").argument("<agent_type>", "Agent type to start (e.g. openclaw)").option("--version <version>", "Pin to a specific version instead of latest").option("--force", "Skip confirmation prompts", false).option("--json", "Output machine-readable JSON to stdout (implies --force)", false).option("--no-browser", "Don't auto-open browser for bind confirmation").action(run);
2111
+ program.command("stop").description("Stop a running agent").argument("<agent_type>", "Agent type to stop").action(run2);
2112
+ program.command("status").description("Show status of managed agents").action(run3);
2113
+ program.command("update").description("Update an agent to the latest version").argument("<agent_type>", "Agent type to update").action(run4);
2114
+ program.command("bind-status").description("Check agent binding status").argument("<bind_id>", "Bind session ID returned by `beeos start --json`").option("--json", "Output machine-readable JSON", false).action(run5);
2115
+ var deviceCmd = program.command("device").description("Manage device agents (Android/Desktop/ChromeOS)");
2116
+ deviceCmd.command("attach").description("Attach an ADB-connected device as an AI agent").option("--serial <serial>", "ADB device serial number").option("--name <name>", "Human-readable device name").option("--all", "Attach all connected ADB devices", false).action(attach);
2117
+ deviceCmd.command("detach").description("Detach a device agent").option("--serial <serial>", "ADB device serial number").option("--all", "Detach all devices", false).action(detach);
2118
+ deviceCmd.command("list").description("List attached devices").option("--local", "Only show locally running device agents", false).action(list);
2119
+ deviceCmd.command("exec").description("Send a natural language command to a device").option("--serial <serial>", "ADB device serial number").argument("<prompt>", "The command/prompt to execute").action(exec);
2120
+ deviceCmd.command("upgrade").description("Upgrade the device-agent to the latest version").action(upgrade);
2121
+ program.parseAsync(process.argv).catch((err) => {
2122
+ console.error(err.message);
2123
+ process.exit(1);
2124
+ });