@beeos-ai/cli 1.0.24 → 1.1.1
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 +884 -118
- package/package.json +1 -1
- package/scripts/install.Tests.ps1 +64 -1
- package/scripts/install.ps1 +173 -12
- package/scripts/install.sh +240 -2
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
|
}
|
|
@@ -3344,6 +3438,75 @@ var init_desktop_detect = __esm({
|
|
|
3344
3438
|
}
|
|
3345
3439
|
});
|
|
3346
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
|
+
|
|
3347
3510
|
// ../core/dist/openclaw/macos-desktop-cold-start.js
|
|
3348
3511
|
async function detectColdStartState() {
|
|
3349
3512
|
const p = getPlatformAdapter();
|
|
@@ -3456,15 +3619,6 @@ async function ensureBeeoHome(p) {
|
|
|
3456
3619
|
await p.mkdir(home);
|
|
3457
3620
|
}
|
|
3458
3621
|
}
|
|
3459
|
-
function generateLegacyVncPassword() {
|
|
3460
|
-
const out = new Array(VNC_PASSWORD_LENGTH);
|
|
3461
|
-
const buf = new Uint8Array(VNC_PASSWORD_LENGTH);
|
|
3462
|
-
globalThis.crypto.getRandomValues(buf);
|
|
3463
|
-
for (let i = 0; i < VNC_PASSWORD_LENGTH; i++) {
|
|
3464
|
-
out[i] = VNC_PASSWORD_CHARSET[buf[i] % VNC_PASSWORD_CHARSET.length];
|
|
3465
|
-
}
|
|
3466
|
-
return out.join("");
|
|
3467
|
-
}
|
|
3468
3622
|
function encodeMacosVncPassword(password) {
|
|
3469
3623
|
const pwBytes = Buffer.alloc(APPLE_VNC_FIXED_KEY.length);
|
|
3470
3624
|
Buffer.from(password, "utf8").copy(pwBytes);
|
|
@@ -3540,15 +3694,14 @@ async function probeArdAllLocalUsers() {
|
|
|
3540
3694
|
function sleep3(ms) {
|
|
3541
3695
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3542
3696
|
}
|
|
3543
|
-
var APPLE_VNC_FIXED_KEY,
|
|
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;
|
|
3544
3698
|
var init_macos_desktop_cold_start = __esm({
|
|
3545
3699
|
"../core/dist/openclaw/macos-desktop-cold-start.js"() {
|
|
3546
3700
|
"use strict";
|
|
3547
3701
|
init_platform_adapter();
|
|
3548
3702
|
init_paths();
|
|
3703
|
+
init_common();
|
|
3549
3704
|
APPLE_VNC_FIXED_KEY = Buffer.from("1734516E8BA8C5E2FF1C39567390ADCA", "hex");
|
|
3550
|
-
VNC_PASSWORD_LENGTH = 8;
|
|
3551
|
-
VNC_PASSWORD_CHARSET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
3552
3705
|
ARD_PLIST_PATH = "/Library/Preferences/com.apple.RemoteManagement.plist";
|
|
3553
3706
|
VNC_SETTINGS_PATH = "/Library/Preferences/com.apple.VNCSettings.txt";
|
|
3554
3707
|
PORT_5900_PROBE_TIMEOUT_MS = 200;
|
|
@@ -3558,6 +3711,655 @@ var init_macos_desktop_cold_start = __esm({
|
|
|
3558
3711
|
}
|
|
3559
3712
|
});
|
|
3560
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
|
+
|
|
3561
4363
|
// ../core/dist/detect.js
|
|
3562
4364
|
async function detectExistingInstall() {
|
|
3563
4365
|
const p = getPlatformAdapter();
|
|
@@ -3788,35 +4590,6 @@ var init_registry = __esm({
|
|
|
3788
4590
|
}
|
|
3789
4591
|
});
|
|
3790
4592
|
|
|
3791
|
-
// ../core/dist/services/target-spec.js
|
|
3792
|
-
function buildVncBridgeTargetSpec(binary, opts) {
|
|
3793
|
-
return {
|
|
3794
|
-
id: `vnc-bridge-${opts.serial}`,
|
|
3795
|
-
kind: "vnc-bridge",
|
|
3796
|
-
command: binary,
|
|
3797
|
-
args: [],
|
|
3798
|
-
env: vncBridgeRuntime.buildEnv(opts),
|
|
3799
|
-
restart: "on-failure",
|
|
3800
|
-
label: `vnc-bridge (${opts.serial})`
|
|
3801
|
-
};
|
|
3802
|
-
}
|
|
3803
|
-
function buildOpenclawDesktopVncBridgeSpec(binary, opts) {
|
|
3804
|
-
return buildVncBridgeTargetSpec(binary, {
|
|
3805
|
-
...opts,
|
|
3806
|
-
serial: OPENCLAW_VNC_BRIDGE_SERIAL
|
|
3807
|
-
});
|
|
3808
|
-
}
|
|
3809
|
-
var OPENCLAW_VNC_BRIDGE_SERIAL;
|
|
3810
|
-
var init_target_spec = __esm({
|
|
3811
|
-
"../core/dist/services/target-spec.js"() {
|
|
3812
|
-
"use strict";
|
|
3813
|
-
init_scrcpy_bridge();
|
|
3814
|
-
init_vnc_bridge();
|
|
3815
|
-
init_spawn_env2();
|
|
3816
|
-
OPENCLAW_VNC_BRIDGE_SERIAL = "openclaw";
|
|
3817
|
-
}
|
|
3818
|
-
});
|
|
3819
|
-
|
|
3820
4593
|
// ../core/dist/services/ids.js
|
|
3821
4594
|
import path from "path";
|
|
3822
4595
|
function safeId(id) {
|
|
@@ -5414,6 +6187,7 @@ var init_dist = __esm({
|
|
|
5414
6187
|
init_agent_status();
|
|
5415
6188
|
init_desktop_detect();
|
|
5416
6189
|
init_macos_desktop_cold_start();
|
|
6190
|
+
init_desktop();
|
|
5417
6191
|
init_detect();
|
|
5418
6192
|
init_registry();
|
|
5419
6193
|
init_target_spec();
|
|
@@ -5936,6 +6710,20 @@ function resolvePerDeviceAgentGatewayUrl(cfg, override) {
|
|
|
5936
6710
|
}
|
|
5937
6711
|
return trimmed;
|
|
5938
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
|
+
}
|
|
5939
6727
|
async function pickAttachSerial() {
|
|
5940
6728
|
const devices = await deviceRuntime.listAdbDevices();
|
|
5941
6729
|
const ready = devices.filter((d) => d.status === "device");
|
|
@@ -6009,7 +6797,10 @@ async function attach(options) {
|
|
|
6009
6797
|
});
|
|
6010
6798
|
const fp = fingerprintFromB64(pubkeyB64);
|
|
6011
6799
|
const boundAt = Math.floor(Date.now() / 1e3);
|
|
6012
|
-
const backend =
|
|
6800
|
+
const backend = resolveBackendForAttach(
|
|
6801
|
+
options,
|
|
6802
|
+
inferHostBackend()
|
|
6803
|
+
);
|
|
6013
6804
|
state.devices.push({
|
|
6014
6805
|
serial,
|
|
6015
6806
|
name,
|
|
@@ -6017,12 +6808,6 @@ async function attach(options) {
|
|
|
6017
6808
|
key_file: keyFile,
|
|
6018
6809
|
http_port: httpPort,
|
|
6019
6810
|
video_mode: bridgeInfo.mode,
|
|
6020
|
-
// For now `beeos device attach` only binds ADB devices (the
|
|
6021
|
-
// `--vnc-host` shape spawns a vnc-bridge but does NOT spawn a
|
|
6022
|
-
// device-mcp-server, so `backend` only ever drives the ADB
|
|
6023
|
-
// path through fleet → mcp). When self-attach for
|
|
6024
|
-
// macOS/Linux/Windows lands, this field will be set
|
|
6025
|
-
// accordingly (`macos`/`linux`/`windows`).
|
|
6026
6811
|
backend,
|
|
6027
6812
|
vnc_host: options.vncHost,
|
|
6028
6813
|
vnc_port: options.vncPort,
|
|
@@ -6262,7 +7047,7 @@ async function runAttachStage2(params) {
|
|
|
6262
7047
|
key_file: keyFile,
|
|
6263
7048
|
http_port: httpPort,
|
|
6264
7049
|
video_mode: bridgeInfo.mode,
|
|
6265
|
-
backend: options
|
|
7050
|
+
backend: resolveBackendForAttach(options, inferHostBackend()),
|
|
6266
7051
|
vnc_host: options.vncHost,
|
|
6267
7052
|
vnc_port: options.vncPort,
|
|
6268
7053
|
// P0-D: see attach() above — every new entry self-pins to the
|
|
@@ -6371,7 +7156,7 @@ function shouldOfferFleetEnable(inputs = {
|
|
|
6371
7156
|
stdoutIsTTY: process.stdout.isTTY,
|
|
6372
7157
|
env: process.env
|
|
6373
7158
|
}) {
|
|
6374
|
-
if (inputs.platform !== "darwin") return false;
|
|
7159
|
+
if (inputs.platform !== "darwin" && inputs.platform !== "linux") return false;
|
|
6375
7160
|
if (inputs.env.BEEOS_NO_FLEET_AUTOSTART === "1") return false;
|
|
6376
7161
|
if (!inputs.stdinIsTTY) return false;
|
|
6377
7162
|
if (!inputs.stdoutIsTTY) return false;
|
|
@@ -7112,23 +7897,24 @@ async function run(agentFramework, options) {
|
|
|
7112
7897
|
const launchWarnings = [...launchOutcome.warnings];
|
|
7113
7898
|
reporter.stop();
|
|
7114
7899
|
const ttyHints = !options.json && process.stdin.isTTY === true;
|
|
7900
|
+
const desktopCapable = hasDesktopCapability(driver);
|
|
7901
|
+
const desktopPipeline = desktopCapable ? getDesktopPipeline() : null;
|
|
7115
7902
|
let coldStart = null;
|
|
7116
|
-
if (
|
|
7903
|
+
if (desktopPipeline) {
|
|
7117
7904
|
const interactive = !options.json && process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
7118
|
-
coldStart = await
|
|
7905
|
+
coldStart = await desktopPipeline.coldStart({
|
|
7119
7906
|
prompt: interactive ? promptYesNo : void 0,
|
|
7120
7907
|
log: options.json ? () => void 0 : (m) => console.log(m),
|
|
7121
|
-
yes: options.force === true
|
|
7122
|
-
noDesktop: process.env.BEEOS_NO_DESKTOP === "1"
|
|
7908
|
+
yes: options.force === true
|
|
7123
7909
|
});
|
|
7124
7910
|
if (coldStart.warnings && coldStart.warnings.length > 0) {
|
|
7125
7911
|
launchWarnings.push(...coldStart.warnings);
|
|
7126
7912
|
}
|
|
7127
7913
|
}
|
|
7128
|
-
const vncEndpoint =
|
|
7914
|
+
const vncEndpoint = desktopPipeline ? await desktopPipeline.probe({ ttyHints }) : null;
|
|
7129
7915
|
const hostname = buildHostname();
|
|
7130
7916
|
const cachedBinding = await loadBindingInfo();
|
|
7131
|
-
const bindVncPassword = vncEndpoint ? await
|
|
7917
|
+
const bindVncPassword = vncEndpoint && desktopPipeline ? await desktopPipeline.readPasswordOptional() : void 0;
|
|
7132
7918
|
const outcome = await bindAgent({
|
|
7133
7919
|
apiUrl: cfg.platform.api_url,
|
|
7134
7920
|
dashboardBaseUrl: cfg.platform.dashboard_base_url,
|
|
@@ -7242,18 +8028,36 @@ async function run(agentFramework, options) {
|
|
|
7242
8028
|
});
|
|
7243
8029
|
}
|
|
7244
8030
|
}
|
|
7245
|
-
if (vncEndpoint && !isOffline) {
|
|
7246
|
-
|
|
7247
|
-
|
|
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);
|
|
7248
8035
|
}
|
|
7249
|
-
const
|
|
8036
|
+
const desktopReporter = new CliReporter();
|
|
8037
|
+
const bridgeOutcome = await driver.desktopBridgeSpec(
|
|
7250
8038
|
vncEndpoint,
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
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
|
+
}
|
|
7257
8061
|
}
|
|
7258
8062
|
const gatewayPid = status2.pid ?? void 0;
|
|
7259
8063
|
emit(options.json, {
|
|
@@ -7267,50 +8071,6 @@ async function run(agentFramework, options) {
|
|
|
7267
8071
|
}, isOffline ? `Agent running (offline, cached instance: ${boundInstanceId})` : `Agent bound to instance: ${boundInstanceId}`);
|
|
7268
8072
|
if (!options.json) printLaunchWarnings(launchWarnings);
|
|
7269
8073
|
}
|
|
7270
|
-
async function tryAttachOpenclawDesktopBridge(params) {
|
|
7271
|
-
const reporter = new CliReporter();
|
|
7272
|
-
let binary = null;
|
|
7273
|
-
try {
|
|
7274
|
-
binary = await vncBridgeRuntime.ensureInstalled(reporter);
|
|
7275
|
-
} catch (e) {
|
|
7276
|
-
reporter.stop();
|
|
7277
|
-
const reason = e instanceof Error ? e.message : String(e);
|
|
7278
|
-
return `vnc-bridge install failed: ${reason}; OpenClaw is bound but desktop streaming is unavailable.`;
|
|
7279
|
-
}
|
|
7280
|
-
reporter.stop();
|
|
7281
|
-
if (!binary) {
|
|
7282
|
-
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.`;
|
|
7283
|
-
}
|
|
7284
|
-
const vncPassword = await readVncPasswordOptional();
|
|
7285
|
-
const spec = buildOpenclawDesktopVncBridgeSpec(binary, {
|
|
7286
|
-
deviceId: params.instanceId,
|
|
7287
|
-
vncHost: params.vncEndpoint.host,
|
|
7288
|
-
vncPort: params.vncEndpoint.port,
|
|
7289
|
-
...vncPassword ? { vncPassword } : {},
|
|
7290
|
-
agentGatewayUrl: params.agentGatewayUrl,
|
|
7291
|
-
bridgePrivateKeyFile: params.keyFile,
|
|
7292
|
-
bridgePublicKeyFile: params.keyFile
|
|
7293
|
-
});
|
|
7294
|
-
try {
|
|
7295
|
-
await params.mgr.install(spec);
|
|
7296
|
-
} catch (e) {
|
|
7297
|
-
const reason = e instanceof Error ? e.message : String(e);
|
|
7298
|
-
return `vnc-bridge service install failed: ${reason}; OpenClaw is bound but desktop streaming is unavailable. Re-run \`beeos start openclaw --force\` to retry.`;
|
|
7299
|
-
}
|
|
7300
|
-
return null;
|
|
7301
|
-
}
|
|
7302
|
-
async function readVncPasswordOptional() {
|
|
7303
|
-
const env = process.env.BEEOS_VNC_PASSWORD?.trim();
|
|
7304
|
-
if (env) return env;
|
|
7305
|
-
const p = getPlatformAdapter();
|
|
7306
|
-
const file = p.joinPath(beeoHome(), "vnc.password");
|
|
7307
|
-
try {
|
|
7308
|
-
const raw = (await p.readFile(file)).trim();
|
|
7309
|
-
return raw || void 0;
|
|
7310
|
-
} catch {
|
|
7311
|
-
return void 0;
|
|
7312
|
-
}
|
|
7313
|
-
}
|
|
7314
8074
|
async function promptYesNo(question) {
|
|
7315
8075
|
return new Promise((resolve) => {
|
|
7316
8076
|
const rl = readline.createInterface({
|
|
@@ -7794,6 +8554,9 @@ function printNextSteps() {
|
|
|
7794
8554
|
console.log("Next steps:");
|
|
7795
8555
|
console.log(" beeos status # show running agents");
|
|
7796
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
|
+
}
|
|
7797
8560
|
console.log(" beeos doctor # health check & diagnostics");
|
|
7798
8561
|
console.log("");
|
|
7799
8562
|
}
|
|
@@ -7952,10 +8715,10 @@ async function run8(options) {
|
|
|
7952
8715
|
);
|
|
7953
8716
|
}
|
|
7954
8717
|
if (s.id === `vnc-bridge-${OPENCLAW_VNC_BRIDGE_SERIAL}`) {
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
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);
|
|
7959
8722
|
} else {
|
|
7960
8723
|
hints.push(
|
|
7961
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.`
|
|
@@ -8443,6 +9206,9 @@ deviceCmd.command("attach").description("Attach an ADB-connected device as an AI
|
|
|
8443
9206
|
"--vnc-host <host>",
|
|
8444
9207
|
"Use vnc-bridge against a VNC server at this host (desktop/Linux/macOS). Implies video mode = vnc instead of scrcpy."
|
|
8445
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(
|
|
8446
9212
|
"--agent-gateway-url <url>",
|
|
8447
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."
|
|
8448
9214
|
).action(attach);
|