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