@beeos-ai/cli 1.0.23 → 1.1.0
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 +116 -34
- package/dist/index.js +1129 -140
- package/package.json +1 -1
- package/scripts/install.Tests.ps1 +7 -1
- package/scripts/install.ps1 +46 -0
- package/scripts/install.sh +63 -0
package/dist/index.js
CHANGED
|
@@ -1245,6 +1245,18 @@ var init_upgrade = __esm({
|
|
|
1245
1245
|
});
|
|
1246
1246
|
|
|
1247
1247
|
// ../core/dist/device-setup.js
|
|
1248
|
+
function inferHostBackend() {
|
|
1249
|
+
switch (process.platform) {
|
|
1250
|
+
case "darwin":
|
|
1251
|
+
return "macos";
|
|
1252
|
+
case "linux":
|
|
1253
|
+
return "linux";
|
|
1254
|
+
case "win32":
|
|
1255
|
+
return "windows";
|
|
1256
|
+
default:
|
|
1257
|
+
return null;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1248
1260
|
function agentBinCommandAndArgs(bin) {
|
|
1249
1261
|
if (bin.type === "executable") {
|
|
1250
1262
|
return { cmd: bin.path, args: [] };
|
|
@@ -1746,6 +1758,8 @@ function cargoDistTarget() {
|
|
|
1746
1758
|
case "win32":
|
|
1747
1759
|
if (arch === "x64")
|
|
1748
1760
|
return "x86_64-pc-windows-msvc";
|
|
1761
|
+
if (arch === "arm64")
|
|
1762
|
+
return "aarch64-pc-windows-msvc";
|
|
1749
1763
|
return null;
|
|
1750
1764
|
default:
|
|
1751
1765
|
return null;
|
|
@@ -2335,6 +2349,9 @@ function hasStartupDiagnostics(driver) {
|
|
|
2335
2349
|
function hasPreflightConflictCheck(driver) {
|
|
2336
2350
|
return typeof driver.preflightConflictCheck === "function";
|
|
2337
2351
|
}
|
|
2352
|
+
function hasDesktopCapability(driver) {
|
|
2353
|
+
return typeof driver.desktopBridgeSpec === "function";
|
|
2354
|
+
}
|
|
2338
2355
|
var init_driver = __esm({
|
|
2339
2356
|
"../core/dist/agent/driver.js"() {
|
|
2340
2357
|
"use strict";
|
|
@@ -2721,6 +2738,35 @@ var init_wait_for_healthcheck = __esm({
|
|
|
2721
2738
|
}
|
|
2722
2739
|
});
|
|
2723
2740
|
|
|
2741
|
+
// ../core/dist/services/target-spec.js
|
|
2742
|
+
function buildVncBridgeTargetSpec(binary, opts) {
|
|
2743
|
+
return {
|
|
2744
|
+
id: `vnc-bridge-${opts.serial}`,
|
|
2745
|
+
kind: "vnc-bridge",
|
|
2746
|
+
command: binary,
|
|
2747
|
+
args: [],
|
|
2748
|
+
env: vncBridgeRuntime.buildEnv(opts),
|
|
2749
|
+
restart: "on-failure",
|
|
2750
|
+
label: `vnc-bridge (${opts.serial})`
|
|
2751
|
+
};
|
|
2752
|
+
}
|
|
2753
|
+
function buildOpenclawDesktopVncBridgeSpec(binary, opts) {
|
|
2754
|
+
return buildVncBridgeTargetSpec(binary, {
|
|
2755
|
+
...opts,
|
|
2756
|
+
serial: OPENCLAW_VNC_BRIDGE_SERIAL
|
|
2757
|
+
});
|
|
2758
|
+
}
|
|
2759
|
+
var OPENCLAW_VNC_BRIDGE_SERIAL;
|
|
2760
|
+
var init_target_spec = __esm({
|
|
2761
|
+
"../core/dist/services/target-spec.js"() {
|
|
2762
|
+
"use strict";
|
|
2763
|
+
init_scrcpy_bridge();
|
|
2764
|
+
init_vnc_bridge();
|
|
2765
|
+
init_spawn_env2();
|
|
2766
|
+
OPENCLAW_VNC_BRIDGE_SERIAL = "openclaw";
|
|
2767
|
+
}
|
|
2768
|
+
});
|
|
2769
|
+
|
|
2724
2770
|
// ../core/dist/openclaw/config.js
|
|
2725
2771
|
import { sha256 as sha2562 } from "@noble/hashes/sha256";
|
|
2726
2772
|
import { bytesToHex as bytesToHex2 } from "@noble/hashes/utils";
|
|
@@ -3153,6 +3199,8 @@ var init_driver2 = __esm({
|
|
|
3153
3199
|
init_platform_adapter();
|
|
3154
3200
|
init_paths();
|
|
3155
3201
|
init_detector();
|
|
3202
|
+
init_vnc_bridge();
|
|
3203
|
+
init_target_spec();
|
|
3156
3204
|
init_constants();
|
|
3157
3205
|
init_config();
|
|
3158
3206
|
init_detect_local();
|
|
@@ -3251,6 +3299,56 @@ var init_driver2 = __esm({
|
|
|
3251
3299
|
return { state: "unknown", reason: `unhandled probe state` };
|
|
3252
3300
|
}
|
|
3253
3301
|
}
|
|
3302
|
+
/**
|
|
3303
|
+
* Multi-OS refactor Phase 0: previously `start.ts` knew the
|
|
3304
|
+
* vnc-bridge wiring directly (binary path lookup, env-var shape,
|
|
3305
|
+
* `bridgePrivateKeyFile`/`bridgePublicKeyFile` reusing OpenClaw's
|
|
3306
|
+
* keypair). That coupling broke the "framework axis ⊥ desktop
|
|
3307
|
+
* axis" promise — adding e.g. Hermes-with-desktop required
|
|
3308
|
+
* copying the same boilerplate in `start.ts` for each new
|
|
3309
|
+
* framework. The driver now owns the bridge wiring; `start.ts`
|
|
3310
|
+
* just calls `desktopBridgeSpec()` and folds the outcome into
|
|
3311
|
+
* its existing `launchWarnings` stream.
|
|
3312
|
+
*
|
|
3313
|
+
* Soft-failure contract: any path that today produced a
|
|
3314
|
+
* `launchWarnings` entry in `tryAttachOpenclawDesktopBridge`
|
|
3315
|
+
* (binary unavailable, download failed, install threw) returns
|
|
3316
|
+
* `{ spec: null, warnings: [...] }` here instead of throwing.
|
|
3317
|
+
* The caller MUST NOT abort the bind on a `null` spec — desktop
|
|
3318
|
+
* streaming is bonus on top of an already-bound OpenClaw.
|
|
3319
|
+
*/
|
|
3320
|
+
async desktopBridgeSpec(endpoint, ctx, reporter) {
|
|
3321
|
+
let binary;
|
|
3322
|
+
try {
|
|
3323
|
+
binary = await vncBridgeRuntime.ensureInstalled(reporter);
|
|
3324
|
+
} catch (e) {
|
|
3325
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
3326
|
+
return {
|
|
3327
|
+
spec: null,
|
|
3328
|
+
warnings: [
|
|
3329
|
+
`vnc-bridge install failed: ${reason}; OpenClaw is bound but desktop streaming is unavailable.`
|
|
3330
|
+
]
|
|
3331
|
+
};
|
|
3332
|
+
}
|
|
3333
|
+
if (!binary) {
|
|
3334
|
+
return {
|
|
3335
|
+
spec: null,
|
|
3336
|
+
warnings: [
|
|
3337
|
+
`vnc-bridge binary unavailable (download or PATH lookup failed); OpenClaw is bound but desktop streaming is unavailable. Set BEEOS_VNC_BRIDGE_BIN to override.`
|
|
3338
|
+
]
|
|
3339
|
+
};
|
|
3340
|
+
}
|
|
3341
|
+
const spec = buildOpenclawDesktopVncBridgeSpec(binary, {
|
|
3342
|
+
deviceId: ctx.instanceId,
|
|
3343
|
+
vncHost: endpoint.host,
|
|
3344
|
+
vncPort: endpoint.port,
|
|
3345
|
+
...ctx.vncPassword ? { vncPassword: ctx.vncPassword } : {},
|
|
3346
|
+
agentGatewayUrl: ctx.agentGatewayUrl,
|
|
3347
|
+
bridgePrivateKeyFile: ctx.keyFile,
|
|
3348
|
+
bridgePublicKeyFile: ctx.keyFile
|
|
3349
|
+
});
|
|
3350
|
+
return { spec, warnings: [] };
|
|
3351
|
+
}
|
|
3254
3352
|
};
|
|
3255
3353
|
openClawDriver = new OpenclawDriver();
|
|
3256
3354
|
}
|
|
@@ -3273,10 +3371,6 @@ var init_agent_status = __esm({
|
|
|
3273
3371
|
});
|
|
3274
3372
|
|
|
3275
3373
|
// ../core/dist/openclaw/desktop-detect.js
|
|
3276
|
-
function printMacosDesktopHint() {
|
|
3277
|
-
for (const line of MACOS_DESKTOP_HINT_LINES)
|
|
3278
|
-
console.log(line);
|
|
3279
|
-
}
|
|
3280
3374
|
function readEnv(name) {
|
|
3281
3375
|
return globalThis.process?.env?.[name];
|
|
3282
3376
|
}
|
|
@@ -3314,20 +3408,24 @@ var init_desktop_detect = __esm({
|
|
|
3314
3408
|
init_platform_adapter();
|
|
3315
3409
|
MACOS_DESKTOP_HINT_LINES = [
|
|
3316
3410
|
"Detected macOS Screen Sharing on :5900. Starting vnc-bridge...",
|
|
3317
|
-
" If desktop view shows a black screen
|
|
3318
|
-
" \u2022 System Settings \u2192
|
|
3319
|
-
"
|
|
3320
|
-
"
|
|
3321
|
-
" \u2022
|
|
3322
|
-
"
|
|
3411
|
+
" If the dashboard's desktop view shows a black screen, check:",
|
|
3412
|
+
" \u2022 System Settings \u2192 Privacy & Security \u2192 Screen Recording must",
|
|
3413
|
+
" grant permission to the Screen Sharing helper (look for an",
|
|
3414
|
+
" ARDAgent or screensharingd entry; check the box if missing)",
|
|
3415
|
+
" \u2022 The VNC viewer password BeeOS just installed lives at",
|
|
3416
|
+
" ~/.beeos/vnc.password \u2014 re-run `beeos init` to regenerate",
|
|
3417
|
+
" if you suspect it drifted out of sync with macOS."
|
|
3323
3418
|
];
|
|
3324
3419
|
MACOS_DESKTOP_DOCTOR_HINT_LINES = [
|
|
3325
3420
|
"vnc-bridge-openclaw is failing on macOS. Likely causes:",
|
|
3326
|
-
" \u2022 System Settings \u2192 General \u2192 Sharing \u2192 Screen Sharing
|
|
3327
|
-
"
|
|
3328
|
-
"
|
|
3329
|
-
" \u2022
|
|
3330
|
-
"
|
|
3421
|
+
" \u2022 System Settings \u2192 General \u2192 Sharing \u2192 Screen Sharing got",
|
|
3422
|
+
" turned off after BeeOS enabled it (re-run `beeos init` to",
|
|
3423
|
+
" redo cold-start)",
|
|
3424
|
+
" \u2022 System Settings \u2192 Privacy & Security \u2192 Screen Recording",
|
|
3425
|
+
" permission for the Screen Sharing helper was revoked",
|
|
3426
|
+
" \u2022 The macOS-side VNC viewer password drifted out of sync with",
|
|
3427
|
+
" ~/.beeos/vnc.password \u2014 re-run `beeos init` to regenerate",
|
|
3428
|
+
" both ends in one shot."
|
|
3331
3429
|
];
|
|
3332
3430
|
LINUX_VNC_HINT_LINES = [
|
|
3333
3431
|
"Tip: no VNC server detected on :5901. To enable BeeOS desktop streaming:",
|
|
@@ -3340,6 +3438,928 @@ var init_desktop_detect = __esm({
|
|
|
3340
3438
|
}
|
|
3341
3439
|
});
|
|
3342
3440
|
|
|
3441
|
+
// ../core/dist/desktop/platforms/common.js
|
|
3442
|
+
import { createCipheriv } from "crypto";
|
|
3443
|
+
async function readVncPasswordFromBeeoHome() {
|
|
3444
|
+
const env = process.env.BEEOS_VNC_PASSWORD?.trim();
|
|
3445
|
+
if (env)
|
|
3446
|
+
return env;
|
|
3447
|
+
const p = getPlatformAdapter();
|
|
3448
|
+
const file = p.joinPath(beeoHome(), "vnc.password");
|
|
3449
|
+
try {
|
|
3450
|
+
const raw = (await p.readFile(file)).trim();
|
|
3451
|
+
return raw || void 0;
|
|
3452
|
+
} catch {
|
|
3453
|
+
return void 0;
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
function generateLegacyVncPassword() {
|
|
3457
|
+
const out = new Array(VNC_PASSWORD_LENGTH);
|
|
3458
|
+
const buf = new Uint8Array(VNC_PASSWORD_LENGTH);
|
|
3459
|
+
globalThis.crypto.getRandomValues(buf);
|
|
3460
|
+
for (let i = 0; i < VNC_PASSWORD_LENGTH; i++) {
|
|
3461
|
+
out[i] = VNC_PASSWORD_CHARSET[buf[i] % VNC_PASSWORD_CHARSET.length];
|
|
3462
|
+
}
|
|
3463
|
+
return out.join("");
|
|
3464
|
+
}
|
|
3465
|
+
function isShellSafeVncPassword(p) {
|
|
3466
|
+
return /^[a-zA-Z0-9]{8}$/.test(p);
|
|
3467
|
+
}
|
|
3468
|
+
function encodeVncPasswordBytes(plaintext) {
|
|
3469
|
+
const padded = Buffer.alloc(8);
|
|
3470
|
+
Buffer.from(plaintext, "utf8").copy(padded, 0, 0, Math.min(plaintext.length, 8));
|
|
3471
|
+
const key = Buffer.alloc(8);
|
|
3472
|
+
for (let i = 0; i < 8; i++) {
|
|
3473
|
+
key[i] = reverseBits(padded[i]);
|
|
3474
|
+
}
|
|
3475
|
+
const challenge = Buffer.from([
|
|
3476
|
+
23,
|
|
3477
|
+
82,
|
|
3478
|
+
107,
|
|
3479
|
+
6,
|
|
3480
|
+
35,
|
|
3481
|
+
78,
|
|
3482
|
+
88,
|
|
3483
|
+
7
|
|
3484
|
+
]);
|
|
3485
|
+
const tripleKey = Buffer.concat([key, key, key]);
|
|
3486
|
+
const cipher = createCipheriv("des-ede3-ecb", tripleKey, null);
|
|
3487
|
+
cipher.setAutoPadding(false);
|
|
3488
|
+
return Buffer.concat([cipher.update(challenge), cipher.final()]);
|
|
3489
|
+
}
|
|
3490
|
+
function reverseBits(b) {
|
|
3491
|
+
let r = 0;
|
|
3492
|
+
let v = b & 255;
|
|
3493
|
+
for (let i = 0; i < 8; i++) {
|
|
3494
|
+
r = r << 1 | v & 1;
|
|
3495
|
+
v >>= 1;
|
|
3496
|
+
}
|
|
3497
|
+
return r & 255;
|
|
3498
|
+
}
|
|
3499
|
+
var VNC_PASSWORD_LENGTH, VNC_PASSWORD_CHARSET;
|
|
3500
|
+
var init_common = __esm({
|
|
3501
|
+
"../core/dist/desktop/platforms/common.js"() {
|
|
3502
|
+
"use strict";
|
|
3503
|
+
init_platform_adapter();
|
|
3504
|
+
init_paths();
|
|
3505
|
+
VNC_PASSWORD_LENGTH = 8;
|
|
3506
|
+
VNC_PASSWORD_CHARSET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
3507
|
+
}
|
|
3508
|
+
});
|
|
3509
|
+
|
|
3510
|
+
// ../core/dist/openclaw/macos-desktop-cold-start.js
|
|
3511
|
+
async function detectColdStartState() {
|
|
3512
|
+
const p = getPlatformAdapter();
|
|
3513
|
+
const port5900Listening = await p.tcpProbe("127.0.0.1", 5900, PORT_5900_PROBE_TIMEOUT_MS).catch(() => false);
|
|
3514
|
+
const passwordFile = p.joinPath(beeoHome(), "vnc.password");
|
|
3515
|
+
let vncPasswordFileExists = false;
|
|
3516
|
+
try {
|
|
3517
|
+
const raw = await p.readFile(passwordFile);
|
|
3518
|
+
vncPasswordFileExists = raw.trim().length > 0;
|
|
3519
|
+
} catch {
|
|
3520
|
+
vncPasswordFileExists = false;
|
|
3521
|
+
}
|
|
3522
|
+
const ardAllLocalUsers = await probeArdAllLocalUsers();
|
|
3523
|
+
return {
|
|
3524
|
+
port5900Listening,
|
|
3525
|
+
vncPasswordFileExists,
|
|
3526
|
+
ardAllLocalUsers
|
|
3527
|
+
};
|
|
3528
|
+
}
|
|
3529
|
+
async function macosDesktopColdStart(opts = {}) {
|
|
3530
|
+
const log = opts.log ?? ((m) => console.log(m));
|
|
3531
|
+
const p = getPlatformAdapter();
|
|
3532
|
+
if (opts.noDesktop) {
|
|
3533
|
+
return { status: "skipped_opt_out" };
|
|
3534
|
+
}
|
|
3535
|
+
if (p.platform() !== "darwin") {
|
|
3536
|
+
return { status: "skipped_non_macos" };
|
|
3537
|
+
}
|
|
3538
|
+
const state = await detectColdStartState();
|
|
3539
|
+
const passwordFile = p.joinPath(beeoHome(), "vnc.password");
|
|
3540
|
+
const idempotent = state.port5900Listening && state.vncPasswordFileExists && state.ardAllLocalUsers === false;
|
|
3541
|
+
if (idempotent) {
|
|
3542
|
+
let existing;
|
|
3543
|
+
try {
|
|
3544
|
+
existing = (await p.readFile(passwordFile)).trim();
|
|
3545
|
+
} catch {
|
|
3546
|
+
existing = void 0;
|
|
3547
|
+
}
|
|
3548
|
+
return {
|
|
3549
|
+
status: "already_configured",
|
|
3550
|
+
vncPassword: existing
|
|
3551
|
+
};
|
|
3552
|
+
}
|
|
3553
|
+
if (!opts.prompt && !opts.yes) {
|
|
3554
|
+
return { status: "skipped_no_tty" };
|
|
3555
|
+
}
|
|
3556
|
+
if (!opts.yes) {
|
|
3557
|
+
log("");
|
|
3558
|
+
log("BeeOS desktop streaming needs macOS Screen Sharing enabled.");
|
|
3559
|
+
log("This will:");
|
|
3560
|
+
log(" - Turn on macOS Screen Sharing (RFB scheme 2 / legacy VNC)");
|
|
3561
|
+
log(" - Disable Apple ARD's all-local-users mode (avoids the");
|
|
3562
|
+
log(" noVNC scheme-30 trap that leaves the viewer black)");
|
|
3563
|
+
log(" - Generate a fresh 8-char VNC viewer password and store");
|
|
3564
|
+
log(" it both in /Library/Preferences/com.apple.VNCSettings.txt");
|
|
3565
|
+
log(" and in ~/.beeos/vnc.password");
|
|
3566
|
+
log("If you already use Screen Sharing for other tools the existing");
|
|
3567
|
+
log("VNC viewer password will be REPLACED. macOS will pop up a");
|
|
3568
|
+
log("password dialog (that's the OS asking for your login password,");
|
|
3569
|
+
log("not BeeOS \u2014 only this script runs as root).");
|
|
3570
|
+
log("");
|
|
3571
|
+
const answer = (await opts.prompt("Enable macOS Screen Sharing for desktop streaming? [Y/n]: ")).trim();
|
|
3572
|
+
if (answer !== "" && !/^y(es)?$/i.test(answer)) {
|
|
3573
|
+
return { status: "user_declined" };
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
const newPassword = generateLegacyVncPassword();
|
|
3577
|
+
try {
|
|
3578
|
+
await ensureBeeoHome(p);
|
|
3579
|
+
await p.writeFile(passwordFile, newPassword);
|
|
3580
|
+
await p.chmod(passwordFile, 384);
|
|
3581
|
+
} catch (e) {
|
|
3582
|
+
return {
|
|
3583
|
+
status: "script_failed",
|
|
3584
|
+
warnings: [
|
|
3585
|
+
`cold-start: failed to write ${passwordFile}: ${e instanceof Error ? e.message : String(e)}. Desktop streaming will not work this run.`
|
|
3586
|
+
]
|
|
3587
|
+
};
|
|
3588
|
+
}
|
|
3589
|
+
log("Requesting macOS administrator password (system dialog)...");
|
|
3590
|
+
const scriptResult = await runColdStartRootScript(newPassword);
|
|
3591
|
+
if (!scriptResult.ok) {
|
|
3592
|
+
const canceled = /user (canceled|cancelled)|cancellation|User did not enter/i.test(scriptResult.stderr);
|
|
3593
|
+
await p.rm(passwordFile).catch(() => void 0);
|
|
3594
|
+
return {
|
|
3595
|
+
status: canceled ? "user_canceled_dialog" : "script_failed",
|
|
3596
|
+
warnings: [
|
|
3597
|
+
canceled ? "macOS authorization dialog was canceled \u2014 desktop streaming not enabled." : `cold-start root script failed: ${scriptResult.stderr.trim()}. Desktop streaming will not work this run.`
|
|
3598
|
+
]
|
|
3599
|
+
};
|
|
3600
|
+
}
|
|
3601
|
+
const up = await waitFor5900(VERIFY_5900_TIMEOUT_MS);
|
|
3602
|
+
if (!up) {
|
|
3603
|
+
return {
|
|
3604
|
+
status: "verify_timeout",
|
|
3605
|
+
warnings: [
|
|
3606
|
+
"cold-start ran but :5900 never came up within 5s. Re-run `beeos init` to retrigger, or check `beeos doctor`."
|
|
3607
|
+
]
|
|
3608
|
+
};
|
|
3609
|
+
}
|
|
3610
|
+
log("macOS Screen Sharing enabled and listening on :5900.");
|
|
3611
|
+
return {
|
|
3612
|
+
status: "enabled_now",
|
|
3613
|
+
vncPassword: newPassword
|
|
3614
|
+
};
|
|
3615
|
+
}
|
|
3616
|
+
async function ensureBeeoHome(p) {
|
|
3617
|
+
const home = beeoHome();
|
|
3618
|
+
if (!await p.exists(home)) {
|
|
3619
|
+
await p.mkdir(home);
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
function encodeMacosVncPassword(password) {
|
|
3623
|
+
const pwBytes = Buffer.alloc(APPLE_VNC_FIXED_KEY.length);
|
|
3624
|
+
Buffer.from(password, "utf8").copy(pwBytes);
|
|
3625
|
+
const out = Buffer.alloc(APPLE_VNC_FIXED_KEY.length);
|
|
3626
|
+
for (let i = 0; i < APPLE_VNC_FIXED_KEY.length; i++) {
|
|
3627
|
+
out[i] = pwBytes[i] ^ APPLE_VNC_FIXED_KEY[i];
|
|
3628
|
+
}
|
|
3629
|
+
return out.toString("hex");
|
|
3630
|
+
}
|
|
3631
|
+
async function runColdStartRootScript(vncPassword) {
|
|
3632
|
+
const p = getPlatformAdapter();
|
|
3633
|
+
if (!/^[a-zA-Z0-9]{8}$/.test(vncPassword)) {
|
|
3634
|
+
return {
|
|
3635
|
+
ok: false,
|
|
3636
|
+
stderr: "internal: generated VNC password contains shell-unsafe characters; refusing to run osascript"
|
|
3637
|
+
};
|
|
3638
|
+
}
|
|
3639
|
+
const encodedHex = encodeMacosVncPassword(vncPassword);
|
|
3640
|
+
const shellPayload = `set -e
|
|
3641
|
+
launchctl enable system/com.apple.screensharing 2>/dev/null || true
|
|
3642
|
+
defaults write ${ARD_PLIST_PATH} VNCLegacyConnectionsEnabled -bool YES
|
|
3643
|
+
defaults write ${ARD_PLIST_PATH} ARD_AllLocalUsers -bool NO
|
|
3644
|
+
defaults write ${ARD_PLIST_PATH} ARD_AllLocalUsersPrivs -int 0
|
|
3645
|
+
printf '%s' '${encodedHex}' > ${VNC_SETTINGS_PATH}
|
|
3646
|
+
chmod 0400 ${VNC_SETTINGS_PATH}
|
|
3647
|
+
launchctl kickstart -k system/com.apple.screensharing
|
|
3648
|
+
`;
|
|
3649
|
+
const aslEscaped = shellPayload.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
3650
|
+
const osascriptArg = `do shell script "${aslEscaped}" with administrator privileges`;
|
|
3651
|
+
let stderr = "";
|
|
3652
|
+
let code = 1;
|
|
3653
|
+
try {
|
|
3654
|
+
const result = await p.exec("osascript", ["-e", osascriptArg], {
|
|
3655
|
+
timeout: OSASCRIPT_TIMEOUT_MS
|
|
3656
|
+
});
|
|
3657
|
+
stderr = result.stderr;
|
|
3658
|
+
code = result.code;
|
|
3659
|
+
} catch (e) {
|
|
3660
|
+
return {
|
|
3661
|
+
ok: false,
|
|
3662
|
+
stderr: `osascript invocation failed: ${e instanceof Error ? e.message : String(e)}`
|
|
3663
|
+
};
|
|
3664
|
+
}
|
|
3665
|
+
return { ok: code === 0, stderr };
|
|
3666
|
+
}
|
|
3667
|
+
async function waitFor5900(timeoutMs) {
|
|
3668
|
+
const p = getPlatformAdapter();
|
|
3669
|
+
const deadline = Date.now() + timeoutMs;
|
|
3670
|
+
while (Date.now() < deadline) {
|
|
3671
|
+
if (await p.tcpProbe("127.0.0.1", 5900, PORT_5900_PROBE_TIMEOUT_MS).catch(() => false)) {
|
|
3672
|
+
return true;
|
|
3673
|
+
}
|
|
3674
|
+
await sleep3(VERIFY_5900_POLL_INTERVAL_MS);
|
|
3675
|
+
}
|
|
3676
|
+
return false;
|
|
3677
|
+
}
|
|
3678
|
+
async function probeArdAllLocalUsers() {
|
|
3679
|
+
const p = getPlatformAdapter();
|
|
3680
|
+
try {
|
|
3681
|
+
const result = await p.exec("plutil", ["-extract", "ARD_AllLocalUsers", "raw", ARD_PLIST_PATH], { timeout: 2e3 });
|
|
3682
|
+
if (result.code !== 0)
|
|
3683
|
+
return "unknown";
|
|
3684
|
+
const out = result.stdout.trim().toLowerCase();
|
|
3685
|
+
if (out === "true" || out === "1")
|
|
3686
|
+
return true;
|
|
3687
|
+
if (out === "false" || out === "0")
|
|
3688
|
+
return false;
|
|
3689
|
+
return "unknown";
|
|
3690
|
+
} catch {
|
|
3691
|
+
return "unknown";
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
function sleep3(ms) {
|
|
3695
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3696
|
+
}
|
|
3697
|
+
var APPLE_VNC_FIXED_KEY, ARD_PLIST_PATH, VNC_SETTINGS_PATH, PORT_5900_PROBE_TIMEOUT_MS, VERIFY_5900_TIMEOUT_MS, VERIFY_5900_POLL_INTERVAL_MS, OSASCRIPT_TIMEOUT_MS;
|
|
3698
|
+
var init_macos_desktop_cold_start = __esm({
|
|
3699
|
+
"../core/dist/openclaw/macos-desktop-cold-start.js"() {
|
|
3700
|
+
"use strict";
|
|
3701
|
+
init_platform_adapter();
|
|
3702
|
+
init_paths();
|
|
3703
|
+
init_common();
|
|
3704
|
+
APPLE_VNC_FIXED_KEY = Buffer.from("1734516E8BA8C5E2FF1C39567390ADCA", "hex");
|
|
3705
|
+
ARD_PLIST_PATH = "/Library/Preferences/com.apple.RemoteManagement.plist";
|
|
3706
|
+
VNC_SETTINGS_PATH = "/Library/Preferences/com.apple.VNCSettings.txt";
|
|
3707
|
+
PORT_5900_PROBE_TIMEOUT_MS = 200;
|
|
3708
|
+
VERIFY_5900_TIMEOUT_MS = 5e3;
|
|
3709
|
+
VERIFY_5900_POLL_INTERVAL_MS = 250;
|
|
3710
|
+
OSASCRIPT_TIMEOUT_MS = 6e4;
|
|
3711
|
+
}
|
|
3712
|
+
});
|
|
3713
|
+
|
|
3714
|
+
// ../core/dist/desktop/pipeline.js
|
|
3715
|
+
var DesktopPipeline;
|
|
3716
|
+
var init_pipeline = __esm({
|
|
3717
|
+
"../core/dist/desktop/pipeline.js"() {
|
|
3718
|
+
"use strict";
|
|
3719
|
+
DesktopPipeline = class {
|
|
3720
|
+
platform;
|
|
3721
|
+
constructor(platform) {
|
|
3722
|
+
this.platform = platform;
|
|
3723
|
+
}
|
|
3724
|
+
/** Stable platform tag — proxied from the underlying adapter. */
|
|
3725
|
+
get id() {
|
|
3726
|
+
return this.platform.id;
|
|
3727
|
+
}
|
|
3728
|
+
coldStart(opts) {
|
|
3729
|
+
return this.platform.coldStart(opts);
|
|
3730
|
+
}
|
|
3731
|
+
probe(opts) {
|
|
3732
|
+
return this.platform.probe(opts);
|
|
3733
|
+
}
|
|
3734
|
+
readPasswordOptional() {
|
|
3735
|
+
return this.platform.readPasswordOptional();
|
|
3736
|
+
}
|
|
3737
|
+
postSetupHint(endpoint) {
|
|
3738
|
+
return this.platform.postSetupHint(endpoint);
|
|
3739
|
+
}
|
|
3740
|
+
};
|
|
3741
|
+
}
|
|
3742
|
+
});
|
|
3743
|
+
|
|
3744
|
+
// ../core/dist/desktop/platforms/macos.js
|
|
3745
|
+
var MacosDesktopPlatform, macosDesktopPlatform;
|
|
3746
|
+
var init_macos = __esm({
|
|
3747
|
+
"../core/dist/desktop/platforms/macos.js"() {
|
|
3748
|
+
"use strict";
|
|
3749
|
+
init_desktop_detect();
|
|
3750
|
+
init_macos_desktop_cold_start();
|
|
3751
|
+
init_common();
|
|
3752
|
+
MacosDesktopPlatform = class {
|
|
3753
|
+
id = "macos";
|
|
3754
|
+
coldStart(opts) {
|
|
3755
|
+
return macosDesktopColdStart(opts);
|
|
3756
|
+
}
|
|
3757
|
+
probe(opts) {
|
|
3758
|
+
return probeLocalVnc(opts);
|
|
3759
|
+
}
|
|
3760
|
+
readPasswordOptional() {
|
|
3761
|
+
return readVncPasswordFromBeeoHome();
|
|
3762
|
+
}
|
|
3763
|
+
postSetupHint(endpoint) {
|
|
3764
|
+
if (endpoint.kind !== "macos")
|
|
3765
|
+
return [];
|
|
3766
|
+
return MACOS_DESKTOP_HINT_LINES;
|
|
3767
|
+
}
|
|
3768
|
+
};
|
|
3769
|
+
macosDesktopPlatform = new MacosDesktopPlatform();
|
|
3770
|
+
}
|
|
3771
|
+
});
|
|
3772
|
+
|
|
3773
|
+
// ../core/dist/desktop/platforms/linux.js
|
|
3774
|
+
async function ensureBeeoHome2() {
|
|
3775
|
+
const p = getPlatformAdapter();
|
|
3776
|
+
const home = beeoHome();
|
|
3777
|
+
if (!await p.exists(home)) {
|
|
3778
|
+
await p.mkdir(home);
|
|
3779
|
+
}
|
|
3780
|
+
}
|
|
3781
|
+
async function detectLinuxColdStartState() {
|
|
3782
|
+
const p = getPlatformAdapter();
|
|
3783
|
+
const port5901Listening = await p.tcpProbe("127.0.0.1", LINUX_VNC_PORT, PORT_PROBE_TIMEOUT_MS).catch(() => false);
|
|
3784
|
+
const beeoFile = p.joinPath(beeoHome(), "vnc.password");
|
|
3785
|
+
let beeoPasswordFileExists = false;
|
|
3786
|
+
try {
|
|
3787
|
+
const raw = await p.readFile(beeoFile);
|
|
3788
|
+
beeoPasswordFileExists = raw.trim().length > 0;
|
|
3789
|
+
} catch {
|
|
3790
|
+
beeoPasswordFileExists = false;
|
|
3791
|
+
}
|
|
3792
|
+
const systemFile = p.joinPath(p.homeDir(), ".vnc", "passwd");
|
|
3793
|
+
let systemPasswordFileExists = false;
|
|
3794
|
+
try {
|
|
3795
|
+
const raw = await p.readFile(systemFile);
|
|
3796
|
+
systemPasswordFileExists = raw.length > 0;
|
|
3797
|
+
} catch {
|
|
3798
|
+
systemPasswordFileExists = false;
|
|
3799
|
+
}
|
|
3800
|
+
const passwdToolPath = await firstOnPath(VNCPASSWD_CANDIDATES);
|
|
3801
|
+
const vncServerPath = await firstOnPath(VNCSERVER_CANDIDATES);
|
|
3802
|
+
return {
|
|
3803
|
+
port5901Listening,
|
|
3804
|
+
beeoPasswordFileExists,
|
|
3805
|
+
systemPasswordFileExists,
|
|
3806
|
+
passwdToolPath,
|
|
3807
|
+
vncServerPath
|
|
3808
|
+
};
|
|
3809
|
+
}
|
|
3810
|
+
async function firstOnPath(candidates) {
|
|
3811
|
+
const p = getPlatformAdapter();
|
|
3812
|
+
for (const name of candidates) {
|
|
3813
|
+
try {
|
|
3814
|
+
const result = await p.exec("which", [name]);
|
|
3815
|
+
if (result.code === 0 && result.stdout.trim()) {
|
|
3816
|
+
return result.stdout.trim().split("\n")[0].trim();
|
|
3817
|
+
}
|
|
3818
|
+
} catch {
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3821
|
+
return null;
|
|
3822
|
+
}
|
|
3823
|
+
async function writeSystemVncPasswd(toolPath, password) {
|
|
3824
|
+
if (!isShellSafeVncPassword(password)) {
|
|
3825
|
+
return {
|
|
3826
|
+
ok: false,
|
|
3827
|
+
detail: "internal: refusing to pipe shell-unsafe password into tigervncpasswd"
|
|
3828
|
+
};
|
|
3829
|
+
}
|
|
3830
|
+
const p = getPlatformAdapter();
|
|
3831
|
+
const home = p.homeDir();
|
|
3832
|
+
const vncDir = p.joinPath(home, ".vnc");
|
|
3833
|
+
const target = p.joinPath(vncDir, "passwd");
|
|
3834
|
+
if (!await p.exists(vncDir)) {
|
|
3835
|
+
try {
|
|
3836
|
+
await p.mkdir(vncDir);
|
|
3837
|
+
} catch (e) {
|
|
3838
|
+
return { ok: false, detail: `mkdir ${vncDir}: ${describeError3(e)}` };
|
|
3839
|
+
}
|
|
3840
|
+
}
|
|
3841
|
+
const shellCmd = `printf '%s' '${password}' | '${toolPath}' -f > '${target}' && chmod 0600 '${target}'`;
|
|
3842
|
+
try {
|
|
3843
|
+
const result = await p.exec("sh", ["-c", shellCmd], {
|
|
3844
|
+
timeout: VNCPASSWD_TIMEOUT_MS
|
|
3845
|
+
});
|
|
3846
|
+
if (result.code !== 0) {
|
|
3847
|
+
return {
|
|
3848
|
+
ok: false,
|
|
3849
|
+
detail: result.stderr.trim() || `exit ${result.code}`
|
|
3850
|
+
};
|
|
3851
|
+
}
|
|
3852
|
+
return { ok: true };
|
|
3853
|
+
} catch (e) {
|
|
3854
|
+
return { ok: false, detail: describeError3(e) };
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
async function startVncServer(vncServerPath) {
|
|
3858
|
+
const p = getPlatformAdapter();
|
|
3859
|
+
try {
|
|
3860
|
+
const result = await p.exec(vncServerPath, [
|
|
3861
|
+
LINUX_VNC_DISPLAY,
|
|
3862
|
+
"-localhost",
|
|
3863
|
+
"yes",
|
|
3864
|
+
"-SecurityTypes",
|
|
3865
|
+
"VncAuth"
|
|
3866
|
+
], { timeout: VNCSERVER_TIMEOUT_MS });
|
|
3867
|
+
if (result.code !== 0) {
|
|
3868
|
+
return {
|
|
3869
|
+
ok: false,
|
|
3870
|
+
detail: (result.stderr || result.stdout).trim() || `exit ${result.code}`
|
|
3871
|
+
};
|
|
3872
|
+
}
|
|
3873
|
+
return { ok: true };
|
|
3874
|
+
} catch (e) {
|
|
3875
|
+
return { ok: false, detail: describeError3(e) };
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
async function waitForPort(port, timeoutMs) {
|
|
3879
|
+
const p = getPlatformAdapter();
|
|
3880
|
+
const deadline = Date.now() + timeoutMs;
|
|
3881
|
+
while (Date.now() < deadline) {
|
|
3882
|
+
if (await p.tcpProbe("127.0.0.1", port, PORT_PROBE_TIMEOUT_MS).catch(() => false)) {
|
|
3883
|
+
return true;
|
|
3884
|
+
}
|
|
3885
|
+
await sleep4(VERIFY_POLL_INTERVAL_MS);
|
|
3886
|
+
}
|
|
3887
|
+
return false;
|
|
3888
|
+
}
|
|
3889
|
+
function sleep4(ms) {
|
|
3890
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3891
|
+
}
|
|
3892
|
+
function describeError3(e) {
|
|
3893
|
+
if (e instanceof Error) {
|
|
3894
|
+
const msg = e.message.split(/\r?\n/, 1)[0]?.trim();
|
|
3895
|
+
return msg && msg.length > 0 ? msg : e.name;
|
|
3896
|
+
}
|
|
3897
|
+
return String(e);
|
|
3898
|
+
}
|
|
3899
|
+
var LINUX_VNC_PORT, LINUX_VNC_DISPLAY, PORT_PROBE_TIMEOUT_MS, VERIFY_TIMEOUT_MS, VERIFY_POLL_INTERVAL_MS, VNCSERVER_TIMEOUT_MS, VNCPASSWD_TIMEOUT_MS, VNCPASSWD_CANDIDATES, VNCSERVER_CANDIDATES, LinuxDesktopPlatform, linuxDesktopPlatform;
|
|
3900
|
+
var init_linux = __esm({
|
|
3901
|
+
"../core/dist/desktop/platforms/linux.js"() {
|
|
3902
|
+
"use strict";
|
|
3903
|
+
init_desktop_detect();
|
|
3904
|
+
init_platform_adapter();
|
|
3905
|
+
init_paths();
|
|
3906
|
+
init_common();
|
|
3907
|
+
LINUX_VNC_PORT = 5901;
|
|
3908
|
+
LINUX_VNC_DISPLAY = ":1";
|
|
3909
|
+
PORT_PROBE_TIMEOUT_MS = 200;
|
|
3910
|
+
VERIFY_TIMEOUT_MS = 8e3;
|
|
3911
|
+
VERIFY_POLL_INTERVAL_MS = 250;
|
|
3912
|
+
VNCSERVER_TIMEOUT_MS = 3e4;
|
|
3913
|
+
VNCPASSWD_TIMEOUT_MS = 5e3;
|
|
3914
|
+
VNCPASSWD_CANDIDATES = [
|
|
3915
|
+
"tigervncpasswd",
|
|
3916
|
+
"vncpasswd"
|
|
3917
|
+
];
|
|
3918
|
+
VNCSERVER_CANDIDATES = ["vncserver"];
|
|
3919
|
+
LinuxDesktopPlatform = class {
|
|
3920
|
+
id = "linux";
|
|
3921
|
+
async coldStart(opts) {
|
|
3922
|
+
if (opts.noDesktop)
|
|
3923
|
+
return { status: "skipped_opt_out" };
|
|
3924
|
+
const log = opts.log ?? ((m) => console.log(m));
|
|
3925
|
+
const p = getPlatformAdapter();
|
|
3926
|
+
const state = await detectLinuxColdStartState();
|
|
3927
|
+
if (state.passwdToolPath === null) {
|
|
3928
|
+
return { status: "skipped_no_vnc_server" };
|
|
3929
|
+
}
|
|
3930
|
+
const beeoPasswordFile = p.joinPath(beeoHome(), "vnc.password");
|
|
3931
|
+
if (state.port5901Listening && state.beeoPasswordFileExists && state.systemPasswordFileExists) {
|
|
3932
|
+
let existing;
|
|
3933
|
+
try {
|
|
3934
|
+
existing = (await p.readFile(beeoPasswordFile)).trim() || void 0;
|
|
3935
|
+
} catch {
|
|
3936
|
+
existing = void 0;
|
|
3937
|
+
}
|
|
3938
|
+
return { status: "already_configured", vncPassword: existing };
|
|
3939
|
+
}
|
|
3940
|
+
if (!opts.prompt && !opts.yes) {
|
|
3941
|
+
return { status: "skipped_no_tty" };
|
|
3942
|
+
}
|
|
3943
|
+
if (!opts.yes) {
|
|
3944
|
+
log("");
|
|
3945
|
+
log("BeeOS desktop streaming needs a local VNC server with a known password.");
|
|
3946
|
+
log("This will:");
|
|
3947
|
+
log(" - Generate an 8-char VNC viewer password");
|
|
3948
|
+
log(" - Write it to ~/.vnc/passwd (chmod 600) using `tigervncpasswd -f`");
|
|
3949
|
+
log(" - Mirror the same plaintext into ~/.beeos/vnc.password");
|
|
3950
|
+
log(` - Start \`vncserver ${LINUX_VNC_DISPLAY} -localhost yes\` if it isn't already running`);
|
|
3951
|
+
log("If you already use ~/.vnc/passwd for other tools the existing");
|
|
3952
|
+
log("password will be REPLACED. No sudo, no package install \u2014 only");
|
|
3953
|
+
log("files inside your home directory are modified.");
|
|
3954
|
+
log("");
|
|
3955
|
+
const answer = (await opts.prompt("Configure VNC password and start vncserver for desktop streaming? [Y/n]: ")).trim();
|
|
3956
|
+
if (answer !== "" && !/^y(es)?$/i.test(answer)) {
|
|
3957
|
+
return { status: "user_declined" };
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
const newPassword = generateLegacyVncPassword();
|
|
3961
|
+
if (!isShellSafeVncPassword(newPassword)) {
|
|
3962
|
+
return {
|
|
3963
|
+
status: "script_failed",
|
|
3964
|
+
warnings: [
|
|
3965
|
+
"linux cold-start: generated VNC password contained shell-unsafe characters; refusing to invoke `tigervncpasswd -f` via shell pipe. Re-run `beeos init`."
|
|
3966
|
+
]
|
|
3967
|
+
};
|
|
3968
|
+
}
|
|
3969
|
+
try {
|
|
3970
|
+
await ensureBeeoHome2();
|
|
3971
|
+
await p.writeFile(beeoPasswordFile, newPassword);
|
|
3972
|
+
await p.chmod(beeoPasswordFile, 384);
|
|
3973
|
+
} catch (e) {
|
|
3974
|
+
return {
|
|
3975
|
+
status: "script_failed",
|
|
3976
|
+
warnings: [
|
|
3977
|
+
`linux cold-start: failed to write ${beeoPasswordFile}: ${describeError3(e)}. Desktop streaming will not work this run.`
|
|
3978
|
+
]
|
|
3979
|
+
};
|
|
3980
|
+
}
|
|
3981
|
+
const passwdResult = await writeSystemVncPasswd(state.passwdToolPath, newPassword);
|
|
3982
|
+
if (!passwdResult.ok) {
|
|
3983
|
+
await p.rm(beeoPasswordFile).catch(() => void 0);
|
|
3984
|
+
return {
|
|
3985
|
+
status: "script_failed",
|
|
3986
|
+
warnings: [
|
|
3987
|
+
`linux cold-start: \`${state.passwdToolPath} -f\` failed: ${passwdResult.detail}. Desktop streaming will not work this run.`
|
|
3988
|
+
]
|
|
3989
|
+
};
|
|
3990
|
+
}
|
|
3991
|
+
if (!state.port5901Listening) {
|
|
3992
|
+
if (state.vncServerPath === null) {
|
|
3993
|
+
return {
|
|
3994
|
+
status: "verify_timeout",
|
|
3995
|
+
warnings: [
|
|
3996
|
+
`linux cold-start: password files written but \`vncserver\` is not on PATH \u2014 start it manually with \`vncserver ${LINUX_VNC_DISPLAY} -localhost yes -SecurityTypes VncAuth\` and re-run \`beeos init\`.`
|
|
3997
|
+
]
|
|
3998
|
+
};
|
|
3999
|
+
}
|
|
4000
|
+
const startResult = await startVncServer(state.vncServerPath);
|
|
4001
|
+
if (!startResult.ok) {
|
|
4002
|
+
return {
|
|
4003
|
+
status: "script_failed",
|
|
4004
|
+
warnings: [
|
|
4005
|
+
`linux cold-start: \`vncserver ${LINUX_VNC_DISPLAY}\` failed: ${startResult.detail}. Check ~/.vnc/<host>:1.log for startup errors.`
|
|
4006
|
+
]
|
|
4007
|
+
};
|
|
4008
|
+
}
|
|
4009
|
+
}
|
|
4010
|
+
const up = await waitForPort(LINUX_VNC_PORT, VERIFY_TIMEOUT_MS);
|
|
4011
|
+
if (!up) {
|
|
4012
|
+
return {
|
|
4013
|
+
status: "verify_timeout",
|
|
4014
|
+
warnings: [
|
|
4015
|
+
`linux cold-start ran but :${LINUX_VNC_PORT} never came up within 8s. Check \`~/.vnc/$HOSTNAME${LINUX_VNC_DISPLAY}.log\` or run \`beeos doctor\`.`
|
|
4016
|
+
]
|
|
4017
|
+
};
|
|
4018
|
+
}
|
|
4019
|
+
log(`vncserver ${LINUX_VNC_DISPLAY} is listening on :${LINUX_VNC_PORT} (loopback only).`);
|
|
4020
|
+
return { status: "enabled_now", vncPassword: newPassword };
|
|
4021
|
+
}
|
|
4022
|
+
probe(opts) {
|
|
4023
|
+
return probeLocalVnc(opts);
|
|
4024
|
+
}
|
|
4025
|
+
readPasswordOptional() {
|
|
4026
|
+
return readVncPasswordFromBeeoHome();
|
|
4027
|
+
}
|
|
4028
|
+
postSetupHint(_endpoint) {
|
|
4029
|
+
return [
|
|
4030
|
+
"Linux desktop streaming notes:",
|
|
4031
|
+
" \u2022 vncserver is listening on 127.0.0.1:5901 only \u2014 exposing it externally requires",
|
|
4032
|
+
" explicit firewall + auth setup (out of scope for the BeeOS install).",
|
|
4033
|
+
" \u2022 Headless setups need an X session: edit ~/.vnc/xstartup if the desktop comes up blank."
|
|
4034
|
+
];
|
|
4035
|
+
}
|
|
4036
|
+
};
|
|
4037
|
+
linuxDesktopPlatform = new LinuxDesktopPlatform();
|
|
4038
|
+
}
|
|
4039
|
+
});
|
|
4040
|
+
|
|
4041
|
+
// ../core/dist/desktop/platforms/windows.js
|
|
4042
|
+
async function ensureBeeoHome3() {
|
|
4043
|
+
const p = getPlatformAdapter();
|
|
4044
|
+
const home = beeoHome();
|
|
4045
|
+
if (!await p.exists(home)) {
|
|
4046
|
+
await p.mkdir(home);
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
async function detectWindowsColdStartState() {
|
|
4050
|
+
const p = getPlatformAdapter();
|
|
4051
|
+
const port5900Listening = await p.tcpProbe("127.0.0.1", WIN_VNC_PORT, PORT_PROBE_TIMEOUT_MS2).catch(() => false);
|
|
4052
|
+
const beeoFile = p.joinPath(beeoHome(), "vnc.password");
|
|
4053
|
+
let beeoPasswordFileExists = false;
|
|
4054
|
+
try {
|
|
4055
|
+
const raw = await p.readFile(beeoFile);
|
|
4056
|
+
beeoPasswordFileExists = raw.trim().length > 0;
|
|
4057
|
+
} catch {
|
|
4058
|
+
beeoPasswordFileExists = false;
|
|
4059
|
+
}
|
|
4060
|
+
const [tvnserverServiceExists, tvnserverRegistryExists] = await Promise.all([
|
|
4061
|
+
detectViaPowerShell("(Get-Service tvnserver -ErrorAction SilentlyContinue) -ne $null"),
|
|
4062
|
+
detectViaPowerShell(`Test-Path "${TVNSERVER_REG_PATH}"`)
|
|
4063
|
+
]);
|
|
4064
|
+
return {
|
|
4065
|
+
port5900Listening,
|
|
4066
|
+
beeoPasswordFileExists,
|
|
4067
|
+
tvnserverServiceExists,
|
|
4068
|
+
tvnserverRegistryExists
|
|
4069
|
+
};
|
|
4070
|
+
}
|
|
4071
|
+
async function detectViaPowerShell(expression) {
|
|
4072
|
+
const p = getPlatformAdapter();
|
|
4073
|
+
try {
|
|
4074
|
+
const result = await p.exec("powershell", ["-NoProfile", "-Command", expression], { timeout: 5e3 });
|
|
4075
|
+
if (result.code !== 0)
|
|
4076
|
+
return false;
|
|
4077
|
+
return result.stdout.trim().toLowerCase() === "true";
|
|
4078
|
+
} catch {
|
|
4079
|
+
return false;
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
4082
|
+
async function runElevatedTvnserverConfig(password) {
|
|
4083
|
+
if (!isShellSafeVncPassword(password)) {
|
|
4084
|
+
return { ok: false, detail: "internal: refusing to embed shell-unsafe password" };
|
|
4085
|
+
}
|
|
4086
|
+
const p = getPlatformAdapter();
|
|
4087
|
+
const encoded = encodeVncPasswordBytes(password);
|
|
4088
|
+
const byteLiteral = Array.from(encoded).map((b) => `0x${b.toString(16).padStart(2, "0")}`).join(",");
|
|
4089
|
+
const innerScript = [
|
|
4090
|
+
`$bytes = [byte[]](${byteLiteral})`,
|
|
4091
|
+
`Set-ItemProperty -Path '${TVNSERVER_REG_PATH}' -Name 'Password' -Value $bytes -Type Binary`,
|
|
4092
|
+
`Set-ItemProperty -Path '${TVNSERVER_REG_PATH}' -Name 'AllowLoopback' -Value 1 -Type DWord`,
|
|
4093
|
+
`Set-ItemProperty -Path '${TVNSERVER_REG_PATH}' -Name 'LoopbackOnly' -Value 1 -Type DWord`,
|
|
4094
|
+
`Set-ItemProperty -Path '${TVNSERVER_REG_PATH}' -Name 'LocalInputPriority' -Value 1 -Type DWord`,
|
|
4095
|
+
`Restart-Service -Name 'tvnserver' -Force`,
|
|
4096
|
+
`exit 0`
|
|
4097
|
+
].join("; ");
|
|
4098
|
+
const outerScript = `try { $p = Start-Process powershell -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-Command',"${innerScript.replace(/"/g, '`"')}" -Verb RunAs -Wait -PassThru -ErrorAction Stop; exit $p.ExitCode} catch { if ($_.Exception.Message -match 'cancel|cancelled') { exit 1223 } else { Write-Error $_; exit 1 }}`;
|
|
4099
|
+
try {
|
|
4100
|
+
const result = await p.exec("powershell", ["-NoProfile", "-Command", outerScript], { timeout: POWERSHELL_TIMEOUT_MS });
|
|
4101
|
+
if (result.code === 0)
|
|
4102
|
+
return { ok: true };
|
|
4103
|
+
if (result.code === 1223) {
|
|
4104
|
+
return { ok: false, canceled: true, detail: "UAC denied" };
|
|
4105
|
+
}
|
|
4106
|
+
return {
|
|
4107
|
+
ok: false,
|
|
4108
|
+
detail: result.stderr.trim() || `exit ${result.code}`
|
|
4109
|
+
};
|
|
4110
|
+
} catch (e) {
|
|
4111
|
+
return { ok: false, detail: describeError4(e) };
|
|
4112
|
+
}
|
|
4113
|
+
}
|
|
4114
|
+
async function waitForPort2(port, timeoutMs) {
|
|
4115
|
+
const p = getPlatformAdapter();
|
|
4116
|
+
const deadline = Date.now() + timeoutMs;
|
|
4117
|
+
while (Date.now() < deadline) {
|
|
4118
|
+
if (await p.tcpProbe("127.0.0.1", port, PORT_PROBE_TIMEOUT_MS2).catch(() => false)) {
|
|
4119
|
+
return true;
|
|
4120
|
+
}
|
|
4121
|
+
await sleep5(VERIFY_POLL_INTERVAL_MS2);
|
|
4122
|
+
}
|
|
4123
|
+
return false;
|
|
4124
|
+
}
|
|
4125
|
+
function sleep5(ms) {
|
|
4126
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4127
|
+
}
|
|
4128
|
+
function describeError4(e) {
|
|
4129
|
+
if (e instanceof Error) {
|
|
4130
|
+
const msg = e.message.split(/\r?\n/, 1)[0]?.trim();
|
|
4131
|
+
return msg && msg.length > 0 ? msg : e.name;
|
|
4132
|
+
}
|
|
4133
|
+
return String(e);
|
|
4134
|
+
}
|
|
4135
|
+
var WIN_VNC_PORT, PORT_PROBE_TIMEOUT_MS2, VERIFY_TIMEOUT_MS2, VERIFY_POLL_INTERVAL_MS2, POWERSHELL_TIMEOUT_MS, TVNSERVER_REG_PATH, WINGET_PACKAGE_HINT, WINDOWS_VNC_INSTALL_HINT_LINES, WindowsDesktopPlatform, windowsDesktopPlatform;
|
|
4136
|
+
var init_windows = __esm({
|
|
4137
|
+
"../core/dist/desktop/platforms/windows.js"() {
|
|
4138
|
+
"use strict";
|
|
4139
|
+
init_platform_adapter();
|
|
4140
|
+
init_paths();
|
|
4141
|
+
init_common();
|
|
4142
|
+
WIN_VNC_PORT = 5900;
|
|
4143
|
+
PORT_PROBE_TIMEOUT_MS2 = 200;
|
|
4144
|
+
VERIFY_TIMEOUT_MS2 = 8e3;
|
|
4145
|
+
VERIFY_POLL_INTERVAL_MS2 = 250;
|
|
4146
|
+
POWERSHELL_TIMEOUT_MS = 6e4;
|
|
4147
|
+
TVNSERVER_REG_PATH = "HKLM:\\SOFTWARE\\TightVNC\\Server";
|
|
4148
|
+
WINGET_PACKAGE_HINT = "winget install GlavSoft.TightVNC --accept-package-agreements --accept-source-agreements --silent";
|
|
4149
|
+
WINDOWS_VNC_INSTALL_HINT_LINES = [
|
|
4150
|
+
"Tip: BeeOS desktop streaming on Windows uses TightVNC. To install:",
|
|
4151
|
+
` ${WINGET_PACKAGE_HINT}`,
|
|
4152
|
+
"Then re-run `beeos init`. Set BEEOS_NO_DESKTOP=1 to suppress this hint."
|
|
4153
|
+
];
|
|
4154
|
+
WindowsDesktopPlatform = class {
|
|
4155
|
+
id = "windows";
|
|
4156
|
+
async coldStart(opts) {
|
|
4157
|
+
if (opts.noDesktop)
|
|
4158
|
+
return { status: "skipped_opt_out" };
|
|
4159
|
+
const log = opts.log ?? ((m) => console.log(m));
|
|
4160
|
+
const p = getPlatformAdapter();
|
|
4161
|
+
const state = await detectWindowsColdStartState();
|
|
4162
|
+
if (!state.tvnserverServiceExists && !state.tvnserverRegistryExists) {
|
|
4163
|
+
return {
|
|
4164
|
+
status: "skipped_no_vnc_server",
|
|
4165
|
+
warnings: [...WINDOWS_VNC_INSTALL_HINT_LINES]
|
|
4166
|
+
};
|
|
4167
|
+
}
|
|
4168
|
+
const beeoPasswordFile = p.joinPath(beeoHome(), "vnc.password");
|
|
4169
|
+
if (state.port5900Listening && state.beeoPasswordFileExists) {
|
|
4170
|
+
let existing;
|
|
4171
|
+
try {
|
|
4172
|
+
existing = (await p.readFile(beeoPasswordFile)).trim() || void 0;
|
|
4173
|
+
} catch {
|
|
4174
|
+
existing = void 0;
|
|
4175
|
+
}
|
|
4176
|
+
return { status: "already_configured", vncPassword: existing };
|
|
4177
|
+
}
|
|
4178
|
+
if (!opts.prompt && !opts.yes) {
|
|
4179
|
+
return { status: "skipped_no_tty" };
|
|
4180
|
+
}
|
|
4181
|
+
if (!opts.yes) {
|
|
4182
|
+
log("");
|
|
4183
|
+
log("BeeOS desktop streaming needs TightVNC configured for loopback access.");
|
|
4184
|
+
log("This will:");
|
|
4185
|
+
log(" - Generate an 8-char VNC viewer password");
|
|
4186
|
+
log(" - Write it to HKLM\\SOFTWARE\\TightVNC\\Server (requires UAC)");
|
|
4187
|
+
log(" - Restrict TightVNC to 127.0.0.1:5900 (loopback only)");
|
|
4188
|
+
log(" - Restart the tvnserver service");
|
|
4189
|
+
log("Windows will pop ONE UAC dialog. The CLI never sees your password \u2014");
|
|
4190
|
+
log("only the elevated PowerShell child does, and only inside its own process.");
|
|
4191
|
+
log("");
|
|
4192
|
+
const answer = (await opts.prompt("Configure TightVNC for desktop streaming? [Y/n]: ")).trim();
|
|
4193
|
+
if (answer !== "" && !/^y(es)?$/i.test(answer)) {
|
|
4194
|
+
return { status: "user_declined" };
|
|
4195
|
+
}
|
|
4196
|
+
}
|
|
4197
|
+
const newPassword = generateLegacyVncPassword();
|
|
4198
|
+
if (!isShellSafeVncPassword(newPassword)) {
|
|
4199
|
+
return {
|
|
4200
|
+
status: "script_failed",
|
|
4201
|
+
warnings: [
|
|
4202
|
+
"windows cold-start: generated VNC password contained shell-unsafe characters; refusing to embed in PowerShell payload. Re-run `beeos init`."
|
|
4203
|
+
]
|
|
4204
|
+
};
|
|
4205
|
+
}
|
|
4206
|
+
try {
|
|
4207
|
+
await ensureBeeoHome3();
|
|
4208
|
+
await p.writeFile(beeoPasswordFile, newPassword);
|
|
4209
|
+
await p.chmod(beeoPasswordFile, 384).catch(() => void 0);
|
|
4210
|
+
} catch (e) {
|
|
4211
|
+
return {
|
|
4212
|
+
status: "script_failed",
|
|
4213
|
+
warnings: [
|
|
4214
|
+
`windows cold-start: failed to write ${beeoPasswordFile}: ${describeError4(e)}. Desktop streaming will not work this run.`
|
|
4215
|
+
]
|
|
4216
|
+
};
|
|
4217
|
+
}
|
|
4218
|
+
log("Requesting Windows administrator privileges (UAC dialog) ...");
|
|
4219
|
+
const elevated = await runElevatedTvnserverConfig(newPassword);
|
|
4220
|
+
if (!elevated.ok) {
|
|
4221
|
+
await p.rm(beeoPasswordFile).catch(() => void 0);
|
|
4222
|
+
const canceled = elevated.canceled === true;
|
|
4223
|
+
return {
|
|
4224
|
+
status: canceled ? "user_canceled_dialog" : "script_failed",
|
|
4225
|
+
warnings: [
|
|
4226
|
+
canceled ? "windows cold-start: UAC dialog was canceled \u2014 desktop streaming not enabled." : `windows cold-start: tvnserver registry write failed: ${elevated.detail}. Desktop streaming will not work this run.`
|
|
4227
|
+
]
|
|
4228
|
+
};
|
|
4229
|
+
}
|
|
4230
|
+
const up = await waitForPort2(WIN_VNC_PORT, VERIFY_TIMEOUT_MS2);
|
|
4231
|
+
if (!up) {
|
|
4232
|
+
return {
|
|
4233
|
+
status: "verify_timeout",
|
|
4234
|
+
warnings: [
|
|
4235
|
+
`windows cold-start ran but :${WIN_VNC_PORT} never came up within 8s. Open Services.msc and check \`tvnserver\`, or run \`beeos doctor\`.`
|
|
4236
|
+
]
|
|
4237
|
+
};
|
|
4238
|
+
}
|
|
4239
|
+
log(`tvnserver is listening on 127.0.0.1:${WIN_VNC_PORT}.`);
|
|
4240
|
+
return { status: "enabled_now", vncPassword: newPassword };
|
|
4241
|
+
}
|
|
4242
|
+
async probe(opts) {
|
|
4243
|
+
if (process.env.BEEOS_NO_DESKTOP === "1")
|
|
4244
|
+
return null;
|
|
4245
|
+
const p = getPlatformAdapter();
|
|
4246
|
+
if (p.platform() !== "win32")
|
|
4247
|
+
return null;
|
|
4248
|
+
const ok = await p.tcpProbe("127.0.0.1", WIN_VNC_PORT, PORT_PROBE_TIMEOUT_MS2).catch(() => false);
|
|
4249
|
+
if (!ok) {
|
|
4250
|
+
if (opts?.ttyHints) {
|
|
4251
|
+
for (const line of WINDOWS_VNC_INSTALL_HINT_LINES)
|
|
4252
|
+
console.log(line);
|
|
4253
|
+
}
|
|
4254
|
+
return null;
|
|
4255
|
+
}
|
|
4256
|
+
return {
|
|
4257
|
+
kind: "linux",
|
|
4258
|
+
// VncEndpoint.kind is a coarse RFB-family tag; "linux"
|
|
4259
|
+
// is fine because TightVNC speaks the same RFB scheme-2 surface
|
|
4260
|
+
// as TigerVNC. We deliberately pin osType="windows-desktop" so
|
|
4261
|
+
// the backend `isDesktopInstance` matcher sees the right host
|
|
4262
|
+
// tag without having to extend the union.
|
|
4263
|
+
host: "127.0.0.1",
|
|
4264
|
+
port: WIN_VNC_PORT,
|
|
4265
|
+
osType: "windows-desktop"
|
|
4266
|
+
};
|
|
4267
|
+
}
|
|
4268
|
+
readPasswordOptional() {
|
|
4269
|
+
return readVncPasswordFromBeeoHome();
|
|
4270
|
+
}
|
|
4271
|
+
postSetupHint(_endpoint) {
|
|
4272
|
+
return [
|
|
4273
|
+
"Windows desktop streaming notes:",
|
|
4274
|
+
" \u2022 TightVNC is configured to accept loopback connections only \u2014 exposing it externally",
|
|
4275
|
+
" requires extra Windows Firewall + auth rules outside BeeOS.",
|
|
4276
|
+
" \u2022 If the dashboard's desktop view stays blank, open Services.msc and verify",
|
|
4277
|
+
" that `tvnserver` is in the Running state."
|
|
4278
|
+
];
|
|
4279
|
+
}
|
|
4280
|
+
};
|
|
4281
|
+
windowsDesktopPlatform = new WindowsDesktopPlatform();
|
|
4282
|
+
}
|
|
4283
|
+
});
|
|
4284
|
+
|
|
4285
|
+
// ../core/dist/desktop/hints.js
|
|
4286
|
+
var LINUX_DESKTOP_DOCTOR_HINT_LINES, WINDOWS_DESKTOP_DOCTOR_HINT_LINES;
|
|
4287
|
+
var init_hints = __esm({
|
|
4288
|
+
"../core/dist/desktop/hints.js"() {
|
|
4289
|
+
"use strict";
|
|
4290
|
+
LINUX_DESKTOP_DOCTOR_HINT_LINES = [
|
|
4291
|
+
"vnc-bridge-openclaw is failing on Linux. Likely causes:",
|
|
4292
|
+
" \u2022 `vncserver :1` is not running on 127.0.0.1:5901 \u2014 start it with",
|
|
4293
|
+
" `vncserver :1 -localhost yes -SecurityTypes VncAuth` and re-run",
|
|
4294
|
+
" `beeos doctor`.",
|
|
4295
|
+
" \u2022 ~/.vnc/passwd drifted out of sync with ~/.beeos/vnc.password \u2014",
|
|
4296
|
+
" re-run `beeos init` to regenerate both files in lockstep.",
|
|
4297
|
+
" \u2022 Headless box: ~/.vnc/xstartup boots into a black session \u2014 install",
|
|
4298
|
+
" a desktop environment (`xfce4`, `gnome-session`, \u2026) and add it to",
|
|
4299
|
+
" the xstartup line."
|
|
4300
|
+
];
|
|
4301
|
+
WINDOWS_DESKTOP_DOCTOR_HINT_LINES = [
|
|
4302
|
+
"vnc-bridge-openclaw is failing on Windows. Likely causes:",
|
|
4303
|
+
" \u2022 `tvnserver` service is stopped \u2014 open Services.msc, find",
|
|
4304
|
+
" `TightVNC Server`, click Start. Or re-run `beeos init` to",
|
|
4305
|
+
" re-apply the cold-start configuration.",
|
|
4306
|
+
" \u2022 Someone changed the TightVNC password outside BeeOS \u2014",
|
|
4307
|
+
" `HKLM\\SOFTWARE\\TightVNC\\Server\\Password` no longer matches",
|
|
4308
|
+
" %USERPROFILE%\\.beeos\\vnc.password. Re-run `beeos init` to",
|
|
4309
|
+
" regenerate both ends in one shot (UAC dialog will appear).",
|
|
4310
|
+
" \u2022 Windows Firewall is blocking the loopback bridge \u2014 TightVNC",
|
|
4311
|
+
" is configured for AllowLoopback=1 + LoopbackOnly=1, but a",
|
|
4312
|
+
" firewall rule that blocks 127.0.0.1:5900 will still break it."
|
|
4313
|
+
];
|
|
4314
|
+
}
|
|
4315
|
+
});
|
|
4316
|
+
|
|
4317
|
+
// ../core/dist/desktop/index.js
|
|
4318
|
+
function inferDesktopPlatformId(plat) {
|
|
4319
|
+
switch (plat) {
|
|
4320
|
+
case "darwin":
|
|
4321
|
+
return "macos";
|
|
4322
|
+
case "linux":
|
|
4323
|
+
return "linux";
|
|
4324
|
+
case "win32":
|
|
4325
|
+
return "windows";
|
|
4326
|
+
default:
|
|
4327
|
+
return null;
|
|
4328
|
+
}
|
|
4329
|
+
}
|
|
4330
|
+
function getDesktopPlatform() {
|
|
4331
|
+
const plat = getPlatformAdapter().platform();
|
|
4332
|
+
const id = inferDesktopPlatformId(plat);
|
|
4333
|
+
switch (id) {
|
|
4334
|
+
case "macos":
|
|
4335
|
+
return macosDesktopPlatform;
|
|
4336
|
+
case "linux":
|
|
4337
|
+
return linuxDesktopPlatform;
|
|
4338
|
+
case "windows":
|
|
4339
|
+
return windowsDesktopPlatform;
|
|
4340
|
+
default:
|
|
4341
|
+
return linuxDesktopPlatform;
|
|
4342
|
+
}
|
|
4343
|
+
}
|
|
4344
|
+
function getDesktopPipeline() {
|
|
4345
|
+
if (process.env.BEEOS_NO_DESKTOP === "1")
|
|
4346
|
+
return null;
|
|
4347
|
+
return new DesktopPipeline(getDesktopPlatform());
|
|
4348
|
+
}
|
|
4349
|
+
var init_desktop = __esm({
|
|
4350
|
+
"../core/dist/desktop/index.js"() {
|
|
4351
|
+
"use strict";
|
|
4352
|
+
init_platform_adapter();
|
|
4353
|
+
init_pipeline();
|
|
4354
|
+
init_macos();
|
|
4355
|
+
init_linux();
|
|
4356
|
+
init_windows();
|
|
4357
|
+
init_pipeline();
|
|
4358
|
+
init_common();
|
|
4359
|
+
init_hints();
|
|
4360
|
+
}
|
|
4361
|
+
});
|
|
4362
|
+
|
|
3343
4363
|
// ../core/dist/detect.js
|
|
3344
4364
|
async function detectExistingInstall() {
|
|
3345
4365
|
const p = getPlatformAdapter();
|
|
@@ -3570,35 +4590,6 @@ var init_registry = __esm({
|
|
|
3570
4590
|
}
|
|
3571
4591
|
});
|
|
3572
4592
|
|
|
3573
|
-
// ../core/dist/services/target-spec.js
|
|
3574
|
-
function buildVncBridgeTargetSpec(binary, opts) {
|
|
3575
|
-
return {
|
|
3576
|
-
id: `vnc-bridge-${opts.serial}`,
|
|
3577
|
-
kind: "vnc-bridge",
|
|
3578
|
-
command: binary,
|
|
3579
|
-
args: [],
|
|
3580
|
-
env: vncBridgeRuntime.buildEnv(opts),
|
|
3581
|
-
restart: "on-failure",
|
|
3582
|
-
label: `vnc-bridge (${opts.serial})`
|
|
3583
|
-
};
|
|
3584
|
-
}
|
|
3585
|
-
function buildOpenclawDesktopVncBridgeSpec(binary, opts) {
|
|
3586
|
-
return buildVncBridgeTargetSpec(binary, {
|
|
3587
|
-
...opts,
|
|
3588
|
-
serial: OPENCLAW_VNC_BRIDGE_SERIAL
|
|
3589
|
-
});
|
|
3590
|
-
}
|
|
3591
|
-
var OPENCLAW_VNC_BRIDGE_SERIAL;
|
|
3592
|
-
var init_target_spec = __esm({
|
|
3593
|
-
"../core/dist/services/target-spec.js"() {
|
|
3594
|
-
"use strict";
|
|
3595
|
-
init_scrcpy_bridge();
|
|
3596
|
-
init_vnc_bridge();
|
|
3597
|
-
init_spawn_env2();
|
|
3598
|
-
OPENCLAW_VNC_BRIDGE_SERIAL = "openclaw";
|
|
3599
|
-
}
|
|
3600
|
-
});
|
|
3601
|
-
|
|
3602
4593
|
// ../core/dist/services/ids.js
|
|
3603
4594
|
import path from "path";
|
|
3604
4595
|
function safeId(id) {
|
|
@@ -5195,6 +6186,8 @@ var init_dist = __esm({
|
|
|
5195
6186
|
init_constants();
|
|
5196
6187
|
init_agent_status();
|
|
5197
6188
|
init_desktop_detect();
|
|
6189
|
+
init_macos_desktop_cold_start();
|
|
6190
|
+
init_desktop();
|
|
5198
6191
|
init_detect();
|
|
5199
6192
|
init_registry();
|
|
5200
6193
|
init_target_spec();
|
|
@@ -5420,7 +6413,7 @@ var init_upgrade2 = __esm({
|
|
|
5420
6413
|
});
|
|
5421
6414
|
|
|
5422
6415
|
// src/lib/instance-picker.ts
|
|
5423
|
-
import
|
|
6416
|
+
import readline2 from "readline";
|
|
5424
6417
|
function resolveIO(ctx) {
|
|
5425
6418
|
return {
|
|
5426
6419
|
input: ctx.input ?? process.stdin,
|
|
@@ -5432,7 +6425,7 @@ function isTTY(ctx, io) {
|
|
|
5432
6425
|
return Boolean(io.input.isTTY);
|
|
5433
6426
|
}
|
|
5434
6427
|
function ask(io, question) {
|
|
5435
|
-
const rl =
|
|
6428
|
+
const rl = readline2.createInterface({ input: io.input, output: io.output });
|
|
5436
6429
|
return new Promise((resolve) => {
|
|
5437
6430
|
rl.question(question, (answer) => {
|
|
5438
6431
|
rl.close();
|
|
@@ -5685,7 +6678,7 @@ var init_state2 = __esm({
|
|
|
5685
6678
|
|
|
5686
6679
|
// src/commands/device/attach.ts
|
|
5687
6680
|
import { spawn as spawn2 } from "child_process";
|
|
5688
|
-
import
|
|
6681
|
+
import readline3 from "readline";
|
|
5689
6682
|
function resolvePerDeviceAgentGatewayUrl(cfg, override) {
|
|
5690
6683
|
if (override === void 0) return resolveAgentGatewayUrl(cfg);
|
|
5691
6684
|
const trimmed = override.trim();
|
|
@@ -5717,6 +6710,20 @@ function resolvePerDeviceAgentGatewayUrl(cfg, override) {
|
|
|
5717
6710
|
}
|
|
5718
6711
|
return trimmed;
|
|
5719
6712
|
}
|
|
6713
|
+
function resolveBackendForAttach(options, hostBackend) {
|
|
6714
|
+
if (options.self === true) {
|
|
6715
|
+
if (hostBackend === null || hostBackend === "self" || hostBackend === "adb") {
|
|
6716
|
+
throw new BeeosError({
|
|
6717
|
+
code: "self_attach_unsupported_os",
|
|
6718
|
+
message: "`beeos device attach --self` is not supported on this host yet.",
|
|
6719
|
+
hint: "Self-attach maps the host's native backend (macOS/Linux/Windows). Run on a supported OS, or attach an ADB device with `--serial` instead."
|
|
6720
|
+
});
|
|
6721
|
+
}
|
|
6722
|
+
return hostBackend;
|
|
6723
|
+
}
|
|
6724
|
+
if (options.vncHost) return void 0;
|
|
6725
|
+
return "adb";
|
|
6726
|
+
}
|
|
5720
6727
|
async function pickAttachSerial() {
|
|
5721
6728
|
const devices = await deviceRuntime.listAdbDevices();
|
|
5722
6729
|
const ready = devices.filter((d) => d.status === "device");
|
|
@@ -5790,7 +6797,10 @@ async function attach(options) {
|
|
|
5790
6797
|
});
|
|
5791
6798
|
const fp = fingerprintFromB64(pubkeyB64);
|
|
5792
6799
|
const boundAt = Math.floor(Date.now() / 1e3);
|
|
5793
|
-
const backend =
|
|
6800
|
+
const backend = resolveBackendForAttach(
|
|
6801
|
+
options,
|
|
6802
|
+
inferHostBackend()
|
|
6803
|
+
);
|
|
5794
6804
|
state.devices.push({
|
|
5795
6805
|
serial,
|
|
5796
6806
|
name,
|
|
@@ -5798,12 +6808,6 @@ async function attach(options) {
|
|
|
5798
6808
|
key_file: keyFile,
|
|
5799
6809
|
http_port: httpPort,
|
|
5800
6810
|
video_mode: bridgeInfo.mode,
|
|
5801
|
-
// For now `beeos device attach` only binds ADB devices (the
|
|
5802
|
-
// `--vnc-host` shape spawns a vnc-bridge but does NOT spawn a
|
|
5803
|
-
// device-mcp-server, so `backend` only ever drives the ADB
|
|
5804
|
-
// path through fleet → mcp). When self-attach for
|
|
5805
|
-
// macOS/Linux/Windows lands, this field will be set
|
|
5806
|
-
// accordingly (`macos`/`linux`/`windows`).
|
|
5807
6811
|
backend,
|
|
5808
6812
|
vnc_host: options.vncHost,
|
|
5809
6813
|
vnc_port: options.vncPort,
|
|
@@ -6043,7 +7047,7 @@ async function runAttachStage2(params) {
|
|
|
6043
7047
|
key_file: keyFile,
|
|
6044
7048
|
http_port: httpPort,
|
|
6045
7049
|
video_mode: bridgeInfo.mode,
|
|
6046
|
-
backend: options
|
|
7050
|
+
backend: resolveBackendForAttach(options, inferHostBackend()),
|
|
6047
7051
|
vnc_host: options.vncHost,
|
|
6048
7052
|
vnc_port: options.vncPort,
|
|
6049
7053
|
// P0-D: see attach() above — every new entry self-pins to the
|
|
@@ -6121,7 +7125,7 @@ async function maybeNotifyFleetWithHint(cfg) {
|
|
|
6121
7125
|
console.log("it, the device is recorded in ~/.beeos/devices.json but");
|
|
6122
7126
|
console.log("no process is supervising the per-device agent.");
|
|
6123
7127
|
console.log("");
|
|
6124
|
-
const rl =
|
|
7128
|
+
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
6125
7129
|
const answer = await new Promise((resolve) => {
|
|
6126
7130
|
rl.question("Enable fleet now (registers a launchd service)? [Y/n]: ", (a) => {
|
|
6127
7131
|
rl.close();
|
|
@@ -6152,7 +7156,7 @@ function shouldOfferFleetEnable(inputs = {
|
|
|
6152
7156
|
stdoutIsTTY: process.stdout.isTTY,
|
|
6153
7157
|
env: process.env
|
|
6154
7158
|
}) {
|
|
6155
|
-
if (inputs.platform !== "darwin") return false;
|
|
7159
|
+
if (inputs.platform !== "darwin" && inputs.platform !== "linux") return false;
|
|
6156
7160
|
if (inputs.env.BEEOS_NO_FLEET_AUTOSTART === "1") return false;
|
|
6157
7161
|
if (!inputs.stdinIsTTY) return false;
|
|
6158
7162
|
if (!inputs.stdoutIsTTY) return false;
|
|
@@ -6295,7 +7299,7 @@ Android SDK Platform-Tools (adb) is licensed under the Android SDK License Agree
|
|
|
6295
7299
|
Set BEEOS_ACCEPT_ADB_LICENSE=1 in your environment to skip this prompt next time.
|
|
6296
7300
|
`
|
|
6297
7301
|
);
|
|
6298
|
-
const rl =
|
|
7302
|
+
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
6299
7303
|
const ans = await new Promise((resolve) => {
|
|
6300
7304
|
rl.question("Agree to the Android SDK license and download platform-tools? [Y/n]: ", (a) => {
|
|
6301
7305
|
rl.close();
|
|
@@ -6846,6 +7850,7 @@ init_progress2();
|
|
|
6846
7850
|
init_fallback_banner();
|
|
6847
7851
|
init_json_envelope();
|
|
6848
7852
|
import os6 from "os";
|
|
7853
|
+
import readline from "readline";
|
|
6849
7854
|
async function run(agentFramework, options) {
|
|
6850
7855
|
const p = getPlatformAdapter();
|
|
6851
7856
|
await ensureDirs();
|
|
@@ -6892,10 +7897,24 @@ async function run(agentFramework, options) {
|
|
|
6892
7897
|
const launchWarnings = [...launchOutcome.warnings];
|
|
6893
7898
|
reporter.stop();
|
|
6894
7899
|
const ttyHints = !options.json && process.stdin.isTTY === true;
|
|
6895
|
-
const
|
|
7900
|
+
const desktopCapable = hasDesktopCapability(driver);
|
|
7901
|
+
const desktopPipeline = desktopCapable ? getDesktopPipeline() : null;
|
|
7902
|
+
let coldStart = null;
|
|
7903
|
+
if (desktopPipeline) {
|
|
7904
|
+
const interactive = !options.json && process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
7905
|
+
coldStart = await desktopPipeline.coldStart({
|
|
7906
|
+
prompt: interactive ? promptYesNo : void 0,
|
|
7907
|
+
log: options.json ? () => void 0 : (m) => console.log(m),
|
|
7908
|
+
yes: options.force === true
|
|
7909
|
+
});
|
|
7910
|
+
if (coldStart.warnings && coldStart.warnings.length > 0) {
|
|
7911
|
+
launchWarnings.push(...coldStart.warnings);
|
|
7912
|
+
}
|
|
7913
|
+
}
|
|
7914
|
+
const vncEndpoint = desktopPipeline ? await desktopPipeline.probe({ ttyHints }) : null;
|
|
6896
7915
|
const hostname = buildHostname();
|
|
6897
7916
|
const cachedBinding = await loadBindingInfo();
|
|
6898
|
-
const bindVncPassword = vncEndpoint ? await
|
|
7917
|
+
const bindVncPassword = vncEndpoint && desktopPipeline ? await desktopPipeline.readPasswordOptional() : void 0;
|
|
6899
7918
|
const outcome = await bindAgent({
|
|
6900
7919
|
apiUrl: cfg.platform.api_url,
|
|
6901
7920
|
dashboardBaseUrl: cfg.platform.dashboard_base_url,
|
|
@@ -7009,22 +8028,36 @@ async function run(agentFramework, options) {
|
|
|
7009
8028
|
});
|
|
7010
8029
|
}
|
|
7011
8030
|
}
|
|
7012
|
-
if (vncEndpoint && !isOffline) {
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
if (vncEndpoint.kind === "macos") {
|
|
7017
|
-
const ardWarning = await checkMacosARDConflict();
|
|
7018
|
-
if (ardWarning) launchWarnings.push(ardWarning);
|
|
8031
|
+
if (vncEndpoint && !isOffline && desktopPipeline && hasDesktopCapability(driver)) {
|
|
8032
|
+
const platformHints = desktopPipeline.postSetupHint(vncEndpoint);
|
|
8033
|
+
if (platformHints.length > 0 && ttyHints) {
|
|
8034
|
+
for (const line of platformHints) console.log(line);
|
|
7019
8035
|
}
|
|
7020
|
-
const
|
|
8036
|
+
const desktopReporter = new CliReporter();
|
|
8037
|
+
const bridgeOutcome = await driver.desktopBridgeSpec(
|
|
7021
8038
|
vncEndpoint,
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
8039
|
+
{
|
|
8040
|
+
instanceId: boundInstanceId,
|
|
8041
|
+
keyFile,
|
|
8042
|
+
agentGatewayUrl,
|
|
8043
|
+
...bindVncPassword ? { vncPassword: bindVncPassword } : {}
|
|
8044
|
+
},
|
|
8045
|
+
desktopReporter
|
|
8046
|
+
);
|
|
8047
|
+
desktopReporter.stop();
|
|
8048
|
+
if (bridgeOutcome.warnings.length > 0) {
|
|
8049
|
+
launchWarnings.push(...bridgeOutcome.warnings);
|
|
8050
|
+
}
|
|
8051
|
+
if (bridgeOutcome.spec) {
|
|
8052
|
+
try {
|
|
8053
|
+
await mgr.install(bridgeOutcome.spec);
|
|
8054
|
+
} catch (e) {
|
|
8055
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
8056
|
+
launchWarnings.push(
|
|
8057
|
+
`vnc-bridge service install failed: ${reason}; ${driver.displayName} is bound but desktop streaming is unavailable. Re-run \`beeos start ${agentFramework} --force\` to retry.`
|
|
8058
|
+
);
|
|
8059
|
+
}
|
|
8060
|
+
}
|
|
7028
8061
|
}
|
|
7029
8062
|
const gatewayPid = status2.pid ?? void 0;
|
|
7030
8063
|
emit(options.json, {
|
|
@@ -7038,67 +8071,17 @@ async function run(agentFramework, options) {
|
|
|
7038
8071
|
}, isOffline ? `Agent running (offline, cached instance: ${boundInstanceId})` : `Agent bound to instance: ${boundInstanceId}`);
|
|
7039
8072
|
if (!options.json) printLaunchWarnings(launchWarnings);
|
|
7040
8073
|
}
|
|
7041
|
-
async function
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
reporter.stop();
|
|
7052
|
-
if (!binary) {
|
|
7053
|
-
return `vnc-bridge binary unavailable (download or PATH lookup failed); OpenClaw is bound but desktop streaming is unavailable. Set BEEOS_VNC_BRIDGE_BIN to override.`;
|
|
7054
|
-
}
|
|
7055
|
-
const vncPassword = await readVncPasswordOptional();
|
|
7056
|
-
const spec = buildOpenclawDesktopVncBridgeSpec(binary, {
|
|
7057
|
-
deviceId: params.instanceId,
|
|
7058
|
-
vncHost: params.vncEndpoint.host,
|
|
7059
|
-
vncPort: params.vncEndpoint.port,
|
|
7060
|
-
...vncPassword ? { vncPassword } : {},
|
|
7061
|
-
agentGatewayUrl: params.agentGatewayUrl,
|
|
7062
|
-
bridgePrivateKeyFile: params.keyFile,
|
|
7063
|
-
bridgePublicKeyFile: params.keyFile
|
|
8074
|
+
async function promptYesNo(question) {
|
|
8075
|
+
return new Promise((resolve) => {
|
|
8076
|
+
const rl = readline.createInterface({
|
|
8077
|
+
input: process.stdin,
|
|
8078
|
+
output: process.stdout
|
|
8079
|
+
});
|
|
8080
|
+
rl.question(question, (answer) => {
|
|
8081
|
+
rl.close();
|
|
8082
|
+
resolve(answer);
|
|
8083
|
+
});
|
|
7064
8084
|
});
|
|
7065
|
-
try {
|
|
7066
|
-
await params.mgr.install(spec);
|
|
7067
|
-
} catch (e) {
|
|
7068
|
-
const reason = e instanceof Error ? e.message : String(e);
|
|
7069
|
-
return `vnc-bridge service install failed: ${reason}; OpenClaw is bound but desktop streaming is unavailable. Re-run \`beeos start openclaw --force\` to retry.`;
|
|
7070
|
-
}
|
|
7071
|
-
return null;
|
|
7072
|
-
}
|
|
7073
|
-
async function readVncPasswordOptional() {
|
|
7074
|
-
const env = process.env.BEEOS_VNC_PASSWORD?.trim();
|
|
7075
|
-
if (env) return env;
|
|
7076
|
-
const p = getPlatformAdapter();
|
|
7077
|
-
const file = p.joinPath(beeoHome(), "vnc.password");
|
|
7078
|
-
try {
|
|
7079
|
-
const raw = (await p.readFile(file)).trim();
|
|
7080
|
-
return raw || void 0;
|
|
7081
|
-
} catch {
|
|
7082
|
-
return void 0;
|
|
7083
|
-
}
|
|
7084
|
-
}
|
|
7085
|
-
async function checkMacosARDConflict() {
|
|
7086
|
-
if (process.platform !== "darwin") return null;
|
|
7087
|
-
const p = getPlatformAdapter();
|
|
7088
|
-
try {
|
|
7089
|
-
const result = await p.exec("plutil", [
|
|
7090
|
-
"-extract",
|
|
7091
|
-
"ARD_AllLocalUsers",
|
|
7092
|
-
"raw",
|
|
7093
|
-
"/Library/Preferences/com.apple.RemoteManagement.plist"
|
|
7094
|
-
], { timeout: 2e3 });
|
|
7095
|
-
const out = String(result.stdout || "").trim().toLowerCase();
|
|
7096
|
-
if (out === "true" || out === "1") {
|
|
7097
|
-
return "macOS ARD_AllLocalUsers is enabled \u2014 Screen Sharing also advertises RFB scheme 30 (Apple Auth) which noVNC will prefer over scheme 2 (legacy VNC password). The dashboard's device-viewer will then loop on `credentialsrequired` and never paint. Disable ARD to keep only legacy VNC:\n sudo defaults write /Library/Preferences/com.apple.RemoteManagement ARD_AllLocalUsers -bool NO\n sudo launchctl kickstart -k system/com.apple.screensharing";
|
|
7098
|
-
}
|
|
7099
|
-
} catch {
|
|
7100
|
-
}
|
|
7101
|
-
return null;
|
|
7102
8085
|
}
|
|
7103
8086
|
function buildHostname() {
|
|
7104
8087
|
const machine = os6.hostname();
|
|
@@ -7323,7 +8306,7 @@ init_device2();
|
|
|
7323
8306
|
|
|
7324
8307
|
// src/commands/init.ts
|
|
7325
8308
|
init_dist();
|
|
7326
|
-
import
|
|
8309
|
+
import readline4 from "readline";
|
|
7327
8310
|
init_attach();
|
|
7328
8311
|
init_progress2();
|
|
7329
8312
|
init_fallback_banner();
|
|
@@ -7571,11 +8554,14 @@ function printNextSteps() {
|
|
|
7571
8554
|
console.log("Next steps:");
|
|
7572
8555
|
console.log(" beeos status # show running agents");
|
|
7573
8556
|
console.log(" beeos device attach # bind a physical Android device");
|
|
8557
|
+
if (inferDesktopPlatformId(process.platform) === "macos" || inferDesktopPlatformId(process.platform) === "linux") {
|
|
8558
|
+
console.log(" beeos device attach --self # (experimental) attach this Mac/Linux desktop directly");
|
|
8559
|
+
}
|
|
7574
8560
|
console.log(" beeos doctor # health check & diagnostics");
|
|
7575
8561
|
console.log("");
|
|
7576
8562
|
}
|
|
7577
8563
|
function prompt(question) {
|
|
7578
|
-
const rl =
|
|
8564
|
+
const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
|
|
7579
8565
|
return new Promise((resolve) => {
|
|
7580
8566
|
rl.question(question, (answer) => {
|
|
7581
8567
|
rl.close();
|
|
@@ -7729,10 +8715,10 @@ async function run8(options) {
|
|
|
7729
8715
|
);
|
|
7730
8716
|
}
|
|
7731
8717
|
if (s.id === `vnc-bridge-${OPENCLAW_VNC_BRIDGE_SERIAL}`) {
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
8718
|
+
const desktopId = inferDesktopPlatformId(process.platform);
|
|
8719
|
+
const platformHints = desktopId === "macos" ? MACOS_DESKTOP_DOCTOR_HINT_LINES : desktopId === "linux" ? LINUX_DESKTOP_DOCTOR_HINT_LINES : desktopId === "windows" ? WINDOWS_DESKTOP_DOCTOR_HINT_LINES : null;
|
|
8720
|
+
if (platformHints) {
|
|
8721
|
+
for (const line of platformHints) hints.push(line);
|
|
7736
8722
|
} else {
|
|
7737
8723
|
hints.push(
|
|
7738
8724
|
`vnc-bridge-${OPENCLAW_VNC_BRIDGE_SERIAL} is failing \u2014 see ${s.logFile} for the underlying error. Common causes: VNC server moved port, BEEOS_VNC_PASSWORD missing/incorrect, network blocking outbound MQTT.`
|
|
@@ -8220,6 +9206,9 @@ deviceCmd.command("attach").description("Attach an ADB-connected device as an AI
|
|
|
8220
9206
|
"--vnc-host <host>",
|
|
8221
9207
|
"Use vnc-bridge against a VNC server at this host (desktop/Linux/macOS). Implies video mode = vnc instead of scrcpy."
|
|
8222
9208
|
).option("--vnc-port <port>", "VNC TCP port (default 5900)", (v) => Number(v)).option("--vnc-password <password>", "VNC password (forwarded via env)").option(
|
|
9209
|
+
"--self",
|
|
9210
|
+
"(experimental) Tag the persisted DeviceEntry with the host's native backend (macOS/Linux/Windows) instead of `adb`. Forward-compatible with the upcoming self-attach lifecycle (Phase 4 of the multi-OS install-link refactor); today only the persisted backend tag changes."
|
|
9211
|
+
).option(
|
|
8223
9212
|
"--agent-gateway-url <url>",
|
|
8224
9213
|
"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."
|
|
8225
9214
|
).action(attach);
|