@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/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 / connection error on the dashboard:",
3318
- " \u2022 System Settings \u2192 General \u2192 Sharing \u2192 Screen Sharing must be enabled",
3319
- " \u2022 System Settings \u2192 Privacy & Security \u2192 Screen Recording must grant",
3320
- " permission to the macOS Screen Sharing process",
3321
- " \u2022 If a VNC password was set, export BEEOS_VNC_PASSWORD=<password>",
3322
- " before re-running `beeos init`."
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 turned off",
3327
- " \u2022 System Settings \u2192 Privacy & Security \u2192 Screen Recording missing",
3328
- " permission for the macOS Screen Sharing process",
3329
- " \u2022 A VNC password is set; export BEEOS_VNC_PASSWORD=<password>",
3330
- " before `beeos start openclaw --force` to retry."
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 readline from "readline";
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 = readline.createInterface({ input: io.input, output: io.output });
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 readline2 from "readline";
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 = options.vncHost ? void 0 : "adb";
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.vncHost ? void 0 : "adb",
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 = readline2.createInterface({ input: process.stdin, output: process.stdout });
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 = readline2.createInterface({ input: process.stdin, output: process.stdout });
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 vncEndpoint = agentFramework === OPENCLAW_ID ? await probeLocalVnc({ ttyHints }) : null;
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 readVncPasswordOptional() : void 0;
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
- if (vncEndpoint.kind === "macos" && ttyHints) {
7014
- printMacosDesktopHint();
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 desktopWarning = await tryAttachOpenclawDesktopBridge({
8036
+ const desktopReporter = new CliReporter();
8037
+ const bridgeOutcome = await driver.desktopBridgeSpec(
7021
8038
  vncEndpoint,
7022
- instanceId: boundInstanceId,
7023
- keyFile,
7024
- agentGatewayUrl,
7025
- mgr
7026
- });
7027
- if (desktopWarning) launchWarnings.push(desktopWarning);
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 tryAttachOpenclawDesktopBridge(params) {
7042
- const reporter = new CliReporter();
7043
- let binary = null;
7044
- try {
7045
- binary = await vncBridgeRuntime.ensureInstalled(reporter);
7046
- } catch (e) {
7047
- reporter.stop();
7048
- const reason = e instanceof Error ? e.message : String(e);
7049
- return `vnc-bridge install failed: ${reason}; OpenClaw is bound but desktop streaming is unavailable.`;
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 readline3 from "readline";
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 = readline3.createInterface({ input: process.stdin, output: process.stdout });
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
- if (process.platform === "darwin") {
7733
- for (const line of MACOS_DESKTOP_DOCTOR_HINT_LINES) {
7734
- hints.push(line);
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);