@beeos-ai/cli 1.1.1 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +940 -215
- package/package.json +1 -1
- package/scripts/install.sh +138 -9
package/dist/index.js
CHANGED
|
@@ -127,14 +127,14 @@ function projectSpawnEnv(cfg) {
|
|
|
127
127
|
}
|
|
128
128
|
async function saveSpawnEnvSnapshot(cfg) {
|
|
129
129
|
const p = getPlatformAdapter();
|
|
130
|
-
const
|
|
130
|
+
const path12 = spawnEnvJsonPath();
|
|
131
131
|
const snapshot = projectSpawnEnv(cfg);
|
|
132
132
|
try {
|
|
133
|
-
await p.mkdir(p.dirname(
|
|
134
|
-
await p.writeFile(
|
|
133
|
+
await p.mkdir(p.dirname(path12));
|
|
134
|
+
await p.writeFile(path12, JSON.stringify(snapshot, null, 2) + "\n");
|
|
135
135
|
} catch (err) {
|
|
136
136
|
const message = err instanceof Error ? err.message : String(err);
|
|
137
|
-
console.error(`! warning: could not write ${
|
|
137
|
+
console.error(`! warning: could not write ${path12}: ${message}
|
|
138
138
|
device-agent fleet will fall back to parsing config.toml directly.`);
|
|
139
139
|
}
|
|
140
140
|
}
|
|
@@ -208,17 +208,17 @@ function defaultState() {
|
|
|
208
208
|
}
|
|
209
209
|
async function loadState() {
|
|
210
210
|
const p = getPlatformAdapter();
|
|
211
|
-
const
|
|
212
|
-
if (await p.exists(
|
|
211
|
+
const path12 = statePath();
|
|
212
|
+
if (await p.exists(path12)) {
|
|
213
213
|
let raw;
|
|
214
214
|
try {
|
|
215
|
-
raw = await p.readFile(
|
|
215
|
+
raw = await p.readFile(path12);
|
|
216
216
|
} catch (e) {
|
|
217
217
|
throw new BeeosError({
|
|
218
218
|
code: "state_corrupted",
|
|
219
|
-
message: `failed to read ${
|
|
219
|
+
message: `failed to read ${path12}: ${describeError(e)}`,
|
|
220
220
|
hint: "Inspect the file or move it aside and re-run `beeos doctor` to regenerate from legacy state.",
|
|
221
|
-
details: { path:
|
|
221
|
+
details: { path: path12 },
|
|
222
222
|
cause: e
|
|
223
223
|
});
|
|
224
224
|
}
|
|
@@ -228,23 +228,23 @@ async function loadState() {
|
|
|
228
228
|
} catch (e) {
|
|
229
229
|
throw new BeeosError({
|
|
230
230
|
code: "state_corrupted",
|
|
231
|
-
message: `${
|
|
231
|
+
message: `${path12} is not valid JSON: ${describeError(e)}`,
|
|
232
232
|
hint: "Move the file aside (e.g. `mv ~/.beeos/state.json ~/.beeos/state.json.broken`) and re-run `beeos doctor` to migrate from the legacy state files.",
|
|
233
|
-
details: { path:
|
|
233
|
+
details: { path: path12 }
|
|
234
234
|
});
|
|
235
235
|
}
|
|
236
|
-
return validateState(parsed,
|
|
236
|
+
return validateState(parsed, path12);
|
|
237
237
|
}
|
|
238
238
|
return migrateLegacy();
|
|
239
239
|
}
|
|
240
240
|
async function saveState(state) {
|
|
241
241
|
const p = getPlatformAdapter();
|
|
242
|
-
const
|
|
243
|
-
await p.mkdir(p.dirname(
|
|
244
|
-
await p.writeFile(
|
|
242
|
+
const path12 = statePath();
|
|
243
|
+
await p.mkdir(p.dirname(path12));
|
|
244
|
+
await p.writeFile(path12, JSON.stringify(state, null, 2) + "\n");
|
|
245
245
|
if (p.platform() !== "win32") {
|
|
246
246
|
try {
|
|
247
|
-
await p.chmod(
|
|
247
|
+
await p.chmod(path12, 384);
|
|
248
248
|
} catch {
|
|
249
249
|
}
|
|
250
250
|
}
|
|
@@ -319,11 +319,11 @@ async function migrateLegacy() {
|
|
|
319
319
|
}
|
|
320
320
|
async function tryReadBinding() {
|
|
321
321
|
const p = getPlatformAdapter();
|
|
322
|
-
const
|
|
323
|
-
if (!await p.exists(
|
|
322
|
+
const path12 = bindingPath();
|
|
323
|
+
if (!await p.exists(path12))
|
|
324
324
|
return null;
|
|
325
325
|
try {
|
|
326
|
-
const raw = await p.readFile(
|
|
326
|
+
const raw = await p.readFile(path12);
|
|
327
327
|
return JSON.parse(raw);
|
|
328
328
|
} catch {
|
|
329
329
|
return null;
|
|
@@ -331,11 +331,11 @@ async function tryReadBinding() {
|
|
|
331
331
|
}
|
|
332
332
|
async function tryReadDevices() {
|
|
333
333
|
const p = getPlatformAdapter();
|
|
334
|
-
const
|
|
335
|
-
if (!await p.exists(
|
|
334
|
+
const path12 = legacyDevicesPath();
|
|
335
|
+
if (!await p.exists(path12))
|
|
336
336
|
return [];
|
|
337
337
|
try {
|
|
338
|
-
const raw = await p.readFile(
|
|
338
|
+
const raw = await p.readFile(path12);
|
|
339
339
|
const parsed = JSON.parse(raw);
|
|
340
340
|
return Array.isArray(parsed?.devices) ? parsed.devices : [];
|
|
341
341
|
} catch {
|
|
@@ -344,11 +344,11 @@ async function tryReadDevices() {
|
|
|
344
344
|
}
|
|
345
345
|
async function tryReadSpawnEnvIntoPlatform(state) {
|
|
346
346
|
const p = getPlatformAdapter();
|
|
347
|
-
const
|
|
348
|
-
if (!await p.exists(
|
|
347
|
+
const path12 = spawnEnvJsonPath();
|
|
348
|
+
if (!await p.exists(path12))
|
|
349
349
|
return;
|
|
350
350
|
try {
|
|
351
|
-
const raw = await p.readFile(
|
|
351
|
+
const raw = await p.readFile(path12);
|
|
352
352
|
const parsed = JSON.parse(raw);
|
|
353
353
|
if (!parsed || typeof parsed !== "object")
|
|
354
354
|
return;
|
|
@@ -364,7 +364,7 @@ async function tryReadSpawnEnvIntoPlatform(state) {
|
|
|
364
364
|
async function tryFoldVlmConfigs(state) {
|
|
365
365
|
const p = getPlatformAdapter();
|
|
366
366
|
const root = beeoHome();
|
|
367
|
-
let entries
|
|
367
|
+
let entries;
|
|
368
368
|
try {
|
|
369
369
|
entries = await p.readdir(root);
|
|
370
370
|
} catch {
|
|
@@ -391,13 +391,13 @@ async function tryFoldVlmConfigs(state) {
|
|
|
391
391
|
}
|
|
392
392
|
}
|
|
393
393
|
}
|
|
394
|
-
function validateState(parsed,
|
|
394
|
+
function validateState(parsed, path12) {
|
|
395
395
|
if (!parsed || typeof parsed !== "object") {
|
|
396
396
|
throw new BeeosError({
|
|
397
397
|
code: "state_corrupted",
|
|
398
|
-
message: `${
|
|
398
|
+
message: `${path12} did not deserialise to a JSON object.`,
|
|
399
399
|
hint: "Move the file aside and re-run `beeos doctor` to regenerate.",
|
|
400
|
-
details: { path:
|
|
400
|
+
details: { path: path12 }
|
|
401
401
|
});
|
|
402
402
|
}
|
|
403
403
|
const obj = parsed;
|
|
@@ -405,26 +405,26 @@ function validateState(parsed, path10) {
|
|
|
405
405
|
if (version !== 2) {
|
|
406
406
|
throw new BeeosError({
|
|
407
407
|
code: "state_version_mismatch",
|
|
408
|
-
message: `${
|
|
408
|
+
message: `${path12} declares version=${JSON.stringify(version)}; this CLI only understands version=2.`,
|
|
409
409
|
hint: "Either upgrade the CLI (`beeos --version` to see what you have) or move the file aside and re-bind your agents with the older CLI's flow.",
|
|
410
|
-
details: { path:
|
|
410
|
+
details: { path: path12, observed: version, expected: 2 }
|
|
411
411
|
});
|
|
412
412
|
}
|
|
413
413
|
const platform = obj.platform;
|
|
414
414
|
if (!platform || typeof platform !== "object") {
|
|
415
415
|
throw new BeeosError({
|
|
416
416
|
code: "state_corrupted",
|
|
417
|
-
message: `${
|
|
417
|
+
message: `${path12} is missing a \`platform\` object.`,
|
|
418
418
|
hint: "Move the file aside and re-run `beeos doctor`.",
|
|
419
|
-
details: { path:
|
|
419
|
+
details: { path: path12 }
|
|
420
420
|
});
|
|
421
421
|
}
|
|
422
422
|
if (!Array.isArray(obj.agents)) {
|
|
423
423
|
throw new BeeosError({
|
|
424
424
|
code: "state_corrupted",
|
|
425
|
-
message: `${
|
|
425
|
+
message: `${path12} \`agents\` is not an array.`,
|
|
426
426
|
hint: "Move the file aside and re-run `beeos doctor`.",
|
|
427
|
-
details: { path:
|
|
427
|
+
details: { path: path12 }
|
|
428
428
|
});
|
|
429
429
|
}
|
|
430
430
|
return obj;
|
|
@@ -448,9 +448,9 @@ var init_state = __esm({
|
|
|
448
448
|
import * as TOML from "smol-toml";
|
|
449
449
|
async function loadOrCreateConfig() {
|
|
450
450
|
const p = getPlatformAdapter();
|
|
451
|
-
const
|
|
452
|
-
if (await p.exists(
|
|
453
|
-
const raw = await p.readFile(
|
|
451
|
+
const path12 = configPath();
|
|
452
|
+
if (await p.exists(path12)) {
|
|
453
|
+
const raw = await p.readFile(path12);
|
|
454
454
|
const parsed = TOML.parse(raw);
|
|
455
455
|
const cfg2 = mergeWithDefaults(parsed);
|
|
456
456
|
await saveSpawnEnvSnapshot(cfg2);
|
|
@@ -478,8 +478,8 @@ function applyEnvOverrides(cfg) {
|
|
|
478
478
|
}
|
|
479
479
|
async function saveConfig(cfg) {
|
|
480
480
|
const p = getPlatformAdapter();
|
|
481
|
-
const
|
|
482
|
-
await p.mkdir(p.dirname(
|
|
481
|
+
const path12 = configPath();
|
|
482
|
+
await p.mkdir(p.dirname(path12));
|
|
483
483
|
const platformBlock = {
|
|
484
484
|
api_url: cfg.platform.api_url,
|
|
485
485
|
agent_gateway_url: cfg.platform.agent_gateway_url,
|
|
@@ -496,7 +496,7 @@ async function saveConfig(cfg) {
|
|
|
496
496
|
}
|
|
497
497
|
};
|
|
498
498
|
const raw = TOML.stringify(serializable);
|
|
499
|
-
await p.writeFile(
|
|
499
|
+
await p.writeFile(path12, raw);
|
|
500
500
|
await saveSpawnEnvSnapshot(cfg);
|
|
501
501
|
await refreshStatePlatformBestEffort(cfg);
|
|
502
502
|
}
|
|
@@ -570,22 +570,22 @@ var init_toml = __esm({
|
|
|
570
570
|
// ../core/dist/config/binding.js
|
|
571
571
|
async function loadBindingInfo() {
|
|
572
572
|
const p = getPlatformAdapter();
|
|
573
|
-
const
|
|
574
|
-
if (!await p.exists(
|
|
573
|
+
const path12 = bindingPath();
|
|
574
|
+
if (!await p.exists(path12))
|
|
575
575
|
return null;
|
|
576
|
-
const raw = await p.readFile(
|
|
576
|
+
const raw = await p.readFile(path12);
|
|
577
577
|
return JSON.parse(raw);
|
|
578
578
|
}
|
|
579
579
|
async function saveBindingInfo(info) {
|
|
580
580
|
const p = getPlatformAdapter();
|
|
581
|
-
const
|
|
582
|
-
await p.writeFile(
|
|
581
|
+
const path12 = bindingPath();
|
|
582
|
+
await p.writeFile(path12, JSON.stringify(info, null, 2));
|
|
583
583
|
}
|
|
584
584
|
async function removeBindingInfo() {
|
|
585
585
|
const p = getPlatformAdapter();
|
|
586
|
-
const
|
|
587
|
-
if (await p.exists(
|
|
588
|
-
await p.rm(
|
|
586
|
+
const path12 = bindingPath();
|
|
587
|
+
if (await p.exists(path12)) {
|
|
588
|
+
await p.rm(path12);
|
|
589
589
|
}
|
|
590
590
|
}
|
|
591
591
|
var init_binding = __esm({
|
|
@@ -599,17 +599,17 @@ var init_binding = __esm({
|
|
|
599
599
|
// ../core/dist/config/gateway-token.js
|
|
600
600
|
async function loadOrCreateGatewayToken() {
|
|
601
601
|
const p = getPlatformAdapter();
|
|
602
|
-
const
|
|
603
|
-
if (await p.exists(
|
|
604
|
-
const token2 = (await p.readFile(
|
|
602
|
+
const path12 = p.joinPath(beeoHome(), "gateway_token");
|
|
603
|
+
if (await p.exists(path12)) {
|
|
604
|
+
const token2 = (await p.readFile(path12)).trim();
|
|
605
605
|
if (token2)
|
|
606
606
|
return token2;
|
|
607
607
|
}
|
|
608
608
|
const token = crypto.randomUUID();
|
|
609
|
-
await p.mkdir(p.dirname(
|
|
610
|
-
await p.writeFile(
|
|
609
|
+
await p.mkdir(p.dirname(path12));
|
|
610
|
+
await p.writeFile(path12, token);
|
|
611
611
|
if (p.platform() !== "win32") {
|
|
612
|
-
await p.chmod(
|
|
612
|
+
await p.chmod(path12, 384);
|
|
613
613
|
}
|
|
614
614
|
return token;
|
|
615
615
|
}
|
|
@@ -650,9 +650,9 @@ function fingerprintFromB64(pubkeyB64) {
|
|
|
650
650
|
}
|
|
651
651
|
async function loadOrCreateIdentity() {
|
|
652
652
|
const p = getPlatformAdapter();
|
|
653
|
-
const
|
|
654
|
-
if (await p.exists(
|
|
655
|
-
return loadFromFile(
|
|
653
|
+
const path12 = keypairPath();
|
|
654
|
+
if (await p.exists(path12)) {
|
|
655
|
+
return loadFromFile(path12);
|
|
656
656
|
}
|
|
657
657
|
const id = generate();
|
|
658
658
|
await save(id);
|
|
@@ -684,10 +684,10 @@ async function readPubkeyFromKeyFile(keyPath) {
|
|
|
684
684
|
async function loadIdentityFromKeyFile(keyPath) {
|
|
685
685
|
return loadFromFile(keyPath);
|
|
686
686
|
}
|
|
687
|
-
async function signRequest(method,
|
|
687
|
+
async function signRequest(method, path12, id) {
|
|
688
688
|
const timestamp = Math.floor(Date.now() / 1e3).toString();
|
|
689
689
|
const nonce = crypto.randomUUID();
|
|
690
|
-
const message = `${method.toUpperCase()}|${
|
|
690
|
+
const message = `${method.toUpperCase()}|${path12}|${timestamp}|${nonce}`;
|
|
691
691
|
const msgBytes = new TextEncoder().encode(message);
|
|
692
692
|
const sig = await ed.signAsync(msgBytes, id.privateKey);
|
|
693
693
|
return {
|
|
@@ -705,9 +705,9 @@ function fingerprintFilePath() {
|
|
|
705
705
|
const p = getPlatformAdapter();
|
|
706
706
|
return p.joinPath(beeoHome(), "identity", "fingerprint");
|
|
707
707
|
}
|
|
708
|
-
async function loadFromFile(
|
|
708
|
+
async function loadFromFile(path12) {
|
|
709
709
|
const p = getPlatformAdapter();
|
|
710
|
-
const raw = await p.readFile(
|
|
710
|
+
const raw = await p.readFile(path12);
|
|
711
711
|
const stored = JSON.parse(raw);
|
|
712
712
|
const privateKey = fromBase64(stored.privateKey);
|
|
713
713
|
if (privateKey.length !== 32) {
|
|
@@ -745,6 +745,62 @@ var init_keypair = __esm({
|
|
|
745
745
|
}
|
|
746
746
|
});
|
|
747
747
|
|
|
748
|
+
// ../core/dist/identity/qr.js
|
|
749
|
+
import QRCode from "qrcode";
|
|
750
|
+
async function qrModules(url) {
|
|
751
|
+
const segments = QRCode.create(url, { errorCorrectionLevel: "M" });
|
|
752
|
+
const size = segments.modules.size;
|
|
753
|
+
const modules = [];
|
|
754
|
+
for (let y = 0; y < size; y++) {
|
|
755
|
+
for (let x = 0; x < size; x++) {
|
|
756
|
+
modules.push(segments.modules.get(x, y) !== 0);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
return { width: size, modules };
|
|
760
|
+
}
|
|
761
|
+
async function qrToTerminal(url) {
|
|
762
|
+
const { width, modules } = await qrModules(url);
|
|
763
|
+
const rows = [];
|
|
764
|
+
for (let i = 0; i < modules.length; i += width) {
|
|
765
|
+
rows.push(modules.slice(i, i + width));
|
|
766
|
+
}
|
|
767
|
+
const quiet = 2;
|
|
768
|
+
const fullWidth = width + quiet * 2;
|
|
769
|
+
const blankLine = " ".repeat(fullWidth);
|
|
770
|
+
const lines = [];
|
|
771
|
+
for (let i = 0; i < quiet; i++)
|
|
772
|
+
lines.push(blankLine);
|
|
773
|
+
let y = 0;
|
|
774
|
+
while (y < rows.length) {
|
|
775
|
+
const topRow = rows[y];
|
|
776
|
+
const botRow = y + 1 < rows.length ? rows[y + 1] : null;
|
|
777
|
+
let line = " ".repeat(quiet);
|
|
778
|
+
for (let x = 0; x < width; x++) {
|
|
779
|
+
const top = topRow[x];
|
|
780
|
+
const bot = botRow ? botRow[x] : false;
|
|
781
|
+
if (top && bot)
|
|
782
|
+
line += "\u2588";
|
|
783
|
+
else if (top && !bot)
|
|
784
|
+
line += "\u2580";
|
|
785
|
+
else if (!top && bot)
|
|
786
|
+
line += "\u2584";
|
|
787
|
+
else
|
|
788
|
+
line += " ";
|
|
789
|
+
}
|
|
790
|
+
line += " ".repeat(quiet);
|
|
791
|
+
lines.push(line);
|
|
792
|
+
y += 2;
|
|
793
|
+
}
|
|
794
|
+
for (let i = 0; i < quiet; i++)
|
|
795
|
+
lines.push(blankLine);
|
|
796
|
+
return lines.join("\n");
|
|
797
|
+
}
|
|
798
|
+
var init_qr = __esm({
|
|
799
|
+
"../core/dist/identity/qr.js"() {
|
|
800
|
+
"use strict";
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
|
|
748
804
|
// ../core/dist/platform/client.js
|
|
749
805
|
async function bindHttpError(resp, op) {
|
|
750
806
|
const body = await resp.text().catch(() => "");
|
|
@@ -870,62 +926,6 @@ var init_process = __esm({
|
|
|
870
926
|
}
|
|
871
927
|
});
|
|
872
928
|
|
|
873
|
-
// ../core/dist/identity/qr.js
|
|
874
|
-
import QRCode from "qrcode";
|
|
875
|
-
async function qrModules(url) {
|
|
876
|
-
const segments = QRCode.create(url, { errorCorrectionLevel: "M" });
|
|
877
|
-
const size = segments.modules.size;
|
|
878
|
-
const modules = [];
|
|
879
|
-
for (let y = 0; y < size; y++) {
|
|
880
|
-
for (let x = 0; x < size; x++) {
|
|
881
|
-
modules.push(segments.modules.get(x, y) !== 0);
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
return { width: size, modules };
|
|
885
|
-
}
|
|
886
|
-
async function qrToTerminal(url) {
|
|
887
|
-
const { width, modules } = await qrModules(url);
|
|
888
|
-
const rows = [];
|
|
889
|
-
for (let i = 0; i < modules.length; i += width) {
|
|
890
|
-
rows.push(modules.slice(i, i + width));
|
|
891
|
-
}
|
|
892
|
-
const quiet = 2;
|
|
893
|
-
const fullWidth = width + quiet * 2;
|
|
894
|
-
const blankLine = " ".repeat(fullWidth);
|
|
895
|
-
const lines = [];
|
|
896
|
-
for (let i = 0; i < quiet; i++)
|
|
897
|
-
lines.push(blankLine);
|
|
898
|
-
let y = 0;
|
|
899
|
-
while (y < rows.length) {
|
|
900
|
-
const topRow = rows[y];
|
|
901
|
-
const botRow = y + 1 < rows.length ? rows[y + 1] : null;
|
|
902
|
-
let line = " ".repeat(quiet);
|
|
903
|
-
for (let x = 0; x < width; x++) {
|
|
904
|
-
const top = topRow[x];
|
|
905
|
-
const bot = botRow ? botRow[x] : false;
|
|
906
|
-
if (top && bot)
|
|
907
|
-
line += "\u2588";
|
|
908
|
-
else if (top && !bot)
|
|
909
|
-
line += "\u2580";
|
|
910
|
-
else if (!top && bot)
|
|
911
|
-
line += "\u2584";
|
|
912
|
-
else
|
|
913
|
-
line += " ";
|
|
914
|
-
}
|
|
915
|
-
line += " ".repeat(quiet);
|
|
916
|
-
lines.push(line);
|
|
917
|
-
y += 2;
|
|
918
|
-
}
|
|
919
|
-
for (let i = 0; i < quiet; i++)
|
|
920
|
-
lines.push(blankLine);
|
|
921
|
-
return lines.join("\n");
|
|
922
|
-
}
|
|
923
|
-
var init_qr = __esm({
|
|
924
|
-
"../core/dist/identity/qr.js"() {
|
|
925
|
-
"use strict";
|
|
926
|
-
}
|
|
927
|
-
});
|
|
928
|
-
|
|
929
929
|
// ../core/dist/bind/orchestrator.js
|
|
930
930
|
async function bindAgent(opts) {
|
|
931
931
|
const log = opts.log ?? ((m) => console.log(m));
|
|
@@ -1112,6 +1112,135 @@ var init_orchestrator = __esm({
|
|
|
1112
1112
|
}
|
|
1113
1113
|
});
|
|
1114
1114
|
|
|
1115
|
+
// ../core/dist/runtime/npm-prefix-self-heal.js
|
|
1116
|
+
function looksLikeEacces(stderr) {
|
|
1117
|
+
const s = stderr.toLowerCase();
|
|
1118
|
+
return s.includes("eacces") || s.includes("eperm") || s.includes("permission denied");
|
|
1119
|
+
}
|
|
1120
|
+
function looksLikeEnospc(stderr) {
|
|
1121
|
+
const s = stderr.toLowerCase();
|
|
1122
|
+
return s.includes("enospc") || s.includes("no space left");
|
|
1123
|
+
}
|
|
1124
|
+
async function trySwitchToUserPrefix(progress) {
|
|
1125
|
+
const p = getPlatformAdapter();
|
|
1126
|
+
const optOut = (p.env("BEEOS_NO_NPM_PREFIX_FIX") ?? "").trim();
|
|
1127
|
+
if (optOut === "1" || optOut.toLowerCase() === "true") {
|
|
1128
|
+
return {
|
|
1129
|
+
success: false,
|
|
1130
|
+
prefix: "",
|
|
1131
|
+
persisted: false,
|
|
1132
|
+
note: "BEEOS_NO_NPM_PREFIX_FIX=1 \u2014 refusing to auto-switch npm prefix."
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
const home = p.homeDir();
|
|
1136
|
+
const newPrefix = p.joinPath(home, ".npm-global");
|
|
1137
|
+
const current = await readCurrentPrefix();
|
|
1138
|
+
if (current && current.startsWith(home)) {
|
|
1139
|
+
return {
|
|
1140
|
+
success: true,
|
|
1141
|
+
prefix: current,
|
|
1142
|
+
persisted: false,
|
|
1143
|
+
note: `npm prefix already user-owned: ${current}`
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
progress.onStatus(`Switching npm global prefix to ${newPrefix} (user-owned).`);
|
|
1147
|
+
await p.mkdir(newPrefix);
|
|
1148
|
+
const setRes = await p.exec("npm", ["config", "set", "prefix", newPrefix]);
|
|
1149
|
+
if (setRes.code !== 0) {
|
|
1150
|
+
return {
|
|
1151
|
+
success: false,
|
|
1152
|
+
prefix: newPrefix,
|
|
1153
|
+
persisted: false,
|
|
1154
|
+
note: `npm config set prefix failed: ${(setRes.stderr || setRes.stdout).trim()}`
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
const binDir = p.platform() === "win32" ? newPrefix : p.joinPath(newPrefix, "bin");
|
|
1158
|
+
const pathKey = p.platform() === "win32" ? "Path" : "PATH";
|
|
1159
|
+
const sep = p.platform() === "win32" ? ";" : ":";
|
|
1160
|
+
const existing = process.env[pathKey] ?? process.env.PATH ?? "";
|
|
1161
|
+
if (!existing.split(sep).includes(binDir)) {
|
|
1162
|
+
process.env[pathKey] = `${binDir}${sep}${existing}`;
|
|
1163
|
+
if (pathKey !== "PATH")
|
|
1164
|
+
process.env.PATH = process.env[pathKey];
|
|
1165
|
+
}
|
|
1166
|
+
let persisted = false;
|
|
1167
|
+
if (p.platform() !== "win32") {
|
|
1168
|
+
persisted = await persistShellRcBestEffort(binDir);
|
|
1169
|
+
}
|
|
1170
|
+
return {
|
|
1171
|
+
success: true,
|
|
1172
|
+
prefix: newPrefix,
|
|
1173
|
+
persisted,
|
|
1174
|
+
note: persisted ? `Persisted PATH to your shell rc.` : `PATH updated for this session; add '${binDir}' to your shell init to make it permanent.`
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
async function readCurrentPrefix() {
|
|
1178
|
+
const p = getPlatformAdapter();
|
|
1179
|
+
try {
|
|
1180
|
+
const res = await p.exec("npm", ["prefix", "-g"], { timeout: 1e4 });
|
|
1181
|
+
if (res.code !== 0)
|
|
1182
|
+
return null;
|
|
1183
|
+
const trimmed = res.stdout.trim();
|
|
1184
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1185
|
+
} catch {
|
|
1186
|
+
return null;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
async function persistShellRcBestEffort(binDir) {
|
|
1190
|
+
const p = getPlatformAdapter();
|
|
1191
|
+
const rc = pickRcPath();
|
|
1192
|
+
if (!rc)
|
|
1193
|
+
return false;
|
|
1194
|
+
let existing = "";
|
|
1195
|
+
try {
|
|
1196
|
+
if (await p.exists(rc))
|
|
1197
|
+
existing = await p.readFile(rc);
|
|
1198
|
+
} catch {
|
|
1199
|
+
return false;
|
|
1200
|
+
}
|
|
1201
|
+
if (existing.includes(SENTINEL_BEGIN)) {
|
|
1202
|
+
return true;
|
|
1203
|
+
}
|
|
1204
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1205
|
+
const block = `
|
|
1206
|
+
${SENTINEL_BEGIN}
|
|
1207
|
+
# Added by @beeos/core npm-prefix-self-heal on ${stamp}
|
|
1208
|
+
# Reason: lazy install hit EACCES, switched npm prefix to a user-owned dir.
|
|
1209
|
+
# To revert: delete this block AND run \`npm config delete prefix\`.
|
|
1210
|
+
export PATH="${binDir}:$PATH"
|
|
1211
|
+
${SENTINEL_END}
|
|
1212
|
+
`;
|
|
1213
|
+
try {
|
|
1214
|
+
await p.writeFile(rc, existing + block);
|
|
1215
|
+
return true;
|
|
1216
|
+
} catch {
|
|
1217
|
+
return false;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
function pickRcPath() {
|
|
1221
|
+
const p = getPlatformAdapter();
|
|
1222
|
+
const home = p.homeDir();
|
|
1223
|
+
const shell = p.env("SHELL") ?? "";
|
|
1224
|
+
const base = shell.split("/").pop() ?? "";
|
|
1225
|
+
switch (base) {
|
|
1226
|
+
case "zsh":
|
|
1227
|
+
return p.joinPath(home, ".zshrc");
|
|
1228
|
+
case "bash":
|
|
1229
|
+
return p.platform() === "darwin" ? p.joinPath(home, ".bash_profile") : p.joinPath(home, ".bashrc");
|
|
1230
|
+
default:
|
|
1231
|
+
return null;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
var SENTINEL_BEGIN, SENTINEL_END;
|
|
1235
|
+
var init_npm_prefix_self_heal = __esm({
|
|
1236
|
+
"../core/dist/runtime/npm-prefix-self-heal.js"() {
|
|
1237
|
+
"use strict";
|
|
1238
|
+
init_platform_adapter();
|
|
1239
|
+
SENTINEL_BEGIN = "# >>> beeos npm-prefix >>>";
|
|
1240
|
+
SENTINEL_END = "# <<< beeos npm-prefix <<<";
|
|
1241
|
+
}
|
|
1242
|
+
});
|
|
1243
|
+
|
|
1115
1244
|
// ../core/dist/upgrade.js
|
|
1116
1245
|
function readPinSourcesFromEnv() {
|
|
1117
1246
|
const env = globalThis.process?.env ?? {};
|
|
@@ -1183,13 +1312,23 @@ async function upgradeBeeosSuite(opts) {
|
|
|
1183
1312
|
if (needsInstall) {
|
|
1184
1313
|
const args = ["install", "-g", ...specs.map((s) => s.spec)];
|
|
1185
1314
|
progress?.onStatus(`npm ${args.join(" ")}`);
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1315
|
+
installFailure = await runNpmInstallOnce(args);
|
|
1316
|
+
if (installFailure && looksLikeEacces(installFailure)) {
|
|
1317
|
+
const reporter = progress ?? {
|
|
1318
|
+
onStatus() {
|
|
1319
|
+
},
|
|
1320
|
+
onComplete() {
|
|
1321
|
+
}
|
|
1322
|
+
};
|
|
1323
|
+
reporter.onStatus("npm install -g failed with EACCES \u2014 attempting prefix self-heal.");
|
|
1324
|
+
const heal = await trySwitchToUserPrefix(reporter);
|
|
1325
|
+
reporter.onStatus(heal.note);
|
|
1326
|
+
if (heal.success) {
|
|
1327
|
+
reporter.onStatus(`Retrying: npm ${args.join(" ")}`);
|
|
1328
|
+
installFailure = await runNpmInstallOnce(args);
|
|
1190
1329
|
}
|
|
1191
|
-
}
|
|
1192
|
-
installFailure =
|
|
1330
|
+
} else if (installFailure && looksLikeEnospc(installFailure)) {
|
|
1331
|
+
installFailure = "npm install -g failed because the filesystem is out of space (typically /usr/local/lib/node_modules or %APPDATA%\\npm). Free disk space and retry. Original error:\n" + installFailure;
|
|
1193
1332
|
}
|
|
1194
1333
|
} else {
|
|
1195
1334
|
progress?.onStatus("Already up to date \u2014 skipping npm install -g");
|
|
@@ -1220,6 +1359,58 @@ async function upgradeBeeosSuite(opts) {
|
|
|
1220
1359
|
}
|
|
1221
1360
|
return result;
|
|
1222
1361
|
}
|
|
1362
|
+
async function runNpmInstallOnce(args) {
|
|
1363
|
+
const p = getPlatformAdapter();
|
|
1364
|
+
const useInherit = shouldInheritStdio();
|
|
1365
|
+
try {
|
|
1366
|
+
const result = await p.exec("npm", args, {
|
|
1367
|
+
timeout: 6e5,
|
|
1368
|
+
stdio: useInherit ? "inherit" : "pipe"
|
|
1369
|
+
});
|
|
1370
|
+
if (result.code !== 0) {
|
|
1371
|
+
if (useInherit) {
|
|
1372
|
+
const probe = await probeEaccesViaSet().catch(() => null);
|
|
1373
|
+
if (probe)
|
|
1374
|
+
return `npm install -g exited ${result.code}; ${probe}`;
|
|
1375
|
+
return `npm install -g exited ${result.code}`;
|
|
1376
|
+
}
|
|
1377
|
+
return (result.stderr || result.stdout || `npm install -g exited ${result.code}`).trim();
|
|
1378
|
+
}
|
|
1379
|
+
return void 0;
|
|
1380
|
+
} catch (e) {
|
|
1381
|
+
return e instanceof Error ? e.message : String(e);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
function shouldInheritStdio() {
|
|
1385
|
+
const proc = globalThis.process;
|
|
1386
|
+
if (!proc)
|
|
1387
|
+
return false;
|
|
1388
|
+
const isTty = Boolean(proc.stdout?.isTTY);
|
|
1389
|
+
if (!isTty)
|
|
1390
|
+
return false;
|
|
1391
|
+
const env = proc.env ?? {};
|
|
1392
|
+
if (env.BEEOS_NPM_QUIET === "1")
|
|
1393
|
+
return false;
|
|
1394
|
+
const argv = proc.argv ?? [];
|
|
1395
|
+
if (argv.some((a) => a === "--json"))
|
|
1396
|
+
return false;
|
|
1397
|
+
return true;
|
|
1398
|
+
}
|
|
1399
|
+
async function probeEaccesViaSet() {
|
|
1400
|
+
const p = getPlatformAdapter();
|
|
1401
|
+
const res = await p.exec("npm", ["config", "get", "prefix"], {
|
|
1402
|
+
timeout: 1e4
|
|
1403
|
+
});
|
|
1404
|
+
if (res.code !== 0)
|
|
1405
|
+
return null;
|
|
1406
|
+
const prefix = res.stdout.trim();
|
|
1407
|
+
if (!prefix)
|
|
1408
|
+
return null;
|
|
1409
|
+
const home = p.homeDir();
|
|
1410
|
+
if (prefix.startsWith(home))
|
|
1411
|
+
return null;
|
|
1412
|
+
return `EACCES likely (prefix ${prefix} outside $HOME)`;
|
|
1413
|
+
}
|
|
1223
1414
|
function trimOrUndef(v) {
|
|
1224
1415
|
if (v === void 0)
|
|
1225
1416
|
return void 0;
|
|
@@ -1231,6 +1422,7 @@ var init_upgrade = __esm({
|
|
|
1231
1422
|
"../core/dist/upgrade.js"() {
|
|
1232
1423
|
"use strict";
|
|
1233
1424
|
init_platform_adapter();
|
|
1425
|
+
init_npm_prefix_self_heal();
|
|
1234
1426
|
NPM_PKGS = {
|
|
1235
1427
|
CLI: "@beeos-ai/cli",
|
|
1236
1428
|
DEVICE_AGENT: "@beeos-ai/device-agent",
|
|
@@ -1371,6 +1563,75 @@ var init_device_setup = __esm({
|
|
|
1371
1563
|
}
|
|
1372
1564
|
});
|
|
1373
1565
|
|
|
1566
|
+
// ../core/dist/runtime/http-fetch-retry.js
|
|
1567
|
+
async function fetchWithRetry(url, opts) {
|
|
1568
|
+
const p = getPlatformAdapter();
|
|
1569
|
+
const max = opts.maxAttempts ?? 3;
|
|
1570
|
+
let lastError = "";
|
|
1571
|
+
let lastStatus = null;
|
|
1572
|
+
for (let attempt = 1; attempt <= max; attempt++) {
|
|
1573
|
+
let resp;
|
|
1574
|
+
try {
|
|
1575
|
+
resp = await p.fetch(url);
|
|
1576
|
+
} catch (e) {
|
|
1577
|
+
lastError = e instanceof Error ? e.message : String(e);
|
|
1578
|
+
opts.progress.onStatus(`${opts.label} attempt ${attempt}/${max} failed: ${lastError}`);
|
|
1579
|
+
if (attempt < max)
|
|
1580
|
+
await sleepBackoff(attempt);
|
|
1581
|
+
continue;
|
|
1582
|
+
}
|
|
1583
|
+
if (!resp.ok) {
|
|
1584
|
+
lastStatus = resp.status;
|
|
1585
|
+
opts.progress.onStatus(`${opts.label} attempt ${attempt}/${max}: HTTP ${resp.status} ${resp.statusText}`);
|
|
1586
|
+
if (resp.status === 401 || resp.status === 403 || resp.status === 404) {
|
|
1587
|
+
return {
|
|
1588
|
+
ok: false,
|
|
1589
|
+
status: resp.status,
|
|
1590
|
+
lastError: `HTTP ${resp.status} ${resp.statusText}`
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
lastError = `HTTP ${resp.status} ${resp.statusText}`;
|
|
1594
|
+
if (attempt < max)
|
|
1595
|
+
await sleepBackoff(attempt);
|
|
1596
|
+
continue;
|
|
1597
|
+
}
|
|
1598
|
+
try {
|
|
1599
|
+
return { ok: true, bytes: new Uint8Array(await resp.arrayBuffer()) };
|
|
1600
|
+
} catch (e) {
|
|
1601
|
+
lastError = e instanceof Error ? e.message : String(e);
|
|
1602
|
+
opts.progress.onStatus(`${opts.label} stream interrupted on attempt ${attempt}/${max}: ${lastError}`);
|
|
1603
|
+
if (attempt < max)
|
|
1604
|
+
await sleepBackoff(attempt);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
opts.progress.onStatus(`${opts.label} gave up after ${max} attempts (last: ${lastError || "unknown"}).`);
|
|
1608
|
+
return { ok: false, status: lastStatus, lastError };
|
|
1609
|
+
}
|
|
1610
|
+
async function fetchWithRetryOrNull(url, opts) {
|
|
1611
|
+
const out = await fetchWithRetry(url, opts);
|
|
1612
|
+
return out.ok ? out.bytes : null;
|
|
1613
|
+
}
|
|
1614
|
+
async function fetchTextWithRetryOrNull(url, opts) {
|
|
1615
|
+
const bytes = await fetchWithRetryOrNull(url, opts);
|
|
1616
|
+
if (!bytes)
|
|
1617
|
+
return null;
|
|
1618
|
+
try {
|
|
1619
|
+
return new TextDecoder("utf-8").decode(bytes);
|
|
1620
|
+
} catch {
|
|
1621
|
+
return null;
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
function sleepBackoff(attempt) {
|
|
1625
|
+
const ms = 1e3 * Math.pow(2, attempt - 1);
|
|
1626
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
1627
|
+
}
|
|
1628
|
+
var init_http_fetch_retry = __esm({
|
|
1629
|
+
"../core/dist/runtime/http-fetch-retry.js"() {
|
|
1630
|
+
"use strict";
|
|
1631
|
+
init_platform_adapter();
|
|
1632
|
+
}
|
|
1633
|
+
});
|
|
1634
|
+
|
|
1374
1635
|
// ../core/dist/adb-setup.js
|
|
1375
1636
|
import { createHash } from "crypto";
|
|
1376
1637
|
function managedAdbPath() {
|
|
@@ -1408,11 +1669,14 @@ async function installAdb(progress) {
|
|
|
1408
1669
|
}
|
|
1409
1670
|
const url = `${PLATFORM_TOOLS_BASE}/${zipName}`;
|
|
1410
1671
|
progress.onStatus(`Downloading Android platform-tools (${zipName})...`);
|
|
1411
|
-
const
|
|
1412
|
-
|
|
1413
|
-
|
|
1672
|
+
const result = await fetchWithRetry(url, {
|
|
1673
|
+
label: "platform-tools",
|
|
1674
|
+
progress
|
|
1675
|
+
});
|
|
1676
|
+
if (!result.ok) {
|
|
1677
|
+
throw new Error(`platform-tools download failed: ${result.lastError}` + (result.status ? ` (HTTP ${result.status})` : ""));
|
|
1414
1678
|
}
|
|
1415
|
-
const data =
|
|
1679
|
+
const data = result.bytes;
|
|
1416
1680
|
verifyPlatformToolsChecksum(zipName, data, progress);
|
|
1417
1681
|
const binDir = p.joinPath(beeoHome(), "bin");
|
|
1418
1682
|
await p.mkdir(binDir);
|
|
@@ -1511,6 +1775,7 @@ var init_adb_setup = __esm({
|
|
|
1511
1775
|
init_platform_adapter();
|
|
1512
1776
|
init_paths();
|
|
1513
1777
|
init_errors();
|
|
1778
|
+
init_http_fetch_retry();
|
|
1514
1779
|
PLATFORM_TOOLS_BASE = "https://dl.google.com/android/repository";
|
|
1515
1780
|
PLATFORM_TOOLS_REVISION = "r37.0.0";
|
|
1516
1781
|
PLATFORM_TOOLS_ARCHIVE = {
|
|
@@ -1797,46 +2062,16 @@ function archiveName(cfg, target) {
|
|
|
1797
2062
|
const suffix = target.includes("windows") ? "zip" : "tar.gz";
|
|
1798
2063
|
return `${cfg.name}-${target}.${suffix}`;
|
|
1799
2064
|
}
|
|
1800
|
-
async function fetchArchiveBuffer(cfg, url, progress
|
|
1801
|
-
const
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
progress.onStatus(`${cfg.name} download attempt ${attempt}/${maxAttempts} failed: ${lastError}`);
|
|
1810
|
-
if (attempt < maxAttempts) {
|
|
1811
|
-
await new Promise((r) => setTimeout(r, 1e3 * 2 ** (attempt - 1)));
|
|
1812
|
-
}
|
|
1813
|
-
continue;
|
|
1814
|
-
}
|
|
1815
|
-
if (!resp.ok) {
|
|
1816
|
-
progress.onStatus(`${cfg.name} download: ${resp.status} ${resp.statusText} (${url})`);
|
|
1817
|
-
if (resp.status === 404) {
|
|
1818
|
-
progress.onStatus(`Hint: the current release may not include the requested 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.`));
|
|
1819
|
-
}
|
|
1820
|
-
if (resp.status === 404 || resp.status === 403 || resp.status === 401) {
|
|
1821
|
-
return null;
|
|
1822
|
-
}
|
|
1823
|
-
lastError = `${resp.status} ${resp.statusText}`;
|
|
1824
|
-
if (attempt < maxAttempts) {
|
|
1825
|
-
await new Promise((r) => setTimeout(r, 1e3 * 2 ** (attempt - 1)));
|
|
1826
|
-
}
|
|
1827
|
-
continue;
|
|
1828
|
-
}
|
|
1829
|
-
try {
|
|
1830
|
-
return new Uint8Array(await resp.arrayBuffer());
|
|
1831
|
-
} catch (e) {
|
|
1832
|
-
lastError = String(e);
|
|
1833
|
-
progress.onStatus(`${cfg.name} stream interrupted on attempt ${attempt}/${maxAttempts}: ${lastError}`);
|
|
1834
|
-
if (attempt < maxAttempts) {
|
|
1835
|
-
await new Promise((r) => setTimeout(r, 1e3 * 2 ** (attempt - 1)));
|
|
1836
|
-
}
|
|
1837
|
-
}
|
|
2065
|
+
async function fetchArchiveBuffer(cfg, url, progress) {
|
|
2066
|
+
const out = await fetchWithRetry(url, {
|
|
2067
|
+
label: cfg.name,
|
|
2068
|
+
progress
|
|
2069
|
+
});
|
|
2070
|
+
if (out.ok)
|
|
2071
|
+
return out.bytes;
|
|
2072
|
+
if (out.status === 404) {
|
|
2073
|
+
progress.onStatus(`Hint: the current release may not include the requested 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.`));
|
|
1838
2074
|
}
|
|
1839
|
-
progress.onStatus(`${cfg.name} download gave up after ${maxAttempts} attempts (last: ${lastError ?? "unknown"}).`);
|
|
1840
2075
|
return null;
|
|
1841
2076
|
}
|
|
1842
2077
|
async function downloadManagedBinary(cfg, target, progress) {
|
|
@@ -1851,20 +2086,19 @@ async function downloadManagedBinary(cfg, target, progress) {
|
|
|
1851
2086
|
const allowUnverified = (process.env?.BEEOS_ALLOW_UNVERIFIED_SIDECAR ?? "").toLowerCase() === "1" || (process.env?.BEEOS_ALLOW_UNVERIFIED_SIDECAR ?? "").toLowerCase() === "true";
|
|
1852
2087
|
let expected = null;
|
|
1853
2088
|
let digestFetchError = null;
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
digestFetchError = `
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
digestFetchError = e instanceof Error ? e.message : String(e);
|
|
2089
|
+
const digestText = await fetchTextWithRetryOrNull(digestUrl, {
|
|
2090
|
+
label: `${cfg.name}.sha256`,
|
|
2091
|
+
progress
|
|
2092
|
+
});
|
|
2093
|
+
if (digestText !== null) {
|
|
2094
|
+
const trimmed = digestText.trim();
|
|
2095
|
+
const first = trimmed.split(/\s+/)[0] ?? "";
|
|
2096
|
+
if (/^[a-f0-9]{64}$/i.test(first))
|
|
2097
|
+
expected = first.toLowerCase();
|
|
2098
|
+
else
|
|
2099
|
+
digestFetchError = `malformed .sha256 body: ${trimmed.slice(0, 80)}`;
|
|
2100
|
+
} else {
|
|
2101
|
+
digestFetchError = "fetch failed (see attempts above)";
|
|
1868
2102
|
}
|
|
1869
2103
|
if (expected) {
|
|
1870
2104
|
const actual = createHash2("sha256").update(data).digest("hex");
|
|
@@ -1955,6 +2189,7 @@ var init_cargo_dist = __esm({
|
|
|
1955
2189
|
init_platform_adapter();
|
|
1956
2190
|
init_paths();
|
|
1957
2191
|
init_errors();
|
|
2192
|
+
init_http_fetch_retry();
|
|
1958
2193
|
}
|
|
1959
2194
|
});
|
|
1960
2195
|
|
|
@@ -2301,8 +2536,8 @@ function normalizeOpenAiBaseUrl(input) {
|
|
|
2301
2536
|
} catch {
|
|
2302
2537
|
return input;
|
|
2303
2538
|
}
|
|
2304
|
-
const
|
|
2305
|
-
if (
|
|
2539
|
+
const path12 = url.pathname.replace(/\/+$/, "");
|
|
2540
|
+
if (path12 === "" || path12 === "/") {
|
|
2306
2541
|
url.pathname = "/v1";
|
|
2307
2542
|
return url.toString().replace(/\/+$/, "");
|
|
2308
2543
|
}
|
|
@@ -2637,7 +2872,7 @@ async function identifyGateway(opts) {
|
|
|
2637
2872
|
const url = `http://${host}:${port}/beeos/status`;
|
|
2638
2873
|
const controller = globalThis.AbortController ? new AbortController() : null;
|
|
2639
2874
|
const timer = controller ? setTimeout(() => controller.abort(), timeoutMs) : null;
|
|
2640
|
-
let resp
|
|
2875
|
+
let resp;
|
|
2641
2876
|
try {
|
|
2642
2877
|
resp = await p.fetch(url, {
|
|
2643
2878
|
method: "GET",
|
|
@@ -2799,12 +3034,12 @@ async function generateOpenclawConfig(ctx) {
|
|
|
2799
3034
|
seedResult = await p.exec(ctx.agentBinary, ["setup", "--non-interactive", "--mode", "local"], { env });
|
|
2800
3035
|
}
|
|
2801
3036
|
if (seedResult.code === 0) {
|
|
2802
|
-
for (const [
|
|
3037
|
+
for (const [path12, value] of [
|
|
2803
3038
|
["gateway.auth.token", ctx.gatewayToken],
|
|
2804
3039
|
["gateway.remote.token", ctx.gatewayToken],
|
|
2805
3040
|
["gateway.bind", "lan"]
|
|
2806
3041
|
]) {
|
|
2807
|
-
await runConfigSet(ctx.agentBinary, ctx.agentHome,
|
|
3042
|
+
await runConfigSet(ctx.agentBinary, ctx.agentHome, path12, value, false, false);
|
|
2808
3043
|
}
|
|
2809
3044
|
await runConfigSet(ctx.agentBinary, ctx.agentHome, "gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback", "true", true, false);
|
|
2810
3045
|
await configurePluginViaCli(ctx);
|
|
@@ -2869,9 +3104,9 @@ async function writeConfigFallback(ctx) {
|
|
|
2869
3104
|
await p.writeFile(configPath2, JSON.stringify(config, null, 2));
|
|
2870
3105
|
await writePairedJson(ctx.agentHome, ctx.keyFile);
|
|
2871
3106
|
}
|
|
2872
|
-
async function runConfigSet(bin, home,
|
|
3107
|
+
async function runConfigSet(bin, home, path12, value, json, isSystemHome) {
|
|
2873
3108
|
const p = getPlatformAdapter();
|
|
2874
|
-
const args = ["config", "set",
|
|
3109
|
+
const args = ["config", "set", path12, value];
|
|
2875
3110
|
if (json)
|
|
2876
3111
|
args.push("--json");
|
|
2877
3112
|
const env = {};
|
|
@@ -2879,7 +3114,7 @@ async function runConfigSet(bin, home, path10, value, json, isSystemHome) {
|
|
|
2879
3114
|
env.OPENCLAW_STATE_DIR = home;
|
|
2880
3115
|
const result = await p.exec(bin, args, { env });
|
|
2881
3116
|
if (result.code !== 0) {
|
|
2882
|
-
throw new Error(`\`openclaw config set ${
|
|
3117
|
+
throw new Error(`\`openclaw config set ${path12}\` exited with ${result.code}`);
|
|
2883
3118
|
}
|
|
2884
3119
|
}
|
|
2885
3120
|
async function writePairedJson(home, keyFile) {
|
|
@@ -3512,7 +3747,7 @@ async function detectColdStartState() {
|
|
|
3512
3747
|
const p = getPlatformAdapter();
|
|
3513
3748
|
const port5900Listening = await p.tcpProbe("127.0.0.1", 5900, PORT_5900_PROBE_TIMEOUT_MS).catch(() => false);
|
|
3514
3749
|
const passwordFile = p.joinPath(beeoHome(), "vnc.password");
|
|
3515
|
-
let vncPasswordFileExists
|
|
3750
|
+
let vncPasswordFileExists;
|
|
3516
3751
|
try {
|
|
3517
3752
|
const raw = await p.readFile(passwordFile);
|
|
3518
3753
|
vncPasswordFileExists = raw.trim().length > 0;
|
|
@@ -3648,8 +3883,8 @@ launchctl kickstart -k system/com.apple.screensharing
|
|
|
3648
3883
|
`;
|
|
3649
3884
|
const aslEscaped = shellPayload.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
3650
3885
|
const osascriptArg = `do shell script "${aslEscaped}" with administrator privileges`;
|
|
3651
|
-
let stderr
|
|
3652
|
-
let code
|
|
3886
|
+
let stderr;
|
|
3887
|
+
let code;
|
|
3653
3888
|
try {
|
|
3654
3889
|
const result = await p.exec("osascript", ["-e", osascriptArg], {
|
|
3655
3890
|
timeout: OSASCRIPT_TIMEOUT_MS
|
|
@@ -3782,7 +4017,7 @@ async function detectLinuxColdStartState() {
|
|
|
3782
4017
|
const p = getPlatformAdapter();
|
|
3783
4018
|
const port5901Listening = await p.tcpProbe("127.0.0.1", LINUX_VNC_PORT, PORT_PROBE_TIMEOUT_MS).catch(() => false);
|
|
3784
4019
|
const beeoFile = p.joinPath(beeoHome(), "vnc.password");
|
|
3785
|
-
let beeoPasswordFileExists
|
|
4020
|
+
let beeoPasswordFileExists;
|
|
3786
4021
|
try {
|
|
3787
4022
|
const raw = await p.readFile(beeoFile);
|
|
3788
4023
|
beeoPasswordFileExists = raw.trim().length > 0;
|
|
@@ -3790,7 +4025,7 @@ async function detectLinuxColdStartState() {
|
|
|
3790
4025
|
beeoPasswordFileExists = false;
|
|
3791
4026
|
}
|
|
3792
4027
|
const systemFile = p.joinPath(p.homeDir(), ".vnc", "passwd");
|
|
3793
|
-
let systemPasswordFileExists
|
|
4028
|
+
let systemPasswordFileExists;
|
|
3794
4029
|
try {
|
|
3795
4030
|
const raw = await p.readFile(systemFile);
|
|
3796
4031
|
systemPasswordFileExists = raw.length > 0;
|
|
@@ -4050,7 +4285,7 @@ async function detectWindowsColdStartState() {
|
|
|
4050
4285
|
const p = getPlatformAdapter();
|
|
4051
4286
|
const port5900Listening = await p.tcpProbe("127.0.0.1", WIN_VNC_PORT, PORT_PROBE_TIMEOUT_MS2).catch(() => false);
|
|
4052
4287
|
const beeoFile = p.joinPath(beeoHome(), "vnc.password");
|
|
4053
|
-
let beeoPasswordFileExists
|
|
4288
|
+
let beeoPasswordFileExists;
|
|
4054
4289
|
try {
|
|
4055
4290
|
const raw = await p.readFile(beeoFile);
|
|
4056
4291
|
beeoPasswordFileExists = raw.trim().length > 0;
|
|
@@ -5923,7 +6158,7 @@ ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
|
5923
6158
|
return false;
|
|
5924
6159
|
}
|
|
5925
6160
|
}
|
|
5926
|
-
let stale
|
|
6161
|
+
let stale;
|
|
5927
6162
|
try {
|
|
5928
6163
|
const stat = await fs5.stat(lockFile);
|
|
5929
6164
|
stale = Date.now() - stat.mtimeMs > STALE_LOCK_AGE_MS;
|
|
@@ -6167,6 +6402,7 @@ var init_dist = __esm({
|
|
|
6167
6402
|
init_gateway_token();
|
|
6168
6403
|
init_state();
|
|
6169
6404
|
init_keypair();
|
|
6405
|
+
init_qr();
|
|
6170
6406
|
init_client();
|
|
6171
6407
|
init_orchestrator();
|
|
6172
6408
|
init_process();
|
|
@@ -6185,6 +6421,10 @@ var init_dist = __esm({
|
|
|
6185
6421
|
init_driver2();
|
|
6186
6422
|
init_constants();
|
|
6187
6423
|
init_agent_status();
|
|
6424
|
+
init_config();
|
|
6425
|
+
init_download();
|
|
6426
|
+
init_plugin();
|
|
6427
|
+
init_token();
|
|
6188
6428
|
init_desktop_detect();
|
|
6189
6429
|
init_macos_desktop_cold_start();
|
|
6190
6430
|
init_desktop();
|
|
@@ -6197,6 +6437,8 @@ var init_dist = __esm({
|
|
|
6197
6437
|
init_tail_logs();
|
|
6198
6438
|
init_cli_version();
|
|
6199
6439
|
init_upgrade();
|
|
6440
|
+
init_npm_prefix_self_heal();
|
|
6441
|
+
init_http_fetch_retry();
|
|
6200
6442
|
}
|
|
6201
6443
|
});
|
|
6202
6444
|
|
|
@@ -6630,10 +6872,10 @@ function devicesPath() {
|
|
|
6630
6872
|
}
|
|
6631
6873
|
async function loadDeviceState() {
|
|
6632
6874
|
const p = getPlatformAdapter();
|
|
6633
|
-
const
|
|
6634
|
-
if (!await p.exists(
|
|
6875
|
+
const path12 = devicesPath();
|
|
6876
|
+
if (!await p.exists(path12)) return { devices: [] };
|
|
6635
6877
|
try {
|
|
6636
|
-
const raw = await p.readFile(
|
|
6878
|
+
const raw = await p.readFile(path12);
|
|
6637
6879
|
return JSON.parse(raw);
|
|
6638
6880
|
} catch {
|
|
6639
6881
|
return { devices: [] };
|
|
@@ -6645,12 +6887,12 @@ async function saveDeviceState(state) {
|
|
|
6645
6887
|
}
|
|
6646
6888
|
async function withDeviceLock(fn) {
|
|
6647
6889
|
const p = getPlatformAdapter();
|
|
6648
|
-
const
|
|
6649
|
-
await p.mkdir(p.dirname(
|
|
6650
|
-
if (!await p.exists(
|
|
6651
|
-
await p.writeFile(
|
|
6890
|
+
const path12 = devicesPath();
|
|
6891
|
+
await p.mkdir(p.dirname(path12));
|
|
6892
|
+
if (!await p.exists(path12)) {
|
|
6893
|
+
await p.writeFile(path12, JSON.stringify({ devices: [] }));
|
|
6652
6894
|
}
|
|
6653
|
-
const release = await lockfile.lock(
|
|
6895
|
+
const release = await lockfile.lock(path12, { retries: 3 });
|
|
6654
6896
|
try {
|
|
6655
6897
|
return await fn();
|
|
6656
6898
|
} finally {
|
|
@@ -7321,9 +7563,12 @@ var init_attach = __esm({
|
|
|
7321
7563
|
});
|
|
7322
7564
|
|
|
7323
7565
|
// src/commands/device/detach.ts
|
|
7566
|
+
import fs9 from "fs/promises";
|
|
7567
|
+
import path9 from "path";
|
|
7324
7568
|
async function detach(options) {
|
|
7325
7569
|
const cfg = await loadOrCreateConfig();
|
|
7326
7570
|
const mgr = await getServiceManager();
|
|
7571
|
+
const rotated = [];
|
|
7327
7572
|
await withDeviceLock(async () => {
|
|
7328
7573
|
const state = await loadDeviceState();
|
|
7329
7574
|
if (options.all) {
|
|
@@ -7331,11 +7576,19 @@ async function detach(options) {
|
|
|
7331
7576
|
await removeTargetsForSerial(mgr, entry2.serial);
|
|
7332
7577
|
legacyKillDeviceEntry(entry2);
|
|
7333
7578
|
await notifyOffline(cfg.platform.api_url, entry2.instance_id);
|
|
7579
|
+
if (options.rotateIdentity) {
|
|
7580
|
+
if (await tryRotateIdentity(entry2.serial)) rotated.push(entry2.serial);
|
|
7581
|
+
}
|
|
7334
7582
|
}
|
|
7335
7583
|
const count = state.devices.length;
|
|
7336
7584
|
state.devices = [];
|
|
7337
7585
|
await saveDeviceState(state);
|
|
7338
7586
|
console.log(`Detached ${count} device(s)`);
|
|
7587
|
+
if (options.rotateIdentity) {
|
|
7588
|
+
console.log(
|
|
7589
|
+
`Rotated identity for ${rotated.length} device(s); next attach will re-bind from scratch.`
|
|
7590
|
+
);
|
|
7591
|
+
}
|
|
7339
7592
|
return;
|
|
7340
7593
|
}
|
|
7341
7594
|
if (!options.serial) {
|
|
@@ -7348,10 +7601,35 @@ async function detach(options) {
|
|
|
7348
7601
|
legacyKillDeviceEntry(entry);
|
|
7349
7602
|
await notifyOffline(cfg.platform.api_url, entry.instance_id);
|
|
7350
7603
|
await saveDeviceState(state);
|
|
7604
|
+
if (options.rotateIdentity) {
|
|
7605
|
+
if (await tryRotateIdentity(entry.serial)) rotated.push(entry.serial);
|
|
7606
|
+
}
|
|
7351
7607
|
console.log(`Detached device ${options.serial}`);
|
|
7608
|
+
if (options.rotateIdentity) {
|
|
7609
|
+
console.log(
|
|
7610
|
+
rotated.length > 0 ? `Rotated local identity (~/.beeos/identity/device-${options.serial}.key.json removed). Next attach will re-bind from scratch.` : `No local identity file found for ${options.serial} \u2014 nothing to rotate.`
|
|
7611
|
+
);
|
|
7612
|
+
}
|
|
7352
7613
|
});
|
|
7353
7614
|
await notifyFleetReloadBestEffort();
|
|
7354
7615
|
}
|
|
7616
|
+
async function tryRotateIdentity(serial) {
|
|
7617
|
+
const keyPath = path9.join(
|
|
7618
|
+
beeoHome(),
|
|
7619
|
+
"identity",
|
|
7620
|
+
`device-${serial}.key.json`
|
|
7621
|
+
);
|
|
7622
|
+
try {
|
|
7623
|
+
await fs9.unlink(keyPath);
|
|
7624
|
+
return true;
|
|
7625
|
+
} catch (e) {
|
|
7626
|
+
if (e.code === "ENOENT") return false;
|
|
7627
|
+
console.error(
|
|
7628
|
+
` identity file ${keyPath} could not be removed: ${e.message}`
|
|
7629
|
+
);
|
|
7630
|
+
return false;
|
|
7631
|
+
}
|
|
7632
|
+
}
|
|
7355
7633
|
function legacyKillDeviceEntry(entry) {
|
|
7356
7634
|
if (entry.pid && isProcessAlive(entry.pid)) killProcess(entry.pid);
|
|
7357
7635
|
if (entry.bridge_pid && isProcessAlive(entry.bridge_pid)) killProcess(entry.bridge_pid);
|
|
@@ -7714,8 +7992,36 @@ var NodePlatformAdapter = class {
|
|
|
7714
7992
|
}
|
|
7715
7993
|
// ── Process execution ───────────────────────────────────
|
|
7716
7994
|
exec(cmd, args, options) {
|
|
7995
|
+
const env = options?.env ? { ...process.env, ...options.env } : process.env;
|
|
7996
|
+
if (options?.stdio === "inherit") {
|
|
7997
|
+
return new Promise((resolve) => {
|
|
7998
|
+
const child = nodeSpawn(cmd, args, {
|
|
7999
|
+
cwd: options?.cwd,
|
|
8000
|
+
env,
|
|
8001
|
+
shell: process.platform === "win32",
|
|
8002
|
+
windowsHide: true,
|
|
8003
|
+
stdio: "inherit"
|
|
8004
|
+
});
|
|
8005
|
+
let timer = null;
|
|
8006
|
+
if (options?.timeout && options.timeout > 0) {
|
|
8007
|
+
timer = setTimeout(() => {
|
|
8008
|
+
try {
|
|
8009
|
+
child.kill("SIGTERM");
|
|
8010
|
+
} catch {
|
|
8011
|
+
}
|
|
8012
|
+
}, options.timeout);
|
|
8013
|
+
}
|
|
8014
|
+
child.on("error", () => {
|
|
8015
|
+
if (timer) clearTimeout(timer);
|
|
8016
|
+
resolve({ stdout: "", stderr: "", code: 1 });
|
|
8017
|
+
});
|
|
8018
|
+
child.on("exit", (code) => {
|
|
8019
|
+
if (timer) clearTimeout(timer);
|
|
8020
|
+
resolve({ stdout: "", stderr: "", code: code ?? 1 });
|
|
8021
|
+
});
|
|
8022
|
+
});
|
|
8023
|
+
}
|
|
7717
8024
|
return new Promise((resolve) => {
|
|
7718
|
-
const env = options?.env ? { ...process.env, ...options.env } : process.env;
|
|
7719
8025
|
execFile6(
|
|
7720
8026
|
cmd,
|
|
7721
8027
|
args,
|
|
@@ -7899,10 +8205,9 @@ async function run(agentFramework, options) {
|
|
|
7899
8205
|
const ttyHints = !options.json && process.stdin.isTTY === true;
|
|
7900
8206
|
const desktopCapable = hasDesktopCapability(driver);
|
|
7901
8207
|
const desktopPipeline = desktopCapable ? getDesktopPipeline() : null;
|
|
7902
|
-
let coldStart = null;
|
|
7903
8208
|
if (desktopPipeline) {
|
|
7904
8209
|
const interactive = !options.json && process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
7905
|
-
coldStart = await desktopPipeline.coldStart({
|
|
8210
|
+
const coldStart = await desktopPipeline.coldStart({
|
|
7906
8211
|
prompt: interactive ? promptYesNo : void 0,
|
|
7907
8212
|
log: options.json ? () => void 0 : (m) => console.log(m),
|
|
7908
8213
|
yes: options.force === true
|
|
@@ -8638,8 +8943,8 @@ async function pickInstalledPackages() {
|
|
|
8638
8943
|
// src/commands/doctor.ts
|
|
8639
8944
|
init_dist();
|
|
8640
8945
|
init_json_envelope();
|
|
8641
|
-
import
|
|
8642
|
-
import
|
|
8946
|
+
import fs10 from "fs/promises";
|
|
8947
|
+
import path10 from "path";
|
|
8643
8948
|
var LOG_SIZE_WARN_BYTES = 50 * 1024 * 1024;
|
|
8644
8949
|
async function run8(options) {
|
|
8645
8950
|
const state = await detectExistingInstall();
|
|
@@ -8656,8 +8961,13 @@ async function run8(options) {
|
|
|
8656
8961
|
const resolvedAgentGatewayUrl = resolveAgentGatewayUrl(cfg);
|
|
8657
8962
|
const agentGatewayHealth = await probeAgentGateway(resolvedAgentGatewayUrl);
|
|
8658
8963
|
const tools = await collectToolStatus();
|
|
8964
|
+
const adbDaemon = tools.adb.path ? await probeAdbDaemon(tools.adb.path) : { reachable: false, deviceCount: 0, detail: "adb not on PATH" };
|
|
8965
|
+
const identityFile = await probeIdentityKeypair();
|
|
8966
|
+
const ports = await probePorts();
|
|
8967
|
+
const nodeEngines = await probeNodeEngines();
|
|
8659
8968
|
const hasBoundDevices = state.devices.entries.length > 0;
|
|
8660
8969
|
const shimReport = await collectShimReport(hasBoundDevices);
|
|
8970
|
+
const npmPrefix = await readCurrentPrefix();
|
|
8661
8971
|
const warnings = [];
|
|
8662
8972
|
const hints = [];
|
|
8663
8973
|
if (!state.hasIdentity) {
|
|
@@ -8731,6 +9041,42 @@ async function run8(options) {
|
|
|
8731
9041
|
warnings.push(
|
|
8732
9042
|
"adb not found \u2014 `beeos device attach` will download Android platform-tools on first use"
|
|
8733
9043
|
);
|
|
9044
|
+
} else if (!adbDaemon.reachable) {
|
|
9045
|
+
warnings.push(`adb daemon unreachable: ${adbDaemon.detail}`);
|
|
9046
|
+
hints.push(
|
|
9047
|
+
"try `adb kill-server && adb start-server` to reset; on Linux check udev rules; on Windows reinstall Android USB drivers"
|
|
9048
|
+
);
|
|
9049
|
+
}
|
|
9050
|
+
if (state.hasIdentity && !identityFile.parseable) {
|
|
9051
|
+
warnings.push(
|
|
9052
|
+
`identity keypair file present but unreadable/malformed (${identityFile.detail}). Binding will fail; backup and regenerate via \`beeos init\` (option [3] reset all).`
|
|
9053
|
+
);
|
|
9054
|
+
}
|
|
9055
|
+
if (ports.port7900Occupied) {
|
|
9056
|
+
warnings.push(
|
|
9057
|
+
"port 7900 is in use \u2014 device-mcp-server may not start cleanly on the next attach."
|
|
9058
|
+
);
|
|
9059
|
+
hints.push(
|
|
9060
|
+
"find the holder: `lsof -i :7900` (POSIX) or `netstat -ano | findstr :7900` (Windows)"
|
|
9061
|
+
);
|
|
9062
|
+
}
|
|
9063
|
+
if (ports.port7950Occupied) {
|
|
9064
|
+
warnings.push(
|
|
9065
|
+
"port 7950 is in use \u2014 fleet IPC may not bind on the next attach."
|
|
9066
|
+
);
|
|
9067
|
+
hints.push(
|
|
9068
|
+
"find the holder: `lsof -i :7950` (POSIX) or `netstat -ano | findstr :7950` (Windows)"
|
|
9069
|
+
);
|
|
9070
|
+
}
|
|
9071
|
+
for (const e of nodeEngines.entries) {
|
|
9072
|
+
if (e.satisfies === false) {
|
|
9073
|
+
warnings.push(
|
|
9074
|
+
`${e.pkg}@${e.installed} requires Node ${e.required} but you are on ${e.current}.`
|
|
9075
|
+
);
|
|
9076
|
+
hints.push(
|
|
9077
|
+
`upgrade Node (e.g. 'nvm install --lts' / 'fnm install --lts') and re-run \`beeos device attach\``
|
|
9078
|
+
);
|
|
9079
|
+
}
|
|
8734
9080
|
}
|
|
8735
9081
|
for (const e of shimReport.entries) {
|
|
8736
9082
|
if (e.outcome === "outdated") {
|
|
@@ -8750,7 +9096,7 @@ async function run8(options) {
|
|
|
8750
9096
|
const bloatedLogs = await checkLogFileSizes();
|
|
8751
9097
|
for (const l of bloatedLogs) {
|
|
8752
9098
|
warnings.push(
|
|
8753
|
-
`Service log large: ${
|
|
9099
|
+
`Service log large: ${path10.basename(l.path)} = ${formatBytes(l.size)}`
|
|
8754
9100
|
);
|
|
8755
9101
|
hints.push(`truncate safely: : > ${l.path}`);
|
|
8756
9102
|
}
|
|
@@ -8771,7 +9117,12 @@ async function run8(options) {
|
|
|
8771
9117
|
},
|
|
8772
9118
|
agentGateway: agentGatewayHealth,
|
|
8773
9119
|
tools,
|
|
9120
|
+
adbDaemon,
|
|
9121
|
+
identityFile,
|
|
9122
|
+
ports,
|
|
9123
|
+
nodeEngines,
|
|
8774
9124
|
npmShims: shimReport,
|
|
9125
|
+
npmPrefix,
|
|
8775
9126
|
warnings,
|
|
8776
9127
|
hints
|
|
8777
9128
|
})
|
|
@@ -8798,8 +9149,20 @@ async function run8(options) {
|
|
|
8798
9149
|
console.log(` - ${s.id.padEnd(28)} ${status2}${pid}`);
|
|
8799
9150
|
}
|
|
8800
9151
|
console.log(` adb : ${tools.adb.path ?? "not found"}`);
|
|
9152
|
+
if (tools.adb.path) {
|
|
9153
|
+
console.log(
|
|
9154
|
+
` daemon : ${adbDaemon.reachable ? `reachable (${adbDaemon.deviceCount} device${adbDaemon.deviceCount === 1 ? "" : "s"})` : `unreachable \u2014 ${adbDaemon.detail}`}`
|
|
9155
|
+
);
|
|
9156
|
+
}
|
|
8801
9157
|
console.log(` scrcpy-bridge: ${tools.scrcpyBridge.path ?? "not installed (auto on attach)"}`);
|
|
8802
9158
|
console.log(` vnc-bridge : ${tools.vncBridge.path ?? "not installed (auto on --vnc-host)"}`);
|
|
9159
|
+
console.log(` npm prefix : ${npmPrefix ?? "(unknown \u2014 npm not on PATH?)"}`);
|
|
9160
|
+
console.log(
|
|
9161
|
+
` identity : ${identityFile.parseable ? "ok" : identityFile.exists ? `corrupt (${identityFile.detail})` : "not yet created"}`
|
|
9162
|
+
);
|
|
9163
|
+
console.log(
|
|
9164
|
+
` ports : 7900 ${ports.port7900Occupied ? "in use" : "free"}, 7950 ${ports.port7950Occupied ? "in use" : "free"}`
|
|
9165
|
+
);
|
|
8803
9166
|
console.log("");
|
|
8804
9167
|
console.log(" npm shims:");
|
|
8805
9168
|
for (const e of shimReport.entries) {
|
|
@@ -8882,19 +9245,19 @@ function shimMarker(outcome) {
|
|
|
8882
9245
|
}
|
|
8883
9246
|
}
|
|
8884
9247
|
async function checkLogFileSizes() {
|
|
8885
|
-
const dir =
|
|
9248
|
+
const dir = path10.join(beeoHome(), "logs", "services");
|
|
8886
9249
|
let entries;
|
|
8887
9250
|
try {
|
|
8888
|
-
entries = await
|
|
9251
|
+
entries = await fs10.readdir(dir);
|
|
8889
9252
|
} catch {
|
|
8890
9253
|
return [];
|
|
8891
9254
|
}
|
|
8892
9255
|
const bloated = [];
|
|
8893
9256
|
for (const name of entries) {
|
|
8894
9257
|
if (!name.endsWith(".log")) continue;
|
|
8895
|
-
const full =
|
|
9258
|
+
const full = path10.join(dir, name);
|
|
8896
9259
|
try {
|
|
8897
|
-
const st = await
|
|
9260
|
+
const st = await fs10.stat(full);
|
|
8898
9261
|
if (st.isFile() && st.size >= LOG_SIZE_WARN_BYTES) {
|
|
8899
9262
|
bloated.push({ path: full, size: st.size });
|
|
8900
9263
|
}
|
|
@@ -8961,6 +9324,173 @@ function formatAgentGatewayStatus(h) {
|
|
|
8961
9324
|
if (h.status === "unhealthy") return `HTTP ${h.httpStatus ?? "?"}`;
|
|
8962
9325
|
return h.detail;
|
|
8963
9326
|
}
|
|
9327
|
+
var ADB_PROBE_START_SERVER_TIMEOUT_MS = 3e3;
|
|
9328
|
+
var ADB_PROBE_DEVICES_TIMEOUT_MS = 1500;
|
|
9329
|
+
async function probeAdbDaemon(adbPath) {
|
|
9330
|
+
const p = getPlatformAdapter();
|
|
9331
|
+
try {
|
|
9332
|
+
const start = await p.exec(adbPath, ["start-server"], {
|
|
9333
|
+
timeout: ADB_PROBE_START_SERVER_TIMEOUT_MS
|
|
9334
|
+
});
|
|
9335
|
+
if (start.code !== 0) {
|
|
9336
|
+
return {
|
|
9337
|
+
reachable: false,
|
|
9338
|
+
deviceCount: 0,
|
|
9339
|
+
detail: `adb start-server exited ${start.code} (timeout ${ADB_PROBE_START_SERVER_TIMEOUT_MS}ms): ${(start.stderr || start.stdout).trim().slice(0, 120)}`
|
|
9340
|
+
};
|
|
9341
|
+
}
|
|
9342
|
+
} catch (e) {
|
|
9343
|
+
return {
|
|
9344
|
+
reachable: false,
|
|
9345
|
+
deviceCount: 0,
|
|
9346
|
+
detail: `adb start-server failed (timeout ${ADB_PROBE_START_SERVER_TIMEOUT_MS}ms): ${e instanceof Error ? e.message : String(e)}`
|
|
9347
|
+
};
|
|
9348
|
+
}
|
|
9349
|
+
try {
|
|
9350
|
+
const list2 = await p.exec(adbPath, ["devices"], {
|
|
9351
|
+
timeout: ADB_PROBE_DEVICES_TIMEOUT_MS
|
|
9352
|
+
});
|
|
9353
|
+
if (list2.code !== 0) {
|
|
9354
|
+
return {
|
|
9355
|
+
reachable: false,
|
|
9356
|
+
deviceCount: 0,
|
|
9357
|
+
detail: `adb devices exited ${list2.code} (timeout ${ADB_PROBE_DEVICES_TIMEOUT_MS}ms)`
|
|
9358
|
+
};
|
|
9359
|
+
}
|
|
9360
|
+
const lines = list2.stdout.split("\n").slice(1).map((l) => l.trim());
|
|
9361
|
+
const deviceCount = lines.filter((l) => l.endsWith(" device")).length;
|
|
9362
|
+
return {
|
|
9363
|
+
reachable: true,
|
|
9364
|
+
deviceCount,
|
|
9365
|
+
detail: `${deviceCount} device(s) authorized`
|
|
9366
|
+
};
|
|
9367
|
+
} catch (e) {
|
|
9368
|
+
return {
|
|
9369
|
+
reachable: false,
|
|
9370
|
+
deviceCount: 0,
|
|
9371
|
+
detail: `adb devices failed (timeout ${ADB_PROBE_DEVICES_TIMEOUT_MS}ms): ${e instanceof Error ? e.message : String(e)}`
|
|
9372
|
+
};
|
|
9373
|
+
}
|
|
9374
|
+
}
|
|
9375
|
+
async function probeIdentityKeypair() {
|
|
9376
|
+
const p = getPlatformAdapter();
|
|
9377
|
+
const kpPath = p.joinPath(beeoHome(), "identity", "keypair.json");
|
|
9378
|
+
if (!await p.exists(kpPath)) {
|
|
9379
|
+
return {
|
|
9380
|
+
exists: false,
|
|
9381
|
+
readable: false,
|
|
9382
|
+
parseable: false,
|
|
9383
|
+
detail: "not yet generated"
|
|
9384
|
+
};
|
|
9385
|
+
}
|
|
9386
|
+
let raw;
|
|
9387
|
+
try {
|
|
9388
|
+
raw = await p.readFile(kpPath);
|
|
9389
|
+
} catch (e) {
|
|
9390
|
+
return {
|
|
9391
|
+
exists: true,
|
|
9392
|
+
readable: false,
|
|
9393
|
+
parseable: false,
|
|
9394
|
+
detail: `read failed: ${e instanceof Error ? e.message : String(e)}`
|
|
9395
|
+
};
|
|
9396
|
+
}
|
|
9397
|
+
try {
|
|
9398
|
+
const parsed = JSON.parse(raw);
|
|
9399
|
+
if (typeof parsed.publicKey !== "string" || typeof parsed.privateKey !== "string") {
|
|
9400
|
+
return {
|
|
9401
|
+
exists: true,
|
|
9402
|
+
readable: true,
|
|
9403
|
+
parseable: false,
|
|
9404
|
+
detail: "missing publicKey / privateKey fields"
|
|
9405
|
+
};
|
|
9406
|
+
}
|
|
9407
|
+
return {
|
|
9408
|
+
exists: true,
|
|
9409
|
+
readable: true,
|
|
9410
|
+
parseable: true,
|
|
9411
|
+
detail: "ok"
|
|
9412
|
+
};
|
|
9413
|
+
} catch (e) {
|
|
9414
|
+
return {
|
|
9415
|
+
exists: true,
|
|
9416
|
+
readable: true,
|
|
9417
|
+
parseable: false,
|
|
9418
|
+
detail: `JSON parse failed: ${e instanceof Error ? e.message : String(e)}`
|
|
9419
|
+
};
|
|
9420
|
+
}
|
|
9421
|
+
}
|
|
9422
|
+
async function probePorts() {
|
|
9423
|
+
const p = getPlatformAdapter();
|
|
9424
|
+
const [p7900, p7950] = await Promise.all([
|
|
9425
|
+
p.tcpProbe("127.0.0.1", 7900, 500),
|
|
9426
|
+
p.tcpProbe("127.0.0.1", 7950, 500)
|
|
9427
|
+
]);
|
|
9428
|
+
return { port7900Occupied: p7900, port7950Occupied: p7950 };
|
|
9429
|
+
}
|
|
9430
|
+
async function probeNodeEngines() {
|
|
9431
|
+
const targets = [NPM_PKGS.DEVICE_AGENT, NPM_PKGS.DEVICE_MCP_SERVER];
|
|
9432
|
+
const current = process.version.replace(/^v/, "");
|
|
9433
|
+
const entries = [];
|
|
9434
|
+
for (const pkg of targets) {
|
|
9435
|
+
const installed = await npmGlobalVersion(pkg).catch(() => null);
|
|
9436
|
+
if (!installed) {
|
|
9437
|
+
entries.push({
|
|
9438
|
+
pkg,
|
|
9439
|
+
installed: null,
|
|
9440
|
+
required: null,
|
|
9441
|
+
current,
|
|
9442
|
+
satisfies: null
|
|
9443
|
+
});
|
|
9444
|
+
continue;
|
|
9445
|
+
}
|
|
9446
|
+
const required = await readPkgEnginesNode(pkg);
|
|
9447
|
+
entries.push({
|
|
9448
|
+
pkg,
|
|
9449
|
+
installed,
|
|
9450
|
+
required,
|
|
9451
|
+
current,
|
|
9452
|
+
satisfies: required ? satisfiesMinNode(current, required) : null
|
|
9453
|
+
});
|
|
9454
|
+
}
|
|
9455
|
+
return { entries };
|
|
9456
|
+
}
|
|
9457
|
+
async function readPkgEnginesNode(pkg) {
|
|
9458
|
+
const p = getPlatformAdapter();
|
|
9459
|
+
const rootRes = await p.exec("npm", ["root", "-g"], { timeout: 5e3 });
|
|
9460
|
+
if (rootRes.code !== 0) return null;
|
|
9461
|
+
const root = rootRes.stdout.trim();
|
|
9462
|
+
if (!root) return null;
|
|
9463
|
+
const pkgJsonPath = path10.join(root, pkg, "package.json");
|
|
9464
|
+
try {
|
|
9465
|
+
const raw = await fs10.readFile(pkgJsonPath, "utf-8");
|
|
9466
|
+
const parsed = JSON.parse(raw);
|
|
9467
|
+
const node = parsed.engines?.node?.trim();
|
|
9468
|
+
return node && node.length > 0 ? node : null;
|
|
9469
|
+
} catch {
|
|
9470
|
+
return null;
|
|
9471
|
+
}
|
|
9472
|
+
}
|
|
9473
|
+
function satisfiesMinNode(current, spec) {
|
|
9474
|
+
const m = /^>=\s*(\d+)(?:\.(\d+))?(?:\.(\d+))?$/.exec(spec.trim());
|
|
9475
|
+
if (!m) return null;
|
|
9476
|
+
const want = [
|
|
9477
|
+
Number(m[1]),
|
|
9478
|
+
Number(m[2] ?? 0),
|
|
9479
|
+
Number(m[3] ?? 0)
|
|
9480
|
+
];
|
|
9481
|
+
const have = parseSemver(current);
|
|
9482
|
+
if (!have) return null;
|
|
9483
|
+
for (let i = 0; i < 3; i++) {
|
|
9484
|
+
if (have[i] > want[i]) return true;
|
|
9485
|
+
if (have[i] < want[i]) return false;
|
|
9486
|
+
}
|
|
9487
|
+
return true;
|
|
9488
|
+
}
|
|
9489
|
+
function parseSemver(v) {
|
|
9490
|
+
const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v);
|
|
9491
|
+
if (!m) return null;
|
|
9492
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
9493
|
+
}
|
|
8964
9494
|
|
|
8965
9495
|
// src/commands/service.ts
|
|
8966
9496
|
init_dist();
|
|
@@ -9178,6 +9708,194 @@ async function run10(idArg, options) {
|
|
|
9178
9708
|
}
|
|
9179
9709
|
}
|
|
9180
9710
|
|
|
9711
|
+
// src/commands/report.ts
|
|
9712
|
+
init_dist();
|
|
9713
|
+
init_json_envelope();
|
|
9714
|
+
import fs11 from "fs/promises";
|
|
9715
|
+
import { createReadStream, createWriteStream } from "fs";
|
|
9716
|
+
import path11 from "path";
|
|
9717
|
+
import { spawn as spawn3 } from "child_process";
|
|
9718
|
+
import { create as tarCreate } from "tar";
|
|
9719
|
+
var LOG_TAIL_BYTES = 1024 * 1024;
|
|
9720
|
+
async function run11(opts = {}) {
|
|
9721
|
+
const home = beeoHome();
|
|
9722
|
+
const reportsDir = path11.join(home, "reports");
|
|
9723
|
+
await fs11.mkdir(reportsDir, { recursive: true });
|
|
9724
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
9725
|
+
const reportName = `beeos-report-${stamp}`;
|
|
9726
|
+
const stagingDir = path11.join(reportsDir, reportName);
|
|
9727
|
+
await fs11.mkdir(stagingDir, { recursive: true });
|
|
9728
|
+
try {
|
|
9729
|
+
if (!opts.skipDoctor) {
|
|
9730
|
+
const doctorJson = await runDoctorJson();
|
|
9731
|
+
await fs11.writeFile(
|
|
9732
|
+
path11.join(stagingDir, "doctor.json"),
|
|
9733
|
+
doctorJson,
|
|
9734
|
+
"utf-8"
|
|
9735
|
+
);
|
|
9736
|
+
}
|
|
9737
|
+
try {
|
|
9738
|
+
const cfgRaw = await fs11.readFile(configPath(), "utf-8");
|
|
9739
|
+
await fs11.writeFile(
|
|
9740
|
+
path11.join(stagingDir, "config.toml"),
|
|
9741
|
+
cfgRaw,
|
|
9742
|
+
"utf-8"
|
|
9743
|
+
);
|
|
9744
|
+
} catch {
|
|
9745
|
+
await fs11.writeFile(
|
|
9746
|
+
path11.join(stagingDir, "config.toml"),
|
|
9747
|
+
"# (no ~/.beeos/config.toml on this machine)\n",
|
|
9748
|
+
"utf-8"
|
|
9749
|
+
);
|
|
9750
|
+
}
|
|
9751
|
+
try {
|
|
9752
|
+
const state = await loadState();
|
|
9753
|
+
await fs11.writeFile(
|
|
9754
|
+
path11.join(stagingDir, "state.json"),
|
|
9755
|
+
JSON.stringify(redactSecrets(state), null, 2),
|
|
9756
|
+
"utf-8"
|
|
9757
|
+
);
|
|
9758
|
+
} catch {
|
|
9759
|
+
await fs11.writeFile(
|
|
9760
|
+
path11.join(stagingDir, "state.json"),
|
|
9761
|
+
"{}\n",
|
|
9762
|
+
"utf-8"
|
|
9763
|
+
);
|
|
9764
|
+
}
|
|
9765
|
+
const logsSrc = path11.join(home, "logs", "services");
|
|
9766
|
+
const logsDst = path11.join(stagingDir, "services-logs");
|
|
9767
|
+
await fs11.mkdir(logsDst, { recursive: true });
|
|
9768
|
+
let logEntries = [];
|
|
9769
|
+
try {
|
|
9770
|
+
logEntries = await fs11.readdir(logsSrc);
|
|
9771
|
+
} catch {
|
|
9772
|
+
}
|
|
9773
|
+
for (const name of logEntries) {
|
|
9774
|
+
if (!name.endsWith(".log")) continue;
|
|
9775
|
+
const src = path11.join(logsSrc, name);
|
|
9776
|
+
const dst = path11.join(logsDst, name);
|
|
9777
|
+
try {
|
|
9778
|
+
await tailCopy(src, dst, LOG_TAIL_BYTES);
|
|
9779
|
+
} catch {
|
|
9780
|
+
}
|
|
9781
|
+
}
|
|
9782
|
+
const tarballPath = path11.join(reportsDir, `${reportName}.tar.gz`);
|
|
9783
|
+
await tarCreate(
|
|
9784
|
+
{ gzip: true, file: tarballPath, cwd: reportsDir },
|
|
9785
|
+
[reportName]
|
|
9786
|
+
);
|
|
9787
|
+
if (opts.json) {
|
|
9788
|
+
emitJsonEnvelope(
|
|
9789
|
+
jsonOk({
|
|
9790
|
+
tarball: tarballPath,
|
|
9791
|
+
included: [
|
|
9792
|
+
"doctor.json",
|
|
9793
|
+
"config.toml",
|
|
9794
|
+
"state.json",
|
|
9795
|
+
"services-logs/"
|
|
9796
|
+
],
|
|
9797
|
+
excluded: [
|
|
9798
|
+
"identity/keypair.json",
|
|
9799
|
+
"identity/device-*.key.json"
|
|
9800
|
+
]
|
|
9801
|
+
})
|
|
9802
|
+
);
|
|
9803
|
+
} else {
|
|
9804
|
+
console.log("");
|
|
9805
|
+
console.log(` Report: ${tarballPath}`);
|
|
9806
|
+
console.log("");
|
|
9807
|
+
console.log(" Attach this file to your support ticket.");
|
|
9808
|
+
console.log(
|
|
9809
|
+
" It contains: doctor.json, config.toml, state.json, last 1MiB of every service log."
|
|
9810
|
+
);
|
|
9811
|
+
console.log(" It does NOT contain: identity/keypair.json (private key).");
|
|
9812
|
+
console.log("");
|
|
9813
|
+
}
|
|
9814
|
+
} finally {
|
|
9815
|
+
await fs11.rm(stagingDir, { recursive: true, force: true }).catch(() => {
|
|
9816
|
+
});
|
|
9817
|
+
}
|
|
9818
|
+
}
|
|
9819
|
+
async function runDoctorJson() {
|
|
9820
|
+
const cliEntry = process.argv[1];
|
|
9821
|
+
if (!cliEntry) {
|
|
9822
|
+
return JSON.stringify(
|
|
9823
|
+
{ ok: false, error: { code: "no_entry", message: "process.argv[1] missing" } },
|
|
9824
|
+
null,
|
|
9825
|
+
2
|
|
9826
|
+
);
|
|
9827
|
+
}
|
|
9828
|
+
return new Promise((resolve) => {
|
|
9829
|
+
let stdout = "";
|
|
9830
|
+
let stderr = "";
|
|
9831
|
+
const child = spawn3(process.execPath, [cliEntry, "doctor", "--json"], {
|
|
9832
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
9833
|
+
});
|
|
9834
|
+
child.stdout?.on("data", (c) => {
|
|
9835
|
+
stdout += c.toString();
|
|
9836
|
+
});
|
|
9837
|
+
child.stderr?.on("data", (c) => {
|
|
9838
|
+
stderr += c.toString();
|
|
9839
|
+
});
|
|
9840
|
+
const timer = setTimeout(() => {
|
|
9841
|
+
try {
|
|
9842
|
+
child.kill("SIGTERM");
|
|
9843
|
+
} catch {
|
|
9844
|
+
}
|
|
9845
|
+
}, 3e4);
|
|
9846
|
+
child.on("close", (code) => {
|
|
9847
|
+
clearTimeout(timer);
|
|
9848
|
+
if (code === 0 && stdout.trim().length > 0) {
|
|
9849
|
+
resolve(stdout);
|
|
9850
|
+
} else {
|
|
9851
|
+
resolve(
|
|
9852
|
+
JSON.stringify(
|
|
9853
|
+
{
|
|
9854
|
+
ok: false,
|
|
9855
|
+
error: {
|
|
9856
|
+
code: "doctor_failed",
|
|
9857
|
+
exitCode: code,
|
|
9858
|
+
stderr: stderr.trim()
|
|
9859
|
+
}
|
|
9860
|
+
},
|
|
9861
|
+
null,
|
|
9862
|
+
2
|
|
9863
|
+
)
|
|
9864
|
+
);
|
|
9865
|
+
}
|
|
9866
|
+
});
|
|
9867
|
+
});
|
|
9868
|
+
}
|
|
9869
|
+
async function tailCopy(src, dst, tailBytes) {
|
|
9870
|
+
const stat = await fs11.stat(src);
|
|
9871
|
+
const start = stat.size > tailBytes ? stat.size - tailBytes : 0;
|
|
9872
|
+
await new Promise((resolve, reject) => {
|
|
9873
|
+
const rs = createReadStream(src, { start });
|
|
9874
|
+
const ws = createWriteStream(dst);
|
|
9875
|
+
rs.on("error", reject);
|
|
9876
|
+
ws.on("error", reject);
|
|
9877
|
+
ws.on("finish", () => resolve());
|
|
9878
|
+
rs.pipe(ws);
|
|
9879
|
+
});
|
|
9880
|
+
}
|
|
9881
|
+
function redactSecrets(state) {
|
|
9882
|
+
const SECRET_KEY_RE = /(token|secret|password|api_?key|bearer|jwt|cookie)/i;
|
|
9883
|
+
function walk(value) {
|
|
9884
|
+
if (value === null || typeof value !== "object") return value;
|
|
9885
|
+
if (Array.isArray(value)) return value.map(walk);
|
|
9886
|
+
const out = {};
|
|
9887
|
+
for (const [k, v] of Object.entries(value)) {
|
|
9888
|
+
if (typeof v === "string" && SECRET_KEY_RE.test(k)) {
|
|
9889
|
+
out[k] = `<redacted ${v.length}b>`;
|
|
9890
|
+
} else {
|
|
9891
|
+
out[k] = walk(v);
|
|
9892
|
+
}
|
|
9893
|
+
}
|
|
9894
|
+
return out;
|
|
9895
|
+
}
|
|
9896
|
+
return walk(state);
|
|
9897
|
+
}
|
|
9898
|
+
|
|
9181
9899
|
// src/index.ts
|
|
9182
9900
|
setPlatformAdapter(new NodePlatformAdapter());
|
|
9183
9901
|
var program = new Command();
|
|
@@ -9185,6 +9903,9 @@ var cliVersion = getCliVersion(import.meta.url, resolveActiveDistTag());
|
|
|
9185
9903
|
program.name("beeos").version(cliVersion.display).description("BeeOS \u2014 run AI agents from your desktop");
|
|
9186
9904
|
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));
|
|
9187
9905
|
program.command("doctor").description("Inspect local BeeOS state (install, binding, services, warnings)").option("--json", "Output machine-readable JSON", false).action((opts) => run8(opts));
|
|
9906
|
+
program.command("report").description(
|
|
9907
|
+
"Bundle a diagnostic tarball (~/.beeos/reports/beeos-report-<ts>.tar.gz) for support tickets. Includes doctor.json + redacted config + last 1MiB of every service log. NEVER includes identity/keypair.json."
|
|
9908
|
+
).option("--json", "Output machine-readable JSON envelope ({ok,data})", false).option("--skip-doctor", "Skip running `beeos doctor` as a subprocess", false).action((opts) => run11(opts));
|
|
9188
9909
|
program.command("migrate").description("Migrate legacy Node supervisor state to OS-native services (one-shot)").option("--json", "Output machine-readable JSON", false).action((opts) => run9(opts));
|
|
9189
9910
|
program.command("logs").description(
|
|
9190
9911
|
"Tail logs for a BeeOS service. Pass an ADB serial or service id; omit to list every registered service's log file path."
|
|
@@ -9212,7 +9933,11 @@ deviceCmd.command("attach").description("Attach an ADB-connected device as an AI
|
|
|
9212
9933
|
"--agent-gateway-url <url>",
|
|
9213
9934
|
"Override the Agent Gateway URL for THIS device only (advanced; multi-region staging). Persists to the device entry so the fleet supervisor reuses it on every restart. Falls back to the global config when omitted."
|
|
9214
9935
|
).action(attach);
|
|
9215
|
-
deviceCmd.command("detach").description("Detach a device agent").option("--serial <serial>", "ADB device serial number").option("--all", "Detach all devices", false).
|
|
9936
|
+
deviceCmd.command("detach").description("Detach a device agent").option("--serial <serial>", "ADB device serial number").option("--all", "Detach all devices", false).option(
|
|
9937
|
+
"--rotate-identity",
|
|
9938
|
+
"Also delete ~/.beeos/identity/device-<serial>.key.json so the next `attach` re-binds from scratch (use this to switch the device to a different BeeOS account / org).",
|
|
9939
|
+
false
|
|
9940
|
+
).action(detach);
|
|
9216
9941
|
deviceCmd.command("list").description("List attached devices").option("--local", "Only show locally running device agents", false).action(list);
|
|
9217
9942
|
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);
|
|
9218
9943
|
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);
|