@beeos-ai/cli 1.0.4 → 1.0.6
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/README.md +74 -3
- package/dist/index.js +3431 -592
- package/package.json +12 -10
- package/scripts/install.ps1 +255 -0
- package/scripts/install.sh +373 -0
package/dist/index.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
3
10
|
|
|
4
11
|
// ../core/dist/platform-adapter.js
|
|
5
|
-
var _adapter = null;
|
|
6
12
|
function setPlatformAdapter(adapter) {
|
|
7
13
|
_adapter = adapter;
|
|
8
14
|
}
|
|
@@ -12,21 +18,30 @@ function getPlatformAdapter() {
|
|
|
12
18
|
}
|
|
13
19
|
return _adapter;
|
|
14
20
|
}
|
|
21
|
+
var _adapter;
|
|
22
|
+
var init_platform_adapter = __esm({
|
|
23
|
+
"../core/dist/platform-adapter.js"() {
|
|
24
|
+
"use strict";
|
|
25
|
+
_adapter = null;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
15
28
|
|
|
16
29
|
// ../core/dist/progress.js
|
|
17
|
-
var noopReporter
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
30
|
+
var noopReporter;
|
|
31
|
+
var init_progress = __esm({
|
|
32
|
+
"../core/dist/progress.js"() {
|
|
33
|
+
"use strict";
|
|
34
|
+
noopReporter = {
|
|
35
|
+
onStatus() {
|
|
36
|
+
},
|
|
37
|
+
onComplete() {
|
|
38
|
+
}
|
|
39
|
+
};
|
|
21
40
|
}
|
|
22
|
-
};
|
|
41
|
+
});
|
|
23
42
|
|
|
24
43
|
// ../core/dist/config.js
|
|
25
44
|
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
45
|
function defaultPlatformConfig() {
|
|
31
46
|
return {
|
|
32
47
|
api_url: DEFAULT_API_URL,
|
|
@@ -47,8 +62,36 @@ function defaultConfig() {
|
|
|
47
62
|
device: defaultDeviceConfig()
|
|
48
63
|
};
|
|
49
64
|
}
|
|
65
|
+
function resolveAgentGatewayUrl(cfg) {
|
|
66
|
+
const envOverride = globalThis.process?.env?.BEEOS_AGENT_GATEWAY_URL;
|
|
67
|
+
if (envOverride && envOverride.trim() !== "")
|
|
68
|
+
return envOverride.trim();
|
|
69
|
+
const configured = cfg.platform.agent_gateway_url?.trim() ?? "";
|
|
70
|
+
const apiUrl = cfg.platform.api_url?.trim() ?? "";
|
|
71
|
+
const apiIsLocal = isLocalhostUrl(apiUrl);
|
|
72
|
+
const configuredIsDefault = configured === DEFAULT_AGENT_GATEWAY_URL || configured === "";
|
|
73
|
+
if (apiIsLocal && configuredIsDefault) {
|
|
74
|
+
return "http://localhost:8083";
|
|
75
|
+
}
|
|
76
|
+
return configured || DEFAULT_AGENT_GATEWAY_URL;
|
|
77
|
+
}
|
|
78
|
+
function isLocalhostUrl(url) {
|
|
79
|
+
if (!url)
|
|
80
|
+
return false;
|
|
81
|
+
try {
|
|
82
|
+
const u = new URL(url);
|
|
83
|
+
const h = u.hostname;
|
|
84
|
+
return h === "localhost" || h === "127.0.0.1" || h === "0.0.0.0" || h.endsWith(".local") || h.endsWith(".localhost");
|
|
85
|
+
} catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
50
89
|
function beeoHome() {
|
|
51
90
|
const p = getPlatformAdapter();
|
|
91
|
+
const envOverride = globalThis.process?.env?.BEEOS_HOME;
|
|
92
|
+
if (envOverride && envOverride.trim() !== "") {
|
|
93
|
+
return envOverride.trim();
|
|
94
|
+
}
|
|
52
95
|
return p.joinPath(p.homeDir(), ".beeos");
|
|
53
96
|
}
|
|
54
97
|
async function ensureDirs() {
|
|
@@ -81,9 +124,9 @@ function pidFile(agentFramework) {
|
|
|
81
124
|
}
|
|
82
125
|
async function loadOrCreateConfig() {
|
|
83
126
|
const p = getPlatformAdapter();
|
|
84
|
-
const
|
|
85
|
-
if (await p.exists(
|
|
86
|
-
const raw = await p.readFile(
|
|
127
|
+
const path9 = configPath();
|
|
128
|
+
if (await p.exists(path9)) {
|
|
129
|
+
const raw = await p.readFile(path9);
|
|
87
130
|
const parsed = TOML.parse(raw);
|
|
88
131
|
const cfg2 = mergeWithDefaults(parsed);
|
|
89
132
|
return cfg2;
|
|
@@ -94,8 +137,8 @@ async function loadOrCreateConfig() {
|
|
|
94
137
|
}
|
|
95
138
|
async function saveConfig(cfg) {
|
|
96
139
|
const p = getPlatformAdapter();
|
|
97
|
-
const
|
|
98
|
-
await p.mkdir(p.dirname(
|
|
140
|
+
const path9 = configPath();
|
|
141
|
+
await p.mkdir(p.dirname(path9));
|
|
99
142
|
const serializable = {
|
|
100
143
|
platform: {
|
|
101
144
|
api_url: cfg.platform.api_url,
|
|
@@ -109,7 +152,7 @@ async function saveConfig(cfg) {
|
|
|
109
152
|
}
|
|
110
153
|
};
|
|
111
154
|
const raw = TOML.stringify(serializable);
|
|
112
|
-
await p.writeFile(
|
|
155
|
+
await p.writeFile(path9, raw);
|
|
113
156
|
}
|
|
114
157
|
function mergeWithDefaults(parsed) {
|
|
115
158
|
const platform = parsed.platform ?? {};
|
|
@@ -133,23 +176,23 @@ function mergeWithDefaults(parsed) {
|
|
|
133
176
|
}
|
|
134
177
|
async function loadBindingInfo() {
|
|
135
178
|
const p = getPlatformAdapter();
|
|
136
|
-
const
|
|
137
|
-
if (!await p.exists(
|
|
179
|
+
const path9 = bindingPath();
|
|
180
|
+
if (!await p.exists(path9)) {
|
|
138
181
|
return migratePairedToBinding();
|
|
139
182
|
}
|
|
140
|
-
const raw = await p.readFile(
|
|
183
|
+
const raw = await p.readFile(path9);
|
|
141
184
|
return JSON.parse(raw);
|
|
142
185
|
}
|
|
143
186
|
async function saveBindingInfo(info) {
|
|
144
187
|
const p = getPlatformAdapter();
|
|
145
|
-
const
|
|
146
|
-
await p.writeFile(
|
|
188
|
+
const path9 = bindingPath();
|
|
189
|
+
await p.writeFile(path9, JSON.stringify(info, null, 2));
|
|
147
190
|
}
|
|
148
191
|
async function removeBindingInfo() {
|
|
149
192
|
const p = getPlatformAdapter();
|
|
150
|
-
const
|
|
151
|
-
if (await p.exists(
|
|
152
|
-
await p.rm(
|
|
193
|
+
const path9 = bindingPath();
|
|
194
|
+
if (await p.exists(path9)) {
|
|
195
|
+
await p.rm(path9);
|
|
153
196
|
}
|
|
154
197
|
}
|
|
155
198
|
async function migratePairedToBinding() {
|
|
@@ -174,26 +217,26 @@ async function migratePairedToBinding() {
|
|
|
174
217
|
}
|
|
175
218
|
async function loadOrCreateGatewayToken() {
|
|
176
219
|
const p = getPlatformAdapter();
|
|
177
|
-
const
|
|
178
|
-
if (await p.exists(
|
|
179
|
-
const token2 = (await p.readFile(
|
|
220
|
+
const path9 = p.joinPath(beeoHome(), "gateway_token");
|
|
221
|
+
if (await p.exists(path9)) {
|
|
222
|
+
const token2 = (await p.readFile(path9)).trim();
|
|
180
223
|
if (token2)
|
|
181
224
|
return token2;
|
|
182
225
|
}
|
|
183
226
|
const token = crypto.randomUUID();
|
|
184
|
-
await p.mkdir(p.dirname(
|
|
185
|
-
await p.writeFile(
|
|
227
|
+
await p.mkdir(p.dirname(path9));
|
|
228
|
+
await p.writeFile(path9, token);
|
|
186
229
|
if (p.platform() !== "win32") {
|
|
187
|
-
await p.chmod(
|
|
230
|
+
await p.chmod(path9, 384);
|
|
188
231
|
}
|
|
189
232
|
return token;
|
|
190
233
|
}
|
|
191
234
|
async function readPid(agentFramework) {
|
|
192
235
|
const p = getPlatformAdapter();
|
|
193
|
-
const
|
|
194
|
-
if (!await p.exists(
|
|
236
|
+
const path9 = pidFile(agentFramework);
|
|
237
|
+
if (!await p.exists(path9))
|
|
195
238
|
return null;
|
|
196
|
-
const raw = (await p.readFile(
|
|
239
|
+
const raw = (await p.readFile(path9)).trim();
|
|
197
240
|
const pid = parseInt(raw, 10);
|
|
198
241
|
return isNaN(pid) ? null : pid;
|
|
199
242
|
}
|
|
@@ -203,18 +246,28 @@ async function writePid(agentFramework, pid) {
|
|
|
203
246
|
}
|
|
204
247
|
async function removePid(agentFramework) {
|
|
205
248
|
const p = getPlatformAdapter();
|
|
206
|
-
const
|
|
207
|
-
if (await p.exists(
|
|
208
|
-
await p.rm(
|
|
249
|
+
const path9 = pidFile(agentFramework);
|
|
250
|
+
if (await p.exists(path9)) {
|
|
251
|
+
await p.rm(path9);
|
|
209
252
|
}
|
|
210
253
|
}
|
|
254
|
+
var DEFAULT_API_URL, DEFAULT_AGENT_GATEWAY_URL, DEFAULT_DASHBOARD_URL, DEFAULT_BRIDGE_URL;
|
|
255
|
+
var init_config = __esm({
|
|
256
|
+
"../core/dist/config.js"() {
|
|
257
|
+
"use strict";
|
|
258
|
+
init_platform_adapter();
|
|
259
|
+
DEFAULT_API_URL = "https://api.beeos.ai";
|
|
260
|
+
DEFAULT_AGENT_GATEWAY_URL = "https://agent-gw.beeos.ai";
|
|
261
|
+
DEFAULT_DASHBOARD_URL = "https://beeos.ai";
|
|
262
|
+
DEFAULT_BRIDGE_URL = "wss://bridge-sg.beeos.ai";
|
|
263
|
+
}
|
|
264
|
+
});
|
|
211
265
|
|
|
212
266
|
// ../core/dist/identity/keypair.js
|
|
213
267
|
import * as ed from "@noble/ed25519";
|
|
214
268
|
import { sha512 } from "@noble/hashes/sha512";
|
|
215
269
|
import { sha256 } from "@noble/hashes/sha256";
|
|
216
270
|
import { bytesToHex } from "@noble/hashes/utils";
|
|
217
|
-
ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));
|
|
218
271
|
function toBase64(bytes) {
|
|
219
272
|
let binary = "";
|
|
220
273
|
for (let i = 0; i < bytes.length; i++)
|
|
@@ -239,9 +292,9 @@ function fingerprintFromB64(pubkeyB64) {
|
|
|
239
292
|
}
|
|
240
293
|
async function loadOrCreateIdentity() {
|
|
241
294
|
const p = getPlatformAdapter();
|
|
242
|
-
const
|
|
243
|
-
if (await p.exists(
|
|
244
|
-
return loadFromFile(
|
|
295
|
+
const path9 = keypairPath();
|
|
296
|
+
if (await p.exists(path9)) {
|
|
297
|
+
return loadFromFile(path9);
|
|
245
298
|
}
|
|
246
299
|
const id = generate();
|
|
247
300
|
await save(id);
|
|
@@ -278,9 +331,9 @@ function fingerprintFilePath() {
|
|
|
278
331
|
const p = getPlatformAdapter();
|
|
279
332
|
return p.joinPath(beeoHome(), "identity", "fingerprint");
|
|
280
333
|
}
|
|
281
|
-
async function loadFromFile(
|
|
334
|
+
async function loadFromFile(path9) {
|
|
282
335
|
const p = getPlatformAdapter();
|
|
283
|
-
const raw = await p.readFile(
|
|
336
|
+
const raw = await p.readFile(path9);
|
|
284
337
|
const stored = JSON.parse(raw);
|
|
285
338
|
const privateKey = fromBase64(stored.privateKey);
|
|
286
339
|
if (privateKey.length !== 32) {
|
|
@@ -309,6 +362,14 @@ async function save(id) {
|
|
|
309
362
|
const fpPath = fingerprintFilePath();
|
|
310
363
|
await p.writeFile(fpPath, fingerprint(id));
|
|
311
364
|
}
|
|
365
|
+
var init_keypair = __esm({
|
|
366
|
+
"../core/dist/identity/keypair.js"() {
|
|
367
|
+
"use strict";
|
|
368
|
+
init_platform_adapter();
|
|
369
|
+
init_config();
|
|
370
|
+
ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));
|
|
371
|
+
}
|
|
372
|
+
});
|
|
312
373
|
|
|
313
374
|
// ../core/dist/identity/qr.js
|
|
314
375
|
import QRCode from "qrcode";
|
|
@@ -360,6 +421,11 @@ async function qrToTerminal(url) {
|
|
|
360
421
|
lines.push(blankLine);
|
|
361
422
|
return lines.join("\n");
|
|
362
423
|
}
|
|
424
|
+
var init_qr = __esm({
|
|
425
|
+
"../core/dist/identity/qr.js"() {
|
|
426
|
+
"use strict";
|
|
427
|
+
}
|
|
428
|
+
});
|
|
363
429
|
|
|
364
430
|
// ../core/dist/platform/client.js
|
|
365
431
|
function buildBindUrl(dashboardBaseUrl, bindId) {
|
|
@@ -418,6 +484,13 @@ async function getBindStatus(apiUrl, bindId) {
|
|
|
418
484
|
}
|
|
419
485
|
return await resp.json();
|
|
420
486
|
}
|
|
487
|
+
var init_client = __esm({
|
|
488
|
+
"../core/dist/platform/client.js"() {
|
|
489
|
+
"use strict";
|
|
490
|
+
init_platform_adapter();
|
|
491
|
+
init_keypair();
|
|
492
|
+
}
|
|
493
|
+
});
|
|
421
494
|
|
|
422
495
|
// ../core/dist/platform/npm.js
|
|
423
496
|
async function fetchNpmPackageInfo(packageName) {
|
|
@@ -441,6 +514,12 @@ async function downloadTarball(url) {
|
|
|
441
514
|
const buf = await resp.arrayBuffer();
|
|
442
515
|
return new Uint8Array(buf);
|
|
443
516
|
}
|
|
517
|
+
var init_npm = __esm({
|
|
518
|
+
"../core/dist/platform/npm.js"() {
|
|
519
|
+
"use strict";
|
|
520
|
+
init_platform_adapter();
|
|
521
|
+
}
|
|
522
|
+
});
|
|
444
523
|
|
|
445
524
|
// ../core/dist/process.js
|
|
446
525
|
function killProcess(pid) {
|
|
@@ -464,6 +543,12 @@ function shouldOpenBrowser() {
|
|
|
464
543
|
return true;
|
|
465
544
|
return !!(p.env("DISPLAY") || p.env("WAYLAND_DISPLAY"));
|
|
466
545
|
}
|
|
546
|
+
var init_process = __esm({
|
|
547
|
+
"../core/dist/process.js"() {
|
|
548
|
+
"use strict";
|
|
549
|
+
init_platform_adapter();
|
|
550
|
+
}
|
|
551
|
+
});
|
|
467
552
|
|
|
468
553
|
// ../core/dist/bind.js
|
|
469
554
|
async function presentBindUrl(bindUrl, forceHeadless, log = console.log) {
|
|
@@ -523,10 +608,17 @@ async function printQr(url, log) {
|
|
|
523
608
|
function sleep(ms) {
|
|
524
609
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
525
610
|
}
|
|
611
|
+
var init_bind = __esm({
|
|
612
|
+
"../core/dist/bind.js"() {
|
|
613
|
+
"use strict";
|
|
614
|
+
init_platform_adapter();
|
|
615
|
+
init_client();
|
|
616
|
+
init_process();
|
|
617
|
+
init_qr();
|
|
618
|
+
}
|
|
619
|
+
});
|
|
526
620
|
|
|
527
621
|
// ../core/dist/device-setup.js
|
|
528
|
-
var MIN_PYTHON_VERSION = [3, 11];
|
|
529
|
-
var RECOMMENDED_AGENT_VERSION = "0.2.7";
|
|
530
622
|
function agentBinCommandAndArgs(bin) {
|
|
531
623
|
if (bin.type === "executable") {
|
|
532
624
|
return { cmd: bin.path, args: [] };
|
|
@@ -771,117 +863,257 @@ async function runCmd(cmd, args) {
|
|
|
771
863
|
${stderr}`);
|
|
772
864
|
}
|
|
773
865
|
}
|
|
866
|
+
var MIN_PYTHON_VERSION, RECOMMENDED_AGENT_VERSION;
|
|
867
|
+
var init_device_setup = __esm({
|
|
868
|
+
"../core/dist/device-setup.js"() {
|
|
869
|
+
"use strict";
|
|
870
|
+
init_platform_adapter();
|
|
871
|
+
init_config();
|
|
872
|
+
MIN_PYTHON_VERSION = [3, 11];
|
|
873
|
+
RECOMMENDED_AGENT_VERSION = "0.2.7";
|
|
874
|
+
}
|
|
875
|
+
});
|
|
774
876
|
|
|
775
|
-
// ../core/dist/
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
877
|
+
// ../core/dist/adb-setup.js
|
|
878
|
+
function managedAdbPath() {
|
|
879
|
+
const p = getPlatformAdapter();
|
|
880
|
+
const exe = p.platform() === "win32" ? "adb.exe" : "adb";
|
|
881
|
+
return p.joinPath(beeoHome(), "bin", "platform-tools", exe);
|
|
882
|
+
}
|
|
883
|
+
async function findAdb() {
|
|
884
|
+
const p = getPlatformAdapter();
|
|
885
|
+
const override = process.env?.BEEOS_ADB_BIN;
|
|
886
|
+
if (override && await p.exists(override))
|
|
887
|
+
return override;
|
|
888
|
+
const whichCmd = p.platform() === "win32" ? "where" : "which";
|
|
889
|
+
try {
|
|
890
|
+
const result = await p.exec(whichCmd, ["adb"]);
|
|
891
|
+
if (result.code === 0 && result.stdout.trim()) {
|
|
892
|
+
return result.stdout.trim().split("\n")[0].trim();
|
|
893
|
+
}
|
|
894
|
+
} catch {
|
|
895
|
+
}
|
|
896
|
+
const managed = managedAdbPath();
|
|
897
|
+
if (await p.exists(managed))
|
|
898
|
+
return managed;
|
|
899
|
+
return null;
|
|
900
|
+
}
|
|
901
|
+
async function installAdb(progress) {
|
|
902
|
+
const p = getPlatformAdapter();
|
|
903
|
+
const zipName = adbZipName();
|
|
904
|
+
if (!zipName) {
|
|
905
|
+
throw new Error(`No platform-tools archive for ${p.platform()}/${process.arch}. Install 'adb' manually and set $BEEOS_ADB_BIN, or add it to PATH.`);
|
|
906
|
+
}
|
|
907
|
+
const url = `${PLATFORM_TOOLS_BASE}/${zipName}`;
|
|
908
|
+
progress.onStatus(`Downloading Android platform-tools (${zipName})...`);
|
|
909
|
+
const resp = await p.fetch(url);
|
|
910
|
+
if (!resp.ok) {
|
|
911
|
+
throw new Error(`platform-tools download failed: ${resp.status} ${resp.statusText}`);
|
|
912
|
+
}
|
|
913
|
+
const data = new Uint8Array(await resp.arrayBuffer());
|
|
914
|
+
const binDir = p.joinPath(beeoHome(), "bin");
|
|
915
|
+
await p.mkdir(binDir);
|
|
916
|
+
const tmpZip = p.joinPath(binDir, `platform-tools.download.${Date.now()}.zip`);
|
|
917
|
+
await p.writeFileBytes(tmpZip, data);
|
|
918
|
+
try {
|
|
919
|
+
progress.onStatus("Extracting platform-tools...");
|
|
920
|
+
if (p.platform() === "win32") {
|
|
921
|
+
const exec2 = await p.exec("powershell", [
|
|
922
|
+
"-NoProfile",
|
|
923
|
+
"-Command",
|
|
924
|
+
`Expand-Archive -Force -Path "${tmpZip}" -DestinationPath "${binDir}"`
|
|
925
|
+
]);
|
|
926
|
+
if (exec2.code !== 0) {
|
|
927
|
+
throw new Error(`Expand-Archive failed: ${exec2.stderr || exec2.stdout}`.trim());
|
|
928
|
+
}
|
|
929
|
+
} else {
|
|
930
|
+
const exec2 = await p.exec("unzip", ["-o", "-q", tmpZip, "-d", binDir]);
|
|
931
|
+
if (exec2.code !== 0) {
|
|
932
|
+
throw new Error(`unzip failed: ${exec2.stderr || exec2.stdout}`.trim());
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
const adb = managedAdbPath();
|
|
936
|
+
if (!await p.exists(adb)) {
|
|
937
|
+
throw new Error(`platform-tools zip did not contain ${p.basename(adb)}`);
|
|
938
|
+
}
|
|
939
|
+
if (p.platform() !== "win32") {
|
|
940
|
+
try {
|
|
941
|
+
await p.chmod(adb, 493);
|
|
942
|
+
} catch {
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
progress.onComplete(`adb installed: ${adb}`);
|
|
946
|
+
return adb;
|
|
947
|
+
} finally {
|
|
784
948
|
try {
|
|
785
|
-
|
|
786
|
-
const { cmd } = agentBinCommandAndArgs(bin);
|
|
787
|
-
return { binary: cmd, version: "", home: beeoHome() };
|
|
949
|
+
await p.rm(tmpZip);
|
|
788
950
|
} catch {
|
|
789
|
-
return null;
|
|
790
951
|
}
|
|
791
|
-
},
|
|
792
|
-
async ensure(_version, progress) {
|
|
793
|
-
const bin = await ensureDeviceAgent(progress);
|
|
794
|
-
const { cmd } = agentBinCommandAndArgs(bin);
|
|
795
|
-
return { binary: cmd, version: "", home: beeoHome() };
|
|
796
|
-
},
|
|
797
|
-
async launch(_ctx) {
|
|
798
|
-
throw new Error("DeviceRuntime.launch() should not be called directly \u2014 use spawnForDevice() instead");
|
|
799
|
-
},
|
|
800
|
-
async isHealthy() {
|
|
801
|
-
return false;
|
|
802
|
-
},
|
|
803
|
-
async stop() {
|
|
804
|
-
},
|
|
805
|
-
async update(progress) {
|
|
806
|
-
await upgradeDeviceAgent(progress);
|
|
807
|
-
},
|
|
808
|
-
// ── Device-specific extensions ──────────────────────────
|
|
809
|
-
async ensureAgent(progress) {
|
|
810
|
-
return ensureDeviceAgent(progress);
|
|
811
|
-
},
|
|
812
|
-
async upgradeAgent(progress) {
|
|
813
|
-
return upgradeDeviceAgent(progress);
|
|
814
|
-
},
|
|
815
|
-
async listAdbDevices() {
|
|
816
|
-
const p = getPlatformAdapter();
|
|
817
|
-
const result = await p.exec("adb", ["devices"]);
|
|
818
|
-
if (result.code !== 0) {
|
|
819
|
-
throw new Error("adb not found \u2014 install Android platform-tools");
|
|
820
|
-
}
|
|
821
|
-
const devices = [];
|
|
822
|
-
const lines = result.stdout.split("\n").slice(1);
|
|
823
|
-
for (const line of lines) {
|
|
824
|
-
const parts = line.trim().split(/\s+/);
|
|
825
|
-
if (parts.length >= 2 && parts[1] === "device") {
|
|
826
|
-
devices.push({ serial: parts[0] });
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
return devices;
|
|
830
|
-
},
|
|
831
|
-
async detectSingleDevice() {
|
|
832
|
-
const devices = await this.listAdbDevices();
|
|
833
|
-
if (devices.length === 0) {
|
|
834
|
-
throw new Error("No ADB devices found. Connect a device and try again.");
|
|
835
|
-
}
|
|
836
|
-
if (devices.length > 1) {
|
|
837
|
-
const serials = devices.map((d) => d.serial).join(", ");
|
|
838
|
-
throw new Error(`Multiple ADB devices found: ${serials}. Use --serial to specify one.`);
|
|
839
|
-
}
|
|
840
|
-
return devices[0].serial;
|
|
841
|
-
},
|
|
842
|
-
async spawnForDevice(serial, agentBin, bridgeUrl, httpEnabled, httpPort) {
|
|
843
|
-
const p = getPlatformAdapter();
|
|
844
|
-
const keyFile = this.deviceKeyPath(serial);
|
|
845
|
-
await ensureDeviceKey(keyFile);
|
|
846
|
-
if (!bridgeUrl) {
|
|
847
|
-
throw new Error("bridge_url is not configured in ~/.beeos/config.toml.\nSet [platform] bridge_url (e.g. wss://bridge.beeos.ai).");
|
|
848
|
-
}
|
|
849
|
-
const { cmd, args: baseArgs } = agentBinCommandAndArgs(agentBin);
|
|
850
|
-
const args = [
|
|
851
|
-
...baseArgs,
|
|
852
|
-
"--key",
|
|
853
|
-
keyFile,
|
|
854
|
-
"--adb",
|
|
855
|
-
serial,
|
|
856
|
-
"--bridge",
|
|
857
|
-
bridgeUrl
|
|
858
|
-
];
|
|
859
|
-
if (httpEnabled) {
|
|
860
|
-
args.push("--http", "--http-port", String(httpPort));
|
|
861
|
-
}
|
|
862
|
-
const logDir = p.joinPath(beeoHome(), "logs");
|
|
863
|
-
await p.mkdir(logDir);
|
|
864
|
-
const logFile = p.joinPath(logDir, `device-${serial}.log`);
|
|
865
|
-
const result = await p.spawn(cmd, args, {
|
|
866
|
-
detached: true,
|
|
867
|
-
stdoutFile: logFile,
|
|
868
|
-
stderrFile: logFile
|
|
869
|
-
});
|
|
870
|
-
return result.pid;
|
|
871
|
-
},
|
|
872
|
-
deviceKeyPath(serial) {
|
|
873
|
-
const p = getPlatformAdapter();
|
|
874
|
-
return p.joinPath(beeoHome(), "identity", `device-${serial}.key.json`);
|
|
875
|
-
},
|
|
876
|
-
async ensureKeyAndGetPubkey(serial) {
|
|
877
|
-
const keyFile = this.deviceKeyPath(serial);
|
|
878
|
-
await ensureDeviceKey(keyFile);
|
|
879
|
-
return readPubkeyFromKeyFile(keyFile);
|
|
880
952
|
}
|
|
881
|
-
}
|
|
953
|
+
}
|
|
954
|
+
async function ensureAdb(progress, options = {}) {
|
|
955
|
+
const existing = await findAdb();
|
|
956
|
+
if (existing)
|
|
957
|
+
return existing;
|
|
958
|
+
if (options.autoInstall)
|
|
959
|
+
return installAdb(progress);
|
|
960
|
+
return null;
|
|
961
|
+
}
|
|
962
|
+
function adbZipName() {
|
|
963
|
+
const p = getPlatformAdapter();
|
|
964
|
+
switch (p.platform()) {
|
|
965
|
+
case "darwin":
|
|
966
|
+
return "platform-tools-latest-darwin.zip";
|
|
967
|
+
case "linux":
|
|
968
|
+
return "platform-tools-latest-linux.zip";
|
|
969
|
+
case "win32":
|
|
970
|
+
return "platform-tools-latest-windows.zip";
|
|
971
|
+
default:
|
|
972
|
+
return null;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
var PLATFORM_TOOLS_BASE;
|
|
976
|
+
var init_adb_setup = __esm({
|
|
977
|
+
"../core/dist/adb-setup.js"() {
|
|
978
|
+
"use strict";
|
|
979
|
+
init_platform_adapter();
|
|
980
|
+
init_config();
|
|
981
|
+
PLATFORM_TOOLS_BASE = "https://dl.google.com/android/repository";
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
// ../core/dist/runtime/types.js
|
|
986
|
+
var init_types = __esm({
|
|
987
|
+
"../core/dist/runtime/types.js"() {
|
|
988
|
+
"use strict";
|
|
989
|
+
}
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
// ../core/dist/runtime/device.js
|
|
993
|
+
var deviceRuntime;
|
|
994
|
+
var init_device = __esm({
|
|
995
|
+
"../core/dist/runtime/device.js"() {
|
|
996
|
+
"use strict";
|
|
997
|
+
init_platform_adapter();
|
|
998
|
+
init_config();
|
|
999
|
+
init_keypair();
|
|
1000
|
+
init_device_setup();
|
|
1001
|
+
init_progress();
|
|
1002
|
+
deviceRuntime = {
|
|
1003
|
+
agentFramework() {
|
|
1004
|
+
return "device";
|
|
1005
|
+
},
|
|
1006
|
+
displayName() {
|
|
1007
|
+
return "Device Agent";
|
|
1008
|
+
},
|
|
1009
|
+
async detect() {
|
|
1010
|
+
try {
|
|
1011
|
+
const bin = await ensureDeviceAgent(noopReporter);
|
|
1012
|
+
const { cmd } = agentBinCommandAndArgs(bin);
|
|
1013
|
+
return { binary: cmd, version: "", home: beeoHome() };
|
|
1014
|
+
} catch {
|
|
1015
|
+
return null;
|
|
1016
|
+
}
|
|
1017
|
+
},
|
|
1018
|
+
async ensure(_version, progress) {
|
|
1019
|
+
const bin = await ensureDeviceAgent(progress);
|
|
1020
|
+
const { cmd } = agentBinCommandAndArgs(bin);
|
|
1021
|
+
return { binary: cmd, version: "", home: beeoHome() };
|
|
1022
|
+
},
|
|
1023
|
+
async launch(_ctx) {
|
|
1024
|
+
throw new Error("DeviceRuntime.launch() should not be called directly \u2014 use spawnForDevice() instead");
|
|
1025
|
+
},
|
|
1026
|
+
async isHealthy() {
|
|
1027
|
+
return false;
|
|
1028
|
+
},
|
|
1029
|
+
async stop() {
|
|
1030
|
+
},
|
|
1031
|
+
async update(progress) {
|
|
1032
|
+
await upgradeDeviceAgent(progress);
|
|
1033
|
+
},
|
|
1034
|
+
// ── Device-specific extensions ──────────────────────────
|
|
1035
|
+
async ensureAgent(progress) {
|
|
1036
|
+
return ensureDeviceAgent(progress);
|
|
1037
|
+
},
|
|
1038
|
+
async upgradeAgent(progress) {
|
|
1039
|
+
return upgradeDeviceAgent(progress);
|
|
1040
|
+
},
|
|
1041
|
+
async listAdbDevices() {
|
|
1042
|
+
const p = getPlatformAdapter();
|
|
1043
|
+
const result = await p.exec("adb", ["devices"]);
|
|
1044
|
+
if (result.code !== 0) {
|
|
1045
|
+
throw new Error("adb not found \u2014 install Android platform-tools");
|
|
1046
|
+
}
|
|
1047
|
+
const devices = [];
|
|
1048
|
+
const lines = result.stdout.split("\n").slice(1);
|
|
1049
|
+
for (const line of lines) {
|
|
1050
|
+
const parts = line.trim().split(/\s+/);
|
|
1051
|
+
if (parts.length >= 2 && parts[1] === "device") {
|
|
1052
|
+
devices.push({ serial: parts[0] });
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
return devices;
|
|
1056
|
+
},
|
|
1057
|
+
async detectSingleDevice() {
|
|
1058
|
+
const devices = await this.listAdbDevices();
|
|
1059
|
+
if (devices.length === 0) {
|
|
1060
|
+
throw new Error("No ADB devices found. Connect a device and try again.");
|
|
1061
|
+
}
|
|
1062
|
+
if (devices.length > 1) {
|
|
1063
|
+
const serials = devices.map((d) => d.serial).join(", ");
|
|
1064
|
+
throw new Error(`Multiple ADB devices found: ${serials}. Use --serial to specify one.`);
|
|
1065
|
+
}
|
|
1066
|
+
return devices[0].serial;
|
|
1067
|
+
},
|
|
1068
|
+
async spawnForDevice(serial, agentBin, bridgeUrl, httpEnabled, httpPort, agentGatewayUrl) {
|
|
1069
|
+
const p = getPlatformAdapter();
|
|
1070
|
+
const keyFile = this.deviceKeyPath(serial);
|
|
1071
|
+
await ensureDeviceKey(keyFile);
|
|
1072
|
+
if (!bridgeUrl) {
|
|
1073
|
+
throw new Error("bridge_url is not configured in ~/.beeos/config.toml.\nSet [platform] bridge_url (e.g. wss://bridge.beeos.ai).");
|
|
1074
|
+
}
|
|
1075
|
+
const { cmd, args: baseArgs } = agentBinCommandAndArgs(agentBin);
|
|
1076
|
+
const args = [
|
|
1077
|
+
...baseArgs,
|
|
1078
|
+
"--key",
|
|
1079
|
+
keyFile,
|
|
1080
|
+
"--adb",
|
|
1081
|
+
serial,
|
|
1082
|
+
"--bridge",
|
|
1083
|
+
bridgeUrl
|
|
1084
|
+
];
|
|
1085
|
+
if (httpEnabled) {
|
|
1086
|
+
args.push("--http", "--http-port", String(httpPort));
|
|
1087
|
+
}
|
|
1088
|
+
const logDir = p.joinPath(beeoHome(), "logs");
|
|
1089
|
+
await p.mkdir(logDir);
|
|
1090
|
+
const logFile = p.joinPath(logDir, `device-${serial}.log`);
|
|
1091
|
+
const env = {};
|
|
1092
|
+
if (agentGatewayUrl) {
|
|
1093
|
+
env.AGENT_GATEWAY_URL = agentGatewayUrl;
|
|
1094
|
+
}
|
|
1095
|
+
const result = await p.spawn(cmd, args, {
|
|
1096
|
+
detached: true,
|
|
1097
|
+
stdoutFile: logFile,
|
|
1098
|
+
stderrFile: logFile,
|
|
1099
|
+
env: Object.keys(env).length > 0 ? env : void 0
|
|
1100
|
+
});
|
|
1101
|
+
return result.pid;
|
|
1102
|
+
},
|
|
1103
|
+
deviceKeyPath(serial) {
|
|
1104
|
+
const p = getPlatformAdapter();
|
|
1105
|
+
return p.joinPath(beeoHome(), "identity", `device-${serial}.key.json`);
|
|
1106
|
+
},
|
|
1107
|
+
async ensureKeyAndGetPubkey(serial) {
|
|
1108
|
+
const keyFile = this.deviceKeyPath(serial);
|
|
1109
|
+
await ensureDeviceKey(keyFile);
|
|
1110
|
+
return readPubkeyFromKeyFile(keyFile);
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
882
1115
|
|
|
883
1116
|
// ../core/dist/agent/detector.js
|
|
884
|
-
var GATEWAY_PORT = 18789;
|
|
885
1117
|
async function isGatewayRunning() {
|
|
886
1118
|
return getPlatformAdapter().tcpProbe("127.0.0.1", GATEWAY_PORT, 500);
|
|
887
1119
|
}
|
|
@@ -950,6 +1182,15 @@ async function findAgent(driver) {
|
|
|
950
1182
|
}
|
|
951
1183
|
return { type: "notFound" };
|
|
952
1184
|
}
|
|
1185
|
+
var GATEWAY_PORT;
|
|
1186
|
+
var init_detector = __esm({
|
|
1187
|
+
"../core/dist/agent/detector.js"() {
|
|
1188
|
+
"use strict";
|
|
1189
|
+
init_platform_adapter();
|
|
1190
|
+
init_config();
|
|
1191
|
+
GATEWAY_PORT = 18789;
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
953
1194
|
|
|
954
1195
|
// ../core/dist/agent/downloader.js
|
|
955
1196
|
async function downloadAgent(npmPackage, requestedVersion, agentFramework, progress) {
|
|
@@ -1059,11 +1300,18 @@ function semverGte(version, minVersion) {
|
|
|
1059
1300
|
}
|
|
1060
1301
|
return true;
|
|
1061
1302
|
}
|
|
1303
|
+
var init_downloader = __esm({
|
|
1304
|
+
"../core/dist/agent/downloader.js"() {
|
|
1305
|
+
"use strict";
|
|
1306
|
+
init_platform_adapter();
|
|
1307
|
+
init_config();
|
|
1308
|
+
init_npm();
|
|
1309
|
+
}
|
|
1310
|
+
});
|
|
1062
1311
|
|
|
1063
1312
|
// ../core/dist/agent/launcher.js
|
|
1064
1313
|
import { sha256 as sha2562 } from "@noble/hashes/sha256";
|
|
1065
1314
|
import { bytesToHex as bytesToHex2 } from "@noble/hashes/utils";
|
|
1066
|
-
var MAX_BACKUPS = 5;
|
|
1067
1315
|
async function backupOpenclawConfig(agentHome2) {
|
|
1068
1316
|
const p = getPlatformAdapter();
|
|
1069
1317
|
const configPath2 = p.joinPath(agentHome2, "openclaw.json");
|
|
@@ -1127,12 +1375,12 @@ async function generateOpenclawConfig(ctx) {
|
|
|
1127
1375
|
const env = { OPENCLAW_HOME: ctx.agentHome };
|
|
1128
1376
|
const setupResult = await p.exec(ctx.agentBinary, ["setup", "--non-interactive", "--mode", "local"], { env });
|
|
1129
1377
|
if (setupResult.code === 0) {
|
|
1130
|
-
for (const [
|
|
1378
|
+
for (const [path9, value] of [
|
|
1131
1379
|
["gateway.auth.token", ctx.gatewayToken],
|
|
1132
1380
|
["gateway.remote.token", ctx.gatewayToken],
|
|
1133
1381
|
["gateway.bind", "lan"]
|
|
1134
1382
|
]) {
|
|
1135
|
-
await runConfigSet(ctx.agentBinary, ctx.agentHome,
|
|
1383
|
+
await runConfigSet(ctx.agentBinary, ctx.agentHome, path9, value, false, false);
|
|
1136
1384
|
}
|
|
1137
1385
|
await configurePluginViaCli(ctx);
|
|
1138
1386
|
} else {
|
|
@@ -1163,9 +1411,9 @@ async function generateConfigFallback(ctx) {
|
|
|
1163
1411
|
await p.writeFile(configPath2, JSON.stringify(config, null, 2));
|
|
1164
1412
|
await writePairedJson(ctx.agentHome, ctx.keyFile);
|
|
1165
1413
|
}
|
|
1166
|
-
async function runConfigSet(bin, home,
|
|
1414
|
+
async function runConfigSet(bin, home, path9, value, json, isSystemHome) {
|
|
1167
1415
|
const p = getPlatformAdapter();
|
|
1168
|
-
const args = ["config", "set",
|
|
1416
|
+
const args = ["config", "set", path9, value];
|
|
1169
1417
|
if (json)
|
|
1170
1418
|
args.push("--json");
|
|
1171
1419
|
const env = {};
|
|
@@ -1173,7 +1421,7 @@ async function runConfigSet(bin, home, path2, value, json, isSystemHome) {
|
|
|
1173
1421
|
env.OPENCLAW_HOME = home;
|
|
1174
1422
|
const result = await p.exec(bin, args, { env });
|
|
1175
1423
|
if (result.code !== 0) {
|
|
1176
|
-
throw new Error(`\`openclaw config set ${
|
|
1424
|
+
throw new Error(`\`openclaw config set ${path9}\` exited with ${result.code}`);
|
|
1177
1425
|
}
|
|
1178
1426
|
}
|
|
1179
1427
|
async function restartGatewayViaCli(ctx) {
|
|
@@ -1266,92 +1514,506 @@ async function writePairedJson(home, keyFile) {
|
|
|
1266
1514
|
const pairedPath = p.joinPath(devicesDir, "paired.json");
|
|
1267
1515
|
await p.writeFile(pairedPath, JSON.stringify(paired, null, 2));
|
|
1268
1516
|
}
|
|
1517
|
+
var MAX_BACKUPS;
|
|
1518
|
+
var init_launcher = __esm({
|
|
1519
|
+
"../core/dist/agent/launcher.js"() {
|
|
1520
|
+
"use strict";
|
|
1521
|
+
init_platform_adapter();
|
|
1522
|
+
MAX_BACKUPS = 5;
|
|
1523
|
+
}
|
|
1524
|
+
});
|
|
1269
1525
|
|
|
1270
1526
|
// ../core/dist/agent/registry.js
|
|
1271
|
-
var openClawDriver
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1527
|
+
var openClawDriver;
|
|
1528
|
+
var init_registry = __esm({
|
|
1529
|
+
"../core/dist/agent/registry.js"() {
|
|
1530
|
+
"use strict";
|
|
1531
|
+
init_platform_adapter();
|
|
1532
|
+
openClawDriver = {
|
|
1533
|
+
agentFramework() {
|
|
1534
|
+
return "openclaw";
|
|
1535
|
+
},
|
|
1536
|
+
npmPackage() {
|
|
1537
|
+
return "openclaw";
|
|
1538
|
+
},
|
|
1539
|
+
pluginPackage() {
|
|
1540
|
+
return "beeos-claw";
|
|
1541
|
+
},
|
|
1542
|
+
async detectLocal() {
|
|
1543
|
+
const p = getPlatformAdapter();
|
|
1544
|
+
const cmd = p.platform() === "win32" ? "where" : "which";
|
|
1545
|
+
let binary;
|
|
1546
|
+
try {
|
|
1547
|
+
const result = await p.exec(cmd, ["openclaw"]);
|
|
1548
|
+
if (result.code !== 0)
|
|
1549
|
+
return null;
|
|
1550
|
+
binary = result.stdout.trim().split("\n")[0].trim();
|
|
1551
|
+
} catch {
|
|
1552
|
+
return null;
|
|
1553
|
+
}
|
|
1554
|
+
let version = "";
|
|
1555
|
+
try {
|
|
1556
|
+
const verResult = await p.exec(binary, ["--version"]);
|
|
1557
|
+
version = verResult.stdout.trim();
|
|
1558
|
+
} catch {
|
|
1559
|
+
}
|
|
1560
|
+
const homeDir = p.joinPath(p.homeDir(), ".openclaw");
|
|
1561
|
+
return { binary, version, homeDir };
|
|
1562
|
+
}
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
});
|
|
1566
|
+
|
|
1567
|
+
// ../core/dist/runtime/openclaw.js
|
|
1568
|
+
var GATEWAY_PORT2, openclawRuntime;
|
|
1569
|
+
var init_openclaw = __esm({
|
|
1570
|
+
"../core/dist/runtime/openclaw.js"() {
|
|
1571
|
+
"use strict";
|
|
1572
|
+
init_platform_adapter();
|
|
1573
|
+
init_config();
|
|
1574
|
+
init_process();
|
|
1575
|
+
init_detector();
|
|
1576
|
+
init_downloader();
|
|
1577
|
+
init_launcher();
|
|
1578
|
+
init_registry();
|
|
1579
|
+
GATEWAY_PORT2 = 18789;
|
|
1580
|
+
openclawRuntime = {
|
|
1581
|
+
agentFramework() {
|
|
1582
|
+
return "openclaw";
|
|
1583
|
+
},
|
|
1584
|
+
displayName() {
|
|
1585
|
+
return "OpenClaw";
|
|
1586
|
+
},
|
|
1587
|
+
async detect() {
|
|
1588
|
+
const location = await findAgent(openClawDriver);
|
|
1589
|
+
if (location.type === "notFound")
|
|
1590
|
+
return null;
|
|
1591
|
+
return {
|
|
1592
|
+
binary: location.binary,
|
|
1593
|
+
version: location.type === "system" ? location.version : "",
|
|
1594
|
+
home: location.home
|
|
1595
|
+
};
|
|
1596
|
+
},
|
|
1597
|
+
async ensure(version, progress) {
|
|
1598
|
+
const location = await findAgent(openClawDriver);
|
|
1599
|
+
if (location.type === "managed") {
|
|
1600
|
+
return { binary: location.binary, version: "", home: location.home };
|
|
1601
|
+
}
|
|
1602
|
+
if (location.type === "system") {
|
|
1603
|
+
return { binary: location.binary, version: location.version, home: location.home };
|
|
1604
|
+
}
|
|
1605
|
+
const dest = await downloadAgent(openClawDriver.npmPackage(), version ?? void 0, "openclaw", progress);
|
|
1606
|
+
const home = agentHome("openclaw");
|
|
1607
|
+
await getPlatformAdapter().mkdir(home);
|
|
1608
|
+
await downloadPlugin(openClawDriver.pluginPackage(), "openclaw", progress);
|
|
1609
|
+
return { binary: dest, version: "", home };
|
|
1610
|
+
},
|
|
1611
|
+
async launch(ctx) {
|
|
1612
|
+
return spawnOpenclaw(ctx);
|
|
1613
|
+
},
|
|
1614
|
+
async isHealthy() {
|
|
1615
|
+
return getPlatformAdapter().tcpProbe("127.0.0.1", GATEWAY_PORT2, 500);
|
|
1616
|
+
},
|
|
1617
|
+
async stop(agentFramework) {
|
|
1618
|
+
const pid = await readPid(agentFramework);
|
|
1619
|
+
if (pid != null) {
|
|
1620
|
+
killProcess(pid);
|
|
1621
|
+
await removePid(agentFramework);
|
|
1622
|
+
}
|
|
1623
|
+
},
|
|
1624
|
+
async update(progress) {
|
|
1625
|
+
await downloadAgent(openClawDriver.npmPackage(), void 0, "openclaw", progress);
|
|
1626
|
+
await downloadPlugin(openClawDriver.pluginPackage(), "openclaw", progress);
|
|
1627
|
+
}
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
});
|
|
1631
|
+
|
|
1632
|
+
// ../core/dist/sidecars/cargo-dist.js
|
|
1633
|
+
import { createHash } from "crypto";
|
|
1634
|
+
function managedBinaryPath(cfg) {
|
|
1635
|
+
const p = getPlatformAdapter();
|
|
1636
|
+
const name = p.platform() === "win32" ? `${cfg.name}.exe` : cfg.name;
|
|
1637
|
+
return p.joinPath(beeoHome(), "bin", name);
|
|
1638
|
+
}
|
|
1639
|
+
async function findSidecarBinary(cfg) {
|
|
1640
|
+
const p = getPlatformAdapter();
|
|
1641
|
+
const override = process.env?.[cfg.binaryEnv];
|
|
1642
|
+
if (override && await p.exists(override))
|
|
1643
|
+
return override;
|
|
1644
|
+
const whichCmd = p.platform() === "win32" ? "where" : "which";
|
|
1645
|
+
try {
|
|
1646
|
+
const result = await p.exec(whichCmd, [cfg.name]);
|
|
1647
|
+
if (result.code === 0 && result.stdout.trim()) {
|
|
1648
|
+
return result.stdout.trim().split("\n")[0].trim();
|
|
1649
|
+
}
|
|
1650
|
+
} catch {
|
|
1651
|
+
}
|
|
1652
|
+
const managed = managedBinaryPath(cfg);
|
|
1653
|
+
if (await p.exists(managed))
|
|
1654
|
+
return managed;
|
|
1655
|
+
return null;
|
|
1656
|
+
}
|
|
1657
|
+
async function ensureSidecarInstalled(cfg, progress) {
|
|
1658
|
+
const existing = await findSidecarBinary(cfg);
|
|
1659
|
+
if (existing) {
|
|
1660
|
+
progress.onStatus(`${cfg.name} found: ${existing}`);
|
|
1661
|
+
return existing;
|
|
1662
|
+
}
|
|
1663
|
+
const target = cargoDistTarget();
|
|
1664
|
+
if (!target) {
|
|
1665
|
+
reportUnsupportedTarget(cfg, progress);
|
|
1666
|
+
return null;
|
|
1667
|
+
}
|
|
1668
|
+
return downloadManagedBinary(cfg, target, progress);
|
|
1669
|
+
}
|
|
1670
|
+
async function upgradeSidecar(cfg, progress) {
|
|
1671
|
+
const target = cargoDistTarget();
|
|
1672
|
+
if (!target) {
|
|
1673
|
+
reportUnsupportedTarget(cfg, progress);
|
|
1674
|
+
throw new Error(`No prebuilt ${cfg.name} archive available for ${platformKey()}. See the log above for fallback options.`);
|
|
1675
|
+
}
|
|
1676
|
+
const p = getPlatformAdapter();
|
|
1677
|
+
const managed = managedBinaryPath(cfg);
|
|
1678
|
+
if (await p.exists(managed)) {
|
|
1285
1679
|
try {
|
|
1286
|
-
|
|
1287
|
-
if (result.code !== 0)
|
|
1288
|
-
return null;
|
|
1289
|
-
binary = result.stdout.trim().split("\n")[0].trim();
|
|
1680
|
+
await p.rm(managed);
|
|
1290
1681
|
} catch {
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
const installed = await downloadManagedBinary(cfg, target, progress);
|
|
1685
|
+
if (!installed) {
|
|
1686
|
+
throw new Error(`${cfg.name} upgrade failed`);
|
|
1687
|
+
}
|
|
1688
|
+
return installed;
|
|
1689
|
+
}
|
|
1690
|
+
function cargoDistTarget() {
|
|
1691
|
+
const p = getPlatformAdapter();
|
|
1692
|
+
const plat = p.platform();
|
|
1693
|
+
const arch = process.arch;
|
|
1694
|
+
switch (plat) {
|
|
1695
|
+
case "darwin":
|
|
1696
|
+
if (arch === "arm64")
|
|
1697
|
+
return "aarch64-apple-darwin";
|
|
1698
|
+
if (arch === "x64")
|
|
1699
|
+
return "x86_64-apple-darwin";
|
|
1700
|
+
return null;
|
|
1701
|
+
case "linux":
|
|
1702
|
+
if (arch === "arm64")
|
|
1703
|
+
return "aarch64-unknown-linux-gnu";
|
|
1704
|
+
if (arch === "x64")
|
|
1705
|
+
return "x86_64-unknown-linux-gnu";
|
|
1706
|
+
return null;
|
|
1707
|
+
case "win32":
|
|
1708
|
+
if (arch === "x64")
|
|
1709
|
+
return "x86_64-pc-windows-msvc";
|
|
1710
|
+
return null;
|
|
1711
|
+
default:
|
|
1712
|
+
return null;
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
function platformKey() {
|
|
1716
|
+
const p = getPlatformAdapter();
|
|
1717
|
+
return `${p.platform()}/${process.arch}`;
|
|
1718
|
+
}
|
|
1719
|
+
function reportUnsupportedTarget(cfg, progress) {
|
|
1720
|
+
const repoHint = cfg.defaultReleaseUrl.replace(/\/releases\/(latest\/download|download\/[^/]+).*$/, "");
|
|
1721
|
+
const lines = [
|
|
1722
|
+
`${cfg.name}: no prebuilt archive for ${platformKey()}.`,
|
|
1723
|
+
`Supported targets: darwin arm64/x64, linux arm64/x64, windows x64.`,
|
|
1724
|
+
`Fallbacks:`,
|
|
1725
|
+
` 1. Build from source and put the binary on PATH (see ${repoHint || cfg.defaultReleaseUrl}).`,
|
|
1726
|
+
` 2. Copy an existing binary to ~/.beeos/bin/${cfg.name}${process.platform === "win32" ? ".exe" : ""}.`,
|
|
1727
|
+
` 3. Point $${cfg.binaryEnv} at an absolute path to skip discovery entirely.`
|
|
1728
|
+
];
|
|
1729
|
+
if (cfg.releaseUrlEnv) {
|
|
1730
|
+
lines.push(` 4. Set $${cfg.releaseUrlEnv} to a custom release URL (e.g. an internal mirror).`);
|
|
1731
|
+
}
|
|
1732
|
+
for (const line of lines)
|
|
1733
|
+
progress.onStatus(line);
|
|
1734
|
+
}
|
|
1735
|
+
function releaseBaseUrl(cfg) {
|
|
1736
|
+
if (cfg.releaseUrlEnv) {
|
|
1737
|
+
const v = process.env?.[cfg.releaseUrlEnv];
|
|
1738
|
+
if (v)
|
|
1739
|
+
return v;
|
|
1740
|
+
}
|
|
1741
|
+
return cfg.defaultReleaseUrl;
|
|
1742
|
+
}
|
|
1743
|
+
function archiveName(cfg, target) {
|
|
1744
|
+
const suffix = target.includes("windows") ? "zip" : "tar.gz";
|
|
1745
|
+
return `${cfg.name}-${target}.${suffix}`;
|
|
1746
|
+
}
|
|
1747
|
+
async function downloadManagedBinary(cfg, target, progress) {
|
|
1748
|
+
const p = getPlatformAdapter();
|
|
1749
|
+
const base = releaseBaseUrl(cfg);
|
|
1750
|
+
const url = `${base}/${archiveName(cfg, target)}`;
|
|
1751
|
+
progress.onStatus(`Downloading ${cfg.name} (${target})...`);
|
|
1752
|
+
let resp;
|
|
1753
|
+
try {
|
|
1754
|
+
resp = await p.fetch(url);
|
|
1755
|
+
} catch (e) {
|
|
1756
|
+
progress.onStatus(`${cfg.name} download failed: ${String(e)}`);
|
|
1757
|
+
return null;
|
|
1758
|
+
}
|
|
1759
|
+
if (!resp.ok) {
|
|
1760
|
+
progress.onStatus(`${cfg.name} download: ${resp.status} ${resp.statusText} (${url})`);
|
|
1761
|
+
if (resp.status === 404) {
|
|
1762
|
+
progress.onStatus(`Hint: the current release may not include ${target}. Try setting $${cfg.binaryEnv} to a locally built binary, ` + (cfg.releaseUrlEnv ? `or $${cfg.releaseUrlEnv} to a different release URL.` : `or check for a newer release.`));
|
|
1763
|
+
}
|
|
1764
|
+
return null;
|
|
1765
|
+
}
|
|
1766
|
+
const data = new Uint8Array(await resp.arrayBuffer());
|
|
1767
|
+
const digestUrl = `${url}.sha256`;
|
|
1768
|
+
let expected = null;
|
|
1769
|
+
try {
|
|
1770
|
+
const digResp = await p.fetch(digestUrl);
|
|
1771
|
+
if (digResp.ok) {
|
|
1772
|
+
const text = (await digResp.text()).trim();
|
|
1773
|
+
const first = text.split(/\s+/)[0] ?? "";
|
|
1774
|
+
if (/^[a-f0-9]{64}$/i.test(first))
|
|
1775
|
+
expected = first.toLowerCase();
|
|
1776
|
+
}
|
|
1777
|
+
} catch {
|
|
1778
|
+
}
|
|
1779
|
+
if (expected) {
|
|
1780
|
+
const actual = createHash("sha256").update(data).digest("hex");
|
|
1781
|
+
if (actual !== expected) {
|
|
1782
|
+
progress.onStatus(`${cfg.name} checksum mismatch (expected ${expected.slice(0, 12)}\u2026, got ${actual.slice(0, 12)}\u2026) \u2014 aborting install.`);
|
|
1291
1783
|
return null;
|
|
1292
1784
|
}
|
|
1293
|
-
|
|
1785
|
+
progress.onStatus(`${cfg.name} checksum ok (${expected.slice(0, 12)}\u2026).`);
|
|
1786
|
+
} else {
|
|
1787
|
+
progress.onStatus(`${cfg.name}: no .sha256 sidecar at ${digestUrl} \u2014 continuing without integrity check`);
|
|
1788
|
+
}
|
|
1789
|
+
const binDir = p.joinPath(beeoHome(), "bin");
|
|
1790
|
+
await p.mkdir(binDir);
|
|
1791
|
+
const finalPath = managedBinaryPath(cfg);
|
|
1792
|
+
const isZip = target.includes("windows");
|
|
1793
|
+
const tmpArchive = p.joinPath(binDir, `${cfg.name}.download.${Date.now()}.${isZip ? "zip" : "tar.gz"}`);
|
|
1794
|
+
await p.writeFileBytes(tmpArchive, data);
|
|
1795
|
+
try {
|
|
1796
|
+
progress.onStatus(`Extracting ${cfg.name}...`);
|
|
1797
|
+
if (isZip) {
|
|
1798
|
+
const exec2 = await p.exec("powershell", [
|
|
1799
|
+
"-NoProfile",
|
|
1800
|
+
"-Command",
|
|
1801
|
+
`Expand-Archive -Force -Path "${tmpArchive}" -DestinationPath "${binDir}"`
|
|
1802
|
+
]);
|
|
1803
|
+
if (exec2.code !== 0) {
|
|
1804
|
+
throw new Error(`Expand-Archive failed: ${exec2.stderr || exec2.stdout}`.trim());
|
|
1805
|
+
}
|
|
1806
|
+
} else {
|
|
1807
|
+
try {
|
|
1808
|
+
const tar = await import("tar");
|
|
1809
|
+
await tar.extract({ file: tmpArchive, cwd: binDir, strip: 1 });
|
|
1810
|
+
} catch {
|
|
1811
|
+
const exec2 = await p.exec("tar", [
|
|
1812
|
+
"-xzf",
|
|
1813
|
+
tmpArchive,
|
|
1814
|
+
"--strip-components=1",
|
|
1815
|
+
"-C",
|
|
1816
|
+
binDir
|
|
1817
|
+
]);
|
|
1818
|
+
if (exec2.code !== 0) {
|
|
1819
|
+
throw new Error(`tar extract failed: ${exec2.stderr}`.trim());
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
if (!await p.exists(finalPath)) {
|
|
1824
|
+
throw new Error(`extracted archive did not contain ${p.basename(finalPath)}`);
|
|
1825
|
+
}
|
|
1826
|
+
if (p.platform() !== "win32") {
|
|
1827
|
+
try {
|
|
1828
|
+
await p.chmod(finalPath, 493);
|
|
1829
|
+
} catch {
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
if (p.platform() === "darwin") {
|
|
1833
|
+
try {
|
|
1834
|
+
await p.exec("xattr", ["-d", "com.apple.quarantine", finalPath]);
|
|
1835
|
+
} catch {
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
progress.onComplete(`${cfg.name} installed: ${finalPath}`);
|
|
1839
|
+
return finalPath;
|
|
1840
|
+
} catch (e) {
|
|
1841
|
+
progress.onStatus(`${cfg.name} install failed: ${String(e)}`);
|
|
1842
|
+
return null;
|
|
1843
|
+
} finally {
|
|
1294
1844
|
try {
|
|
1295
|
-
|
|
1296
|
-
version = verResult.stdout.trim();
|
|
1845
|
+
await p.rm(tmpArchive);
|
|
1297
1846
|
} catch {
|
|
1298
1847
|
}
|
|
1299
|
-
const homeDir = p.joinPath(p.homeDir(), ".openclaw");
|
|
1300
|
-
return { binary, version, homeDir };
|
|
1301
1848
|
}
|
|
1302
|
-
}
|
|
1849
|
+
}
|
|
1850
|
+
var init_cargo_dist = __esm({
|
|
1851
|
+
"../core/dist/sidecars/cargo-dist.js"() {
|
|
1852
|
+
"use strict";
|
|
1853
|
+
init_platform_adapter();
|
|
1854
|
+
init_config();
|
|
1855
|
+
}
|
|
1856
|
+
});
|
|
1303
1857
|
|
|
1304
|
-
// ../core/dist/runtime/
|
|
1305
|
-
var
|
|
1306
|
-
var
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1858
|
+
// ../core/dist/runtime/scrcpy-bridge.js
|
|
1859
|
+
var CONFIG, scrcpyBridgeRuntime;
|
|
1860
|
+
var init_scrcpy_bridge = __esm({
|
|
1861
|
+
"../core/dist/runtime/scrcpy-bridge.js"() {
|
|
1862
|
+
"use strict";
|
|
1863
|
+
init_platform_adapter();
|
|
1864
|
+
init_config();
|
|
1865
|
+
init_cargo_dist();
|
|
1866
|
+
CONFIG = {
|
|
1867
|
+
name: "scrcpy-bridge",
|
|
1868
|
+
defaultReleaseUrl: "https://github.com/beeos-ai/scrcpy-bridge/releases/latest/download",
|
|
1869
|
+
releaseUrlEnv: "BEEOS_SCRCPY_BRIDGE_RELEASE_URL",
|
|
1870
|
+
binaryEnv: "BEEOS_SCRCPY_BRIDGE_BIN"
|
|
1871
|
+
};
|
|
1872
|
+
scrcpyBridgeRuntime = {
|
|
1873
|
+
async findBinary() {
|
|
1874
|
+
return findSidecarBinary(CONFIG);
|
|
1875
|
+
},
|
|
1876
|
+
async ensureInstalled(progress) {
|
|
1877
|
+
return ensureSidecarInstalled(CONFIG, progress);
|
|
1878
|
+
},
|
|
1879
|
+
async upgrade(progress) {
|
|
1880
|
+
return upgradeSidecar(CONFIG, progress);
|
|
1881
|
+
},
|
|
1882
|
+
/**
|
|
1883
|
+
* Build the environment map used when launching scrcpy-bridge for a
|
|
1884
|
+
* device. Exposed separately so the supervisor target-spec builder
|
|
1885
|
+
* can reuse the same logic without duplicating it.
|
|
1886
|
+
*
|
|
1887
|
+
* scrcpy-bridge authenticates to EMQX exclusively via Agent Gateway
|
|
1888
|
+
* bootstrap. `MQTT_URL` / `MQTT_TOKEN` / `MQTT_USERNAME` are
|
|
1889
|
+
* intentionally NOT forwarded — the binary rejects a static token
|
|
1890
|
+
* path because a Runtime-issued 10-minute JWT would silently expire
|
|
1891
|
+
* and `rumqttc` reuses the initial password on reconnect.
|
|
1892
|
+
*/
|
|
1893
|
+
buildEnv(opts) {
|
|
1894
|
+
return {
|
|
1895
|
+
DEVICE_ID: opts.deviceId,
|
|
1896
|
+
ADB_SERIAL: opts.adbSerial,
|
|
1897
|
+
AGENT_GATEWAY_URL: opts.agentGatewayUrl,
|
|
1898
|
+
BRIDGE_PRIVATE_KEY_FILE: opts.bridgePrivateKeyFile,
|
|
1899
|
+
BRIDGE_PUBLIC_KEY_FILE: opts.bridgePublicKeyFile,
|
|
1900
|
+
LOG_FORMAT: "json",
|
|
1901
|
+
...opts.jwtRefreshLeadSecs != null ? { JWT_REFRESH_LEAD_SECS: String(opts.jwtRefreshLeadSecs) } : {},
|
|
1902
|
+
...opts.jwtRefreshMinIntervalSecs != null ? { JWT_REFRESH_MIN_INTERVAL_SECS: String(opts.jwtRefreshMinIntervalSecs) } : {},
|
|
1903
|
+
...opts.videoBitrateKbps ? { VIDEO_BITRATE_KBPS: String(opts.videoBitrateKbps) } : {},
|
|
1904
|
+
...opts.maxFps ? { MAX_FPS: String(opts.maxFps) } : {},
|
|
1905
|
+
...opts.iceUrls?.length ? { ICE_URLS: opts.iceUrls.join(",") } : {},
|
|
1906
|
+
...opts.turnUrls?.length ? { TURN_URLS: opts.turnUrls.join(",") } : {},
|
|
1907
|
+
...opts.turnUsername ? { TURN_USERNAME: opts.turnUsername } : {},
|
|
1908
|
+
...opts.turnCredential ? { TURN_CREDENTIAL: opts.turnCredential } : {},
|
|
1909
|
+
...opts.extraEnv ?? {}
|
|
1910
|
+
};
|
|
1911
|
+
},
|
|
1912
|
+
/**
|
|
1913
|
+
* Spawn scrcpy-bridge detached for a single device. Kept for callers
|
|
1914
|
+
* that haven't migrated to the supervisor yet — new code should use
|
|
1915
|
+
* `buildScrcpyBridgeTargetSpec` + supervisor `upsertTarget` instead.
|
|
1916
|
+
*/
|
|
1917
|
+
async spawnForDevice(opts, progress) {
|
|
1918
|
+
const binary = await this.ensureInstalled(progress);
|
|
1919
|
+
if (!binary) {
|
|
1920
|
+
progress.onStatus("scrcpy-bridge not installed \u2014 falling back to Python video path.");
|
|
1921
|
+
return null;
|
|
1922
|
+
}
|
|
1923
|
+
const p = getPlatformAdapter();
|
|
1924
|
+
const logDir = p.joinPath(beeoHome(), "logs");
|
|
1925
|
+
await p.mkdir(logDir);
|
|
1926
|
+
const logFile = p.joinPath(logDir, `scrcpy-bridge-${opts.deviceId}.log`);
|
|
1927
|
+
const env = this.buildEnv(opts);
|
|
1928
|
+
const result = await p.spawn(binary, [], {
|
|
1929
|
+
detached: true,
|
|
1930
|
+
env,
|
|
1931
|
+
stdoutFile: logFile,
|
|
1932
|
+
stderrFile: logFile
|
|
1933
|
+
});
|
|
1934
|
+
return { binary, pid: result.pid, logFile };
|
|
1935
|
+
}
|
|
1321
1936
|
};
|
|
1322
|
-
},
|
|
1323
|
-
async ensure(version, progress) {
|
|
1324
|
-
const location = await findAgent(openClawDriver);
|
|
1325
|
-
if (location.type === "managed") {
|
|
1326
|
-
return { binary: location.binary, version: "", home: location.home };
|
|
1327
|
-
}
|
|
1328
|
-
if (location.type === "system") {
|
|
1329
|
-
return { binary: location.binary, version: location.version, home: location.home };
|
|
1330
|
-
}
|
|
1331
|
-
const dest = await downloadAgent(openClawDriver.npmPackage(), version ?? void 0, "openclaw", progress);
|
|
1332
|
-
const home = agentHome("openclaw");
|
|
1333
|
-
await getPlatformAdapter().mkdir(home);
|
|
1334
|
-
await downloadPlugin(openClawDriver.pluginPackage(), "openclaw", progress);
|
|
1335
|
-
return { binary: dest, version: "", home };
|
|
1336
|
-
},
|
|
1337
|
-
async launch(ctx) {
|
|
1338
|
-
return spawnOpenclaw(ctx);
|
|
1339
|
-
},
|
|
1340
|
-
async isHealthy() {
|
|
1341
|
-
return getPlatformAdapter().tcpProbe("127.0.0.1", GATEWAY_PORT2, 500);
|
|
1342
|
-
},
|
|
1343
|
-
async stop(agentFramework) {
|
|
1344
|
-
const pid = await readPid(agentFramework);
|
|
1345
|
-
if (pid != null) {
|
|
1346
|
-
killProcess(pid);
|
|
1347
|
-
await removePid(agentFramework);
|
|
1348
|
-
}
|
|
1349
|
-
},
|
|
1350
|
-
async update(progress) {
|
|
1351
|
-
await downloadAgent(openClawDriver.npmPackage(), void 0, "openclaw", progress);
|
|
1352
|
-
await downloadPlugin(openClawDriver.pluginPackage(), "openclaw", progress);
|
|
1353
1937
|
}
|
|
1354
|
-
};
|
|
1938
|
+
});
|
|
1939
|
+
|
|
1940
|
+
// ../core/dist/runtime/vnc-bridge.js
|
|
1941
|
+
var CONFIG2, vncBridgeRuntime;
|
|
1942
|
+
var init_vnc_bridge = __esm({
|
|
1943
|
+
"../core/dist/runtime/vnc-bridge.js"() {
|
|
1944
|
+
"use strict";
|
|
1945
|
+
init_platform_adapter();
|
|
1946
|
+
init_config();
|
|
1947
|
+
init_cargo_dist();
|
|
1948
|
+
CONFIG2 = {
|
|
1949
|
+
name: "vnc-bridge",
|
|
1950
|
+
defaultReleaseUrl: "https://github.com/beeos-ai/vnc-bridge/releases/latest/download",
|
|
1951
|
+
releaseUrlEnv: "BEEOS_VNC_BRIDGE_RELEASE_URL",
|
|
1952
|
+
binaryEnv: "BEEOS_VNC_BRIDGE_BIN"
|
|
1953
|
+
};
|
|
1954
|
+
vncBridgeRuntime = {
|
|
1955
|
+
async findBinary() {
|
|
1956
|
+
return findSidecarBinary(CONFIG2);
|
|
1957
|
+
},
|
|
1958
|
+
async ensureInstalled(progress) {
|
|
1959
|
+
return ensureSidecarInstalled(CONFIG2, progress);
|
|
1960
|
+
},
|
|
1961
|
+
async upgrade(progress) {
|
|
1962
|
+
return upgradeSidecar(CONFIG2, progress);
|
|
1963
|
+
},
|
|
1964
|
+
/**
|
|
1965
|
+
* Build the environment map used when launching vnc-bridge for a
|
|
1966
|
+
* device. Exposed separately so the supervisor target-spec builder
|
|
1967
|
+
* can reuse the same logic without duplicating it.
|
|
1968
|
+
*/
|
|
1969
|
+
buildEnv(opts) {
|
|
1970
|
+
return {
|
|
1971
|
+
DEVICE_ID: opts.deviceId,
|
|
1972
|
+
VNC_HOST: opts.vncHost,
|
|
1973
|
+
VNC_PORT: String(opts.vncPort ?? 5900),
|
|
1974
|
+
AGENT_GATEWAY_URL: opts.agentGatewayUrl,
|
|
1975
|
+
BRIDGE_PRIVATE_KEY_FILE: opts.bridgePrivateKeyFile,
|
|
1976
|
+
BRIDGE_PUBLIC_KEY_FILE: opts.bridgePublicKeyFile,
|
|
1977
|
+
LOG_FORMAT: "json",
|
|
1978
|
+
...opts.vncPassword ? { VNC_PASSWORD: opts.vncPassword } : {},
|
|
1979
|
+
...opts.jwtRefreshLeadSecs != null ? { JWT_REFRESH_LEAD_SECS: String(opts.jwtRefreshLeadSecs) } : {},
|
|
1980
|
+
...opts.jwtRefreshMinIntervalSecs != null ? { JWT_REFRESH_MIN_INTERVAL_SECS: String(opts.jwtRefreshMinIntervalSecs) } : {},
|
|
1981
|
+
...opts.videoBitrateKbps ? { VIDEO_BITRATE_KBPS: String(opts.videoBitrateKbps) } : {},
|
|
1982
|
+
...opts.maxFps ? { MAX_FPS: String(opts.maxFps) } : {},
|
|
1983
|
+
...opts.iceUrls?.length ? { ICE_URLS: opts.iceUrls.join(",") } : {},
|
|
1984
|
+
...opts.turnUrls?.length ? { TURN_URLS: opts.turnUrls.join(",") } : {},
|
|
1985
|
+
...opts.turnUsername ? { TURN_USERNAME: opts.turnUsername } : {},
|
|
1986
|
+
...opts.turnCredential ? { TURN_CREDENTIAL: opts.turnCredential } : {},
|
|
1987
|
+
...opts.extraEnv ?? {}
|
|
1988
|
+
};
|
|
1989
|
+
},
|
|
1990
|
+
/**
|
|
1991
|
+
* Spawn vnc-bridge detached for a single device. Kept for callers
|
|
1992
|
+
* that haven't migrated to the supervisor yet — new code should use
|
|
1993
|
+
* `buildVncBridgeTargetSpec` + supervisor `upsertTarget` instead.
|
|
1994
|
+
*/
|
|
1995
|
+
async spawnForDevice(opts, progress) {
|
|
1996
|
+
const binary = await this.ensureInstalled(progress);
|
|
1997
|
+
if (!binary) {
|
|
1998
|
+
progress.onStatus("vnc-bridge not installed \u2014 VNC streaming disabled for this device.");
|
|
1999
|
+
return null;
|
|
2000
|
+
}
|
|
2001
|
+
const p = getPlatformAdapter();
|
|
2002
|
+
const logDir = p.joinPath(beeoHome(), "logs");
|
|
2003
|
+
await p.mkdir(logDir);
|
|
2004
|
+
const logFile = p.joinPath(logDir, `vnc-bridge-${opts.deviceId}.log`);
|
|
2005
|
+
const env = this.buildEnv(opts);
|
|
2006
|
+
const result = await p.spawn(binary, [], {
|
|
2007
|
+
detached: true,
|
|
2008
|
+
env,
|
|
2009
|
+
stdoutFile: logFile,
|
|
2010
|
+
stderrFile: logFile
|
|
2011
|
+
});
|
|
2012
|
+
return { binary, pid: result.pid, logFile };
|
|
2013
|
+
}
|
|
2014
|
+
};
|
|
2015
|
+
}
|
|
2016
|
+
});
|
|
1355
2017
|
|
|
1356
2018
|
// ../core/dist/service/local-agent.js
|
|
1357
2019
|
async function ensurePlugin(agentFramework, agentHomeDir, progress) {
|
|
@@ -1394,28 +2056,2013 @@ async function bind(options) {
|
|
|
1394
2056
|
}
|
|
1395
2057
|
throw new Error(`unexpected bind status: ${resp.status}`);
|
|
1396
2058
|
}
|
|
2059
|
+
var init_local_agent = __esm({
|
|
2060
|
+
"../core/dist/service/local-agent.js"() {
|
|
2061
|
+
"use strict";
|
|
2062
|
+
init_platform_adapter();
|
|
2063
|
+
init_keypair();
|
|
2064
|
+
init_config();
|
|
2065
|
+
init_detector();
|
|
2066
|
+
init_downloader();
|
|
2067
|
+
init_launcher();
|
|
2068
|
+
init_registry();
|
|
2069
|
+
init_client();
|
|
2070
|
+
init_bind();
|
|
2071
|
+
}
|
|
2072
|
+
});
|
|
1397
2073
|
|
|
1398
|
-
//
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
2074
|
+
// ../core/dist/detect.js
|
|
2075
|
+
async function detectExistingInstall() {
|
|
2076
|
+
const p = getPlatformAdapter();
|
|
2077
|
+
const home = beeoHome();
|
|
2078
|
+
const beeosHomeExists = await p.exists(home);
|
|
2079
|
+
const keypairPath2 = p.joinPath(home, "identity", "keypair.json");
|
|
2080
|
+
const fpPath = p.joinPath(home, "identity", "fingerprint");
|
|
2081
|
+
const hasIdentity = await p.exists(keypairPath2);
|
|
2082
|
+
let fingerprint2 = null;
|
|
2083
|
+
if (await p.exists(fpPath)) {
|
|
2084
|
+
try {
|
|
2085
|
+
fingerprint2 = (await p.readFile(fpPath)).trim() || null;
|
|
2086
|
+
} catch {
|
|
2087
|
+
fingerprint2 = null;
|
|
2088
|
+
}
|
|
1412
2089
|
}
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
2090
|
+
let binding = null;
|
|
2091
|
+
try {
|
|
2092
|
+
binding = await loadBindingInfo();
|
|
2093
|
+
} catch {
|
|
2094
|
+
binding = null;
|
|
1416
2095
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
2096
|
+
const openclaw = await detectOpenclaw();
|
|
2097
|
+
const devices = await detectDevices();
|
|
2098
|
+
const supervisor = await detectSupervisor();
|
|
2099
|
+
return {
|
|
2100
|
+
beeosHomeExists,
|
|
2101
|
+
beeosHome: home,
|
|
2102
|
+
hasIdentity,
|
|
2103
|
+
fingerprint: fingerprint2,
|
|
2104
|
+
binding,
|
|
2105
|
+
openclaw,
|
|
2106
|
+
devices,
|
|
2107
|
+
supervisor
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
async function detectOpenclaw() {
|
|
2111
|
+
const location = await findAgent(openClawDriver);
|
|
2112
|
+
const running = await isGatewayRunning();
|
|
2113
|
+
if (location.type === "managed") {
|
|
2114
|
+
return {
|
|
2115
|
+
found: true,
|
|
2116
|
+
source: "managed",
|
|
2117
|
+
binary: location.binary,
|
|
2118
|
+
home: location.home,
|
|
2119
|
+
version: null,
|
|
2120
|
+
gatewayRunning: running
|
|
2121
|
+
};
|
|
2122
|
+
}
|
|
2123
|
+
if (location.type === "system") {
|
|
2124
|
+
return {
|
|
2125
|
+
found: true,
|
|
2126
|
+
source: "system",
|
|
2127
|
+
binary: location.binary,
|
|
2128
|
+
home: location.home,
|
|
2129
|
+
version: location.version || null,
|
|
2130
|
+
gatewayRunning: running
|
|
2131
|
+
};
|
|
2132
|
+
}
|
|
2133
|
+
return {
|
|
2134
|
+
found: false,
|
|
2135
|
+
source: null,
|
|
2136
|
+
binary: null,
|
|
2137
|
+
home: null,
|
|
2138
|
+
version: null,
|
|
2139
|
+
gatewayRunning: running
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2142
|
+
async function detectDevices() {
|
|
2143
|
+
const p = getPlatformAdapter();
|
|
2144
|
+
const identityDir = p.joinPath(beeoHome(), "identity");
|
|
2145
|
+
const legacyFile = p.joinPath(beeoHome(), "devices.json");
|
|
2146
|
+
let keyedSerials = [];
|
|
2147
|
+
try {
|
|
2148
|
+
if (await p.exists(identityDir)) {
|
|
2149
|
+
const entries2 = await p.readdir(identityDir);
|
|
2150
|
+
keyedSerials = entries2.filter((name) => name.startsWith("device-") && name.endsWith(".key.json")).map((name) => name.replace(/^device-/, "").replace(/\.key\.json$/, ""));
|
|
2151
|
+
}
|
|
2152
|
+
} catch {
|
|
2153
|
+
keyedSerials = [];
|
|
2154
|
+
}
|
|
2155
|
+
let entries = [];
|
|
2156
|
+
if (await p.exists(legacyFile)) {
|
|
2157
|
+
try {
|
|
2158
|
+
const raw = await p.readFile(legacyFile);
|
|
2159
|
+
const parsed = JSON.parse(raw);
|
|
2160
|
+
entries = (parsed.devices ?? []).map((d) => ({
|
|
2161
|
+
serial: String(d.serial ?? ""),
|
|
2162
|
+
name: String(d.name ?? d.serial ?? ""),
|
|
2163
|
+
pid: Number(d.pid ?? 0),
|
|
2164
|
+
instanceId: d.instance_id ?? null,
|
|
2165
|
+
bridgePid: typeof d.bridge_pid === "number" ? d.bridge_pid : void 0
|
|
2166
|
+
}));
|
|
2167
|
+
} catch {
|
|
2168
|
+
entries = [];
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
return { legacyFile, keyedSerials, entries };
|
|
2172
|
+
}
|
|
2173
|
+
async function detectSupervisor() {
|
|
2174
|
+
const p = getPlatformAdapter();
|
|
2175
|
+
const stateFile = p.joinPath(beeoHome(), "supervisor", "state.json");
|
|
2176
|
+
const ipcPath = supervisorSocketPath();
|
|
2177
|
+
let ipcReachable = false;
|
|
2178
|
+
try {
|
|
2179
|
+
ipcReachable = await p.exists(ipcPath);
|
|
2180
|
+
} catch {
|
|
2181
|
+
ipcReachable = false;
|
|
2182
|
+
}
|
|
2183
|
+
let targets = [];
|
|
2184
|
+
if (await p.exists(stateFile)) {
|
|
2185
|
+
try {
|
|
2186
|
+
const raw = await p.readFile(stateFile);
|
|
2187
|
+
const parsed = JSON.parse(raw);
|
|
2188
|
+
targets = (parsed.targets ?? []).map((t) => ({
|
|
2189
|
+
id: String(t.id ?? ""),
|
|
2190
|
+
kind: String(t.kind ?? ""),
|
|
2191
|
+
restart: t.restart ?? void 0
|
|
2192
|
+
}));
|
|
2193
|
+
} catch {
|
|
2194
|
+
targets = [];
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
return { stateFile, ipcPath, ipcReachable, targets };
|
|
2198
|
+
}
|
|
2199
|
+
function supervisorSocketPath() {
|
|
2200
|
+
const p = getPlatformAdapter();
|
|
2201
|
+
if (p.platform() === "win32") {
|
|
2202
|
+
return "\\\\.\\pipe\\beeos-supervisor";
|
|
2203
|
+
}
|
|
2204
|
+
return p.joinPath(beeoHome(), "supervisor", "supervisor.sock");
|
|
2205
|
+
}
|
|
2206
|
+
function summarizeExistingInstall(state) {
|
|
2207
|
+
const lines = [];
|
|
2208
|
+
lines.push(`BeeOS home : ${state.beeosHome}${state.beeosHomeExists ? "" : " (not created)"}`);
|
|
2209
|
+
lines.push(`Identity : ${state.hasIdentity ? state.fingerprint ?? "(keypair present)" : "not created"}`);
|
|
2210
|
+
lines.push(`Binding : ${state.binding ? `bound \u2192 instance ${state.binding.instance_id}` : "not bound"}`);
|
|
2211
|
+
lines.push(`OpenClaw : ${state.openclaw.found ? `${state.openclaw.source}${state.openclaw.version ? ` v${state.openclaw.version}` : ""} (gateway ${state.openclaw.gatewayRunning ? "running" : "stopped"})` : "not installed"}`);
|
|
2212
|
+
lines.push(`Devices : ${state.devices.entries.length === 0 && state.devices.keyedSerials.length === 0 ? "none attached" : `${state.devices.entries.length} attached, ${state.devices.keyedSerials.length} key(s)`}`);
|
|
2213
|
+
lines.push(`Supervisor : ${state.supervisor.ipcReachable ? `running (${state.supervisor.targets.length} target(s))` : state.supervisor.targets.length > 0 ? `stopped, ${state.supervisor.targets.length} persisted target(s)` : "not installed"}`);
|
|
2214
|
+
return lines;
|
|
2215
|
+
}
|
|
2216
|
+
function inferInitChoices(state) {
|
|
2217
|
+
if (!state.hasIdentity && !state.openclaw.found && state.devices.entries.length === 0) {
|
|
2218
|
+
return { defaultDecision: "fresh", options: ["fresh"] };
|
|
2219
|
+
}
|
|
2220
|
+
if (state.hasIdentity && state.binding) {
|
|
2221
|
+
return {
|
|
2222
|
+
defaultDecision: "upgrade",
|
|
2223
|
+
options: ["upgrade", "rebind-keep-key", "rebind-new-key", "skip"]
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2226
|
+
if (state.hasIdentity) {
|
|
2227
|
+
return {
|
|
2228
|
+
defaultDecision: "rebind-keep-key",
|
|
2229
|
+
options: ["upgrade", "rebind-keep-key", "rebind-new-key", "skip"]
|
|
2230
|
+
};
|
|
2231
|
+
}
|
|
2232
|
+
return { defaultDecision: "fresh", options: ["fresh", "skip"] };
|
|
2233
|
+
}
|
|
2234
|
+
function initDecisionLabel(decision) {
|
|
2235
|
+
switch (decision) {
|
|
2236
|
+
case "fresh":
|
|
2237
|
+
return "Install + bind";
|
|
2238
|
+
case "upgrade":
|
|
2239
|
+
return "Upgrade CLI & agents (keep binding)";
|
|
2240
|
+
case "rebind-keep-key":
|
|
2241
|
+
return "Re-bind (keep existing Ed25519 key)";
|
|
2242
|
+
case "rebind-new-key":
|
|
2243
|
+
return "Re-bind (rotate Ed25519 key)";
|
|
2244
|
+
case "skip":
|
|
2245
|
+
return "Skip (do nothing)";
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
var init_detect = __esm({
|
|
2249
|
+
"../core/dist/detect.js"() {
|
|
2250
|
+
"use strict";
|
|
2251
|
+
init_platform_adapter();
|
|
2252
|
+
init_config();
|
|
2253
|
+
init_registry();
|
|
2254
|
+
init_detector();
|
|
2255
|
+
}
|
|
2256
|
+
});
|
|
2257
|
+
|
|
2258
|
+
// ../core/dist/supervisor-spec.js
|
|
2259
|
+
function buildOpenclawTargetSpec(ctx) {
|
|
2260
|
+
const isMjs = ctx.agentBinary.endsWith(".mjs") || ctx.agentBinary.endsWith(".js");
|
|
2261
|
+
const command = isMjs ? "node" : ctx.agentBinary;
|
|
2262
|
+
const baseArgs = isMjs ? [ctx.agentBinary] : [];
|
|
2263
|
+
const env = {
|
|
2264
|
+
AGENT_GATEWAY_URL: ctx.agentGatewayUrl
|
|
2265
|
+
};
|
|
2266
|
+
if (!ctx.isSystemHome) {
|
|
2267
|
+
env.OPENCLAW_HOME = ctx.agentHome;
|
|
2268
|
+
env.OPENCLAW_GATEWAY_TOKEN = ctx.gatewayToken;
|
|
2269
|
+
}
|
|
2270
|
+
return {
|
|
2271
|
+
id: "openclaw",
|
|
2272
|
+
kind: "openclaw",
|
|
2273
|
+
command,
|
|
2274
|
+
args: [...baseArgs, "gateway", "--allow-unconfigured", "--bind", "lan"],
|
|
2275
|
+
env,
|
|
2276
|
+
cwd: ctx.agentHome,
|
|
2277
|
+
restart: "on-failure",
|
|
2278
|
+
label: "OpenClaw Gateway"
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
2281
|
+
function buildDeviceAgentTargetSpec(input) {
|
|
2282
|
+
const args = [
|
|
2283
|
+
...input.args,
|
|
2284
|
+
"--key",
|
|
2285
|
+
input.keyFile,
|
|
2286
|
+
"--adb",
|
|
2287
|
+
input.serial,
|
|
2288
|
+
"--bridge",
|
|
2289
|
+
input.bridgeUrl
|
|
2290
|
+
];
|
|
2291
|
+
if (input.httpEnabled) {
|
|
2292
|
+
args.push("--http", "--http-port", String(input.httpPort));
|
|
2293
|
+
}
|
|
2294
|
+
const env = {};
|
|
2295
|
+
if (input.agentGatewayUrl)
|
|
2296
|
+
env.AGENT_GATEWAY_URL = input.agentGatewayUrl;
|
|
2297
|
+
return {
|
|
2298
|
+
id: `device-agent-${input.serial}`,
|
|
2299
|
+
kind: "device-agent",
|
|
2300
|
+
command: input.command,
|
|
2301
|
+
args,
|
|
2302
|
+
env: Object.keys(env).length > 0 ? env : void 0,
|
|
2303
|
+
restart: "on-failure",
|
|
2304
|
+
label: `device-agent (${input.serial})`
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
function buildScrcpyBridgeTargetSpec(binary, opts) {
|
|
2308
|
+
return {
|
|
2309
|
+
id: `scrcpy-bridge-${opts.deviceId}`,
|
|
2310
|
+
kind: "scrcpy-bridge",
|
|
2311
|
+
command: binary,
|
|
2312
|
+
args: [],
|
|
2313
|
+
env: scrcpyBridgeRuntime.buildEnv(opts),
|
|
2314
|
+
restart: "on-failure",
|
|
2315
|
+
label: `scrcpy-bridge (${opts.deviceId})`
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2318
|
+
function buildVncBridgeTargetSpec(binary, opts) {
|
|
2319
|
+
return {
|
|
2320
|
+
id: `vnc-bridge-${opts.deviceId}`,
|
|
2321
|
+
kind: "vnc-bridge",
|
|
2322
|
+
command: binary,
|
|
2323
|
+
args: [],
|
|
2324
|
+
env: vncBridgeRuntime.buildEnv(opts),
|
|
2325
|
+
restart: "on-failure",
|
|
2326
|
+
label: `vnc-bridge (${opts.deviceId})`
|
|
2327
|
+
};
|
|
2328
|
+
}
|
|
2329
|
+
var init_supervisor_spec = __esm({
|
|
2330
|
+
"../core/dist/supervisor-spec.js"() {
|
|
2331
|
+
"use strict";
|
|
2332
|
+
init_scrcpy_bridge();
|
|
2333
|
+
init_vnc_bridge();
|
|
2334
|
+
}
|
|
2335
|
+
});
|
|
2336
|
+
|
|
2337
|
+
// ../core/dist/services/types.js
|
|
2338
|
+
var init_types2 = __esm({
|
|
2339
|
+
"../core/dist/services/types.js"() {
|
|
2340
|
+
"use strict";
|
|
2341
|
+
}
|
|
2342
|
+
});
|
|
2343
|
+
|
|
2344
|
+
// ../core/dist/services/ids.js
|
|
2345
|
+
import path from "path";
|
|
2346
|
+
function safeId(id) {
|
|
2347
|
+
return id.replace(/[:/]+/g, "-").replace(/[^A-Za-z0-9._-]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
2348
|
+
}
|
|
2349
|
+
function launchdLabel(id) {
|
|
2350
|
+
return `${LABEL_PREFIX}${safeId(id)}`;
|
|
2351
|
+
}
|
|
2352
|
+
function systemdUnit(id) {
|
|
2353
|
+
return `${SYSTEMD_PREFIX}${safeId(id)}.service`;
|
|
2354
|
+
}
|
|
2355
|
+
function windowsTaskName(id) {
|
|
2356
|
+
return `${WIN_TASK_PREFIX}${safeId(id)}`;
|
|
2357
|
+
}
|
|
2358
|
+
function serviceLogPath(id) {
|
|
2359
|
+
return path.join(beeoHome(), "logs", "services", `${safeId(id)}.log`);
|
|
2360
|
+
}
|
|
2361
|
+
function idFromLaunchdLabel(label) {
|
|
2362
|
+
if (!label.startsWith(LABEL_PREFIX))
|
|
2363
|
+
return null;
|
|
2364
|
+
return label.slice(LABEL_PREFIX.length);
|
|
2365
|
+
}
|
|
2366
|
+
function idFromSystemdUnit(unit) {
|
|
2367
|
+
if (!unit.startsWith(SYSTEMD_PREFIX))
|
|
2368
|
+
return null;
|
|
2369
|
+
const base = unit.endsWith(".service") ? unit.slice(0, -".service".length) : unit;
|
|
2370
|
+
return base.slice(SYSTEMD_PREFIX.length);
|
|
2371
|
+
}
|
|
2372
|
+
function idFromWindowsTaskName(task) {
|
|
2373
|
+
if (!task.startsWith(WIN_TASK_PREFIX))
|
|
2374
|
+
return null;
|
|
2375
|
+
return task.slice(WIN_TASK_PREFIX.length);
|
|
2376
|
+
}
|
|
2377
|
+
var LABEL_PREFIX, SYSTEMD_PREFIX, WIN_TASK_PREFIX;
|
|
2378
|
+
var init_ids = __esm({
|
|
2379
|
+
"../core/dist/services/ids.js"() {
|
|
2380
|
+
"use strict";
|
|
2381
|
+
init_config();
|
|
2382
|
+
LABEL_PREFIX = "ai.beeos.";
|
|
2383
|
+
SYSTEMD_PREFIX = "beeos-";
|
|
2384
|
+
WIN_TASK_PREFIX = "BeeOS-";
|
|
2385
|
+
}
|
|
2386
|
+
});
|
|
2387
|
+
|
|
2388
|
+
// ../core/dist/services/launchd.js
|
|
2389
|
+
import fs from "fs/promises";
|
|
2390
|
+
import fsSync from "fs";
|
|
2391
|
+
import os from "os";
|
|
2392
|
+
import path2 from "path";
|
|
2393
|
+
import { execFile } from "child_process";
|
|
2394
|
+
import { promisify } from "util";
|
|
2395
|
+
function renderPlist(spec, label, logFile) {
|
|
2396
|
+
const args = [spec.command, ...spec.args].map((a) => ` <string>${escapeXml(a)}</string>`);
|
|
2397
|
+
const envEntries = Object.entries(spec.env ?? {});
|
|
2398
|
+
if (!envEntries.some(([k]) => k === "PATH")) {
|
|
2399
|
+
envEntries.unshift([
|
|
2400
|
+
"PATH",
|
|
2401
|
+
"/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin"
|
|
2402
|
+
]);
|
|
2403
|
+
}
|
|
2404
|
+
const envXml = envEntries.map(([k, v]) => ` <key>${escapeXml(k)}</key>
|
|
2405
|
+
<string>${escapeXml(v)}</string>`).join("\n");
|
|
2406
|
+
const restart = spec.restart ?? "on-failure";
|
|
2407
|
+
let keepAliveXml = "";
|
|
2408
|
+
if (restart === "always") {
|
|
2409
|
+
keepAliveXml = ` <key>KeepAlive</key>
|
|
2410
|
+
<true/>
|
|
2411
|
+
`;
|
|
2412
|
+
} else if (restart === "on-failure") {
|
|
2413
|
+
keepAliveXml = ` <key>KeepAlive</key>
|
|
2414
|
+
<dict>
|
|
2415
|
+
<key>SuccessfulExit</key>
|
|
2416
|
+
<false/>
|
|
2417
|
+
</dict>
|
|
2418
|
+
`;
|
|
2419
|
+
}
|
|
2420
|
+
const cwdXml = spec.cwd ? ` <key>WorkingDirectory</key>
|
|
2421
|
+
<string>${escapeXml(spec.cwd)}</string>
|
|
2422
|
+
` : "";
|
|
2423
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
2424
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
2425
|
+
<plist version="1.0">
|
|
2426
|
+
<dict>
|
|
2427
|
+
<key>Label</key>
|
|
2428
|
+
<string>${escapeXml(label)}</string>
|
|
2429
|
+
<key>ProgramArguments</key>
|
|
2430
|
+
<array>
|
|
2431
|
+
${args.join("\n")}
|
|
2432
|
+
</array>
|
|
2433
|
+
<key>RunAtLoad</key>
|
|
2434
|
+
<true/>
|
|
2435
|
+
${keepAliveXml} <key>ThrottleInterval</key>
|
|
2436
|
+
<integer>10</integer>
|
|
2437
|
+
<key>ProcessType</key>
|
|
2438
|
+
<string>Background</string>
|
|
2439
|
+
<key>StandardOutPath</key>
|
|
2440
|
+
<string>${escapeXml(logFile)}</string>
|
|
2441
|
+
<key>StandardErrorPath</key>
|
|
2442
|
+
<string>${escapeXml(logFile)}</string>
|
|
2443
|
+
${cwdXml} <key>EnvironmentVariables</key>
|
|
2444
|
+
<dict>
|
|
2445
|
+
${envXml}
|
|
2446
|
+
</dict>
|
|
2447
|
+
</dict>
|
|
2448
|
+
</plist>
|
|
2449
|
+
`;
|
|
2450
|
+
}
|
|
2451
|
+
function escapeXml(s) {
|
|
2452
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2453
|
+
}
|
|
2454
|
+
function detectKind(id) {
|
|
2455
|
+
if (id === "openclaw")
|
|
2456
|
+
return "openclaw";
|
|
2457
|
+
if (id.startsWith("device-agent-"))
|
|
2458
|
+
return "device-agent";
|
|
2459
|
+
if (id.startsWith("scrcpy-bridge-"))
|
|
2460
|
+
return "scrcpy-bridge";
|
|
2461
|
+
if (id.startsWith("vnc-bridge-"))
|
|
2462
|
+
return "vnc-bridge";
|
|
2463
|
+
return "unknown";
|
|
2464
|
+
}
|
|
2465
|
+
function fallbackStatus(spec, _label, logFile, running) {
|
|
2466
|
+
return {
|
|
2467
|
+
id: spec.id,
|
|
2468
|
+
kind: spec.kind,
|
|
2469
|
+
installed: true,
|
|
2470
|
+
running,
|
|
2471
|
+
pid: null,
|
|
2472
|
+
lastExitCode: null,
|
|
2473
|
+
logFile,
|
|
2474
|
+
label: spec.label ?? null
|
|
2475
|
+
};
|
|
2476
|
+
}
|
|
2477
|
+
var execFileP, LaunchdServiceManager;
|
|
2478
|
+
var init_launchd = __esm({
|
|
2479
|
+
"../core/dist/services/launchd.js"() {
|
|
2480
|
+
"use strict";
|
|
2481
|
+
init_ids();
|
|
2482
|
+
execFileP = promisify(execFile);
|
|
2483
|
+
LaunchdServiceManager = class {
|
|
2484
|
+
kind = "launchd";
|
|
2485
|
+
launchAgentsDir = path2.join(os.homedir(), "Library", "LaunchAgents");
|
|
2486
|
+
logPath(id) {
|
|
2487
|
+
return serviceLogPath(id);
|
|
2488
|
+
}
|
|
2489
|
+
async install(spec) {
|
|
2490
|
+
const label = launchdLabel(spec.id);
|
|
2491
|
+
const plist = this.plistPath(label);
|
|
2492
|
+
const logFile = this.logPath(spec.id);
|
|
2493
|
+
await fs.mkdir(path2.dirname(plist), { recursive: true });
|
|
2494
|
+
await fs.mkdir(path2.dirname(logFile), { recursive: true });
|
|
2495
|
+
await fs.writeFile(plist, renderPlist(spec, label, logFile), {
|
|
2496
|
+
mode: 420
|
|
2497
|
+
});
|
|
2498
|
+
const uid = process.getuid?.();
|
|
2499
|
+
if (uid !== void 0) {
|
|
2500
|
+
try {
|
|
2501
|
+
await execFileP("launchctl", ["bootout", `gui/${uid}/${label}`]);
|
|
2502
|
+
} catch {
|
|
2503
|
+
}
|
|
2504
|
+
try {
|
|
2505
|
+
await execFileP("launchctl", ["bootstrap", `gui/${uid}`, plist]);
|
|
2506
|
+
} catch (e) {
|
|
2507
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2508
|
+
try {
|
|
2509
|
+
await execFileP("launchctl", ["load", "-w", plist]);
|
|
2510
|
+
} catch {
|
|
2511
|
+
throw new Error(`launchctl bootstrap failed for ${label}: ${msg}`);
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
try {
|
|
2515
|
+
await execFileP("launchctl", ["kickstart", "-k", `gui/${uid}/${label}`]);
|
|
2516
|
+
} catch {
|
|
2517
|
+
}
|
|
2518
|
+
} else {
|
|
2519
|
+
await execFileP("launchctl", ["load", "-w", plist]);
|
|
2520
|
+
}
|
|
2521
|
+
const status2 = await this.status(spec.id);
|
|
2522
|
+
return status2 ?? fallbackStatus(spec, label, logFile, false);
|
|
2523
|
+
}
|
|
2524
|
+
async uninstall(id) {
|
|
2525
|
+
const label = launchdLabel(id);
|
|
2526
|
+
const plist = this.plistPath(label);
|
|
2527
|
+
const existed = fsSync.existsSync(plist);
|
|
2528
|
+
const uid = process.getuid?.();
|
|
2529
|
+
if (uid !== void 0) {
|
|
2530
|
+
try {
|
|
2531
|
+
await execFileP("launchctl", ["bootout", `gui/${uid}/${label}`]);
|
|
2532
|
+
} catch {
|
|
2533
|
+
try {
|
|
2534
|
+
await execFileP("launchctl", ["unload", "-w", plist]);
|
|
2535
|
+
} catch {
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
} else {
|
|
2539
|
+
try {
|
|
2540
|
+
await execFileP("launchctl", ["unload", "-w", plist]);
|
|
2541
|
+
} catch {
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
if (existed) {
|
|
2545
|
+
try {
|
|
2546
|
+
await fs.unlink(plist);
|
|
2547
|
+
} catch {
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
return existed;
|
|
2551
|
+
}
|
|
2552
|
+
async restart(id) {
|
|
2553
|
+
const label = launchdLabel(id);
|
|
2554
|
+
const uid = process.getuid?.();
|
|
2555
|
+
if (uid === void 0) {
|
|
2556
|
+
throw new Error("launchctl restart requires a valid uid");
|
|
2557
|
+
}
|
|
2558
|
+
await execFileP("launchctl", ["kickstart", "-k", `gui/${uid}/${label}`]);
|
|
2559
|
+
}
|
|
2560
|
+
async status(id) {
|
|
2561
|
+
const label = launchdLabel(id);
|
|
2562
|
+
const plist = this.plistPath(label);
|
|
2563
|
+
if (!fsSync.existsSync(plist))
|
|
2564
|
+
return null;
|
|
2565
|
+
const logFile = this.logPath(id);
|
|
2566
|
+
try {
|
|
2567
|
+
const { stdout } = await execFileP("launchctl", ["list", label]);
|
|
2568
|
+
const pidMatch = /"PID"\s*=\s*(\d+)/.exec(stdout);
|
|
2569
|
+
const exitMatch = /"LastExitStatus"\s*=\s*(-?\d+)/.exec(stdout);
|
|
2570
|
+
const pid = pidMatch ? Number(pidMatch[1]) : null;
|
|
2571
|
+
return {
|
|
2572
|
+
id,
|
|
2573
|
+
kind: detectKind(id),
|
|
2574
|
+
installed: true,
|
|
2575
|
+
running: pid !== null && pid > 0,
|
|
2576
|
+
pid,
|
|
2577
|
+
lastExitCode: exitMatch ? Number(exitMatch[1]) : null,
|
|
2578
|
+
logFile,
|
|
2579
|
+
label: null
|
|
2580
|
+
};
|
|
2581
|
+
} catch {
|
|
2582
|
+
return {
|
|
2583
|
+
id,
|
|
2584
|
+
kind: detectKind(id),
|
|
2585
|
+
installed: true,
|
|
2586
|
+
running: false,
|
|
2587
|
+
pid: null,
|
|
2588
|
+
lastExitCode: null,
|
|
2589
|
+
logFile,
|
|
2590
|
+
label: null
|
|
2591
|
+
};
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
async list() {
|
|
2595
|
+
let stdout = "";
|
|
2596
|
+
try {
|
|
2597
|
+
({ stdout } = await execFileP("launchctl", ["list"]));
|
|
2598
|
+
} catch {
|
|
2599
|
+
return [];
|
|
2600
|
+
}
|
|
2601
|
+
const results = [];
|
|
2602
|
+
for (const raw of stdout.split("\n")) {
|
|
2603
|
+
const line = raw.trim();
|
|
2604
|
+
if (!line)
|
|
2605
|
+
continue;
|
|
2606
|
+
const cols = line.split(/\s+/);
|
|
2607
|
+
if (cols.length < 3)
|
|
2608
|
+
continue;
|
|
2609
|
+
const label = cols[cols.length - 1];
|
|
2610
|
+
const id = idFromLaunchdLabel(label);
|
|
2611
|
+
if (!id)
|
|
2612
|
+
continue;
|
|
2613
|
+
const pidCol = cols[0];
|
|
2614
|
+
const pid = pidCol === "-" ? null : Number(pidCol);
|
|
2615
|
+
const exitCol = cols[1];
|
|
2616
|
+
const lastExitCode = exitCol === "-" ? null : Number(exitCol);
|
|
2617
|
+
results.push({
|
|
2618
|
+
id,
|
|
2619
|
+
kind: detectKind(id),
|
|
2620
|
+
installed: true,
|
|
2621
|
+
running: pid !== null && !Number.isNaN(pid) && pid > 0,
|
|
2622
|
+
pid: pid !== null && !Number.isNaN(pid) ? pid : null,
|
|
2623
|
+
lastExitCode: lastExitCode !== null && !Number.isNaN(lastExitCode) ? lastExitCode : null,
|
|
2624
|
+
logFile: this.logPath(id),
|
|
2625
|
+
label: null
|
|
2626
|
+
});
|
|
2627
|
+
}
|
|
2628
|
+
try {
|
|
2629
|
+
const files = await fs.readdir(this.launchAgentsDir);
|
|
2630
|
+
for (const f of files) {
|
|
2631
|
+
if (!f.startsWith(LABEL_PREFIX) || !f.endsWith(".plist"))
|
|
2632
|
+
continue;
|
|
2633
|
+
const label = f.slice(0, -".plist".length);
|
|
2634
|
+
const id = idFromLaunchdLabel(label);
|
|
2635
|
+
if (!id)
|
|
2636
|
+
continue;
|
|
2637
|
+
if (results.some((r) => r.id === id))
|
|
2638
|
+
continue;
|
|
2639
|
+
results.push({
|
|
2640
|
+
id,
|
|
2641
|
+
kind: detectKind(id),
|
|
2642
|
+
installed: true,
|
|
2643
|
+
running: false,
|
|
2644
|
+
pid: null,
|
|
2645
|
+
lastExitCode: null,
|
|
2646
|
+
logFile: this.logPath(id),
|
|
2647
|
+
label: null
|
|
2648
|
+
});
|
|
2649
|
+
}
|
|
2650
|
+
} catch {
|
|
2651
|
+
}
|
|
2652
|
+
return results;
|
|
2653
|
+
}
|
|
2654
|
+
async selfCheck() {
|
|
2655
|
+
try {
|
|
2656
|
+
await execFileP("launchctl", ["help"]);
|
|
2657
|
+
return { available: true, warnings: [], hints: [] };
|
|
2658
|
+
} catch {
|
|
2659
|
+
return {
|
|
2660
|
+
available: false,
|
|
2661
|
+
warnings: ["launchctl is not available on this system"],
|
|
2662
|
+
hints: ["launchd is only available on macOS"]
|
|
2663
|
+
};
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
plistPath(label) {
|
|
2667
|
+
return path2.join(this.launchAgentsDir, `${label}.plist`);
|
|
2668
|
+
}
|
|
2669
|
+
};
|
|
2670
|
+
}
|
|
2671
|
+
});
|
|
2672
|
+
|
|
2673
|
+
// ../core/dist/services/systemd.js
|
|
2674
|
+
import fs2 from "fs/promises";
|
|
2675
|
+
import fsSync2 from "fs";
|
|
2676
|
+
import os2 from "os";
|
|
2677
|
+
import path3 from "path";
|
|
2678
|
+
import { execFile as execFile2 } from "child_process";
|
|
2679
|
+
import { promisify as promisify2 } from "util";
|
|
2680
|
+
function renderUnit(spec, logFile) {
|
|
2681
|
+
const restart = spec.restart ?? "on-failure";
|
|
2682
|
+
const restartValue = restart === "always" ? "always" : restart === "never" ? "no" : "on-failure";
|
|
2683
|
+
const execStart = [shellQuote(spec.command), ...spec.args.map(shellQuote)].join(" ");
|
|
2684
|
+
const envLines = Object.entries(spec.env ?? {}).map(([k, v]) => `Environment=${k}=${escapeSystemdValue(v)}`).join("\n");
|
|
2685
|
+
const cwdLine = spec.cwd ? `WorkingDirectory=${spec.cwd}
|
|
2686
|
+
` : "";
|
|
2687
|
+
const labelLine = spec.label ? spec.label : `BeeOS ${spec.kind} (${spec.id})`;
|
|
2688
|
+
return `[Unit]
|
|
2689
|
+
Description=${labelLine}
|
|
2690
|
+
After=network-online.target
|
|
2691
|
+
Wants=network-online.target
|
|
2692
|
+
|
|
2693
|
+
[Service]
|
|
2694
|
+
Type=simple
|
|
2695
|
+
ExecStart=${execStart}
|
|
2696
|
+
Restart=${restartValue}
|
|
2697
|
+
RestartSec=5
|
|
2698
|
+
StartLimitBurst=10
|
|
2699
|
+
StartLimitIntervalSec=60
|
|
2700
|
+
StandardOutput=append:${logFile}
|
|
2701
|
+
StandardError=append:${logFile}
|
|
2702
|
+
# Minimal PATH so spawned children can find interpreters.
|
|
2703
|
+
Environment=PATH=/usr/local/bin:/usr/bin:/bin
|
|
2704
|
+
${envLines ? envLines + "\n" : ""}${cwdLine}NoNewPrivileges=true
|
|
2705
|
+
|
|
2706
|
+
[Install]
|
|
2707
|
+
WantedBy=default.target
|
|
2708
|
+
`;
|
|
2709
|
+
}
|
|
2710
|
+
function escapeSystemdValue(v) {
|
|
2711
|
+
if (/[\s"'$]/.test(v))
|
|
2712
|
+
return `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
2713
|
+
return v;
|
|
2714
|
+
}
|
|
2715
|
+
function shellQuote(s) {
|
|
2716
|
+
if (s === "")
|
|
2717
|
+
return '""';
|
|
2718
|
+
if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(s))
|
|
2719
|
+
return s;
|
|
2720
|
+
return `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
2721
|
+
}
|
|
2722
|
+
function parseShowOutput(stdout) {
|
|
2723
|
+
const out = {};
|
|
2724
|
+
for (const line of stdout.split("\n")) {
|
|
2725
|
+
const eq = line.indexOf("=");
|
|
2726
|
+
if (eq < 0)
|
|
2727
|
+
continue;
|
|
2728
|
+
out[line.slice(0, eq).trim()] = line.slice(eq + 1).trim();
|
|
2729
|
+
}
|
|
2730
|
+
return out;
|
|
2731
|
+
}
|
|
2732
|
+
function detectKind2(id) {
|
|
2733
|
+
if (id === "openclaw")
|
|
2734
|
+
return "openclaw";
|
|
2735
|
+
if (id.startsWith("device-agent-"))
|
|
2736
|
+
return "device-agent";
|
|
2737
|
+
if (id.startsWith("scrcpy-bridge-"))
|
|
2738
|
+
return "scrcpy-bridge";
|
|
2739
|
+
if (id.startsWith("vnc-bridge-"))
|
|
2740
|
+
return "vnc-bridge";
|
|
2741
|
+
return "unknown";
|
|
2742
|
+
}
|
|
2743
|
+
var execFileP2, SystemdServiceManager;
|
|
2744
|
+
var init_systemd = __esm({
|
|
2745
|
+
"../core/dist/services/systemd.js"() {
|
|
2746
|
+
"use strict";
|
|
2747
|
+
init_ids();
|
|
2748
|
+
execFileP2 = promisify2(execFile2);
|
|
2749
|
+
SystemdServiceManager = class {
|
|
2750
|
+
kind = "systemd-user";
|
|
2751
|
+
unitDir = path3.join(os2.homedir(), ".config", "systemd", "user");
|
|
2752
|
+
logPath(id) {
|
|
2753
|
+
return serviceLogPath(id);
|
|
2754
|
+
}
|
|
2755
|
+
async install(spec) {
|
|
2756
|
+
const unit = systemdUnit(spec.id);
|
|
2757
|
+
const unitFile = this.unitPath(unit);
|
|
2758
|
+
const logFile = this.logPath(spec.id);
|
|
2759
|
+
await fs2.mkdir(path3.dirname(unitFile), { recursive: true });
|
|
2760
|
+
await fs2.mkdir(path3.dirname(logFile), { recursive: true });
|
|
2761
|
+
await fs2.writeFile(unitFile, renderUnit(spec, logFile), { mode: 420 });
|
|
2762
|
+
try {
|
|
2763
|
+
await execFileP2("systemctl", ["--user", "daemon-reload"]);
|
|
2764
|
+
} catch (e) {
|
|
2765
|
+
throw new Error(`systemctl --user daemon-reload failed (is systemd --user available?): ${e}`);
|
|
2766
|
+
}
|
|
2767
|
+
await execFileP2("systemctl", ["--user", "enable", "--now", unit]);
|
|
2768
|
+
try {
|
|
2769
|
+
await execFileP2("systemctl", ["--user", "restart", unit]);
|
|
2770
|
+
} catch {
|
|
2771
|
+
}
|
|
2772
|
+
const status2 = await this.status(spec.id);
|
|
2773
|
+
return status2 ?? {
|
|
2774
|
+
id: spec.id,
|
|
2775
|
+
kind: spec.kind,
|
|
2776
|
+
installed: true,
|
|
2777
|
+
running: false,
|
|
2778
|
+
pid: null,
|
|
2779
|
+
lastExitCode: null,
|
|
2780
|
+
logFile,
|
|
2781
|
+
label: spec.label ?? null
|
|
2782
|
+
};
|
|
2783
|
+
}
|
|
2784
|
+
async uninstall(id) {
|
|
2785
|
+
const unit = systemdUnit(id);
|
|
2786
|
+
const unitFile = this.unitPath(unit);
|
|
2787
|
+
const existed = fsSync2.existsSync(unitFile);
|
|
2788
|
+
if (existed) {
|
|
2789
|
+
try {
|
|
2790
|
+
await execFileP2("systemctl", ["--user", "disable", "--now", unit]);
|
|
2791
|
+
} catch {
|
|
2792
|
+
}
|
|
2793
|
+
try {
|
|
2794
|
+
await fs2.unlink(unitFile);
|
|
2795
|
+
} catch {
|
|
2796
|
+
}
|
|
2797
|
+
try {
|
|
2798
|
+
await execFileP2("systemctl", ["--user", "daemon-reload"]);
|
|
2799
|
+
} catch {
|
|
2800
|
+
}
|
|
2801
|
+
} else {
|
|
2802
|
+
try {
|
|
2803
|
+
await execFileP2("systemctl", ["--user", "disable", "--now", unit]);
|
|
2804
|
+
} catch {
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
return existed;
|
|
2808
|
+
}
|
|
2809
|
+
async restart(id) {
|
|
2810
|
+
const unit = systemdUnit(id);
|
|
2811
|
+
await execFileP2("systemctl", ["--user", "restart", unit]);
|
|
2812
|
+
}
|
|
2813
|
+
async status(id) {
|
|
2814
|
+
const unit = systemdUnit(id);
|
|
2815
|
+
const unitFile = this.unitPath(unit);
|
|
2816
|
+
if (!fsSync2.existsSync(unitFile))
|
|
2817
|
+
return null;
|
|
2818
|
+
const logFile = this.logPath(id);
|
|
2819
|
+
let active = false;
|
|
2820
|
+
let pid = null;
|
|
2821
|
+
let exitCode = null;
|
|
2822
|
+
try {
|
|
2823
|
+
const { stdout } = await execFileP2("systemctl", [
|
|
2824
|
+
"--user",
|
|
2825
|
+
"show",
|
|
2826
|
+
unit,
|
|
2827
|
+
"--property=ActiveState,MainPID,ExecMainStatus"
|
|
2828
|
+
]);
|
|
2829
|
+
const props = parseShowOutput(stdout);
|
|
2830
|
+
active = props.ActiveState === "active";
|
|
2831
|
+
const mp = Number(props.MainPID);
|
|
2832
|
+
pid = Number.isFinite(mp) && mp > 0 ? mp : null;
|
|
2833
|
+
const ex = Number(props.ExecMainStatus);
|
|
2834
|
+
exitCode = Number.isFinite(ex) ? ex : null;
|
|
2835
|
+
} catch {
|
|
2836
|
+
}
|
|
2837
|
+
return {
|
|
2838
|
+
id,
|
|
2839
|
+
kind: detectKind2(id),
|
|
2840
|
+
installed: true,
|
|
2841
|
+
running: active && pid !== null,
|
|
2842
|
+
pid,
|
|
2843
|
+
lastExitCode: exitCode,
|
|
2844
|
+
logFile,
|
|
2845
|
+
label: null
|
|
2846
|
+
};
|
|
2847
|
+
}
|
|
2848
|
+
async list() {
|
|
2849
|
+
const results = [];
|
|
2850
|
+
let unitFiles = [];
|
|
2851
|
+
try {
|
|
2852
|
+
unitFiles = (await fs2.readdir(this.unitDir)).filter((f) => f.startsWith(SYSTEMD_PREFIX) && f.endsWith(".service"));
|
|
2853
|
+
} catch {
|
|
2854
|
+
return results;
|
|
2855
|
+
}
|
|
2856
|
+
for (const f of unitFiles) {
|
|
2857
|
+
const id = idFromSystemdUnit(f);
|
|
2858
|
+
if (!id)
|
|
2859
|
+
continue;
|
|
2860
|
+
const s = await this.status(id);
|
|
2861
|
+
if (s)
|
|
2862
|
+
results.push(s);
|
|
2863
|
+
}
|
|
2864
|
+
return results;
|
|
2865
|
+
}
|
|
2866
|
+
async selfCheck() {
|
|
2867
|
+
const warnings = [];
|
|
2868
|
+
const hints = [];
|
|
2869
|
+
try {
|
|
2870
|
+
await execFileP2("systemctl", ["--user", "--version"]);
|
|
2871
|
+
} catch {
|
|
2872
|
+
return {
|
|
2873
|
+
available: false,
|
|
2874
|
+
warnings: ["systemctl --user is not available"],
|
|
2875
|
+
hints: [
|
|
2876
|
+
"Install systemd or use a distro with systemd --user enabled."
|
|
2877
|
+
]
|
|
2878
|
+
};
|
|
2879
|
+
}
|
|
2880
|
+
try {
|
|
2881
|
+
const user = os2.userInfo().username;
|
|
2882
|
+
const { stdout } = await execFileP2("loginctl", ["show-user", user]);
|
|
2883
|
+
if (!/Linger=yes/i.test(stdout)) {
|
|
2884
|
+
warnings.push("systemd --user linger is OFF \u2014 BeeOS services will stop when you log out.");
|
|
2885
|
+
hints.push(`sudo loginctl enable-linger ${user}`);
|
|
2886
|
+
}
|
|
2887
|
+
} catch {
|
|
2888
|
+
hints.push("Could not query loginctl; linger status unknown.");
|
|
2889
|
+
}
|
|
2890
|
+
return { available: true, warnings, hints };
|
|
2891
|
+
}
|
|
2892
|
+
unitPath(unit) {
|
|
2893
|
+
return path3.join(this.unitDir, unit);
|
|
2894
|
+
}
|
|
2895
|
+
};
|
|
2896
|
+
}
|
|
2897
|
+
});
|
|
2898
|
+
|
|
2899
|
+
// ../core/dist/services/task-scheduler.js
|
|
2900
|
+
import fs3 from "fs/promises";
|
|
2901
|
+
import os3 from "os";
|
|
2902
|
+
import path4 from "path";
|
|
2903
|
+
import { execFile as execFile3 } from "child_process";
|
|
2904
|
+
import { promisify as promisify3 } from "util";
|
|
2905
|
+
function renderTaskXml(spec, logFile) {
|
|
2906
|
+
const restart = spec.restart ?? "on-failure";
|
|
2907
|
+
const restartXml = restart === "never" ? "" : ` <RestartOnFailure>
|
|
2908
|
+
<Interval>PT1M</Interval>
|
|
2909
|
+
<Count>999</Count>
|
|
2910
|
+
</RestartOnFailure>`;
|
|
2911
|
+
const cwdXml = spec.cwd ? ` <WorkingDirectory>${escapeXml2(spec.cwd)}</WorkingDirectory>
|
|
2912
|
+
` : "";
|
|
2913
|
+
const author = (() => {
|
|
2914
|
+
try {
|
|
2915
|
+
return os3.userInfo().username;
|
|
2916
|
+
} catch {
|
|
2917
|
+
return "BeeOS";
|
|
2918
|
+
}
|
|
2919
|
+
})();
|
|
2920
|
+
const innerArgs = spec.args.map(quoteCmdArg).join(" ");
|
|
2921
|
+
const envPrefix = Object.entries(spec.env ?? {}).map(([k, v]) => `set ${cmdEscapeVar(k)}=${cmdEscapeValue(v)}&& `).join("");
|
|
2922
|
+
const inner = `${envPrefix}${quoteCmdArg(spec.command)}${innerArgs ? " " + innerArgs : ""} >> ${quoteCmdArg(logFile)} 2>&1`;
|
|
2923
|
+
const command = "cmd.exe";
|
|
2924
|
+
const argsAttr = `/d /c "${inner}"`;
|
|
2925
|
+
return `<?xml version="1.0" encoding="UTF-16"?>
|
|
2926
|
+
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
2927
|
+
<RegistrationInfo>
|
|
2928
|
+
<Author>${escapeXml2(author)}</Author>
|
|
2929
|
+
<Description>${escapeXml2(spec.label ?? `BeeOS ${spec.kind} (${spec.id})`)}</Description>
|
|
2930
|
+
</RegistrationInfo>
|
|
2931
|
+
<Triggers>
|
|
2932
|
+
<LogonTrigger>
|
|
2933
|
+
<Enabled>true</Enabled>
|
|
2934
|
+
<UserId>${escapeXml2(author)}</UserId>
|
|
2935
|
+
</LogonTrigger>
|
|
2936
|
+
</Triggers>
|
|
2937
|
+
<Principals>
|
|
2938
|
+
<Principal id="Author">
|
|
2939
|
+
<UserId>${escapeXml2(author)}</UserId>
|
|
2940
|
+
<LogonType>InteractiveToken</LogonType>
|
|
2941
|
+
<RunLevel>LeastPrivilege</RunLevel>
|
|
2942
|
+
</Principal>
|
|
2943
|
+
</Principals>
|
|
2944
|
+
<Settings>
|
|
2945
|
+
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
|
|
2946
|
+
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
|
2947
|
+
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
|
2948
|
+
<AllowHardTerminate>true</AllowHardTerminate>
|
|
2949
|
+
<StartWhenAvailable>true</StartWhenAvailable>
|
|
2950
|
+
<AllowStartOnDemand>true</AllowStartOnDemand>
|
|
2951
|
+
<Enabled>true</Enabled>
|
|
2952
|
+
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
|
|
2953
|
+
${restartXml ? restartXml + "\n" : ""} </Settings>
|
|
2954
|
+
<Actions Context="Author">
|
|
2955
|
+
<Exec>
|
|
2956
|
+
<Command>${escapeXml2(command)}</Command>
|
|
2957
|
+
<Arguments>${escapeXml2(argsAttr)}</Arguments>
|
|
2958
|
+
${cwdXml} </Exec>
|
|
2959
|
+
</Actions>
|
|
2960
|
+
</Task>`;
|
|
2961
|
+
}
|
|
2962
|
+
function quoteCmdArg(s) {
|
|
2963
|
+
return `"${s.replace(/"/g, '""')}"`;
|
|
2964
|
+
}
|
|
2965
|
+
function cmdEscapeVar(k) {
|
|
2966
|
+
return k.replace(/[=]/g, "");
|
|
2967
|
+
}
|
|
2968
|
+
function cmdEscapeValue(v) {
|
|
2969
|
+
return v.replace(/(["&|<>^])/g, "^$1");
|
|
2970
|
+
}
|
|
2971
|
+
function escapeXml2(s) {
|
|
2972
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2973
|
+
}
|
|
2974
|
+
function detectKind3(id) {
|
|
2975
|
+
if (id === "openclaw")
|
|
2976
|
+
return "openclaw";
|
|
2977
|
+
if (id.startsWith("device-agent-"))
|
|
2978
|
+
return "device-agent";
|
|
2979
|
+
if (id.startsWith("scrcpy-bridge-"))
|
|
2980
|
+
return "scrcpy-bridge";
|
|
2981
|
+
if (id.startsWith("vnc-bridge-"))
|
|
2982
|
+
return "vnc-bridge";
|
|
2983
|
+
return "unknown";
|
|
2984
|
+
}
|
|
2985
|
+
var execFileP3, TaskSchedulerServiceManager;
|
|
2986
|
+
var init_task_scheduler = __esm({
|
|
2987
|
+
"../core/dist/services/task-scheduler.js"() {
|
|
2988
|
+
"use strict";
|
|
2989
|
+
init_ids();
|
|
2990
|
+
execFileP3 = promisify3(execFile3);
|
|
2991
|
+
TaskSchedulerServiceManager = class {
|
|
2992
|
+
kind = "task-scheduler";
|
|
2993
|
+
logPath(id) {
|
|
2994
|
+
return serviceLogPath(id);
|
|
2995
|
+
}
|
|
2996
|
+
async install(spec) {
|
|
2997
|
+
const task = windowsTaskName(spec.id);
|
|
2998
|
+
const logFile = this.logPath(spec.id);
|
|
2999
|
+
await fs3.mkdir(path4.dirname(logFile), { recursive: true });
|
|
3000
|
+
const xml = renderTaskXml(spec, logFile);
|
|
3001
|
+
const tmp = path4.join(os3.tmpdir(), `beeos-task-${Date.now()}-${spec.id.replace(/[^a-z0-9]/gi, "_")}.xml`);
|
|
3002
|
+
await fs3.writeFile(tmp, "\uFEFF" + xml, { encoding: "utf16le" });
|
|
3003
|
+
try {
|
|
3004
|
+
await execFileP3("schtasks", ["/Create", "/TN", task, "/XML", tmp, "/F"]);
|
|
3005
|
+
try {
|
|
3006
|
+
await execFileP3("schtasks", ["/Run", "/TN", task]);
|
|
3007
|
+
} catch {
|
|
3008
|
+
}
|
|
3009
|
+
} finally {
|
|
3010
|
+
try {
|
|
3011
|
+
await fs3.unlink(tmp);
|
|
3012
|
+
} catch {
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
const status2 = await this.status(spec.id);
|
|
3016
|
+
return status2 ?? {
|
|
3017
|
+
id: spec.id,
|
|
3018
|
+
kind: spec.kind,
|
|
3019
|
+
installed: true,
|
|
3020
|
+
running: false,
|
|
3021
|
+
pid: null,
|
|
3022
|
+
lastExitCode: null,
|
|
3023
|
+
logFile,
|
|
3024
|
+
label: spec.label ?? null
|
|
3025
|
+
};
|
|
3026
|
+
}
|
|
3027
|
+
async uninstall(id) {
|
|
3028
|
+
const task = windowsTaskName(id);
|
|
3029
|
+
try {
|
|
3030
|
+
await execFileP3("schtasks", ["/Delete", "/TN", task, "/F"]);
|
|
3031
|
+
return true;
|
|
3032
|
+
} catch {
|
|
3033
|
+
return false;
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
async restart(id) {
|
|
3037
|
+
const task = windowsTaskName(id);
|
|
3038
|
+
try {
|
|
3039
|
+
await execFileP3("schtasks", ["/End", "/TN", task]);
|
|
3040
|
+
} catch {
|
|
3041
|
+
}
|
|
3042
|
+
await execFileP3("schtasks", ["/Run", "/TN", task]);
|
|
3043
|
+
}
|
|
3044
|
+
async status(id) {
|
|
3045
|
+
const task = windowsTaskName(id);
|
|
3046
|
+
const logFile = this.logPath(id);
|
|
3047
|
+
try {
|
|
3048
|
+
const { stdout } = await execFileP3("schtasks", [
|
|
3049
|
+
"/Query",
|
|
3050
|
+
"/TN",
|
|
3051
|
+
task,
|
|
3052
|
+
"/V",
|
|
3053
|
+
"/FO",
|
|
3054
|
+
"CSV"
|
|
3055
|
+
]);
|
|
3056
|
+
const running = /"\s*Running\s*"/i.test(stdout);
|
|
3057
|
+
const exitMatch = /"Last Result"\s*,\s*"(-?\d+)"/i.exec(stdout);
|
|
3058
|
+
return {
|
|
3059
|
+
id,
|
|
3060
|
+
kind: detectKind3(id),
|
|
3061
|
+
installed: true,
|
|
3062
|
+
running,
|
|
3063
|
+
pid: null,
|
|
3064
|
+
lastExitCode: exitMatch ? Number(exitMatch[1]) : null,
|
|
3065
|
+
logFile,
|
|
3066
|
+
label: null
|
|
3067
|
+
};
|
|
3068
|
+
} catch {
|
|
3069
|
+
return null;
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
async list() {
|
|
3073
|
+
let stdout = "";
|
|
3074
|
+
try {
|
|
3075
|
+
({ stdout } = await execFileP3("schtasks", ["/Query", "/FO", "CSV"]));
|
|
3076
|
+
} catch {
|
|
3077
|
+
return [];
|
|
3078
|
+
}
|
|
3079
|
+
const results = [];
|
|
3080
|
+
const lines = stdout.split(/\r?\n/).slice(1);
|
|
3081
|
+
for (const line of lines) {
|
|
3082
|
+
const m = /^"([^"]+)","([^"]*)","([^"]*)"/.exec(line);
|
|
3083
|
+
if (!m)
|
|
3084
|
+
continue;
|
|
3085
|
+
const rawName = m[1];
|
|
3086
|
+
const bare = rawName.startsWith("\\") ? rawName.slice(1) : rawName;
|
|
3087
|
+
if (!bare.startsWith(WIN_TASK_PREFIX))
|
|
3088
|
+
continue;
|
|
3089
|
+
const id = idFromWindowsTaskName(bare);
|
|
3090
|
+
if (!id)
|
|
3091
|
+
continue;
|
|
3092
|
+
results.push({
|
|
3093
|
+
id,
|
|
3094
|
+
kind: detectKind3(id),
|
|
3095
|
+
installed: true,
|
|
3096
|
+
running: /Running/i.test(m[3]),
|
|
3097
|
+
pid: null,
|
|
3098
|
+
lastExitCode: null,
|
|
3099
|
+
logFile: this.logPath(id),
|
|
3100
|
+
label: null
|
|
3101
|
+
});
|
|
3102
|
+
}
|
|
3103
|
+
return results;
|
|
3104
|
+
}
|
|
3105
|
+
async selfCheck() {
|
|
3106
|
+
try {
|
|
3107
|
+
await execFileP3("schtasks", ["/?"]);
|
|
3108
|
+
return {
|
|
3109
|
+
available: true,
|
|
3110
|
+
warnings: ["Windows Task Scheduler support is in preview \u2014 restart-on-crash is best-effort."],
|
|
3111
|
+
hints: []
|
|
3112
|
+
};
|
|
3113
|
+
} catch {
|
|
3114
|
+
return {
|
|
3115
|
+
available: false,
|
|
3116
|
+
warnings: ["schtasks.exe is not available"],
|
|
3117
|
+
hints: ["Windows Task Scheduler is only available on Windows."]
|
|
3118
|
+
};
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
};
|
|
3122
|
+
}
|
|
3123
|
+
});
|
|
3124
|
+
|
|
3125
|
+
// ../core/dist/services/fallback.js
|
|
3126
|
+
import fs4 from "fs/promises";
|
|
3127
|
+
import fsSync3 from "fs";
|
|
3128
|
+
import path5 from "path";
|
|
3129
|
+
import { spawn } from "child_process";
|
|
3130
|
+
function isAlive(pid) {
|
|
3131
|
+
try {
|
|
3132
|
+
process.kill(pid, 0);
|
|
3133
|
+
return true;
|
|
3134
|
+
} catch {
|
|
3135
|
+
return false;
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
var FallbackServiceManager;
|
|
3139
|
+
var init_fallback = __esm({
|
|
3140
|
+
"../core/dist/services/fallback.js"() {
|
|
3141
|
+
"use strict";
|
|
3142
|
+
init_ids();
|
|
3143
|
+
init_config();
|
|
3144
|
+
FallbackServiceManager = class {
|
|
3145
|
+
kind = "fallback";
|
|
3146
|
+
stateDir = path5.join(beeoHome(), "services", "fallback");
|
|
3147
|
+
logPath(id) {
|
|
3148
|
+
return serviceLogPath(id);
|
|
3149
|
+
}
|
|
3150
|
+
async install(spec) {
|
|
3151
|
+
const logFile = this.logPath(spec.id);
|
|
3152
|
+
await fs4.mkdir(path5.dirname(logFile), { recursive: true });
|
|
3153
|
+
await fs4.mkdir(this.stateDir, { recursive: true });
|
|
3154
|
+
await this.uninstall(spec.id);
|
|
3155
|
+
const out = fsSync3.openSync(logFile, "a");
|
|
3156
|
+
const err = fsSync3.openSync(logFile, "a");
|
|
3157
|
+
const child = spawn(spec.command, spec.args, {
|
|
3158
|
+
cwd: spec.cwd,
|
|
3159
|
+
env: { ...process.env, ...spec.env ?? {} },
|
|
3160
|
+
detached: true,
|
|
3161
|
+
stdio: ["ignore", out, err]
|
|
3162
|
+
});
|
|
3163
|
+
child.unref();
|
|
3164
|
+
if (child.pid) {
|
|
3165
|
+
await fs4.writeFile(this.pidPath(spec.id), `${child.pid}
|
|
3166
|
+
${spec.id}
|
|
3167
|
+
`);
|
|
3168
|
+
await fs4.writeFile(this.specPath(spec.id), JSON.stringify({ spec, startedAt: Date.now() }, null, 2));
|
|
3169
|
+
}
|
|
3170
|
+
return {
|
|
3171
|
+
id: spec.id,
|
|
3172
|
+
kind: spec.kind,
|
|
3173
|
+
installed: true,
|
|
3174
|
+
running: child.pid !== void 0,
|
|
3175
|
+
pid: child.pid ?? null,
|
|
3176
|
+
lastExitCode: null,
|
|
3177
|
+
logFile,
|
|
3178
|
+
label: spec.label ?? null
|
|
3179
|
+
};
|
|
3180
|
+
}
|
|
3181
|
+
async uninstall(id) {
|
|
3182
|
+
const pidFile2 = this.pidPath(id);
|
|
3183
|
+
const specFile = this.specPath(id);
|
|
3184
|
+
const existed = fsSync3.existsSync(pidFile2) || fsSync3.existsSync(specFile);
|
|
3185
|
+
try {
|
|
3186
|
+
const pid = await this.readPid(id);
|
|
3187
|
+
if (pid !== null) {
|
|
3188
|
+
try {
|
|
3189
|
+
process.kill(pid, "SIGTERM");
|
|
3190
|
+
} catch {
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
} catch {
|
|
3194
|
+
}
|
|
3195
|
+
try {
|
|
3196
|
+
await fs4.unlink(pidFile2);
|
|
3197
|
+
} catch {
|
|
3198
|
+
}
|
|
3199
|
+
try {
|
|
3200
|
+
await fs4.unlink(specFile);
|
|
3201
|
+
} catch {
|
|
3202
|
+
}
|
|
3203
|
+
return existed;
|
|
3204
|
+
}
|
|
3205
|
+
async restart(id) {
|
|
3206
|
+
const specFile = this.specPath(id);
|
|
3207
|
+
if (!fsSync3.existsSync(specFile)) {
|
|
3208
|
+
throw new Error(`Fallback service '${id}' is not installed`);
|
|
3209
|
+
}
|
|
3210
|
+
const raw = JSON.parse(await fs4.readFile(specFile, "utf-8"));
|
|
3211
|
+
await this.install(raw.spec);
|
|
3212
|
+
}
|
|
3213
|
+
async status(id) {
|
|
3214
|
+
const specFile = this.specPath(id);
|
|
3215
|
+
if (!fsSync3.existsSync(specFile))
|
|
3216
|
+
return null;
|
|
3217
|
+
const pid = await this.readPid(id);
|
|
3218
|
+
const running = pid !== null && isAlive(pid);
|
|
3219
|
+
const logFile = this.logPath(id);
|
|
3220
|
+
let spec = null;
|
|
3221
|
+
try {
|
|
3222
|
+
const raw = JSON.parse(await fs4.readFile(specFile, "utf-8"));
|
|
3223
|
+
spec = raw.spec;
|
|
3224
|
+
} catch {
|
|
3225
|
+
}
|
|
3226
|
+
return {
|
|
3227
|
+
id,
|
|
3228
|
+
kind: spec?.kind ?? "unknown",
|
|
3229
|
+
installed: true,
|
|
3230
|
+
running,
|
|
3231
|
+
pid: running ? pid : null,
|
|
3232
|
+
lastExitCode: null,
|
|
3233
|
+
logFile,
|
|
3234
|
+
label: spec?.label ?? null
|
|
3235
|
+
};
|
|
3236
|
+
}
|
|
3237
|
+
async list() {
|
|
3238
|
+
let files = [];
|
|
3239
|
+
try {
|
|
3240
|
+
files = (await fs4.readdir(this.stateDir)).filter((f) => f.endsWith(".json"));
|
|
3241
|
+
} catch {
|
|
3242
|
+
return [];
|
|
3243
|
+
}
|
|
3244
|
+
const results = [];
|
|
3245
|
+
for (const f of files) {
|
|
3246
|
+
const id = path5.basename(f, ".json");
|
|
3247
|
+
const s = await this.status(id);
|
|
3248
|
+
if (s)
|
|
3249
|
+
results.push(s);
|
|
3250
|
+
}
|
|
3251
|
+
return results;
|
|
3252
|
+
}
|
|
3253
|
+
async selfCheck() {
|
|
3254
|
+
return {
|
|
3255
|
+
available: true,
|
|
3256
|
+
warnings: [
|
|
3257
|
+
"Using fallback process manager \u2014 services will NOT restart on crash or reboot."
|
|
3258
|
+
],
|
|
3259
|
+
hints: [
|
|
3260
|
+
"Install a real service manager (systemd, launchd, or Task Scheduler) for production use."
|
|
3261
|
+
]
|
|
3262
|
+
};
|
|
3263
|
+
}
|
|
3264
|
+
pidPath(id) {
|
|
3265
|
+
return path5.join(this.stateDir, `${safeId(id)}.pid`);
|
|
3266
|
+
}
|
|
3267
|
+
specPath(id) {
|
|
3268
|
+
return path5.join(this.stateDir, `${safeId(id)}.json`);
|
|
3269
|
+
}
|
|
3270
|
+
async readPid(id) {
|
|
3271
|
+
try {
|
|
3272
|
+
const raw = await fs4.readFile(this.pidPath(id), "utf-8");
|
|
3273
|
+
const pid = Number(raw.split("\n")[0]?.trim());
|
|
3274
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
3275
|
+
} catch {
|
|
3276
|
+
return null;
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
};
|
|
3280
|
+
}
|
|
3281
|
+
});
|
|
3282
|
+
|
|
3283
|
+
// ../core/dist/services/factory.js
|
|
3284
|
+
import { execFile as execFile4 } from "child_process";
|
|
3285
|
+
import { promisify as promisify4 } from "util";
|
|
3286
|
+
async function getServiceManager() {
|
|
3287
|
+
if (cached)
|
|
3288
|
+
return cached;
|
|
3289
|
+
if (process.platform === "darwin") {
|
|
3290
|
+
cached = new LaunchdServiceManager();
|
|
3291
|
+
return cached;
|
|
3292
|
+
}
|
|
3293
|
+
if (process.platform === "win32") {
|
|
3294
|
+
const mgr = new TaskSchedulerServiceManager();
|
|
3295
|
+
const check = await mgr.selfCheck();
|
|
3296
|
+
if (check.available) {
|
|
3297
|
+
cached = mgr;
|
|
3298
|
+
return cached;
|
|
3299
|
+
}
|
|
3300
|
+
fallbackReason = "schtasks.exe is not available";
|
|
3301
|
+
cached = new FallbackServiceManager();
|
|
3302
|
+
return cached;
|
|
3303
|
+
}
|
|
3304
|
+
if (process.platform === "linux") {
|
|
3305
|
+
try {
|
|
3306
|
+
await execFileP4("systemctl", ["--user", "--version"]);
|
|
3307
|
+
cached = new SystemdServiceManager();
|
|
3308
|
+
return cached;
|
|
3309
|
+
} catch {
|
|
3310
|
+
fallbackReason = "systemctl --user is not available (systemd-free distro?)";
|
|
3311
|
+
}
|
|
3312
|
+
} else {
|
|
3313
|
+
fallbackReason = `no native service manager for platform '${process.platform}'`;
|
|
3314
|
+
}
|
|
3315
|
+
cached = new FallbackServiceManager();
|
|
3316
|
+
return cached;
|
|
3317
|
+
}
|
|
3318
|
+
function activeFallbackReason() {
|
|
3319
|
+
return fallbackReason;
|
|
3320
|
+
}
|
|
3321
|
+
var execFileP4, cached, fallbackReason;
|
|
3322
|
+
var init_factory = __esm({
|
|
3323
|
+
"../core/dist/services/factory.js"() {
|
|
3324
|
+
"use strict";
|
|
3325
|
+
init_launchd();
|
|
3326
|
+
init_systemd();
|
|
3327
|
+
init_task_scheduler();
|
|
3328
|
+
init_fallback();
|
|
3329
|
+
execFileP4 = promisify4(execFile4);
|
|
3330
|
+
cached = null;
|
|
3331
|
+
fallbackReason = null;
|
|
3332
|
+
}
|
|
3333
|
+
});
|
|
3334
|
+
|
|
3335
|
+
// ../core/dist/services/migrate.js
|
|
3336
|
+
import fs5 from "fs/promises";
|
|
3337
|
+
import fsSync4 from "fs";
|
|
3338
|
+
import path6 from "path";
|
|
3339
|
+
import os4 from "os";
|
|
3340
|
+
import { execFile as execFile5 } from "child_process";
|
|
3341
|
+
import { promisify as promisify5 } from "util";
|
|
3342
|
+
async function migrateLegacySupervisor(mgr) {
|
|
3343
|
+
const home = beeoHome();
|
|
3344
|
+
const supervisorDir = path6.join(home, "supervisor");
|
|
3345
|
+
const stateFile = path6.join(supervisorDir, "state.json");
|
|
3346
|
+
const flagFile = path6.join(home, MIGRATION_FLAG);
|
|
3347
|
+
if (fsSync4.existsSync(flagFile)) {
|
|
3348
|
+
return { ran: false, migrated: 0, errors: [], backupPath: null };
|
|
3349
|
+
}
|
|
3350
|
+
if (!fsSync4.existsSync(stateFile)) {
|
|
3351
|
+
try {
|
|
3352
|
+
await fs5.writeFile(flagFile, (/* @__PURE__ */ new Date()).toISOString());
|
|
3353
|
+
} catch {
|
|
3354
|
+
}
|
|
3355
|
+
return { ran: false, migrated: 0, errors: [], backupPath: null };
|
|
3356
|
+
}
|
|
3357
|
+
const backupPath = `${stateFile}.migration-backup-${Date.now()}`;
|
|
3358
|
+
try {
|
|
3359
|
+
await fs5.copyFile(stateFile, backupPath);
|
|
3360
|
+
} catch {
|
|
3361
|
+
}
|
|
3362
|
+
let parsed;
|
|
3363
|
+
try {
|
|
3364
|
+
const raw = await fs5.readFile(stateFile, "utf-8");
|
|
3365
|
+
parsed = JSON.parse(raw);
|
|
3366
|
+
} catch (e) {
|
|
3367
|
+
return {
|
|
3368
|
+
ran: true,
|
|
3369
|
+
migrated: 0,
|
|
3370
|
+
errors: [{ id: "<state.json>", error: `unreadable: ${e}` }],
|
|
3371
|
+
backupPath
|
|
3372
|
+
};
|
|
3373
|
+
}
|
|
3374
|
+
const targets = parsed.targets ?? [];
|
|
3375
|
+
const errors = [];
|
|
3376
|
+
let migrated = 0;
|
|
3377
|
+
for (const t of targets) {
|
|
3378
|
+
const canonicalId = safeId(t.id);
|
|
3379
|
+
try {
|
|
3380
|
+
const spec = {
|
|
3381
|
+
id: canonicalId,
|
|
3382
|
+
kind: t.kind,
|
|
3383
|
+
command: t.command,
|
|
3384
|
+
args: t.args ?? [],
|
|
3385
|
+
env: t.env,
|
|
3386
|
+
cwd: t.cwd,
|
|
3387
|
+
restart: t.restart ?? "on-failure",
|
|
3388
|
+
label: t.label
|
|
3389
|
+
};
|
|
3390
|
+
await mgr.install(spec);
|
|
3391
|
+
migrated++;
|
|
3392
|
+
} catch (e) {
|
|
3393
|
+
errors.push({ id: canonicalId, error: e instanceof Error ? e.message : String(e) });
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
await stopLegacyDaemon();
|
|
3397
|
+
if (errors.length === 0) {
|
|
3398
|
+
try {
|
|
3399
|
+
await fs5.writeFile(flagFile, (/* @__PURE__ */ new Date()).toISOString());
|
|
3400
|
+
} catch {
|
|
3401
|
+
}
|
|
3402
|
+
try {
|
|
3403
|
+
await fs5.rm(supervisorDir, { recursive: true, force: true });
|
|
3404
|
+
} catch {
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
return { ran: true, migrated, errors, backupPath };
|
|
3408
|
+
}
|
|
3409
|
+
async function stopLegacyDaemon() {
|
|
3410
|
+
if (process.platform === "darwin") {
|
|
3411
|
+
const uid = process.getuid?.();
|
|
3412
|
+
if (uid !== void 0) {
|
|
3413
|
+
try {
|
|
3414
|
+
await execFileP5("launchctl", ["bootout", `gui/${uid}/${LEGACY_LABEL}`]);
|
|
3415
|
+
} catch {
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
const legacyPlist = path6.join(os4.homedir(), "Library", "LaunchAgents", `${LEGACY_LABEL}.plist`);
|
|
3419
|
+
try {
|
|
3420
|
+
await fs5.unlink(legacyPlist);
|
|
3421
|
+
} catch {
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
if (process.platform === "linux") {
|
|
3425
|
+
try {
|
|
3426
|
+
await execFileP5("systemctl", ["--user", "disable", "--now", LEGACY_SYSTEMD_UNIT]);
|
|
3427
|
+
} catch {
|
|
3428
|
+
}
|
|
3429
|
+
const legacyUnit = path6.join(os4.homedir(), ".config", "systemd", "user", LEGACY_SYSTEMD_UNIT);
|
|
3430
|
+
try {
|
|
3431
|
+
await fs5.unlink(legacyUnit);
|
|
3432
|
+
} catch {
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
if (process.platform !== "win32") {
|
|
3436
|
+
try {
|
|
3437
|
+
await execFileP5("pkill", ["-f", "beeos-supervisor"]);
|
|
3438
|
+
} catch {
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
var execFileP5, LEGACY_LABEL, LEGACY_SYSTEMD_UNIT, MIGRATION_FLAG;
|
|
3443
|
+
var init_migrate = __esm({
|
|
3444
|
+
"../core/dist/services/migrate.js"() {
|
|
3445
|
+
"use strict";
|
|
3446
|
+
init_config();
|
|
3447
|
+
init_ids();
|
|
3448
|
+
execFileP5 = promisify5(execFile5);
|
|
3449
|
+
LEGACY_LABEL = "ai.beeos.supervisor";
|
|
3450
|
+
LEGACY_SYSTEMD_UNIT = "beeos-supervisor.service";
|
|
3451
|
+
MIGRATION_FLAG = "migrated-to-services.flag";
|
|
3452
|
+
}
|
|
3453
|
+
});
|
|
3454
|
+
|
|
3455
|
+
// ../core/dist/services/index.js
|
|
3456
|
+
var init_services = __esm({
|
|
3457
|
+
"../core/dist/services/index.js"() {
|
|
3458
|
+
"use strict";
|
|
3459
|
+
init_types2();
|
|
3460
|
+
init_ids();
|
|
3461
|
+
init_launchd();
|
|
3462
|
+
init_systemd();
|
|
3463
|
+
init_task_scheduler();
|
|
3464
|
+
init_fallback();
|
|
3465
|
+
init_factory();
|
|
3466
|
+
init_migrate();
|
|
3467
|
+
}
|
|
3468
|
+
});
|
|
3469
|
+
|
|
3470
|
+
// ../core/dist/index.js
|
|
3471
|
+
var init_dist = __esm({
|
|
3472
|
+
"../core/dist/index.js"() {
|
|
3473
|
+
"use strict";
|
|
3474
|
+
init_platform_adapter();
|
|
3475
|
+
init_progress();
|
|
3476
|
+
init_config();
|
|
3477
|
+
init_keypair();
|
|
3478
|
+
init_qr();
|
|
3479
|
+
init_client();
|
|
3480
|
+
init_npm();
|
|
3481
|
+
init_process();
|
|
3482
|
+
init_bind();
|
|
3483
|
+
init_device_setup();
|
|
3484
|
+
init_adb_setup();
|
|
3485
|
+
init_types();
|
|
3486
|
+
init_device();
|
|
3487
|
+
init_openclaw();
|
|
3488
|
+
init_scrcpy_bridge();
|
|
3489
|
+
init_vnc_bridge();
|
|
3490
|
+
init_cargo_dist();
|
|
3491
|
+
init_detector();
|
|
3492
|
+
init_downloader();
|
|
3493
|
+
init_launcher();
|
|
3494
|
+
init_registry();
|
|
3495
|
+
init_local_agent();
|
|
3496
|
+
init_detect();
|
|
3497
|
+
init_supervisor_spec();
|
|
3498
|
+
init_services();
|
|
3499
|
+
}
|
|
3500
|
+
});
|
|
3501
|
+
|
|
3502
|
+
// src/progress.ts
|
|
3503
|
+
import ora from "ora";
|
|
3504
|
+
var CliReporter;
|
|
3505
|
+
var init_progress2 = __esm({
|
|
3506
|
+
"src/progress.ts"() {
|
|
3507
|
+
"use strict";
|
|
3508
|
+
CliReporter = class {
|
|
3509
|
+
spinner;
|
|
3510
|
+
constructor() {
|
|
3511
|
+
this.spinner = ora({ stream: process.stderr });
|
|
3512
|
+
}
|
|
3513
|
+
onStatus(message) {
|
|
3514
|
+
if (this.spinner.isSpinning) {
|
|
3515
|
+
this.spinner.text = message;
|
|
3516
|
+
} else {
|
|
3517
|
+
this.spinner.start(message);
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
onComplete(message) {
|
|
3521
|
+
if (this.spinner.isSpinning) {
|
|
3522
|
+
this.spinner.succeed(message);
|
|
3523
|
+
} else {
|
|
3524
|
+
console.log(`\u2713 ${message}`);
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
stop() {
|
|
3528
|
+
if (this.spinner.isSpinning) {
|
|
3529
|
+
this.spinner.stop();
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
};
|
|
3533
|
+
}
|
|
3534
|
+
});
|
|
3535
|
+
|
|
3536
|
+
// src/commands/fallback-banner.ts
|
|
3537
|
+
function maybePrintFallbackBanner(kind) {
|
|
3538
|
+
if (kind !== "fallback") return;
|
|
3539
|
+
if (printed) return;
|
|
3540
|
+
const reason = activeFallbackReason();
|
|
3541
|
+
if (!reason) return;
|
|
3542
|
+
printed = true;
|
|
3543
|
+
const bar = "!".repeat(70);
|
|
3544
|
+
console.log("");
|
|
3545
|
+
console.log(` ${bar}`);
|
|
3546
|
+
console.log(" !! WARNING: no native OS service manager available");
|
|
3547
|
+
console.log(` !! Reason : ${reason}`);
|
|
3548
|
+
console.log(" !! Impact : services will NOT restart on crash or reboot.");
|
|
3549
|
+
console.log(" !! Fix : use a distro with systemd --user (and run");
|
|
3550
|
+
console.log(" !! `loginctl enable-linger $USER` once), or");
|
|
3551
|
+
console.log(" !! launchd (macOS), or Task Scheduler (Windows).");
|
|
3552
|
+
console.log(` ${bar}`);
|
|
3553
|
+
console.log("");
|
|
3554
|
+
}
|
|
3555
|
+
var printed;
|
|
3556
|
+
var init_fallback_banner = __esm({
|
|
3557
|
+
"src/commands/fallback-banner.ts"() {
|
|
3558
|
+
"use strict";
|
|
3559
|
+
init_dist();
|
|
3560
|
+
printed = false;
|
|
3561
|
+
}
|
|
3562
|
+
});
|
|
3563
|
+
|
|
3564
|
+
// src/commands/device.ts
|
|
3565
|
+
var device_exports = {};
|
|
3566
|
+
__export(device_exports, {
|
|
3567
|
+
attach: () => attach,
|
|
3568
|
+
detach: () => detach,
|
|
3569
|
+
exec: () => exec,
|
|
3570
|
+
list: () => list,
|
|
3571
|
+
upgrade: () => upgrade
|
|
3572
|
+
});
|
|
3573
|
+
import lockfile from "proper-lockfile";
|
|
3574
|
+
function deviceAgentTargetId(serial) {
|
|
3575
|
+
return `device-agent-${serial}`;
|
|
3576
|
+
}
|
|
3577
|
+
function scrcpyTargetId(serial) {
|
|
3578
|
+
return `scrcpy-bridge-${serial}`;
|
|
3579
|
+
}
|
|
3580
|
+
function vncTargetId(serial) {
|
|
3581
|
+
return `vnc-bridge-${serial}`;
|
|
3582
|
+
}
|
|
3583
|
+
function devicesPath() {
|
|
3584
|
+
const p = getPlatformAdapter();
|
|
3585
|
+
return p.joinPath(beeoHome(), "devices.json");
|
|
3586
|
+
}
|
|
3587
|
+
async function loadDeviceState() {
|
|
3588
|
+
const p = getPlatformAdapter();
|
|
3589
|
+
const path9 = devicesPath();
|
|
3590
|
+
if (!await p.exists(path9)) return { devices: [] };
|
|
3591
|
+
try {
|
|
3592
|
+
const raw = await p.readFile(path9);
|
|
3593
|
+
return JSON.parse(raw);
|
|
3594
|
+
} catch {
|
|
3595
|
+
return { devices: [] };
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
async function saveDeviceState(state) {
|
|
3599
|
+
const p = getPlatformAdapter();
|
|
3600
|
+
await p.writeFile(devicesPath(), JSON.stringify(state, null, 2));
|
|
3601
|
+
}
|
|
3602
|
+
async function withDeviceLock(fn) {
|
|
3603
|
+
const p = getPlatformAdapter();
|
|
3604
|
+
const path9 = devicesPath();
|
|
3605
|
+
await p.mkdir(p.dirname(path9));
|
|
3606
|
+
if (!await p.exists(path9)) {
|
|
3607
|
+
await p.writeFile(path9, JSON.stringify({ devices: [] }));
|
|
3608
|
+
}
|
|
3609
|
+
const release = await lockfile.lock(path9, { retries: 3 });
|
|
3610
|
+
try {
|
|
3611
|
+
return await fn();
|
|
3612
|
+
} finally {
|
|
3613
|
+
await release();
|
|
3614
|
+
}
|
|
3615
|
+
}
|
|
3616
|
+
async function ensureAdbAvailable(reporter) {
|
|
3617
|
+
const existing = await findAdb();
|
|
3618
|
+
if (existing) return;
|
|
3619
|
+
if (process.env.BEEOS_SKIP_ADB_INSTALL === "1") {
|
|
3620
|
+
throw new Error(
|
|
3621
|
+
"adb not found on PATH and BEEOS_SKIP_ADB_INSTALL=1 is set.\nInstall Android platform-tools manually and re-run, or unset the env var."
|
|
3622
|
+
);
|
|
3623
|
+
}
|
|
3624
|
+
console.log("adb not found \u2014 downloading Android platform-tools (one-time, ~10MB)...");
|
|
3625
|
+
try {
|
|
3626
|
+
await ensureAdb(reporter, { autoInstall: true });
|
|
3627
|
+
} catch (e) {
|
|
3628
|
+
throw new Error(
|
|
3629
|
+
"Failed to install adb automatically: " + String(e) + "\nWorkarounds:\n \u2022 macOS: brew install android-platform-tools\n \u2022 Linux: apt install android-tools-adb (or equivalent)\n \u2022 Windows: install Android SDK Platform-Tools from https://developer.android.com/tools/releases/platform-tools\n \u2022 Or point $BEEOS_ADB_BIN at an existing adb binary.",
|
|
3630
|
+
{ cause: e }
|
|
3631
|
+
);
|
|
3632
|
+
}
|
|
3633
|
+
}
|
|
3634
|
+
async function installService(mgr, spec) {
|
|
3635
|
+
maybePrintFallbackBanner(mgr.kind);
|
|
3636
|
+
await mgr.install(spec);
|
|
3637
|
+
}
|
|
3638
|
+
async function removeTargetsForSerial(mgr, serial) {
|
|
3639
|
+
for (const id of [
|
|
3640
|
+
deviceAgentTargetId(serial),
|
|
3641
|
+
scrcpyTargetId(serial),
|
|
3642
|
+
vncTargetId(serial)
|
|
3643
|
+
]) {
|
|
3644
|
+
try {
|
|
3645
|
+
await mgr.uninstall(id);
|
|
3646
|
+
} catch {
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
async function attach(options) {
|
|
3651
|
+
const cfg = await loadOrCreateConfig();
|
|
3652
|
+
const reporter = new CliReporter();
|
|
3653
|
+
const withVideo = options.video !== false;
|
|
3654
|
+
const isDesktopAttach = Boolean(options.vncHost);
|
|
3655
|
+
if (!isDesktopAttach) {
|
|
3656
|
+
await ensureAdbAvailable(reporter);
|
|
3657
|
+
}
|
|
3658
|
+
if (options.all) {
|
|
3659
|
+
return attachAll(cfg, reporter, withVideo, options);
|
|
3660
|
+
}
|
|
3661
|
+
const serial = options.serial ?? await deviceRuntime.detectSingleDevice();
|
|
3662
|
+
const name = options.name ?? serial;
|
|
3663
|
+
const agentBin = await deviceRuntime.ensureAgent(reporter);
|
|
3664
|
+
reporter.stop();
|
|
3665
|
+
const pubkeyB64 = await deviceRuntime.ensureKeyAndGetPubkey(serial);
|
|
3666
|
+
const instanceId = await tryBindDevice(pubkeyB64, name, cfg);
|
|
3667
|
+
if (!instanceId) {
|
|
3668
|
+
console.error("Bind was not completed \u2014 device-agent will not start without a platform binding.");
|
|
3669
|
+
console.error("Run `beeos device attach` again to retry.");
|
|
3670
|
+
return;
|
|
3671
|
+
}
|
|
3672
|
+
const mgr = await getServiceManager();
|
|
3673
|
+
await withDeviceLock(async () => {
|
|
3674
|
+
const state = await loadDeviceState();
|
|
3675
|
+
const existingIdx = state.devices.findIndex((d) => d.serial === serial);
|
|
3676
|
+
if (existingIdx >= 0) {
|
|
3677
|
+
await removeTargetsForSerial(mgr, serial);
|
|
3678
|
+
state.devices.splice(existingIdx, 1);
|
|
3679
|
+
}
|
|
3680
|
+
const httpPort = nextHttpPort(state, cfg.device.http_port);
|
|
3681
|
+
const keyFile = deviceRuntime.deviceKeyPath(serial);
|
|
3682
|
+
const agentGatewayUrl = resolveAgentGatewayUrl(cfg);
|
|
3683
|
+
const agentSpec = buildDeviceAgentTargetSpecFromBin(
|
|
3684
|
+
agentBin,
|
|
3685
|
+
{
|
|
3686
|
+
serial,
|
|
3687
|
+
bridgeUrl: cfg.platform.bridge_url,
|
|
3688
|
+
httpEnabled: cfg.device.http_enabled,
|
|
3689
|
+
httpPort,
|
|
3690
|
+
agentGatewayUrl,
|
|
3691
|
+
keyFile
|
|
3692
|
+
}
|
|
3693
|
+
);
|
|
3694
|
+
await installService(mgr, agentSpec);
|
|
3695
|
+
const bridgeInfo = await registerVideoBridge(mgr, reporter, {
|
|
3696
|
+
cfg,
|
|
3697
|
+
serial,
|
|
3698
|
+
instanceId,
|
|
3699
|
+
keyFile,
|
|
3700
|
+
withVideo,
|
|
3701
|
+
vncHost: options.vncHost,
|
|
3702
|
+
vncPort: options.vncPort,
|
|
3703
|
+
vncPassword: options.vncPassword
|
|
3704
|
+
});
|
|
3705
|
+
state.devices.push({
|
|
3706
|
+
serial,
|
|
3707
|
+
name,
|
|
3708
|
+
instance_id: instanceId,
|
|
3709
|
+
key_file: keyFile,
|
|
3710
|
+
http_port: httpPort,
|
|
3711
|
+
video_mode: bridgeInfo.mode,
|
|
3712
|
+
vnc_host: options.vncHost,
|
|
3713
|
+
vnc_port: options.vncPort
|
|
3714
|
+
});
|
|
3715
|
+
await saveDeviceState(state);
|
|
3716
|
+
const logPath = mgr.logPath(deviceAgentTargetId(serial));
|
|
3717
|
+
console.log(`Attached device ${serial} (${name}) \u2014 instance ${instanceId}`);
|
|
3718
|
+
console.log(` local http: :${httpPort} (if enabled)`);
|
|
3719
|
+
console.log(` logs: ${logPath}`);
|
|
3720
|
+
if (bridgeInfo.mode !== "none") {
|
|
3721
|
+
console.log(` video: ${bridgeInfo.mode}-bridge (managed by ${mgr.kind})`);
|
|
3722
|
+
} else {
|
|
3723
|
+
console.log(" video: disabled");
|
|
3724
|
+
}
|
|
3725
|
+
});
|
|
3726
|
+
}
|
|
3727
|
+
async function attachAll(cfg, reporter, withVideo, options) {
|
|
3728
|
+
const devices = await deviceRuntime.listAdbDevices();
|
|
3729
|
+
if (devices.length === 0) {
|
|
3730
|
+
console.log("No ADB devices found");
|
|
3731
|
+
return;
|
|
3732
|
+
}
|
|
3733
|
+
const agentBin = await deviceRuntime.ensureAgent(reporter);
|
|
3734
|
+
reporter.stop();
|
|
3735
|
+
const agentGatewayUrl = resolveAgentGatewayUrl(cfg);
|
|
3736
|
+
const mgr = await getServiceManager();
|
|
3737
|
+
const binds = [];
|
|
3738
|
+
for (const device of devices) {
|
|
3739
|
+
try {
|
|
3740
|
+
const pubkeyB64 = await deviceRuntime.ensureKeyAndGetPubkey(device.serial);
|
|
3741
|
+
const instanceId = await tryBindDevice(pubkeyB64, device.serial, cfg);
|
|
3742
|
+
if (!instanceId) {
|
|
3743
|
+
console.error(` Skipping ${device.serial} \u2014 bind not completed`);
|
|
3744
|
+
continue;
|
|
3745
|
+
}
|
|
3746
|
+
binds.push({
|
|
3747
|
+
serial: device.serial,
|
|
3748
|
+
instanceId,
|
|
3749
|
+
keyFile: deviceRuntime.deviceKeyPath(device.serial)
|
|
3750
|
+
});
|
|
3751
|
+
} catch (e) {
|
|
3752
|
+
console.error(` Failed to bind ${device.serial}: ${e}`);
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
if (binds.length === 0) {
|
|
3756
|
+
console.log("No devices attached \u2014 all binds failed or were skipped.");
|
|
3757
|
+
return;
|
|
3758
|
+
}
|
|
3759
|
+
await withDeviceLock(async () => {
|
|
3760
|
+
const state = await loadDeviceState();
|
|
3761
|
+
const alreadyAttached = new Set(state.devices.map((d) => d.serial));
|
|
3762
|
+
await Promise.all(
|
|
3763
|
+
binds.map(async ({ serial, instanceId, keyFile }) => {
|
|
3764
|
+
try {
|
|
3765
|
+
const existingIdx = state.devices.findIndex((d) => d.serial === serial);
|
|
3766
|
+
if (existingIdx >= 0) {
|
|
3767
|
+
await removeTargetsForSerial(mgr, serial);
|
|
3768
|
+
state.devices.splice(existingIdx, 1);
|
|
3769
|
+
}
|
|
3770
|
+
const httpPort = nextHttpPort(state, cfg.device.http_port);
|
|
3771
|
+
const agentSpec = buildDeviceAgentTargetSpecFromBin(agentBin, {
|
|
3772
|
+
serial,
|
|
3773
|
+
bridgeUrl: cfg.platform.bridge_url,
|
|
3774
|
+
httpEnabled: cfg.device.http_enabled,
|
|
3775
|
+
httpPort,
|
|
3776
|
+
agentGatewayUrl,
|
|
3777
|
+
keyFile
|
|
3778
|
+
});
|
|
3779
|
+
await installService(mgr, agentSpec);
|
|
3780
|
+
const bridgeInfo = await registerVideoBridge(mgr, reporter, {
|
|
3781
|
+
cfg,
|
|
3782
|
+
serial,
|
|
3783
|
+
instanceId,
|
|
3784
|
+
keyFile,
|
|
3785
|
+
withVideo,
|
|
3786
|
+
vncHost: options.vncHost,
|
|
3787
|
+
vncPort: options.vncPort,
|
|
3788
|
+
vncPassword: options.vncPassword
|
|
3789
|
+
});
|
|
3790
|
+
state.devices.push({
|
|
3791
|
+
serial,
|
|
3792
|
+
name: serial,
|
|
3793
|
+
instance_id: instanceId,
|
|
3794
|
+
key_file: keyFile,
|
|
3795
|
+
http_port: httpPort,
|
|
3796
|
+
video_mode: bridgeInfo.mode,
|
|
3797
|
+
vnc_host: options.vncHost,
|
|
3798
|
+
vnc_port: options.vncPort
|
|
3799
|
+
});
|
|
3800
|
+
alreadyAttached.add(serial);
|
|
3801
|
+
console.log(
|
|
3802
|
+
`Attached device ${serial} \u2014 instance ${instanceId}` + (bridgeInfo.mode !== "none" ? ` (video: ${bridgeInfo.mode})` : "")
|
|
3803
|
+
);
|
|
3804
|
+
} catch (e) {
|
|
3805
|
+
console.error(`Failed to attach ${serial}: ${e}`);
|
|
3806
|
+
}
|
|
3807
|
+
})
|
|
3808
|
+
);
|
|
3809
|
+
await saveDeviceState(state);
|
|
3810
|
+
console.log(`Attached ${binds.length} device(s) via ${mgr.kind}.`);
|
|
3811
|
+
});
|
|
3812
|
+
}
|
|
3813
|
+
function buildDeviceAgentTargetSpecFromBin(agentBin, input) {
|
|
3814
|
+
if (!input.bridgeUrl) {
|
|
3815
|
+
throw new Error(
|
|
3816
|
+
"bridge_url is not configured in ~/.beeos/config.toml.\nSet [platform] bridge_url (e.g. wss://bridge.beeos.ai)."
|
|
3817
|
+
);
|
|
3818
|
+
}
|
|
3819
|
+
const { cmd, args: baseArgs } = agentBinCommandAndArgs(agentBin);
|
|
3820
|
+
return buildDeviceAgentTargetSpec({
|
|
3821
|
+
serial: input.serial,
|
|
3822
|
+
command: cmd,
|
|
3823
|
+
args: baseArgs,
|
|
3824
|
+
bridgeUrl: input.bridgeUrl,
|
|
3825
|
+
keyFile: input.keyFile,
|
|
3826
|
+
httpEnabled: input.httpEnabled,
|
|
3827
|
+
httpPort: input.httpPort,
|
|
3828
|
+
agentGatewayUrl: input.agentGatewayUrl
|
|
3829
|
+
});
|
|
3830
|
+
}
|
|
3831
|
+
async function registerVideoBridge(mgr, reporter, params) {
|
|
3832
|
+
if (!params.withVideo) return { mode: "none" };
|
|
3833
|
+
const agentGatewayUrl = resolveAgentGatewayUrl(params.cfg);
|
|
3834
|
+
if (params.vncHost) {
|
|
3835
|
+
const binary2 = await vncBridgeRuntime.ensureInstalled(reporter);
|
|
3836
|
+
if (!binary2) {
|
|
3837
|
+
console.error(
|
|
3838
|
+
` vnc-bridge binary unavailable \u2014 ${params.serial} will run without VNC streaming.`
|
|
3839
|
+
);
|
|
3840
|
+
return { mode: "none" };
|
|
3841
|
+
}
|
|
3842
|
+
const spec2 = buildVncBridgeTargetSpec(binary2, {
|
|
3843
|
+
deviceId: `device-${params.instanceId}`,
|
|
3844
|
+
vncHost: params.vncHost,
|
|
3845
|
+
vncPort: params.vncPort ?? 5900,
|
|
3846
|
+
...params.vncPassword ? { vncPassword: params.vncPassword } : {},
|
|
3847
|
+
agentGatewayUrl,
|
|
3848
|
+
bridgePrivateKeyFile: params.keyFile,
|
|
3849
|
+
bridgePublicKeyFile: params.keyFile
|
|
3850
|
+
});
|
|
3851
|
+
await installService(mgr, spec2);
|
|
3852
|
+
return { mode: "vnc" };
|
|
3853
|
+
}
|
|
3854
|
+
const binary = await scrcpyBridgeRuntime.ensureInstalled(reporter);
|
|
3855
|
+
if (!binary) {
|
|
3856
|
+
console.error(
|
|
3857
|
+
` scrcpy-bridge binary unavailable \u2014 ${params.serial} will run without WebRTC video.
|
|
3858
|
+
Install from https://github.com/beeos-ai/scrcpy-bridge or set BEEOS_SCRCPY_BRIDGE_BIN.`
|
|
3859
|
+
);
|
|
3860
|
+
return { mode: "none" };
|
|
3861
|
+
}
|
|
3862
|
+
const spec = buildScrcpyBridgeTargetSpec(binary, {
|
|
3863
|
+
deviceId: `device-${params.instanceId}`,
|
|
3864
|
+
adbSerial: params.serial,
|
|
3865
|
+
agentGatewayUrl,
|
|
3866
|
+
bridgePrivateKeyFile: params.keyFile,
|
|
3867
|
+
bridgePublicKeyFile: params.keyFile
|
|
3868
|
+
});
|
|
3869
|
+
await installService(mgr, spec);
|
|
3870
|
+
return { mode: "scrcpy" };
|
|
3871
|
+
}
|
|
3872
|
+
async function detach(options) {
|
|
3873
|
+
const cfg = await loadOrCreateConfig();
|
|
3874
|
+
const mgr = await getServiceManager();
|
|
3875
|
+
await withDeviceLock(async () => {
|
|
3876
|
+
const state = await loadDeviceState();
|
|
3877
|
+
if (options.all) {
|
|
3878
|
+
for (const entry2 of state.devices) {
|
|
3879
|
+
await removeTargetsForSerial(mgr, entry2.serial);
|
|
3880
|
+
legacyKillDeviceEntry(entry2);
|
|
3881
|
+
await notifyOffline(cfg.platform.api_url, entry2.instance_id);
|
|
3882
|
+
}
|
|
3883
|
+
const count = state.devices.length;
|
|
3884
|
+
state.devices = [];
|
|
3885
|
+
await saveDeviceState(state);
|
|
3886
|
+
console.log(`Detached ${count} device(s)`);
|
|
3887
|
+
return;
|
|
3888
|
+
}
|
|
3889
|
+
if (!options.serial) {
|
|
3890
|
+
throw new Error("Specify --serial or use --all");
|
|
3891
|
+
}
|
|
3892
|
+
const idx = state.devices.findIndex((d) => d.serial === options.serial);
|
|
3893
|
+
if (idx < 0) throw new Error(`Device ${options.serial} not found`);
|
|
3894
|
+
const entry = state.devices.splice(idx, 1)[0];
|
|
3895
|
+
await removeTargetsForSerial(mgr, entry.serial);
|
|
3896
|
+
legacyKillDeviceEntry(entry);
|
|
3897
|
+
await notifyOffline(cfg.platform.api_url, entry.instance_id);
|
|
3898
|
+
await saveDeviceState(state);
|
|
3899
|
+
console.log(`Detached device ${options.serial}`);
|
|
3900
|
+
});
|
|
3901
|
+
}
|
|
3902
|
+
function legacyKillDeviceEntry(entry) {
|
|
3903
|
+
if (entry.pid && isProcessAlive(entry.pid)) killProcess(entry.pid);
|
|
3904
|
+
if (entry.bridge_pid && isProcessAlive(entry.bridge_pid)) killProcess(entry.bridge_pid);
|
|
3905
|
+
}
|
|
3906
|
+
async function notifyOffline(apiUrl, instanceId) {
|
|
3907
|
+
if (!instanceId) return;
|
|
3908
|
+
try {
|
|
3909
|
+
await markInstanceOffline(apiUrl, instanceId);
|
|
3910
|
+
} catch {
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
async function list(options) {
|
|
3914
|
+
if (!options.local) {
|
|
3915
|
+
console.log("Remote device listing requires platform API (use --local for local devices)");
|
|
3916
|
+
return;
|
|
3917
|
+
}
|
|
3918
|
+
const state = await loadDeviceState();
|
|
3919
|
+
if (state.devices.length === 0) {
|
|
3920
|
+
console.log("No attached devices");
|
|
3921
|
+
return;
|
|
3922
|
+
}
|
|
3923
|
+
const mgr = await getServiceManager();
|
|
3924
|
+
const statusById = /* @__PURE__ */ new Map();
|
|
3925
|
+
try {
|
|
3926
|
+
const services = await mgr.list();
|
|
3927
|
+
for (const s of services) {
|
|
3928
|
+
statusById.set(s.id, s.running ? "running" : "stopped");
|
|
3929
|
+
}
|
|
3930
|
+
} catch {
|
|
3931
|
+
}
|
|
3932
|
+
console.log(
|
|
3933
|
+
`${"SERIAL".padEnd(16)} ${"NAME".padEnd(20)} ${"STATUS".padEnd(12)} ${"HTTP".padEnd(6)} ${"VIDEO".padEnd(10)} INSTANCE`
|
|
3934
|
+
);
|
|
3935
|
+
for (const entry of state.devices) {
|
|
3936
|
+
const agentState = statusById.get(deviceAgentTargetId(entry.serial)) ?? "unknown";
|
|
3937
|
+
const videoId = entry.video_mode === "vnc" ? vncTargetId(entry.serial) : scrcpyTargetId(entry.serial);
|
|
3938
|
+
const videoState = entry.video_mode === "none" ? "disabled" : statusById.get(videoId) ?? "unknown";
|
|
3939
|
+
console.log(
|
|
3940
|
+
`${entry.serial.padEnd(16)} ${entry.name.padEnd(20)} ${agentState.padEnd(
|
|
3941
|
+
12
|
|
3942
|
+
)} ${String(entry.http_port).padEnd(6)} ${videoState.padEnd(10)} ${entry.instance_id ?? "-"}`
|
|
3943
|
+
);
|
|
3944
|
+
}
|
|
3945
|
+
}
|
|
3946
|
+
async function exec(prompt2, options) {
|
|
3947
|
+
const state = await loadDeviceState();
|
|
3948
|
+
const entry = options.serial ? state.devices.find((d) => d.serial === options.serial) : state.devices[0];
|
|
3949
|
+
if (!entry) {
|
|
3950
|
+
throw new Error(options.serial ? `Device ${options.serial} not attached` : "No attached devices");
|
|
3951
|
+
}
|
|
3952
|
+
if (!entry.http_port) {
|
|
3953
|
+
throw new Error(
|
|
3954
|
+
`Device ${entry.serial} has no local HTTP port configured.
|
|
3955
|
+
Enable http_enabled in ~/.beeos/config.toml and re-attach.`
|
|
3956
|
+
);
|
|
3957
|
+
}
|
|
3958
|
+
const p = getPlatformAdapter();
|
|
3959
|
+
const url = `http://127.0.0.1:${entry.http_port}/prompt`;
|
|
3960
|
+
console.log(`-> ${entry.serial} (local :${entry.http_port}): ${prompt2}`);
|
|
3961
|
+
const resp = await p.fetch(url, {
|
|
3962
|
+
method: "POST",
|
|
3963
|
+
headers: { "Content-Type": "application/json" },
|
|
3964
|
+
body: JSON.stringify({ prompt: prompt2 })
|
|
3965
|
+
});
|
|
3966
|
+
if (!resp.ok) {
|
|
3967
|
+
const body2 = await resp.text().catch(() => "");
|
|
3968
|
+
throw new Error(`device-agent returned ${resp.status} \u2014 ${body2}`);
|
|
3969
|
+
}
|
|
3970
|
+
const body = await resp.json();
|
|
3971
|
+
console.log(JSON.stringify(body, null, 2));
|
|
3972
|
+
}
|
|
3973
|
+
async function upgrade(options = {}) {
|
|
3974
|
+
const reporter = new CliReporter();
|
|
3975
|
+
await deviceRuntime.upgradeAgent(reporter);
|
|
3976
|
+
if (options.bridges !== false) {
|
|
3977
|
+
try {
|
|
3978
|
+
await scrcpyBridgeRuntime.upgrade(reporter);
|
|
3979
|
+
} catch (e) {
|
|
3980
|
+
console.error(`scrcpy-bridge upgrade skipped: ${e}`);
|
|
3981
|
+
}
|
|
3982
|
+
try {
|
|
3983
|
+
await vncBridgeRuntime.upgrade(reporter);
|
|
3984
|
+
} catch (e) {
|
|
3985
|
+
console.error(`vnc-bridge upgrade skipped: ${e}`);
|
|
3986
|
+
}
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
async function tryBindDevice(pubkeyB64, name, cfg) {
|
|
3990
|
+
const fp = fingerprintFromB64(pubkeyB64);
|
|
3991
|
+
try {
|
|
3992
|
+
const resp = await agentBind(
|
|
3993
|
+
cfg.platform.api_url,
|
|
3994
|
+
pubkeyB64,
|
|
3995
|
+
fp,
|
|
3996
|
+
"device",
|
|
3997
|
+
name
|
|
3998
|
+
);
|
|
3999
|
+
if (resp.status === "bound") {
|
|
4000
|
+
console.log(` Already bound \u2014 instance: ${resp.instance_id}`);
|
|
4001
|
+
return resp.instance_id ?? void 0;
|
|
4002
|
+
}
|
|
4003
|
+
if (resp.bind_id) {
|
|
4004
|
+
const bindUrl = buildBindUrl(cfg.platform.dashboard_base_url, resp.bind_id);
|
|
4005
|
+
await presentBindUrl(bindUrl, false);
|
|
4006
|
+
console.log("");
|
|
4007
|
+
console.log(" Waiting for bind approval (timeout: 10min)...");
|
|
4008
|
+
try {
|
|
4009
|
+
const instanceId = await pollUntilBound(
|
|
4010
|
+
cfg.platform.api_url,
|
|
4011
|
+
resp.bind_id,
|
|
4012
|
+
6e5
|
|
4013
|
+
);
|
|
4014
|
+
console.log(` Bind confirmed! Instance: ${instanceId}`);
|
|
4015
|
+
return instanceId;
|
|
4016
|
+
} catch (e) {
|
|
4017
|
+
console.error(` Bind polling stopped: ${e}`);
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
return void 0;
|
|
4021
|
+
} catch (e) {
|
|
4022
|
+
console.error(` Platform bind failed: ${e}`);
|
|
4023
|
+
return void 0;
|
|
4024
|
+
}
|
|
4025
|
+
}
|
|
4026
|
+
function nextHttpPort(state, base) {
|
|
4027
|
+
const used = new Set(state.devices.map((d) => d.http_port));
|
|
4028
|
+
let port = base;
|
|
4029
|
+
while (used.has(port)) port++;
|
|
4030
|
+
return port;
|
|
4031
|
+
}
|
|
4032
|
+
var init_device2 = __esm({
|
|
4033
|
+
"src/commands/device.ts"() {
|
|
4034
|
+
"use strict";
|
|
4035
|
+
init_dist();
|
|
4036
|
+
init_progress2();
|
|
4037
|
+
init_fallback_banner();
|
|
4038
|
+
}
|
|
4039
|
+
});
|
|
4040
|
+
|
|
4041
|
+
// src/index.ts
|
|
4042
|
+
init_dist();
|
|
4043
|
+
import { Command } from "commander";
|
|
4044
|
+
|
|
4045
|
+
// src/node-adapter.ts
|
|
4046
|
+
import {
|
|
4047
|
+
execFile as execFile6,
|
|
4048
|
+
spawn as nodeSpawn
|
|
4049
|
+
} from "child_process";
|
|
4050
|
+
import fs6 from "fs";
|
|
4051
|
+
import fsp from "fs/promises";
|
|
4052
|
+
import net from "net";
|
|
4053
|
+
import os5 from "os";
|
|
4054
|
+
import path7 from "path";
|
|
4055
|
+
var NodePlatformAdapter = class {
|
|
4056
|
+
// ── Filesystem ──────────────────────────────────────────
|
|
4057
|
+
async readFile(filePath) {
|
|
4058
|
+
return fsp.readFile(filePath, "utf-8");
|
|
4059
|
+
}
|
|
4060
|
+
async readFileBytes(filePath) {
|
|
4061
|
+
const buf = await fsp.readFile(filePath);
|
|
4062
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
4063
|
+
}
|
|
4064
|
+
async writeFile(filePath, content) {
|
|
4065
|
+
await fsp.writeFile(filePath, content, "utf-8");
|
|
1419
4066
|
}
|
|
1420
4067
|
async writeFileBytes(filePath, content) {
|
|
1421
4068
|
await fsp.writeFile(filePath, content);
|
|
@@ -1478,7 +4125,7 @@ var NodePlatformAdapter = class {
|
|
|
1478
4125
|
exec(cmd, args, options) {
|
|
1479
4126
|
return new Promise((resolve) => {
|
|
1480
4127
|
const env = options?.env ? { ...process.env, ...options.env } : process.env;
|
|
1481
|
-
|
|
4128
|
+
execFile6(
|
|
1482
4129
|
cmd,
|
|
1483
4130
|
args,
|
|
1484
4131
|
{
|
|
@@ -1506,12 +4153,12 @@ var NodePlatformAdapter = class {
|
|
|
1506
4153
|
let stdoutFd;
|
|
1507
4154
|
let stderrFd;
|
|
1508
4155
|
if (options?.stdoutFile) {
|
|
1509
|
-
await fsp.mkdir(
|
|
1510
|
-
stdoutFd =
|
|
4156
|
+
await fsp.mkdir(path7.dirname(options.stdoutFile), { recursive: true });
|
|
4157
|
+
stdoutFd = fs6.openSync(options.stdoutFile, "a");
|
|
1511
4158
|
}
|
|
1512
4159
|
if (options?.stderrFile) {
|
|
1513
|
-
await fsp.mkdir(
|
|
1514
|
-
stderrFd = options.stderrFile === options.stdoutFile ? stdoutFd :
|
|
4160
|
+
await fsp.mkdir(path7.dirname(options.stderrFile), { recursive: true });
|
|
4161
|
+
stderrFd = options.stderrFile === options.stdoutFile ? stdoutFd : fs6.openSync(options.stderrFile, "a");
|
|
1515
4162
|
}
|
|
1516
4163
|
const child = nodeSpawn(cmd, args, {
|
|
1517
4164
|
cwd: options?.cwd,
|
|
@@ -1528,8 +4175,8 @@ var NodePlatformAdapter = class {
|
|
|
1528
4175
|
if (options?.detached) {
|
|
1529
4176
|
child.unref();
|
|
1530
4177
|
}
|
|
1531
|
-
if (stdoutFd != null)
|
|
1532
|
-
if (stderrFd != null && stderrFd !== stdoutFd)
|
|
4178
|
+
if (stdoutFd != null) fs6.closeSync(stdoutFd);
|
|
4179
|
+
if (stderrFd != null && stderrFd !== stdoutFd) fs6.closeSync(stderrFd);
|
|
1533
4180
|
const pid = child.pid;
|
|
1534
4181
|
if (pid == null) {
|
|
1535
4182
|
throw new Error(`Failed to spawn process: ${cmd} ${args.join(" ")}`);
|
|
@@ -1575,10 +4222,10 @@ var NodePlatformAdapter = class {
|
|
|
1575
4222
|
}
|
|
1576
4223
|
// ── OS info ─────────────────────────────────────────────
|
|
1577
4224
|
homeDir() {
|
|
1578
|
-
return
|
|
4225
|
+
return os5.homedir();
|
|
1579
4226
|
}
|
|
1580
4227
|
hostname() {
|
|
1581
|
-
return
|
|
4228
|
+
return os5.hostname();
|
|
1582
4229
|
}
|
|
1583
4230
|
platform() {
|
|
1584
4231
|
return process.platform;
|
|
@@ -1596,48 +4243,21 @@ var NodePlatformAdapter = class {
|
|
|
1596
4243
|
}
|
|
1597
4244
|
// ── Path utilities ──────────────────────────────────────
|
|
1598
4245
|
joinPath(...segments) {
|
|
1599
|
-
return
|
|
4246
|
+
return path7.join(...segments);
|
|
1600
4247
|
}
|
|
1601
4248
|
dirname(p) {
|
|
1602
|
-
return
|
|
4249
|
+
return path7.dirname(p);
|
|
1603
4250
|
}
|
|
1604
4251
|
basename(p) {
|
|
1605
|
-
return
|
|
1606
|
-
}
|
|
1607
|
-
};
|
|
1608
|
-
|
|
1609
|
-
// src/commands/start.ts
|
|
1610
|
-
import os2 from "os";
|
|
1611
|
-
|
|
1612
|
-
// src/progress.ts
|
|
1613
|
-
import ora from "ora";
|
|
1614
|
-
var CliReporter = class {
|
|
1615
|
-
spinner;
|
|
1616
|
-
constructor() {
|
|
1617
|
-
this.spinner = ora({ stream: process.stderr });
|
|
1618
|
-
}
|
|
1619
|
-
onStatus(message) {
|
|
1620
|
-
if (this.spinner.isSpinning) {
|
|
1621
|
-
this.spinner.text = message;
|
|
1622
|
-
} else {
|
|
1623
|
-
this.spinner.start(message);
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
onComplete(message) {
|
|
1627
|
-
if (this.spinner.isSpinning) {
|
|
1628
|
-
this.spinner.succeed(message);
|
|
1629
|
-
} else {
|
|
1630
|
-
console.log(`\u2713 ${message}`);
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
stop() {
|
|
1634
|
-
if (this.spinner.isSpinning) {
|
|
1635
|
-
this.spinner.stop();
|
|
1636
|
-
}
|
|
4252
|
+
return path7.basename(p);
|
|
1637
4253
|
}
|
|
1638
4254
|
};
|
|
1639
4255
|
|
|
1640
4256
|
// src/commands/start.ts
|
|
4257
|
+
init_dist();
|
|
4258
|
+
init_progress2();
|
|
4259
|
+
init_fallback_banner();
|
|
4260
|
+
import os6 from "os";
|
|
1641
4261
|
async function run(agentFramework, options) {
|
|
1642
4262
|
const p = getPlatformAdapter();
|
|
1643
4263
|
await ensureDirs();
|
|
@@ -1675,7 +4295,7 @@ async function run(agentFramework, options) {
|
|
|
1675
4295
|
effectiveToken = systemToken ?? gwToken;
|
|
1676
4296
|
await backupOpenclawConfig(home);
|
|
1677
4297
|
if (await isGatewayRunning()) {
|
|
1678
|
-
const
|
|
4298
|
+
const ctx2 = {
|
|
1679
4299
|
agentHome: home,
|
|
1680
4300
|
agentBinary: binary,
|
|
1681
4301
|
keyFile,
|
|
@@ -1685,11 +4305,11 @@ async function run(agentFramework, options) {
|
|
|
1685
4305
|
isSystemHome: true
|
|
1686
4306
|
};
|
|
1687
4307
|
try {
|
|
1688
|
-
await configurePluginViaCli(
|
|
4308
|
+
await configurePluginViaCli(ctx2);
|
|
1689
4309
|
} catch {
|
|
1690
4310
|
}
|
|
1691
4311
|
try {
|
|
1692
|
-
await restartGatewayViaCli(
|
|
4312
|
+
await restartGatewayViaCli(ctx2);
|
|
1693
4313
|
} catch {
|
|
1694
4314
|
}
|
|
1695
4315
|
}
|
|
@@ -1704,27 +4324,25 @@ async function run(agentFramework, options) {
|
|
|
1704
4324
|
binary = managedBin;
|
|
1705
4325
|
}
|
|
1706
4326
|
reporter.stop();
|
|
4327
|
+
const ctx = {
|
|
4328
|
+
agentHome: home,
|
|
4329
|
+
agentBinary: binary,
|
|
4330
|
+
keyFile,
|
|
4331
|
+
bridgeUrl: cfg.platform.bridge_url,
|
|
4332
|
+
agentGatewayUrl: cfg.platform.api_url,
|
|
4333
|
+
gatewayToken: effectiveToken,
|
|
4334
|
+
isSystemHome
|
|
4335
|
+
};
|
|
1707
4336
|
let gatewayPid;
|
|
1708
4337
|
if (!await isGatewayRunning()) {
|
|
1709
|
-
const ctx = {
|
|
1710
|
-
agentHome: home,
|
|
1711
|
-
agentBinary: binary,
|
|
1712
|
-
keyFile,
|
|
1713
|
-
bridgeUrl: cfg.platform.bridge_url,
|
|
1714
|
-
agentGatewayUrl: cfg.platform.api_url,
|
|
1715
|
-
gatewayToken: effectiveToken,
|
|
1716
|
-
isSystemHome
|
|
1717
|
-
};
|
|
1718
4338
|
if (isSystemHome) {
|
|
1719
4339
|
await configurePluginViaCli(ctx);
|
|
1720
4340
|
} else {
|
|
1721
4341
|
await generateOpenclawConfig(ctx);
|
|
1722
4342
|
}
|
|
1723
|
-
|
|
1724
|
-
await writePid(agentFramework, pid);
|
|
1725
|
-
gatewayPid = pid;
|
|
4343
|
+
gatewayPid = await installOpenclawService(ctx);
|
|
1726
4344
|
let alive = false;
|
|
1727
|
-
for (let i = 0; i <
|
|
4345
|
+
for (let i = 0; i < 20; i++) {
|
|
1728
4346
|
await sleep2(500);
|
|
1729
4347
|
if (await isGatewayRunning()) {
|
|
1730
4348
|
alive = true;
|
|
@@ -1732,10 +4350,15 @@ async function run(agentFramework, options) {
|
|
|
1732
4350
|
}
|
|
1733
4351
|
}
|
|
1734
4352
|
if (!alive) {
|
|
1735
|
-
throw new Error(
|
|
4353
|
+
throw new Error(
|
|
4354
|
+
"Agent gateway did not start within 10s. Check the service logs: `beeos doctor` or ~/.beeos/logs/services/openclaw.log"
|
|
4355
|
+
);
|
|
1736
4356
|
}
|
|
1737
4357
|
} else {
|
|
1738
|
-
|
|
4358
|
+
try {
|
|
4359
|
+
gatewayPid = await installOpenclawService(ctx);
|
|
4360
|
+
} catch {
|
|
4361
|
+
}
|
|
1739
4362
|
}
|
|
1740
4363
|
const hostname = buildHostname();
|
|
1741
4364
|
try {
|
|
@@ -1758,33 +4381,28 @@ async function run(agentFramework, options) {
|
|
|
1758
4381
|
bridge_url: cfg.platform.bridge_url,
|
|
1759
4382
|
instance_id: result.instanceId
|
|
1760
4383
|
};
|
|
1761
|
-
if (options.json)
|
|
1762
|
-
|
|
1763
|
-
} else {
|
|
1764
|
-
console.log(`Agent bound to instance: ${result.instanceId}`);
|
|
1765
|
-
}
|
|
4384
|
+
if (options.json) console.log(JSON.stringify(out, null, 2));
|
|
4385
|
+
else console.log(`Agent bound to instance: ${result.instanceId}`);
|
|
1766
4386
|
return;
|
|
1767
4387
|
}
|
|
1768
|
-
if (result.status === "pending") {
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
return;
|
|
1783
|
-
}
|
|
4388
|
+
if (result.status === "pending" && options.json) {
|
|
4389
|
+
const out = {
|
|
4390
|
+
status: "pending",
|
|
4391
|
+
public_key: pubkey,
|
|
4392
|
+
fingerprint: fp,
|
|
4393
|
+
bind_id: result.bindId,
|
|
4394
|
+
bind_url: result.bindUrl,
|
|
4395
|
+
qr_data: result.bindUrl,
|
|
4396
|
+
gateway_url: "http://127.0.0.1:18789",
|
|
4397
|
+
gateway_pid: gatewayPid,
|
|
4398
|
+
bridge_url: cfg.platform.bridge_url
|
|
4399
|
+
};
|
|
4400
|
+
console.log(JSON.stringify(out, null, 2));
|
|
4401
|
+
return;
|
|
1784
4402
|
}
|
|
1785
4403
|
} catch (e) {
|
|
1786
|
-
const
|
|
1787
|
-
if (
|
|
4404
|
+
const cached2 = await loadBindingInfo();
|
|
4405
|
+
if (cached2 && cached2.fingerprint === fp) {
|
|
1788
4406
|
if (options.json) {
|
|
1789
4407
|
console.log(JSON.stringify({
|
|
1790
4408
|
status: "bound_offline",
|
|
@@ -1793,18 +4411,31 @@ async function run(agentFramework, options) {
|
|
|
1793
4411
|
gateway_url: "http://127.0.0.1:18789",
|
|
1794
4412
|
gateway_pid: gatewayPid,
|
|
1795
4413
|
bridge_url: cfg.platform.bridge_url,
|
|
1796
|
-
instance_id:
|
|
4414
|
+
instance_id: cached2.instance_id
|
|
1797
4415
|
}, null, 2));
|
|
1798
4416
|
} else {
|
|
1799
|
-
console.log(`Agent running (offline, cached instance: ${
|
|
4417
|
+
console.log(`Agent running (offline, cached instance: ${cached2.instance_id})`);
|
|
1800
4418
|
}
|
|
1801
4419
|
return;
|
|
1802
4420
|
}
|
|
1803
|
-
throw new Error(`Cannot reach platform to bind agent: ${e}
|
|
4421
|
+
throw new Error(`Cannot reach platform to bind agent: ${e}`, { cause: e });
|
|
4422
|
+
}
|
|
4423
|
+
}
|
|
4424
|
+
async function installOpenclawService(ctx) {
|
|
4425
|
+
const mgr = await getServiceManager();
|
|
4426
|
+
maybePrintFallbackBanner(mgr.kind);
|
|
4427
|
+
const spec = buildOpenclawTargetSpec(ctx);
|
|
4428
|
+
const status2 = await mgr.install(spec);
|
|
4429
|
+
if (status2.pid) {
|
|
4430
|
+
try {
|
|
4431
|
+
await writePid("openclaw", status2.pid);
|
|
4432
|
+
} catch {
|
|
4433
|
+
}
|
|
1804
4434
|
}
|
|
4435
|
+
return status2.pid ?? void 0;
|
|
1805
4436
|
}
|
|
1806
4437
|
function buildHostname() {
|
|
1807
|
-
const machine =
|
|
4438
|
+
const machine = os6.hostname();
|
|
1808
4439
|
const user = process.env.USER || process.env.USERNAME || "";
|
|
1809
4440
|
return user ? `${user}@${machine}` : machine;
|
|
1810
4441
|
}
|
|
@@ -1813,18 +4444,37 @@ function sleep2(ms) {
|
|
|
1813
4444
|
}
|
|
1814
4445
|
|
|
1815
4446
|
// src/commands/stop.ts
|
|
4447
|
+
init_dist();
|
|
1816
4448
|
async function run2(agentFramework) {
|
|
4449
|
+
const mgr = await getServiceManager();
|
|
4450
|
+
let handled = false;
|
|
4451
|
+
try {
|
|
4452
|
+
const status2 = await mgr.status(agentFramework);
|
|
4453
|
+
if (status2?.installed) {
|
|
4454
|
+
await mgr.uninstall(agentFramework);
|
|
4455
|
+
console.log(
|
|
4456
|
+
`Stopped ${agentFramework} agent${status2.pid ? ` (pid ${status2.pid})` : ""}`
|
|
4457
|
+
);
|
|
4458
|
+
handled = true;
|
|
4459
|
+
}
|
|
4460
|
+
} catch (err) {
|
|
4461
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4462
|
+
console.log(`Service stop failed: ${msg} \u2014 falling back to legacy PID kill`);
|
|
4463
|
+
}
|
|
1817
4464
|
const pid = await readPid(agentFramework);
|
|
1818
4465
|
if (pid == null) {
|
|
1819
|
-
console.log(`No running ${agentFramework} agent found`);
|
|
4466
|
+
if (!handled) console.log(`No running ${agentFramework} agent found`);
|
|
4467
|
+
await removePid(agentFramework).catch(() => {
|
|
4468
|
+
});
|
|
1820
4469
|
return;
|
|
1821
4470
|
}
|
|
1822
4471
|
killProcess(pid);
|
|
1823
4472
|
await removePid(agentFramework);
|
|
1824
|
-
console.log(`Stopped ${agentFramework} agent (pid ${pid})`);
|
|
4473
|
+
if (!handled) console.log(`Stopped ${agentFramework} agent (pid ${pid})`);
|
|
1825
4474
|
}
|
|
1826
4475
|
|
|
1827
4476
|
// src/commands/status.ts
|
|
4477
|
+
init_dist();
|
|
1828
4478
|
var KNOWN_RUNTIMES = ["openclaw", "device"];
|
|
1829
4479
|
async function run3() {
|
|
1830
4480
|
const home = beeoHome();
|
|
@@ -1842,10 +4492,27 @@ async function run3() {
|
|
|
1842
4492
|
console.log("Identity: not yet created");
|
|
1843
4493
|
}
|
|
1844
4494
|
console.log("");
|
|
4495
|
+
const mgr = await getServiceManager();
|
|
4496
|
+
const reason = activeFallbackReason();
|
|
4497
|
+
console.log(`Service manager: ${mgr.kind}${reason ? ` (${reason})` : ""}`);
|
|
4498
|
+
let services = [];
|
|
4499
|
+
try {
|
|
4500
|
+
services = await mgr.list();
|
|
4501
|
+
} catch {
|
|
4502
|
+
services = [];
|
|
4503
|
+
}
|
|
4504
|
+
if (services.length === 0) {
|
|
4505
|
+
console.log(" (no services registered)");
|
|
4506
|
+
} else {
|
|
4507
|
+
for (const s of services) console.log(` ${formatService(s)}`);
|
|
4508
|
+
}
|
|
4509
|
+
console.log("");
|
|
4510
|
+
const knownIds = new Set(services.map((s) => s.id));
|
|
1845
4511
|
for (const rt of KNOWN_RUNTIMES) {
|
|
4512
|
+
if (knownIds.has(rt)) continue;
|
|
1846
4513
|
const pid = await readPid(rt);
|
|
1847
4514
|
if (pid != null && isProcessAlive(pid)) {
|
|
1848
|
-
console.log(`${rt}: running (pid ${pid})`);
|
|
4515
|
+
console.log(`${rt}: running (pid ${pid}, legacy)`);
|
|
1849
4516
|
} else if (pid != null) {
|
|
1850
4517
|
console.log(`${rt}: stale pid ${pid} (process not alive)`);
|
|
1851
4518
|
} else {
|
|
@@ -1853,8 +4520,16 @@ async function run3() {
|
|
|
1853
4520
|
}
|
|
1854
4521
|
}
|
|
1855
4522
|
}
|
|
4523
|
+
function formatService(s) {
|
|
4524
|
+
const state = s.running ? `\x1B[32mrunning\x1B[0m` : s.installed ? `\x1B[33mstopped\x1B[0m` : `\x1B[31muninstalled\x1B[0m`;
|
|
4525
|
+
const pid = s.pid != null ? ` pid=${s.pid}` : "";
|
|
4526
|
+
const exit = s.lastExitCode != null && s.lastExitCode !== 0 ? ` last_exit=${s.lastExitCode}` : "";
|
|
4527
|
+
return `${s.id.padEnd(28)} ${state}${pid}${exit}`;
|
|
4528
|
+
}
|
|
1856
4529
|
|
|
1857
4530
|
// src/commands/update.ts
|
|
4531
|
+
init_dist();
|
|
4532
|
+
init_progress2();
|
|
1858
4533
|
async function run4(agentFramework) {
|
|
1859
4534
|
const reporter = new CliReporter();
|
|
1860
4535
|
const pid = await readPid(agentFramework);
|
|
@@ -1878,6 +4553,7 @@ async function run4(agentFramework) {
|
|
|
1878
4553
|
}
|
|
1879
4554
|
|
|
1880
4555
|
// src/commands/bind-status.ts
|
|
4556
|
+
init_dist();
|
|
1881
4557
|
async function run5(bindId, options) {
|
|
1882
4558
|
const cfg = await loadOrCreateConfig();
|
|
1883
4559
|
const resp = await getBindStatus(cfg.platform.api_url, bindId);
|
|
@@ -1911,320 +4587,483 @@ async function run5(bindId, options) {
|
|
|
1911
4587
|
}
|
|
1912
4588
|
}
|
|
1913
4589
|
|
|
1914
|
-
// src/
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
4590
|
+
// src/index.ts
|
|
4591
|
+
init_device2();
|
|
4592
|
+
|
|
4593
|
+
// src/commands/init.ts
|
|
4594
|
+
init_dist();
|
|
4595
|
+
import readline from "readline";
|
|
4596
|
+
|
|
4597
|
+
// src/commands/doctor.ts
|
|
4598
|
+
init_dist();
|
|
4599
|
+
import fs7 from "fs/promises";
|
|
4600
|
+
import path8 from "path";
|
|
4601
|
+
var LOG_SIZE_WARN_BYTES = 50 * 1024 * 1024;
|
|
4602
|
+
async function run6(options) {
|
|
4603
|
+
const state = await detectExistingInstall();
|
|
4604
|
+
const cfg = await loadOrCreateConfig();
|
|
1921
4605
|
const p = getPlatformAdapter();
|
|
1922
|
-
const
|
|
1923
|
-
|
|
4606
|
+
const mgr = await getServiceManager();
|
|
4607
|
+
const fallbackReason2 = activeFallbackReason();
|
|
4608
|
+
const serviceCheck = await mgr.selfCheck();
|
|
1924
4609
|
try {
|
|
1925
|
-
|
|
1926
|
-
return JSON.parse(raw);
|
|
4610
|
+
await migrateLegacySupervisor(mgr);
|
|
1927
4611
|
} catch {
|
|
1928
|
-
return { devices: [] };
|
|
1929
|
-
}
|
|
1930
|
-
}
|
|
1931
|
-
async function saveDeviceState(state) {
|
|
1932
|
-
const p = getPlatformAdapter();
|
|
1933
|
-
await p.writeFile(devicesPath(), JSON.stringify(state, null, 2));
|
|
1934
|
-
}
|
|
1935
|
-
async function withDeviceLock(fn) {
|
|
1936
|
-
const p = getPlatformAdapter();
|
|
1937
|
-
const path2 = devicesPath();
|
|
1938
|
-
await p.mkdir(p.dirname(path2));
|
|
1939
|
-
if (!await p.exists(path2)) {
|
|
1940
|
-
await p.writeFile(path2, JSON.stringify({ devices: [] }));
|
|
1941
4612
|
}
|
|
1942
|
-
|
|
4613
|
+
let services = [];
|
|
1943
4614
|
try {
|
|
1944
|
-
|
|
1945
|
-
}
|
|
1946
|
-
|
|
4615
|
+
services = await mgr.list();
|
|
4616
|
+
} catch {
|
|
4617
|
+
}
|
|
4618
|
+
const tools = await collectToolStatus(p);
|
|
4619
|
+
const warnings = [];
|
|
4620
|
+
const hints = [];
|
|
4621
|
+
if (!state.hasIdentity) {
|
|
4622
|
+
warnings.push("identity keypair missing \u2014 will be generated on next bind");
|
|
1947
4623
|
}
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
const cfg = await loadOrCreateConfig();
|
|
1951
|
-
const reporter = new CliReporter();
|
|
1952
|
-
if (options.all) {
|
|
1953
|
-
return attachAll(cfg, reporter);
|
|
4624
|
+
if (state.openclaw.gatewayRunning && !state.openclaw.found) {
|
|
4625
|
+
warnings.push("port 18789 is in use but no OpenClaw install detected \u2014 another app may conflict");
|
|
1954
4626
|
}
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
const pubkeyB64 = await deviceRuntime.ensureKeyAndGetPubkey(serial);
|
|
1960
|
-
const instanceId = await tryBindDevice(pubkeyB64, name, cfg);
|
|
1961
|
-
if (!instanceId) {
|
|
1962
|
-
console.error("Bind was not completed \u2014 device-agent will not start without a platform binding.");
|
|
1963
|
-
console.error("Run `beeos device attach` again to retry.");
|
|
1964
|
-
return;
|
|
4627
|
+
if (state.devices.entries.length > 0 && services.length === 0) {
|
|
4628
|
+
warnings.push(
|
|
4629
|
+
"devices tracked via legacy devices.json \u2014 no OS services registered. Re-run `beeos device attach` to migrate them to the OS service manager."
|
|
4630
|
+
);
|
|
1965
4631
|
}
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
if (idx >= 0) {
|
|
1970
|
-
if (isProcessAlive(state.devices[idx].pid)) {
|
|
1971
|
-
console.log(`Device ${serial} is already attached (pid ${state.devices[idx].pid})`);
|
|
1972
|
-
return;
|
|
1973
|
-
}
|
|
1974
|
-
console.error(` Cleaning stale entry for ${serial} (pid ${state.devices[idx].pid} no longer alive)`);
|
|
1975
|
-
state.devices.splice(idx, 1);
|
|
1976
|
-
}
|
|
1977
|
-
const httpPort = nextHttpPort(state, cfg.device.http_port);
|
|
1978
|
-
const pid = await deviceRuntime.spawnForDevice(
|
|
1979
|
-
serial,
|
|
1980
|
-
agentBin,
|
|
1981
|
-
cfg.platform.bridge_url,
|
|
1982
|
-
cfg.device.http_enabled,
|
|
1983
|
-
httpPort
|
|
4632
|
+
if (fallbackReason2) {
|
|
4633
|
+
warnings.push(
|
|
4634
|
+
`No native OS service manager available (${fallbackReason2}) \u2014 using fallback, services WON'T persist across reboots.`
|
|
1984
4635
|
);
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
`
|
|
1992
|
-
${logPath2}
|
|
1993
|
-
|
|
1994
|
-
${tail}`
|
|
4636
|
+
}
|
|
4637
|
+
for (const w of serviceCheck.warnings) warnings.push(w);
|
|
4638
|
+
for (const h of serviceCheck.hints) hints.push(h);
|
|
4639
|
+
for (const s of services) {
|
|
4640
|
+
if (!s.running && s.installed) {
|
|
4641
|
+
warnings.push(
|
|
4642
|
+
`service '${s.id}' is installed but not running \u2014 see log ${s.logFile}`
|
|
1995
4643
|
);
|
|
1996
4644
|
}
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
4645
|
+
if (s.lastExitCode !== null && s.lastExitCode !== 0) {
|
|
4646
|
+
warnings.push(
|
|
4647
|
+
`service '${s.id}' last exited with code ${s.lastExitCode} \u2014 see log ${s.logFile}`
|
|
4648
|
+
);
|
|
4649
|
+
}
|
|
4650
|
+
}
|
|
4651
|
+
if (!tools.adb.path) {
|
|
4652
|
+
warnings.push(
|
|
4653
|
+
"adb not found \u2014 `beeos device attach` will download Android platform-tools on first use"
|
|
4654
|
+
);
|
|
4655
|
+
}
|
|
4656
|
+
if (!tools.python.path) {
|
|
4657
|
+
warnings.push(
|
|
4658
|
+
"python3 not found on PATH \u2014 device-agent uses a uv-managed interpreter; install python 3.11+ if uv is unavailable"
|
|
4659
|
+
);
|
|
4660
|
+
}
|
|
4661
|
+
const bloatedLogs = await checkLogFileSizes();
|
|
4662
|
+
for (const l of bloatedLogs) {
|
|
4663
|
+
warnings.push(
|
|
4664
|
+
`Service log large: ${path8.basename(l.path)} = ${formatBytes(l.size)}`
|
|
4665
|
+
);
|
|
4666
|
+
hints.push(`truncate safely: : > ${l.path}`);
|
|
4667
|
+
}
|
|
4668
|
+
if (options.json) {
|
|
2010
4669
|
console.log(
|
|
2011
|
-
|
|
4670
|
+
JSON.stringify(
|
|
4671
|
+
{
|
|
4672
|
+
platform: p.platform(),
|
|
4673
|
+
arch: process.arch,
|
|
4674
|
+
node: process.version,
|
|
4675
|
+
beeosHome: state.beeosHome,
|
|
4676
|
+
config: cfg,
|
|
4677
|
+
state,
|
|
4678
|
+
serviceManager: {
|
|
4679
|
+
kind: mgr.kind,
|
|
4680
|
+
available: serviceCheck.available,
|
|
4681
|
+
fallbackReason: fallbackReason2,
|
|
4682
|
+
services
|
|
4683
|
+
},
|
|
4684
|
+
tools,
|
|
4685
|
+
warnings,
|
|
4686
|
+
hints
|
|
4687
|
+
},
|
|
4688
|
+
null,
|
|
4689
|
+
2
|
|
4690
|
+
)
|
|
2012
4691
|
);
|
|
2013
|
-
});
|
|
2014
|
-
}
|
|
2015
|
-
async function attachAll(cfg, reporter) {
|
|
2016
|
-
const devices = await deviceRuntime.listAdbDevices();
|
|
2017
|
-
if (devices.length === 0) {
|
|
2018
|
-
console.log("No ADB devices found");
|
|
2019
4692
|
return;
|
|
2020
4693
|
}
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
const p = getPlatformAdapter();
|
|
2048
|
-
const logPath = p.joinPath(beeoHome(), "logs", `device-${device.serial}.log`);
|
|
2049
|
-
const tail = await readLogTail(logPath, 10);
|
|
2050
|
-
console.error(`device-agent for ${device.serial} exited immediately (pid ${pid}): ${tail}`);
|
|
2051
|
-
continue;
|
|
2052
|
-
}
|
|
2053
|
-
const keyFile = deviceRuntime.deviceKeyPath(device.serial);
|
|
2054
|
-
state.devices.push({
|
|
2055
|
-
serial: device.serial,
|
|
2056
|
-
name: device.serial,
|
|
2057
|
-
pid,
|
|
2058
|
-
instance_id: instanceId,
|
|
2059
|
-
key_file: keyFile,
|
|
2060
|
-
http_port: httpPort
|
|
2061
|
-
});
|
|
2062
|
-
console.log(`Attached device ${device.serial} \u2014 pid ${pid} (http :${httpPort})`);
|
|
2063
|
-
count++;
|
|
2064
|
-
} catch (e) {
|
|
2065
|
-
console.error(`Failed to attach ${device.serial}: ${e}`);
|
|
2066
|
-
}
|
|
4694
|
+
console.log("");
|
|
4695
|
+
console.log(` OS/Arch : ${p.platform()}/${process.arch}`);
|
|
4696
|
+
console.log(` Node.js : ${process.version}`);
|
|
4697
|
+
console.log(` API URL : ${cfg.platform.api_url}`);
|
|
4698
|
+
console.log(` Bridge : ${cfg.platform.bridge_url}`);
|
|
4699
|
+
console.log("");
|
|
4700
|
+
for (const line of summarizeExistingInstall(state)) {
|
|
4701
|
+
console.log(` ${line}`);
|
|
4702
|
+
}
|
|
4703
|
+
console.log(
|
|
4704
|
+
` Service manager: ${mgr.kind}${fallbackReason2 ? " (fallback)" : ""} \u2014 ${services.length} service(s) registered`
|
|
4705
|
+
);
|
|
4706
|
+
for (const s of services) {
|
|
4707
|
+
const status2 = s.running ? "running" : s.installed ? "stopped" : "uninstalled";
|
|
4708
|
+
const pid = s.pid != null ? ` pid=${s.pid}` : "";
|
|
4709
|
+
console.log(` - ${s.id.padEnd(28)} ${status2}${pid}`);
|
|
4710
|
+
}
|
|
4711
|
+
console.log(` adb : ${tools.adb.path ?? "not found"}`);
|
|
4712
|
+
console.log(` python3 : ${tools.python.path ?? "not found"}`);
|
|
4713
|
+
console.log(` scrcpy-bridge: ${tools.scrcpyBridge.path ?? "not installed (auto on attach)"}`);
|
|
4714
|
+
console.log(` vnc-bridge : ${tools.vncBridge.path ?? "not installed (auto on --vnc-host)"}`);
|
|
4715
|
+
console.log("");
|
|
4716
|
+
if (warnings.length > 0) {
|
|
4717
|
+
console.log(" Warnings:");
|
|
4718
|
+
for (const w of warnings) {
|
|
4719
|
+
console.log(` - ${w}`);
|
|
2067
4720
|
}
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
4721
|
+
console.log("");
|
|
4722
|
+
} else {
|
|
4723
|
+
console.log(" No warnings \u2014 machine looks good.\n");
|
|
4724
|
+
}
|
|
4725
|
+
if (hints.length > 0) {
|
|
4726
|
+
console.log(" Hints:");
|
|
4727
|
+
for (const h of hints) {
|
|
4728
|
+
console.log(` $ ${h}`);
|
|
4729
|
+
}
|
|
4730
|
+
console.log("");
|
|
4731
|
+
}
|
|
2071
4732
|
}
|
|
2072
|
-
async function
|
|
2073
|
-
const
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
4733
|
+
async function collectToolStatus(p) {
|
|
4734
|
+
const [adbPath, pythonPath, scrcpyPath, vncPath] = await Promise.all([
|
|
4735
|
+
findAdb().catch(() => null),
|
|
4736
|
+
findPython2(p).catch(() => null),
|
|
4737
|
+
scrcpyBridgeRuntime.findBinary().catch(() => null),
|
|
4738
|
+
vncBridgeRuntime.findBinary().catch(() => null)
|
|
4739
|
+
]);
|
|
4740
|
+
return {
|
|
4741
|
+
adb: { path: adbPath },
|
|
4742
|
+
python: { path: pythonPath },
|
|
4743
|
+
scrcpyBridge: { path: scrcpyPath },
|
|
4744
|
+
vncBridge: { path: vncPath }
|
|
4745
|
+
};
|
|
4746
|
+
}
|
|
4747
|
+
async function findPython2(p) {
|
|
4748
|
+
const whichCmd = p.platform() === "win32" ? "where" : "which";
|
|
4749
|
+
for (const bin of ["python3", "python"]) {
|
|
4750
|
+
try {
|
|
4751
|
+
const r = await p.exec(whichCmd, [bin]);
|
|
4752
|
+
if (r.code === 0 && r.stdout.trim()) {
|
|
4753
|
+
return r.stdout.trim().split("\n")[0].trim();
|
|
2080
4754
|
}
|
|
2081
|
-
|
|
2082
|
-
state.devices = [];
|
|
2083
|
-
await saveDeviceState(state);
|
|
2084
|
-
console.log(`Detached ${count} device(s)`);
|
|
2085
|
-
return;
|
|
2086
|
-
}
|
|
2087
|
-
if (!options.serial) {
|
|
2088
|
-
throw new Error("Specify --serial or use --all");
|
|
4755
|
+
} catch {
|
|
2089
4756
|
}
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
const entry = state.devices.splice(idx, 1)[0];
|
|
2093
|
-
killProcess(entry.pid);
|
|
2094
|
-
await notifyOffline(cfg.platform.api_url, entry.instance_id);
|
|
2095
|
-
await saveDeviceState(state);
|
|
2096
|
-
console.log(`Detached device ${options.serial}`);
|
|
2097
|
-
});
|
|
4757
|
+
}
|
|
4758
|
+
return null;
|
|
2098
4759
|
}
|
|
2099
|
-
async function
|
|
2100
|
-
|
|
4760
|
+
async function checkLogFileSizes() {
|
|
4761
|
+
const dir = path8.join(beeoHome(), "logs", "services");
|
|
4762
|
+
let entries;
|
|
2101
4763
|
try {
|
|
2102
|
-
await
|
|
4764
|
+
entries = await fs7.readdir(dir);
|
|
2103
4765
|
} catch {
|
|
4766
|
+
return [];
|
|
4767
|
+
}
|
|
4768
|
+
const bloated = [];
|
|
4769
|
+
for (const name of entries) {
|
|
4770
|
+
if (!name.endsWith(".log")) continue;
|
|
4771
|
+
const full = path8.join(dir, name);
|
|
4772
|
+
try {
|
|
4773
|
+
const st = await fs7.stat(full);
|
|
4774
|
+
if (st.isFile() && st.size >= LOG_SIZE_WARN_BYTES) {
|
|
4775
|
+
bloated.push({ path: full, size: st.size });
|
|
4776
|
+
}
|
|
4777
|
+
} catch {
|
|
4778
|
+
}
|
|
2104
4779
|
}
|
|
4780
|
+
return bloated;
|
|
2105
4781
|
}
|
|
2106
|
-
|
|
2107
|
-
if (
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
4782
|
+
function formatBytes(n) {
|
|
4783
|
+
if (n >= 1024 * 1024 * 1024) return `${(n / (1024 * 1024 * 1024)).toFixed(1)}GB`;
|
|
4784
|
+
if (n >= 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(0)}MB`;
|
|
4785
|
+
if (n >= 1024) return `${(n / 1024).toFixed(0)}KB`;
|
|
4786
|
+
return `${n}B`;
|
|
4787
|
+
}
|
|
4788
|
+
|
|
4789
|
+
// src/commands/init.ts
|
|
4790
|
+
init_fallback_banner();
|
|
4791
|
+
async function run7(options) {
|
|
4792
|
+
await ensureDirs();
|
|
4793
|
+
try {
|
|
4794
|
+
const mgr = await getServiceManager();
|
|
4795
|
+
const mig = await migrateLegacySupervisor(mgr);
|
|
4796
|
+
if (!options.json && mig.ran) {
|
|
4797
|
+
if (mig.errors.length === 0) {
|
|
4798
|
+
console.log(
|
|
4799
|
+
` Migrated ${mig.migrated} service(s) from the legacy supervisor to ${mgr.kind}.`
|
|
4800
|
+
);
|
|
4801
|
+
} else {
|
|
4802
|
+
console.log(
|
|
4803
|
+
` Migrated ${mig.migrated} service(s) with ${mig.errors.length} error(s); backup at ${mig.backupPath ?? "(unknown)"}.`
|
|
4804
|
+
);
|
|
4805
|
+
for (const e of mig.errors) console.log(` - ${e.id}: ${e.error}`);
|
|
4806
|
+
}
|
|
2112
4807
|
}
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
console.log(
|
|
2117
|
-
`${entry.serial.padEnd(16)} ${entry.name.padEnd(20)} ${status.padEnd(12)} ${entry.pid}`
|
|
2118
|
-
);
|
|
4808
|
+
} catch (e) {
|
|
4809
|
+
if (!options.json) {
|
|
4810
|
+
console.log(` (legacy supervisor migration skipped: ${e instanceof Error ? e.message : String(e)})`);
|
|
2119
4811
|
}
|
|
4812
|
+
}
|
|
4813
|
+
if (options.device) {
|
|
4814
|
+
const { attach: attach2 } = await Promise.resolve().then(() => (init_device2(), device_exports));
|
|
4815
|
+
await attach2({});
|
|
4816
|
+
return;
|
|
4817
|
+
}
|
|
4818
|
+
const state = await detectExistingInstall();
|
|
4819
|
+
if (options.json) {
|
|
4820
|
+
await run6({ json: true });
|
|
2120
4821
|
} else {
|
|
2121
|
-
|
|
4822
|
+
printBanner();
|
|
4823
|
+
for (const line of summarizeExistingInstall(state)) {
|
|
4824
|
+
console.log(` ${line}`);
|
|
4825
|
+
}
|
|
4826
|
+
console.log("");
|
|
2122
4827
|
}
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
if (!entry) {
|
|
2128
|
-
throw new Error(options.serial ? `Device ${options.serial} not attached` : "No attached devices");
|
|
4828
|
+
const decision = await decideAction(state, options);
|
|
4829
|
+
if (decision === "skip") {
|
|
4830
|
+
console.log("Nothing to do. Run `beeos init` again or `beeos device attach` when ready.");
|
|
4831
|
+
return;
|
|
2129
4832
|
}
|
|
2130
|
-
if (
|
|
4833
|
+
if (decision === "upgrade") {
|
|
4834
|
+
console.log("Upgrading / ensuring OpenClaw is running...\n");
|
|
4835
|
+
}
|
|
4836
|
+
if (decision === "rebind-new-key") {
|
|
4837
|
+
await rotateIdentity();
|
|
4838
|
+
}
|
|
4839
|
+
if (decision === "rebind-keep-key" || decision === "rebind-new-key") {
|
|
4840
|
+
await removeBindingInfo();
|
|
4841
|
+
}
|
|
4842
|
+
const framework = options.framework ?? "openclaw";
|
|
4843
|
+
if (framework !== "openclaw") {
|
|
2131
4844
|
throw new Error(
|
|
2132
|
-
`
|
|
2133
|
-
Enable http_enabled in ~/.beeos/config.toml and re-attach.`
|
|
4845
|
+
`Agent framework '${framework}' is not yet supported. Only 'openclaw' is available today.`
|
|
2134
4846
|
);
|
|
2135
4847
|
}
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
method: "POST",
|
|
2141
|
-
headers: { "Content-Type": "application/json" },
|
|
2142
|
-
body: JSON.stringify({ prompt })
|
|
4848
|
+
await run(framework, {
|
|
4849
|
+
force: true,
|
|
4850
|
+
json: options.json,
|
|
4851
|
+
browser: options.browser !== false
|
|
2143
4852
|
});
|
|
2144
|
-
if (!
|
|
2145
|
-
|
|
2146
|
-
|
|
4853
|
+
if (!options.json && !options.skipServicePrompt) {
|
|
4854
|
+
await maybePromptServiceInstall();
|
|
4855
|
+
}
|
|
4856
|
+
if (!options.json) {
|
|
4857
|
+
printNextSteps();
|
|
2147
4858
|
}
|
|
2148
|
-
const body = await resp.json();
|
|
2149
|
-
console.log(JSON.stringify(body, null, 2));
|
|
2150
4859
|
}
|
|
2151
|
-
async function
|
|
2152
|
-
const
|
|
2153
|
-
|
|
4860
|
+
async function decideAction(state, options) {
|
|
4861
|
+
const choice = inferInitChoices(state);
|
|
4862
|
+
if (choice.options.length === 1) {
|
|
4863
|
+
return choice.defaultDecision;
|
|
4864
|
+
}
|
|
4865
|
+
if (options.yes || options.json) {
|
|
4866
|
+
return choice.defaultDecision;
|
|
4867
|
+
}
|
|
4868
|
+
if (!process.stdin.isTTY) {
|
|
4869
|
+
return choice.defaultDecision;
|
|
4870
|
+
}
|
|
4871
|
+
console.log("Detected existing install. Choose an action:");
|
|
4872
|
+
choice.options.forEach((d, i) => {
|
|
4873
|
+
const marker = d === choice.defaultDecision ? "*" : " ";
|
|
4874
|
+
console.log(` ${marker} [${i + 1}] ${initDecisionLabel(d)}`);
|
|
4875
|
+
});
|
|
4876
|
+
console.log("");
|
|
4877
|
+
const answer = await prompt(`Choose [1-${choice.options.length}] (default ${choice.options.indexOf(choice.defaultDecision) + 1}): `);
|
|
4878
|
+
const trimmed = answer.trim();
|
|
4879
|
+
if (!trimmed) return choice.defaultDecision;
|
|
4880
|
+
const idx = parseInt(trimmed, 10);
|
|
4881
|
+
if (!Number.isFinite(idx) || idx < 1 || idx > choice.options.length) {
|
|
4882
|
+
return choice.defaultDecision;
|
|
4883
|
+
}
|
|
4884
|
+
return choice.options[idx - 1];
|
|
2154
4885
|
}
|
|
2155
|
-
async function
|
|
2156
|
-
const fp = fingerprintFromB64(pubkeyB64);
|
|
4886
|
+
async function maybePromptServiceInstall() {
|
|
2157
4887
|
try {
|
|
2158
|
-
const
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
name
|
|
2164
|
-
);
|
|
2165
|
-
if (resp.status === "bound") {
|
|
2166
|
-
console.log(` Already bound \u2014 instance: ${resp.instance_id}`);
|
|
2167
|
-
return resp.instance_id ?? void 0;
|
|
2168
|
-
}
|
|
2169
|
-
if (resp.bind_id) {
|
|
2170
|
-
const bindUrl = buildBindUrl(cfg.platform.dashboard_base_url, resp.bind_id);
|
|
2171
|
-
await presentBindUrl(bindUrl, false);
|
|
2172
|
-
console.log("");
|
|
2173
|
-
console.log(" Waiting for bind approval (timeout: 10min)...");
|
|
2174
|
-
try {
|
|
2175
|
-
const instanceId = await pollUntilBound(
|
|
2176
|
-
cfg.platform.api_url,
|
|
2177
|
-
resp.bind_id,
|
|
2178
|
-
6e5
|
|
2179
|
-
);
|
|
2180
|
-
console.log(` Bind confirmed! Instance: ${instanceId}`);
|
|
2181
|
-
return instanceId;
|
|
2182
|
-
} catch (e) {
|
|
2183
|
-
console.error(` Bind polling stopped: ${e}`);
|
|
2184
|
-
}
|
|
4888
|
+
const mgr = await getServiceManager();
|
|
4889
|
+
maybePrintFallbackBanner(mgr.kind);
|
|
4890
|
+
const check = await mgr.selfCheck();
|
|
4891
|
+
if (!check.available) {
|
|
4892
|
+
console.log(` \u26A0 Service manager '${mgr.kind}' is not fully available \u2014 persistence limited.`);
|
|
2185
4893
|
}
|
|
2186
|
-
|
|
4894
|
+
for (const w of check.warnings) console.log(` ! ${w}`);
|
|
4895
|
+
for (const h of check.hints) console.log(` $ ${h}`);
|
|
2187
4896
|
} catch (e) {
|
|
2188
|
-
console.
|
|
2189
|
-
return void 0;
|
|
4897
|
+
console.log(` (service-manager self-check skipped: ${e instanceof Error ? e.message : String(e)})`);
|
|
2190
4898
|
}
|
|
2191
4899
|
}
|
|
2192
|
-
function
|
|
2193
|
-
const used = new Set(state.devices.map((d) => d.http_port));
|
|
2194
|
-
let port = base;
|
|
2195
|
-
while (used.has(port)) port++;
|
|
2196
|
-
return port;
|
|
2197
|
-
}
|
|
2198
|
-
async function readLogTail(path2, lines) {
|
|
4900
|
+
async function rotateIdentity() {
|
|
2199
4901
|
const p = getPlatformAdapter();
|
|
4902
|
+
const home = beeoHome();
|
|
4903
|
+
const identityDir = p.joinPath(home, "identity");
|
|
4904
|
+
const keypair = p.joinPath(identityDir, "keypair.json");
|
|
4905
|
+
const fp = p.joinPath(identityDir, "fingerprint");
|
|
4906
|
+
if (!await p.exists(keypair)) return;
|
|
4907
|
+
const ts = Math.floor(Date.now() / 1e3);
|
|
4908
|
+
const backup = p.joinPath(identityDir, `keypair.json.backup-${ts}`);
|
|
4909
|
+
try {
|
|
4910
|
+
await p.copyFile(keypair, backup);
|
|
4911
|
+
} catch {
|
|
4912
|
+
}
|
|
2200
4913
|
try {
|
|
2201
|
-
|
|
2202
|
-
const allLines = content.split("\n");
|
|
2203
|
-
const start = Math.max(0, allLines.length - lines);
|
|
2204
|
-
return allLines.slice(start).join("\n");
|
|
4914
|
+
await p.rm(keypair);
|
|
2205
4915
|
} catch {
|
|
2206
|
-
return "(could not read log file)";
|
|
2207
4916
|
}
|
|
4917
|
+
try {
|
|
4918
|
+
await p.rm(fp);
|
|
4919
|
+
} catch {
|
|
4920
|
+
}
|
|
4921
|
+
console.log(`Rotated identity. Previous keypair saved at ${backup}`);
|
|
2208
4922
|
}
|
|
2209
|
-
function
|
|
2210
|
-
|
|
4923
|
+
function printBanner() {
|
|
4924
|
+
console.log("");
|
|
4925
|
+
console.log(" BeeOS init");
|
|
4926
|
+
console.log("");
|
|
4927
|
+
}
|
|
4928
|
+
function printNextSteps() {
|
|
4929
|
+
console.log("");
|
|
4930
|
+
console.log("Next steps:");
|
|
4931
|
+
console.log(" beeos status # show running agents");
|
|
4932
|
+
console.log(" beeos device attach # bind a physical Android device");
|
|
4933
|
+
console.log(" beeos doctor # health check & diagnostics");
|
|
4934
|
+
console.log("");
|
|
4935
|
+
}
|
|
4936
|
+
function prompt(question) {
|
|
4937
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
4938
|
+
return new Promise((resolve) => {
|
|
4939
|
+
rl.question(question, (answer) => {
|
|
4940
|
+
rl.close();
|
|
4941
|
+
resolve(answer);
|
|
4942
|
+
});
|
|
4943
|
+
});
|
|
4944
|
+
}
|
|
4945
|
+
|
|
4946
|
+
// src/commands/service.ts
|
|
4947
|
+
init_dist();
|
|
4948
|
+
async function install2(options) {
|
|
4949
|
+
const mgr = await getServiceManager();
|
|
4950
|
+
const check = await mgr.selfCheck();
|
|
4951
|
+
const reason = activeFallbackReason();
|
|
4952
|
+
if (options.json) {
|
|
4953
|
+
console.log(
|
|
4954
|
+
JSON.stringify(
|
|
4955
|
+
{
|
|
4956
|
+
ok: check.available,
|
|
4957
|
+
manager: mgr.kind,
|
|
4958
|
+
fallbackReason: reason,
|
|
4959
|
+
warnings: check.warnings,
|
|
4960
|
+
hints: check.hints
|
|
4961
|
+
},
|
|
4962
|
+
null,
|
|
4963
|
+
2
|
|
4964
|
+
)
|
|
4965
|
+
);
|
|
4966
|
+
return;
|
|
4967
|
+
}
|
|
4968
|
+
console.log(`Service manager: ${mgr.kind}${reason ? ` (fallback \u2014 ${reason})` : ""}`);
|
|
4969
|
+
if (check.available) {
|
|
4970
|
+
console.log(" \u2713 available \u2014 BeeOS services will persist across reboots.");
|
|
4971
|
+
} else {
|
|
4972
|
+
console.log(" \u2717 not available \u2014 using fallback process runner (NO persistence).");
|
|
4973
|
+
}
|
|
4974
|
+
for (const w of check.warnings) console.log(` ! ${w}`);
|
|
4975
|
+
for (const h of check.hints) console.log(` $ ${h}`);
|
|
4976
|
+
console.log(
|
|
4977
|
+
"\nNote: per-target services are registered automatically by `beeos start` / `beeos device attach`."
|
|
4978
|
+
);
|
|
4979
|
+
}
|
|
4980
|
+
async function uninstall(options) {
|
|
4981
|
+
const mgr = await getServiceManager();
|
|
4982
|
+
let services = [];
|
|
4983
|
+
try {
|
|
4984
|
+
services = await mgr.list();
|
|
4985
|
+
} catch {
|
|
4986
|
+
services = [];
|
|
4987
|
+
}
|
|
4988
|
+
const removed = [];
|
|
4989
|
+
const errors = [];
|
|
4990
|
+
for (const s of services) {
|
|
4991
|
+
try {
|
|
4992
|
+
await mgr.uninstall(s.id);
|
|
4993
|
+
removed.push(s.id);
|
|
4994
|
+
} catch (e) {
|
|
4995
|
+
errors.push({ id: s.id, error: e instanceof Error ? e.message : String(e) });
|
|
4996
|
+
}
|
|
4997
|
+
}
|
|
4998
|
+
if (options.json) {
|
|
4999
|
+
console.log(JSON.stringify({ removed, errors }, null, 2));
|
|
5000
|
+
return;
|
|
5001
|
+
}
|
|
5002
|
+
if (removed.length === 0 && errors.length === 0) {
|
|
5003
|
+
console.log("No BeeOS services registered.");
|
|
5004
|
+
} else {
|
|
5005
|
+
for (const id of removed) console.log(` uninstalled ${id}`);
|
|
5006
|
+
for (const err of errors) console.log(` ! failed to uninstall ${err.id}: ${err.error}`);
|
|
5007
|
+
}
|
|
5008
|
+
}
|
|
5009
|
+
async function status(options) {
|
|
5010
|
+
const mgr = await getServiceManager();
|
|
5011
|
+
const reason = activeFallbackReason();
|
|
5012
|
+
let services = [];
|
|
5013
|
+
try {
|
|
5014
|
+
services = await mgr.list();
|
|
5015
|
+
} catch {
|
|
5016
|
+
services = [];
|
|
5017
|
+
}
|
|
5018
|
+
if (options.json) {
|
|
5019
|
+
console.log(
|
|
5020
|
+
JSON.stringify(
|
|
5021
|
+
{ manager: mgr.kind, fallbackReason: reason, services },
|
|
5022
|
+
null,
|
|
5023
|
+
2
|
|
5024
|
+
)
|
|
5025
|
+
);
|
|
5026
|
+
return;
|
|
5027
|
+
}
|
|
5028
|
+
console.log(`Service manager: ${mgr.kind}${reason ? ` (fallback)` : ""}`);
|
|
5029
|
+
if (services.length === 0) {
|
|
5030
|
+
console.log(" (no services registered)");
|
|
5031
|
+
return;
|
|
5032
|
+
}
|
|
5033
|
+
for (const s of services) {
|
|
5034
|
+
const state = s.running ? "running" : s.installed ? "stopped" : "uninstalled";
|
|
5035
|
+
const pid = s.pid != null ? ` pid=${s.pid}` : "";
|
|
5036
|
+
console.log(` ${s.id.padEnd(28)} ${state}${pid}`);
|
|
5037
|
+
}
|
|
2211
5038
|
}
|
|
2212
5039
|
|
|
2213
5040
|
// src/index.ts
|
|
2214
5041
|
setPlatformAdapter(new NodePlatformAdapter());
|
|
2215
5042
|
var program = new Command();
|
|
2216
5043
|
program.name("beeos").version("1.0.0").description("BeeOS \u2014 run AI agents from your desktop");
|
|
5044
|
+
program.command("init").description("One-shot install + bind flow (default entry from the curl installer)").option("--framework <name>", "Agent framework to install (default: openclaw)").option("--yes", "Non-interactive \u2014 accept the default action", false).option("--json", "Output machine-readable JSON (implies --yes)", false).option("--no-browser", "Don't auto-open a browser for bind confirmation").option("--headless", "Never open a browser (use terminal QR only)", false).option("--skip-service-prompt", "Skip the system-service install prompt", false).option("--device", "Jump straight into `beeos device attach` instead", false).action((opts) => run7(opts));
|
|
5045
|
+
program.command("doctor").description("Inspect local BeeOS state (install, binding, services, warnings)").option("--json", "Output machine-readable JSON", false).action((opts) => run6(opts));
|
|
5046
|
+
var serviceCmd = program.command("service").description("Inspect and manage the OS-native service manager (launchd / systemd --user / Task Scheduler)");
|
|
5047
|
+
serviceCmd.command("install").description("Verify the OS service manager is available (per-target services install automatically)").option("--json", "Output machine-readable JSON", false).action((opts) => install2(opts));
|
|
5048
|
+
serviceCmd.command("uninstall").description("Uninstall every BeeOS-managed OS service").option("--json", "Output machine-readable JSON", false).action((opts) => uninstall(opts));
|
|
5049
|
+
serviceCmd.command("status").description("List every BeeOS-managed OS service").option("--json", "Output machine-readable JSON", false).action((opts) => status(opts));
|
|
2217
5050
|
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((agentFramework, options) => run(agentFramework, options));
|
|
2218
5051
|
program.command("stop").description("Stop a running agent").argument("<agent_type>", "Agent type to stop").action((agentFramework) => run2(agentFramework));
|
|
2219
5052
|
program.command("status").description("Show status of managed agents").action(run3);
|
|
2220
5053
|
program.command("update").description("Update an agent to the latest version").argument("<agent_type>", "Agent type to update").action((agentFramework) => run4(agentFramework));
|
|
2221
5054
|
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);
|
|
2222
5055
|
var deviceCmd = program.command("device").description("Manage device agents (Android/Desktop/ChromeOS)");
|
|
2223
|
-
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).
|
|
5056
|
+
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).option(
|
|
5057
|
+
"--no-video",
|
|
5058
|
+
"Skip the scrcpy-bridge WebRTC sidecar; only run device-agent. Useful when iterating on ACP / Python code without the video stream."
|
|
5059
|
+
).option(
|
|
5060
|
+
"--vnc-host <host>",
|
|
5061
|
+
"Use vnc-bridge against a VNC server at this host (desktop/Linux/macOS). Implies video mode = vnc instead of scrcpy."
|
|
5062
|
+
).option("--vnc-port <port>", "VNC TCP port (default 5900)", (v) => Number(v)).option("--vnc-password <password>", "VNC password (forwarded via env)").action(attach);
|
|
2224
5063
|
deviceCmd.command("detach").description("Detach a device agent").option("--serial <serial>", "ADB device serial number").option("--all", "Detach all devices", false).action(detach);
|
|
2225
5064
|
deviceCmd.command("list").description("List attached devices").option("--local", "Only show locally running device agents", false).action(list);
|
|
2226
5065
|
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);
|
|
2227
|
-
deviceCmd.command("upgrade").description("Upgrade the device-agent to the latest version").action(upgrade);
|
|
5066
|
+
deviceCmd.command("upgrade").description("Upgrade the device-agent (and scrcpy/vnc bridges) to the latest version").option("--no-bridges", "Skip upgrading scrcpy-bridge / vnc-bridge binaries").action(upgrade);
|
|
2228
5067
|
program.parseAsync(process.argv).catch((err) => {
|
|
2229
5068
|
console.error(err.message);
|
|
2230
5069
|
process.exit(1);
|