@beeos-ai/cli 1.0.18 → 1.0.20

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
@@ -746,26 +746,40 @@ var init_keypair = __esm({
746
746
  });
747
747
 
748
748
  // ../core/dist/platform/client.js
749
+ async function bindHttpError(resp, op) {
750
+ const body = await resp.text().catch(() => "");
751
+ const code = resp.status === 401 || resp.status === 403 ? "platform_unauthorized" : resp.status === 429 ? "platform_rate_limited" : resp.status >= 500 ? "platform_unavailable" : "platform_bind_failed";
752
+ const hint = code === "platform_unauthorized" ? "Re-run `beeos init` and complete the OAuth flow again." : code === "platform_rate_limited" ? "Wait a minute and try again." : code === "platform_unavailable" ? "The platform is having trouble. Retry in a moment; if it persists, check status.beeos.ai." : "Re-run the command. If it still fails, contact support with the http_status.";
753
+ return new BeeosError({
754
+ code,
755
+ message: `Platform ${op} request failed: HTTP ${resp.status}${body ? ` \u2014 ${body.slice(0, 200)}` : ""}`,
756
+ hint,
757
+ details: { http_status: resp.status, op, body: body.slice(0, 1e3) }
758
+ });
759
+ }
749
760
  function buildBindUrl(dashboardBaseUrl, bindId) {
750
761
  const base = dashboardBaseUrl.replace(/\/+$/, "");
751
762
  return `${base}/bind/${bindId}`;
752
763
  }
753
- async function agentBind(apiUrl, publicKey, fingerprint2, agentFramework, hostname) {
764
+ async function agentBind(apiUrl, publicKey, fingerprint2, agentFramework, hostname, osType) {
754
765
  const p = getPlatformAdapter();
755
766
  const url = `${apiUrl}/api/v1/agent/bind`;
767
+ const body = {
768
+ public_key: publicKey,
769
+ fingerprint: fingerprint2,
770
+ agent_framework: agentFramework,
771
+ hostname
772
+ };
773
+ if (osType && osType.length > 0) {
774
+ body.os_type = osType;
775
+ }
756
776
  const resp = await p.fetch(url, {
757
777
  method: "POST",
758
778
  headers: { "Content-Type": "application/json" },
759
- body: JSON.stringify({
760
- public_key: publicKey,
761
- fingerprint: fingerprint2,
762
- agent_framework: agentFramework,
763
- hostname
764
- })
779
+ body: JSON.stringify(body)
765
780
  });
766
781
  if (!resp.ok) {
767
- const text = await resp.text().catch(() => "");
768
- throw new Error(`agent bind failed (${resp.status}): ${text}`);
782
+ throw await bindHttpError(resp, "bind");
769
783
  }
770
784
  return await resp.json();
771
785
  }
@@ -776,7 +790,7 @@ async function pollBind(apiUrl, bindId) {
776
790
  signal: AbortSignal.timeout(35e3)
777
791
  });
778
792
  if (!resp.ok) {
779
- throw new Error(`bind poll failed: ${resp.status}`);
793
+ throw await bindHttpError(resp, "poll");
780
794
  }
781
795
  return await resp.json();
782
796
  }
@@ -811,7 +825,7 @@ async function getBindStatus(apiUrl, bindId) {
811
825
  signal: AbortSignal.timeout(5e3)
812
826
  });
813
827
  if (!resp.ok) {
814
- throw new Error(`bind status check failed: ${resp.status}`);
828
+ throw await bindHttpError(resp, "status");
815
829
  }
816
830
  return await resp.json();
817
831
  }
@@ -820,6 +834,7 @@ var init_client = __esm({
820
834
  "use strict";
821
835
  init_platform_adapter();
822
836
  init_keypair();
837
+ init_errors();
823
838
  }
824
839
  });
825
840
 
@@ -914,7 +929,7 @@ async function bindAgent(opts) {
914
929
  const pollTimeoutMs = opts.pollTimeoutMs ?? 6e5;
915
930
  let resp;
916
931
  try {
917
- resp = await agentBind(opts.apiUrl, opts.publicKey, opts.fingerprint, opts.agentFramework, opts.hostname);
932
+ resp = await agentBind(opts.apiUrl, opts.publicKey, opts.fingerprint, opts.agentFramework, opts.hostname, opts.osType);
918
933
  } catch (e) {
919
934
  if (isNetworkError(e) && opts.cachedBinding && opts.cachedBinding.fingerprint === opts.fingerprint) {
920
935
  return {
@@ -925,9 +940,20 @@ async function bindAgent(opts) {
925
940
  throw e;
926
941
  }
927
942
  if (resp.status === "bound") {
943
+ if (!resp.instance_id) {
944
+ throw new BeeosError({
945
+ code: "platform_protocol_violation",
946
+ message: "Platform returned status='bound' but no instance_id.",
947
+ hint: "This indicates a platform bug. Re-run `beeos init`; if it persists, open a support ticket with the fingerprint below.",
948
+ details: {
949
+ fingerprint: opts.fingerprint,
950
+ response: resp
951
+ }
952
+ });
953
+ }
928
954
  return {
929
955
  status: "bound",
930
- instanceId: resp.instance_id ?? "",
956
+ instanceId: resp.instance_id,
931
957
  source: "already_bound"
932
958
  };
933
959
  }
@@ -939,8 +965,10 @@ async function bindAgent(opts) {
939
965
  return { status: "bound", instanceId, source: "fresh" };
940
966
  }
941
967
  await presentBindUrl(bindUrl, opts.headless ?? false, log);
968
+ const expiryMinutes = Math.round(pollTimeoutMs / 6e4);
969
+ log(` Press Ctrl+C to cancel; the bind session expires in ${expiryMinutes} minutes if not approved.`);
942
970
  try {
943
- const instanceId = await pollUntilBound(opts.apiUrl, bindId, pollTimeoutMs);
971
+ const instanceId = await pollUntilBound(opts.apiUrl, bindId, pollTimeoutMs, log);
944
972
  return { status: "bound", instanceId, source: "fresh" };
945
973
  } catch (e) {
946
974
  if (e instanceof BeeosError && e.code === "bind_pending_expired") {
@@ -980,8 +1008,10 @@ async function presentBindUrl(bindUrl, forceHeadless, log = console.log) {
980
1008
  log(" -----------------------------------------------------");
981
1009
  }
982
1010
  }
983
- async function pollUntilBound(apiUrl, bindId, timeoutMs) {
1011
+ async function pollUntilBound(apiUrl, bindId, timeoutMs, log = () => void 0) {
984
1012
  const deadline = Date.now() + timeoutMs;
1013
+ let lastTick = Date.now();
1014
+ const TICK_INTERVAL_MS = 3e4;
985
1015
  while (true) {
986
1016
  if (Date.now() > deadline) {
987
1017
  throw new BeeosError({
@@ -991,10 +1021,23 @@ async function pollUntilBound(apiUrl, bindId, timeoutMs) {
991
1021
  details: { source: "client_timeout", bindId }
992
1022
  });
993
1023
  }
1024
+ if (Date.now() - lastTick >= TICK_INTERVAL_MS) {
1025
+ const remainingSec = Math.max(0, Math.round((deadline - Date.now()) / 1e3));
1026
+ log(` Still waiting for confirmation... (${remainingSec}s remaining)`);
1027
+ lastTick = Date.now();
1028
+ }
994
1029
  try {
995
1030
  const resp = await pollBind(apiUrl, bindId);
996
1031
  if (resp.status === "bound") {
997
- return resp.instance_id ?? "";
1032
+ if (!resp.instance_id) {
1033
+ throw new BeeosError({
1034
+ code: "platform_protocol_violation",
1035
+ message: "Platform poll returned status='bound' but no instance_id.",
1036
+ hint: "This indicates a platform bug. Re-run `beeos init`; if it persists, open a support ticket with the bind_id below.",
1037
+ details: { bindId, response: resp }
1038
+ });
1039
+ }
1040
+ return resp.instance_id;
998
1041
  }
999
1042
  if (resp.status === "expired") {
1000
1043
  throw new BeeosError({
@@ -1005,7 +1048,7 @@ async function pollUntilBound(apiUrl, bindId, timeoutMs) {
1005
1048
  });
1006
1049
  }
1007
1050
  } catch (e) {
1008
- if (e instanceof BeeosError && e.code === "bind_pending_expired") {
1051
+ if (e instanceof BeeosError && (e.code === "bind_pending_expired" || e.code === "platform_protocol_violation" || e.code === "platform_unauthorized")) {
1009
1052
  throw e;
1010
1053
  }
1011
1054
  await sleep(2e3);
@@ -3205,6 +3248,74 @@ var init_agent_status = __esm({
3205
3248
  }
3206
3249
  });
3207
3250
 
3251
+ // ../core/dist/openclaw/desktop-detect.js
3252
+ function printMacosDesktopHint() {
3253
+ for (const line of MACOS_DESKTOP_HINT_LINES)
3254
+ console.log(line);
3255
+ }
3256
+ function readEnv(name) {
3257
+ return globalThis.process?.env?.[name];
3258
+ }
3259
+ async function probeLocalVnc(opts = {}) {
3260
+ if (readEnv("BEEOS_NO_DESKTOP") === "1")
3261
+ return null;
3262
+ const p = getPlatformAdapter();
3263
+ const platform = p.platform();
3264
+ if (platform !== "darwin" && platform !== "linux") {
3265
+ return null;
3266
+ }
3267
+ const host = "127.0.0.1";
3268
+ const timeoutMs = 200;
3269
+ if (platform === "darwin") {
3270
+ const ok2 = await p.tcpProbe(host, 5900, timeoutMs).catch(() => false);
3271
+ if (ok2) {
3272
+ return { kind: "macos", host, port: 5900, osType: "macos-desktop" };
3273
+ }
3274
+ return null;
3275
+ }
3276
+ const ok = await p.tcpProbe(host, 5901, timeoutMs).catch(() => false);
3277
+ if (ok) {
3278
+ return { kind: "linux", host, port: 5901, osType: "linux-desktop" };
3279
+ }
3280
+ if (opts.ttyHints) {
3281
+ for (const line of LINUX_VNC_HINT_LINES)
3282
+ console.log(line);
3283
+ }
3284
+ return null;
3285
+ }
3286
+ var MACOS_DESKTOP_HINT_LINES, MACOS_DESKTOP_DOCTOR_HINT_LINES, LINUX_VNC_HINT_LINES;
3287
+ var init_desktop_detect = __esm({
3288
+ "../core/dist/openclaw/desktop-detect.js"() {
3289
+ "use strict";
3290
+ init_platform_adapter();
3291
+ MACOS_DESKTOP_HINT_LINES = [
3292
+ "Detected macOS Screen Sharing on :5900. Starting vnc-bridge...",
3293
+ " If desktop view shows a black screen / connection error on the dashboard:",
3294
+ " \u2022 System Settings \u2192 General \u2192 Sharing \u2192 Screen Sharing must be enabled",
3295
+ " \u2022 System Settings \u2192 Privacy & Security \u2192 Screen Recording must grant",
3296
+ " permission to the macOS Screen Sharing process",
3297
+ " \u2022 If a VNC password was set, export BEEOS_VNC_PASSWORD=<password>",
3298
+ " before re-running `beeos init`."
3299
+ ];
3300
+ MACOS_DESKTOP_DOCTOR_HINT_LINES = [
3301
+ "vnc-bridge-openclaw is failing on macOS. Likely causes:",
3302
+ " \u2022 System Settings \u2192 General \u2192 Sharing \u2192 Screen Sharing turned off",
3303
+ " \u2022 System Settings \u2192 Privacy & Security \u2192 Screen Recording missing",
3304
+ " permission for the macOS Screen Sharing process",
3305
+ " \u2022 A VNC password is set; export BEEOS_VNC_PASSWORD=<password>",
3306
+ " before `beeos start openclaw --force` to retry."
3307
+ ];
3308
+ LINUX_VNC_HINT_LINES = [
3309
+ "Tip: no VNC server detected on :5901. To enable BeeOS desktop streaming:",
3310
+ " Debian/Ubuntu: sudo apt install tigervnc-standalone-server",
3311
+ " RHEL/Fedora: sudo dnf install tigervnc-server",
3312
+ " Arch: sudo pacman -S tigervnc",
3313
+ "Then run `vncserver :1` and re-run `beeos start openclaw`.",
3314
+ "Set BEEOS_NO_DESKTOP=1 to suppress this hint."
3315
+ ];
3316
+ }
3317
+ });
3318
+
3208
3319
  // ../core/dist/detect.js
3209
3320
  async function detectExistingInstall() {
3210
3321
  const p = getPlatformAdapter();
@@ -3381,7 +3492,7 @@ function initDecisionLabel(decision) {
3381
3492
  case "rebind":
3382
3493
  return "Re-bind (keep key, refresh binding \u2014 same instance)";
3383
3494
  case "reset-all":
3384
- return "Reset everything (NEW key, NEW instance \u2014 for compromised key)";
3495
+ return "Reset everything (NEW key, NEW instance \u2014 old instance becomes orphaned on dashboard, delete it manually if needed; use this only when the key is compromised)";
3385
3496
  case "skip":
3386
3497
  return "Skip (do nothing)";
3387
3498
  }
@@ -3447,12 +3558,20 @@ function buildVncBridgeTargetSpec(binary, opts) {
3447
3558
  label: `vnc-bridge (${opts.serial})`
3448
3559
  };
3449
3560
  }
3561
+ function buildOpenclawDesktopVncBridgeSpec(binary, opts) {
3562
+ return buildVncBridgeTargetSpec(binary, {
3563
+ ...opts,
3564
+ serial: OPENCLAW_VNC_BRIDGE_SERIAL
3565
+ });
3566
+ }
3567
+ var OPENCLAW_VNC_BRIDGE_SERIAL;
3450
3568
  var init_target_spec = __esm({
3451
3569
  "../core/dist/services/target-spec.js"() {
3452
3570
  "use strict";
3453
3571
  init_scrcpy_bridge();
3454
3572
  init_vnc_bridge();
3455
3573
  init_spawn_env2();
3574
+ OPENCLAW_VNC_BRIDGE_SERIAL = "openclaw";
3456
3575
  }
3457
3576
  });
3458
3577
 
@@ -4953,7 +5072,7 @@ function getCliVersion(moduleUrl, distTag) {
4953
5072
  return { version, distTag: safeTag, display };
4954
5073
  }
4955
5074
  function resolveActiveDistTag() {
4956
- const envTag = readEnv("BEEOS_CLI_TAG");
5075
+ const envTag = readEnv2("BEEOS_CLI_TAG");
4957
5076
  if (envTag && envTag.trim())
4958
5077
  return envTag.trim();
4959
5078
  const baked = readBakedDistTag();
@@ -4961,7 +5080,7 @@ function resolveActiveDistTag() {
4961
5080
  return baked.trim();
4962
5081
  return "latest";
4963
5082
  }
4964
- function readEnv(key) {
5083
+ function readEnv2(key) {
4965
5084
  const env = globalThis.process?.env;
4966
5085
  return env?.[key];
4967
5086
  }
@@ -5051,6 +5170,7 @@ var init_dist = __esm({
5051
5170
  init_driver2();
5052
5171
  init_constants();
5053
5172
  init_agent_status();
5173
+ init_desktop_detect();
5054
5174
  init_detect();
5055
5175
  init_registry();
5056
5176
  init_target_spec();
@@ -6744,8 +6864,10 @@ async function run(agentFramework, options) {
6744
6864
  };
6745
6865
  const launchOutcome = await driver.launch(ctx, reporter);
6746
6866
  const spec = launchOutcome.spec;
6747
- const launchWarnings = launchOutcome.warnings;
6867
+ const launchWarnings = [...launchOutcome.warnings];
6748
6868
  reporter.stop();
6869
+ const ttyHints = !options.json && process.stdin.isTTY === true;
6870
+ const vncEndpoint = agentFramework === OPENCLAW_ID ? await probeLocalVnc({ ttyHints }) : null;
6749
6871
  const hostname = buildHostname();
6750
6872
  const cachedBinding = await loadBindingInfo();
6751
6873
  const outcome = await bindAgent({
@@ -6755,6 +6877,7 @@ async function run(agentFramework, options) {
6755
6877
  fingerprint: fp,
6756
6878
  agentFramework,
6757
6879
  hostname,
6880
+ osType: vncEndpoint?.osType,
6758
6881
  headless: options.browser === false,
6759
6882
  cachedBinding: cachedBinding ? {
6760
6883
  fingerprint: cachedBinding.fingerprint,
@@ -6813,11 +6936,28 @@ async function run(agentFramework, options) {
6813
6936
  const isRebind = !isOffline && cachedBinding !== null && cachedBinding.instance_id !== boundInstanceId;
6814
6937
  const mgr = await getServiceManager();
6815
6938
  maybePrintFallbackBanner(mgr.kind);
6816
- let status2 = await mgr.install(spec);
6939
+ let status2;
6940
+ try {
6941
+ status2 = await mgr.install(spec);
6942
+ } catch (err) {
6943
+ throw wrapServiceInstallFailure(err, {
6944
+ stage: "install",
6945
+ instanceId: boundInstanceId,
6946
+ driverId: driver.id
6947
+ });
6948
+ }
6817
6949
  if (isRebind) {
6818
- await mgr.restart(spec.id);
6819
- const refreshed = await mgr.status(spec.id);
6820
- if (refreshed) status2 = refreshed;
6950
+ try {
6951
+ await mgr.restart(spec.id);
6952
+ const refreshed = await mgr.status(spec.id);
6953
+ if (refreshed) status2 = refreshed;
6954
+ } catch (err) {
6955
+ throw wrapServiceInstallFailure(err, {
6956
+ stage: "restart",
6957
+ instanceId: boundInstanceId,
6958
+ driverId: driver.id
6959
+ });
6960
+ }
6821
6961
  }
6822
6962
  if (spec.healthcheck) {
6823
6963
  try {
@@ -6829,15 +6969,32 @@ async function run(agentFramework, options) {
6829
6969
  throw new BeeosError({
6830
6970
  code: "service_install_failed",
6831
6971
  message: diag.reason,
6832
- hint: diag.hints.length > 0 ? diag.hints.map((h) => `\u2022 ${h}`).join("\n ") : void 0,
6972
+ hint: `Binding to instance ${boundInstanceId} is intact on disk. Re-run \`beeos start ${agentFramework} --force\` to retry from this step. ` + (diag.hints.length > 0 ? "Diagnostics: " + diag.hints.map((h) => `\u2022 ${h}`).join("; ") : ""),
6833
6973
  cause: err,
6834
- details: { driver: driver.id }
6974
+ details: { driver: driver.id, instance_id: boundInstanceId }
6835
6975
  });
6836
6976
  }
6837
6977
  }
6838
- throw err;
6978
+ throw wrapServiceInstallFailure(err, {
6979
+ stage: "healthcheck",
6980
+ instanceId: boundInstanceId,
6981
+ driverId: driver.id
6982
+ });
6839
6983
  }
6840
6984
  }
6985
+ if (vncEndpoint && !isOffline) {
6986
+ if (vncEndpoint.kind === "macos" && ttyHints) {
6987
+ printMacosDesktopHint();
6988
+ }
6989
+ const desktopWarning = await tryAttachOpenclawDesktopBridge({
6990
+ vncEndpoint,
6991
+ instanceId: boundInstanceId,
6992
+ keyFile,
6993
+ agentGatewayUrl,
6994
+ mgr
6995
+ });
6996
+ if (desktopWarning) launchWarnings.push(desktopWarning);
6997
+ }
6841
6998
  const gatewayPid = status2.pid ?? void 0;
6842
6999
  emit(options.json, {
6843
7000
  status: isOffline ? "bound_offline" : "bound",
@@ -6850,11 +7007,52 @@ async function run(agentFramework, options) {
6850
7007
  }, isOffline ? `Agent running (offline, cached instance: ${boundInstanceId})` : `Agent bound to instance: ${boundInstanceId}`);
6851
7008
  if (!options.json) printLaunchWarnings(launchWarnings);
6852
7009
  }
7010
+ async function tryAttachOpenclawDesktopBridge(params) {
7011
+ const reporter = new CliReporter();
7012
+ let binary = null;
7013
+ try {
7014
+ binary = await vncBridgeRuntime.ensureInstalled(reporter);
7015
+ } catch (e) {
7016
+ reporter.stop();
7017
+ const reason = e instanceof Error ? e.message : String(e);
7018
+ return `vnc-bridge install failed: ${reason}; OpenClaw is bound but desktop streaming is unavailable.`;
7019
+ }
7020
+ reporter.stop();
7021
+ if (!binary) {
7022
+ 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.`;
7023
+ }
7024
+ const spec = buildOpenclawDesktopVncBridgeSpec(binary, {
7025
+ deviceId: params.instanceId,
7026
+ vncHost: params.vncEndpoint.host,
7027
+ vncPort: params.vncEndpoint.port,
7028
+ agentGatewayUrl: params.agentGatewayUrl,
7029
+ bridgePrivateKeyFile: params.keyFile,
7030
+ bridgePublicKeyFile: params.keyFile
7031
+ });
7032
+ try {
7033
+ await params.mgr.install(spec);
7034
+ } catch (e) {
7035
+ const reason = e instanceof Error ? e.message : String(e);
7036
+ return `vnc-bridge service install failed: ${reason}; OpenClaw is bound but desktop streaming is unavailable. Re-run \`beeos start openclaw --force\` to retry.`;
7037
+ }
7038
+ return null;
7039
+ }
6853
7040
  function buildHostname() {
6854
7041
  const machine = os6.hostname();
6855
7042
  const user = process.env.USER || process.env.USERNAME || "";
6856
7043
  return user ? `${user}@${machine}` : machine;
6857
7044
  }
7045
+ function wrapServiceInstallFailure(err, ctx) {
7046
+ if (err instanceof BeeosError) return err;
7047
+ const msg = err instanceof Error ? err.message : String(err);
7048
+ return new BeeosError({
7049
+ code: "service_install_failed",
7050
+ message: `Bind succeeded (instance ${ctx.instanceId}) but service ${ctx.stage} failed: ${msg}`,
7051
+ hint: `Local binding.json is intact. Re-run \`beeos start ${ctx.driverId} --force\` to retry from the service-${ctx.stage} step \u2014 you do NOT need to bind again.`,
7052
+ cause: err,
7053
+ details: { stage: ctx.stage, instance_id: ctx.instanceId, driver: ctx.driverId }
7054
+ });
7055
+ }
6858
7056
  function emit(json, payload, human) {
6859
7057
  if (json) {
6860
7058
  emitJsonEnvelope(jsonOk(payload));
@@ -7063,644 +7261,656 @@ init_device2();
7063
7261
  // src/commands/init.ts
7064
7262
  init_dist();
7065
7263
  import readline3 from "readline";
7066
-
7067
- // src/commands/doctor.ts
7068
- init_dist();
7069
- init_json_envelope();
7070
- import fs9 from "fs/promises";
7071
- import path9 from "path";
7072
- var LOG_SIZE_WARN_BYTES = 50 * 1024 * 1024;
7264
+ init_attach();
7265
+ init_progress2();
7266
+ init_fallback_banner();
7267
+ init_instance_picker();
7073
7268
  async function run7(options) {
7074
- const state = await detectExistingInstall();
7075
- const cfg = await loadOrCreateConfig();
7076
- const p = getPlatformAdapter();
7077
- const mgr = await getServiceManager();
7078
- const fallbackReason2 = activeFallbackReason();
7079
- const serviceCheck = await mgr.selfCheck();
7080
- let services = [];
7081
- try {
7082
- services = await mgr.list();
7083
- } catch {
7084
- }
7085
- const resolvedAgentGatewayUrl = resolveAgentGatewayUrl(cfg);
7086
- const agentGatewayHealth = await probeAgentGateway(resolvedAgentGatewayUrl);
7087
- const tools = await collectToolStatus();
7088
- const hasBoundDevices = state.devices.entries.length > 0;
7089
- const shimReport = await collectShimReport(hasBoundDevices);
7090
- const warnings = [];
7091
- const hints = [];
7092
- if (!state.hasIdentity) {
7093
- warnings.push("identity keypair missing \u2014 will be generated on next bind");
7094
- }
7095
- if (state.openclaw.gatewayRunning && !state.openclaw.found) {
7096
- warnings.push("port 18789 is in use but no OpenClaw install detected \u2014 another app may conflict");
7269
+ await ensureDirs();
7270
+ if (options.device) {
7271
+ const { attach: attach2 } = await Promise.resolve().then(() => (init_device2(), device_exports));
7272
+ await attach2({});
7273
+ return;
7097
7274
  }
7098
- if (state.openclaw.found && state.openclaw.pluginInstalled === false) {
7099
- warnings.push(
7100
- `OpenClaw is installed at ${state.openclaw.home ?? "?"} but the beeos-claw plugin is not registered. BeeOS-specific tools may be unavailable to agents until this is fixed.`
7101
- );
7102
- hints.push(
7103
- "re-run `beeos start openclaw --force` to re-install the plugin, or check ~/.beeos/logs/services/openclaw.log for the original failure"
7104
- );
7275
+ const state = await detectExistingInstall();
7276
+ if (!options.json) {
7277
+ printBanner();
7278
+ for (const line of summarizeExistingInstall(state)) {
7279
+ console.log(` ${line}`);
7280
+ }
7281
+ if (state.supervisor.targets.length > 0 || state.supervisor.ipcReachable) {
7282
+ console.log("");
7283
+ console.log(
7284
+ " Legacy Node supervisor state detected. Run `beeos migrate` to convert it to OS-native services."
7285
+ );
7286
+ }
7287
+ console.log("");
7105
7288
  }
7106
- if (state.devices.entries.length > 0 && services.length === 0) {
7107
- warnings.push(
7108
- "devices tracked via legacy devices.json \u2014 no OS services registered. Re-run `beeos device attach` to migrate them to the OS service manager."
7109
- );
7289
+ const decision = await decideAction(state, options);
7290
+ if (decision === "skip") {
7291
+ console.log("Nothing to do. Run `beeos init` again or `beeos device attach` when ready.");
7292
+ return;
7110
7293
  }
7111
- if (state.supervisor.targets.length > 0 || state.supervisor.ipcReachable) {
7112
- warnings.push(
7113
- "legacy Node supervisor state detected \u2014 run `beeos migrate` to convert it to OS-native services."
7114
- );
7294
+ if (decision === "upgrade") {
7295
+ if (await shouldUpgradeBeeosSuite(options)) {
7296
+ const exited = await upgradeAndMaybeExit(options);
7297
+ if (exited) return;
7298
+ }
7299
+ console.log("Ensuring OpenClaw is running...\n");
7115
7300
  }
7116
- if (agentGatewayHealth.status !== "ok") {
7117
- warnings.push(
7118
- `Agent Gateway unreachable at ${resolvedAgentGatewayUrl}: ${agentGatewayHealth.detail}. Binding and task delivery will fail until this resolves.`
7119
- );
7120
- hints.push(
7121
- "check BEEOS_AGENT_GATEWAY_URL / platform.agent_gateway_url in ~/.beeos/config.toml"
7122
- );
7301
+ if (decision === "rebind") {
7302
+ await removeBindingInfo();
7123
7303
  }
7124
- if (fallbackReason2) {
7125
- warnings.push(
7126
- `No native OS service manager available (${fallbackReason2}) \u2014 using fallback, services WON'T persist across reboots.`
7127
- );
7304
+ if (decision === "reset-all") {
7305
+ await rotateIdentity();
7306
+ await removeBindingInfo();
7128
7307
  }
7129
- for (const w of serviceCheck.warnings) warnings.push(w);
7130
- for (const h of serviceCheck.hints) hints.push(h);
7131
- for (const s of services) {
7132
- if (!s.running && s.installed) {
7133
- warnings.push(
7134
- `service '${s.id}' is installed but not running \u2014 see log ${s.logFile}`
7135
- );
7308
+ if (decision === "fresh" && !options.framework && !options.yes && !options.json && process.stdin.isTTY) {
7309
+ const sel = await pickFreshInstance(options);
7310
+ if (sel.kind === "skip") {
7311
+ console.log("Skipped. Re-run `beeos init` or `beeos device attach` whenever ready.");
7312
+ return;
7136
7313
  }
7137
- if (s.lastExitCode !== null && s.lastExitCode !== 0) {
7138
- warnings.push(
7139
- `service '${s.id}' last exited with code ${s.lastExitCode} \u2014 see log ${s.logFile}`
7140
- );
7314
+ if (sel.kind === "device") {
7315
+ await attach({ serial: sel.serial });
7316
+ return;
7317
+ }
7318
+ const descriptor2 = frameworkById(sel.id);
7319
+ if (!descriptor2 || descriptor2.status !== "available") {
7320
+ throw new BeeosError({
7321
+ code: "framework_unavailable",
7322
+ message: `Agent framework '${sel.id}' is not available.`,
7323
+ hint: "Pick a different framework or reinstall the CLI.",
7324
+ details: { requested: sel.id, available: availableFrameworks().map((f) => f.id) }
7325
+ });
7326
+ }
7327
+ await run(descriptor2.id, {
7328
+ force: true,
7329
+ json: options.json,
7330
+ browser: options.browser !== false
7331
+ });
7332
+ if (!options.json && !options.skipServicePrompt) {
7333
+ await maybePromptServiceInstall();
7334
+ }
7335
+ if (!options.json) {
7336
+ printNextSteps();
7141
7337
  }
7338
+ return;
7142
7339
  }
7143
- if (!tools.adb.path) {
7144
- warnings.push(
7145
- "adb not found \u2014 `beeos device attach` will download Android platform-tools on first use"
7146
- );
7340
+ const frameworkId = await decideFramework(state, decision, options);
7341
+ const descriptor = frameworkById(frameworkId);
7342
+ if (!descriptor || descriptor.status !== "available") {
7343
+ const avail = availableFrameworks().map((f) => f.id).join(", ");
7344
+ throw new BeeosError({
7345
+ code: "framework_unavailable",
7346
+ message: `Agent framework '${frameworkId}' is not available. Available: ${avail}.`,
7347
+ hint: avail.length > 0 ? `Re-run with --framework <one-of-the-above>` : "No frameworks are registered \u2014 reinstall the CLI.",
7348
+ details: { requested: frameworkId, available: availableFrameworks().map((f) => f.id) }
7349
+ });
7147
7350
  }
7148
- for (const e of shimReport.entries) {
7149
- if (e.outcome === "outdated") {
7150
- warnings.push(
7151
- `${e.pkg}: installed ${e.installed ?? "(missing)"}, npm latest ${e.latest}.`
7152
- );
7153
- } else if (e.outcome === "missing") {
7154
- warnings.push(`${e.pkg}: not installed globally \u2014 run 'beeos init' to install.`);
7155
- } else if (e.outcome === "error" || e.outcome === "not_installed_yet") {
7156
- }
7351
+ await run(descriptor.id, {
7352
+ force: true,
7353
+ json: options.json,
7354
+ browser: options.browser !== false
7355
+ });
7356
+ if (!options.json && !options.skipServicePrompt) {
7357
+ await maybePromptServiceInstall();
7157
7358
  }
7158
- if (shimReport.entries.some((e) => e.outcome === "outdated" || e.outcome === "missing")) {
7159
- hints.push(
7160
- "run `beeos init` and pick option [1] to upgrade CLI + agents to the latest npm release"
7161
- );
7359
+ if (!options.json) {
7360
+ printNextSteps();
7162
7361
  }
7163
- const bloatedLogs = await checkLogFileSizes();
7164
- for (const l of bloatedLogs) {
7165
- warnings.push(
7166
- `Service log large: ${path9.basename(l.path)} = ${formatBytes(l.size)}`
7167
- );
7168
- hints.push(`truncate safely: : > ${l.path}`);
7362
+ }
7363
+ async function decideAction(state, options) {
7364
+ const choice = inferInitChoices(state);
7365
+ if (choice.options.length === 1) {
7366
+ return choice.defaultDecision;
7169
7367
  }
7170
- if (options.json) {
7171
- emitJsonEnvelope(
7172
- jsonOk({
7173
- platform: p.platform(),
7174
- arch: process.arch,
7175
- node: process.version,
7176
- beeosHome: state.beeosHome,
7177
- config: cfg,
7178
- state,
7179
- serviceManager: {
7180
- kind: mgr.kind,
7181
- available: serviceCheck.available,
7182
- fallbackReason: fallbackReason2,
7183
- services
7184
- },
7185
- agentGateway: agentGatewayHealth,
7186
- tools,
7187
- npmShims: shimReport,
7188
- warnings,
7189
- hints
7190
- })
7191
- );
7192
- return;
7368
+ if (options.yes || options.json) {
7369
+ return choice.defaultDecision;
7193
7370
  }
7371
+ if (!process.stdin.isTTY) {
7372
+ return choice.defaultDecision;
7373
+ }
7374
+ console.log("Detected existing install. Choose an action:");
7375
+ choice.options.forEach((d, i) => {
7376
+ const marker = d === choice.defaultDecision ? "*" : " ";
7377
+ console.log(` ${marker} [${i + 1}] ${initDecisionLabel(d)}`);
7378
+ });
7194
7379
  console.log("");
7195
- console.log(` OS/Arch : ${p.platform()}/${process.arch}`);
7196
- console.log(` Node.js : ${process.version}`);
7197
- console.log(` API URL : ${cfg.platform.api_url}`);
7198
- console.log(
7199
- ` AgentGw : ${resolvedAgentGatewayUrl} [${formatAgentGatewayStatus(agentGatewayHealth)}]`
7200
- );
7201
- console.log("");
7202
- for (const line of summarizeExistingInstall(state)) {
7203
- console.log(` ${line}`);
7380
+ const answer = await prompt(`Choose [1-${choice.options.length}] (default ${choice.options.indexOf(choice.defaultDecision) + 1}): `);
7381
+ const trimmed = answer.trim();
7382
+ if (!trimmed) return choice.defaultDecision;
7383
+ const idx = parseInt(trimmed, 10);
7384
+ if (!Number.isFinite(idx) || idx < 1 || idx > choice.options.length) {
7385
+ return choice.defaultDecision;
7204
7386
  }
7205
- console.log(
7206
- ` Service manager: ${mgr.kind}${fallbackReason2 ? " (fallback)" : ""} \u2014 ${services.length} service(s) registered`
7207
- );
7208
- for (const s of services) {
7209
- const status2 = s.running ? "running" : s.installed ? "stopped" : "uninstalled";
7210
- const pid = s.pid != null ? ` pid=${s.pid}` : "";
7211
- console.log(` - ${s.id.padEnd(28)} ${status2}${pid}`);
7387
+ return choice.options[idx - 1];
7388
+ }
7389
+ async function decideFramework(state, decision, options) {
7390
+ if (options.framework && options.framework.trim()) {
7391
+ return options.framework.trim();
7212
7392
  }
7213
- console.log(` adb : ${tools.adb.path ?? "not found"}`);
7214
- console.log(` scrcpy-bridge: ${tools.scrcpyBridge.path ?? "not installed (auto on attach)"}`);
7215
- console.log(` vnc-bridge : ${tools.vncBridge.path ?? "not installed (auto on --vnc-host)"}`);
7216
- console.log("");
7217
- console.log(" npm shims:");
7218
- for (const e of shimReport.entries) {
7219
- const marker = shimMarker(e.outcome);
7220
- const installed = e.installed ?? "(missing)";
7221
- const latest = e.latest ?? "(unknown)";
7222
- console.log(
7223
- ` ${marker} ${e.pkg.padEnd(34)} installed=${installed.padEnd(8)} npm=${latest}`
7224
- );
7393
+ if (state.binding && decision === "upgrade") {
7394
+ const persisted = state.binding.framework?.trim();
7395
+ if (persisted) return persisted;
7396
+ return defaultFrameworkId();
7225
7397
  }
7226
- console.log("");
7227
- if (warnings.length > 0) {
7228
- console.log(" Warnings:");
7229
- for (const w of warnings) {
7230
- console.log(` - ${w}`);
7231
- }
7232
- console.log("");
7233
- } else {
7234
- console.log(" No warnings \u2014 machine looks good.\n");
7398
+ if (options.yes || options.json || !process.stdin.isTTY) {
7399
+ return defaultFrameworkId();
7235
7400
  }
7236
- if (hints.length > 0) {
7237
- console.log(" Hints:");
7238
- for (const h of hints) {
7239
- console.log(` $ ${h}`);
7401
+ const all = listFrameworks();
7402
+ const avail = availableFrameworks();
7403
+ if (avail.length <= 1) {
7404
+ const only = avail[0];
7405
+ if (only) {
7406
+ console.log(`Installing ${only.displayName} (only available framework).`);
7407
+ return only.id;
7240
7408
  }
7241
- console.log("");
7409
+ return defaultFrameworkId();
7410
+ }
7411
+ console.log("");
7412
+ console.log("Choose agent framework:");
7413
+ const def = defaultFrameworkId();
7414
+ all.forEach((f, i) => {
7415
+ const marker = f.id === def ? "*" : " ";
7416
+ const tag = f.status === "coming-soon" ? " [coming soon]" : "";
7417
+ const padName = f.displayName.padEnd(12);
7418
+ console.log(` ${marker} [${i + 1}] ${padName} ${f.description}${tag}`);
7419
+ });
7420
+ console.log("");
7421
+ const defaultIdx = all.findIndex((f) => f.id === def) + 1;
7422
+ const answer = await prompt(`Choose [1-${all.length}] (default ${defaultIdx}): `);
7423
+ const trimmed = answer.trim();
7424
+ if (!trimmed) return def;
7425
+ const idx = parseInt(trimmed, 10);
7426
+ if (!Number.isFinite(idx) || idx < 1 || idx > all.length) {
7427
+ return def;
7428
+ }
7429
+ const picked = all[idx - 1];
7430
+ if (picked.status !== "available") {
7431
+ console.log(
7432
+ ` ${picked.displayName} is not yet available. Falling back to ${def}.`
7433
+ );
7434
+ return def;
7242
7435
  }
7436
+ return picked.id;
7243
7437
  }
7244
- async function collectToolStatus() {
7245
- const [adbPath, scrcpyPath, vncPath] = await Promise.all([
7246
- findAdb().catch(() => null),
7247
- scrcpyBridgeRuntime.findBinary().catch(() => null),
7248
- vncBridgeRuntime.findBinary().catch(() => null)
7249
- ]);
7250
- return {
7251
- adb: { path: adbPath },
7252
- scrcpyBridge: { path: scrcpyPath },
7253
- vncBridge: { path: vncPath }
7254
- };
7438
+ async function safeListAdbDevices() {
7439
+ const adbPath = await findAdb().catch(() => null);
7440
+ if (!adbPath) return [];
7441
+ try {
7442
+ return await deviceRuntime.listAdbDevices();
7443
+ } catch {
7444
+ return [];
7445
+ }
7255
7446
  }
7256
- async function collectShimReport(hasBoundDevices) {
7257
- const sources = readPinSourcesFromEnv();
7258
- const deviceSuite = /* @__PURE__ */ new Set([
7259
- NPM_PKGS.DEVICE_AGENT,
7260
- NPM_PKGS.DEVICE_MCP_SERVER
7261
- ]);
7262
- const entries = await Promise.all(
7263
- ALL_BEEOS_PKGS.map(async (pkg) => {
7264
- const spec = resolveInstallSpec(pkg, sources);
7265
- const [installed, latest] = await Promise.all([
7266
- npmGlobalVersion(pkg).catch(() => null),
7267
- npmRegistryVersion(spec).catch(() => null)
7268
- ]);
7269
- let outcome;
7270
- if (latest === null) outcome = "error";
7271
- else if (installed === null) {
7272
- outcome = deviceSuite.has(pkg) && !hasBoundDevices ? "not_installed_yet" : "missing";
7273
- } else if (installed === latest) outcome = "ok";
7274
- else outcome = "outdated";
7275
- if (pkg === NPM_PKGS.CLI && installed === null) {
7276
- outcome = "missing";
7277
- }
7278
- return { pkg, installed, latest, spec, outcome };
7279
- })
7280
- );
7281
- return { entries };
7447
+ async function pickFreshInstance(_opts) {
7448
+ const frameworks = availableFrameworks().map((f) => ({
7449
+ kind: "framework",
7450
+ id: f.id,
7451
+ label: f.displayName,
7452
+ description: f.description,
7453
+ isDefault: f.id === defaultFrameworkId()
7454
+ }));
7455
+ const devices = (await safeListAdbDevices()).map((d) => ({
7456
+ kind: "device",
7457
+ serial: d.serial,
7458
+ status: d.status
7459
+ }));
7460
+ return pickInstanceInteractive([...frameworks, ...devices], {
7461
+ caller: "init"
7462
+ });
7282
7463
  }
7283
- function shimMarker(outcome) {
7284
- switch (outcome) {
7285
- case "ok":
7286
- return "\u2713";
7287
- case "outdated":
7288
- return "\u2191";
7289
- case "missing":
7290
- return "\u2717";
7291
- case "not_installed_yet":
7292
- return "\xB7";
7293
- case "error":
7294
- return "?";
7464
+ async function maybePromptServiceInstall() {
7465
+ try {
7466
+ const mgr = await getServiceManager();
7467
+ maybePrintFallbackBanner(mgr.kind);
7468
+ const check = await mgr.selfCheck();
7469
+ if (!check.available) {
7470
+ console.log(` \u26A0 Service manager '${mgr.kind}' is not fully available \u2014 persistence limited.`);
7471
+ }
7472
+ for (const w of check.warnings) console.log(` ! ${w}`);
7473
+ for (const h of check.hints) console.log(` $ ${h}`);
7474
+ } catch (e) {
7475
+ console.log(` (service-manager self-check skipped: ${e instanceof Error ? e.message : String(e)})`);
7295
7476
  }
7296
7477
  }
7297
- async function checkLogFileSizes() {
7298
- const dir = path9.join(beeoHome(), "logs", "services");
7299
- let entries;
7478
+ async function rotateIdentity() {
7479
+ const p = getPlatformAdapter();
7480
+ const home = beeoHome();
7481
+ const identityDir = p.joinPath(home, "identity");
7482
+ const keypair = p.joinPath(identityDir, "keypair.json");
7483
+ const fp = p.joinPath(identityDir, "fingerprint");
7484
+ if (!await p.exists(keypair)) return;
7485
+ const ts = Math.floor(Date.now() / 1e3);
7486
+ const backup = p.joinPath(identityDir, `keypair.json.backup-${ts}`);
7300
7487
  try {
7301
- entries = await fs9.readdir(dir);
7488
+ await p.copyFile(keypair, backup);
7302
7489
  } catch {
7303
- return [];
7304
7490
  }
7305
- const bloated = [];
7306
- for (const name of entries) {
7307
- if (!name.endsWith(".log")) continue;
7308
- const full = path9.join(dir, name);
7309
- try {
7310
- const st = await fs9.stat(full);
7311
- if (st.isFile() && st.size >= LOG_SIZE_WARN_BYTES) {
7312
- bloated.push({ path: full, size: st.size });
7313
- }
7314
- } catch {
7315
- }
7491
+ try {
7492
+ await p.rm(keypair);
7493
+ } catch {
7316
7494
  }
7317
- return bloated;
7495
+ try {
7496
+ await p.rm(fp);
7497
+ } catch {
7498
+ }
7499
+ console.log(`Rotated identity. Previous keypair saved at ${backup}`);
7318
7500
  }
7319
- function formatBytes(n) {
7320
- if (n >= 1024 * 1024 * 1024) return `${(n / (1024 * 1024 * 1024)).toFixed(1)}GB`;
7321
- if (n >= 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(0)}MB`;
7322
- if (n >= 1024) return `${(n / 1024).toFixed(0)}KB`;
7323
- return `${n}B`;
7501
+ function printBanner() {
7502
+ console.log("");
7503
+ console.log(" BeeOS init");
7504
+ console.log("");
7324
7505
  }
7325
- async function probeAgentGateway(baseUrl) {
7326
- const url = joinHealthzUrl(baseUrl);
7327
- const started = Date.now();
7328
- const ac = new AbortController();
7329
- const timer = setTimeout(() => ac.abort(), 3e3);
7330
- try {
7331
- const res = await fetch(url, {
7332
- method: "GET",
7333
- signal: ac.signal,
7334
- headers: { accept: "application/json, text/plain;q=0.9, */*;q=0.1" }
7335
- });
7336
- const latency = Date.now() - started;
7337
- if (res.ok) {
7338
- return {
7339
- url,
7340
- status: "ok",
7341
- httpStatus: res.status,
7342
- latencyMs: latency,
7343
- detail: `HTTP ${res.status} in ${latency}ms`
7344
- };
7345
- }
7346
- return {
7347
- url,
7348
- status: "unhealthy",
7349
- httpStatus: res.status,
7350
- latencyMs: latency,
7351
- detail: `HTTP ${res.status} in ${latency}ms`
7352
- };
7353
- } catch (err) {
7354
- const latency = Date.now() - started;
7355
- const msg = err instanceof Error ? err.message : String(err);
7356
- const timedOut = ac.signal.aborted;
7357
- return {
7358
- url,
7359
- status: "unreachable",
7360
- httpStatus: null,
7361
- latencyMs: latency,
7362
- detail: timedOut ? "timeout after 3s" : msg
7363
- };
7364
- } finally {
7365
- clearTimeout(timer);
7366
- }
7506
+ function printNextSteps() {
7507
+ console.log("");
7508
+ console.log("Next steps:");
7509
+ console.log(" beeos status # show running agents");
7510
+ console.log(" beeos device attach # bind a physical Android device");
7511
+ console.log(" beeos doctor # health check & diagnostics");
7512
+ console.log("");
7367
7513
  }
7368
- function joinHealthzUrl(base) {
7369
- const trimmed = base.replace(/\/+$/, "");
7370
- return `${trimmed}/healthz`;
7514
+ function prompt(question) {
7515
+ const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
7516
+ return new Promise((resolve) => {
7517
+ rl.question(question, (answer) => {
7518
+ rl.close();
7519
+ resolve(answer);
7520
+ });
7521
+ });
7371
7522
  }
7372
- function formatAgentGatewayStatus(h) {
7373
- if (h.status === "ok") return `ok ${h.latencyMs}ms`;
7374
- if (h.status === "unhealthy") return `HTTP ${h.httpStatus ?? "?"}`;
7375
- return h.detail;
7523
+ async function shouldUpgradeBeeosSuite(options) {
7524
+ if (options.headless) return false;
7525
+ if (process.env.BEEOS_INIT_SKIP_NPM_UPGRADE === "1") return false;
7526
+ return true;
7376
7527
  }
7377
-
7378
- // src/commands/init.ts
7379
- init_attach();
7380
- init_progress2();
7381
- init_fallback_banner();
7382
- init_instance_picker();
7383
- async function run8(options) {
7384
- await ensureDirs();
7385
- if (options.device) {
7386
- const { attach: attach2 } = await Promise.resolve().then(() => (init_device2(), device_exports));
7387
- await attach2({});
7388
- return;
7389
- }
7390
- const state = await detectExistingInstall();
7528
+ async function upgradeAndMaybeExit(options) {
7391
7529
  if (options.json) {
7392
- await run7({ json: true });
7393
- } else {
7394
- printBanner();
7395
- for (const line of summarizeExistingInstall(state)) {
7396
- console.log(` ${line}`);
7397
- }
7398
- if (state.supervisor.targets.length > 0 || state.supervisor.ipcReachable) {
7399
- console.log("");
7400
- console.log(
7401
- " Legacy Node supervisor state detected. Run `beeos migrate` to convert it to OS-native services."
7402
- );
7403
- }
7404
- console.log("");
7405
- }
7406
- const decision = await decideAction(state, options);
7407
- if (decision === "skip") {
7408
- console.log("Nothing to do. Run `beeos init` again or `beeos device attach` when ready.");
7409
- return;
7530
+ return false;
7410
7531
  }
7411
- if (decision === "upgrade") {
7412
- if (await shouldUpgradeBeeosSuite(options)) {
7413
- const exited = await upgradeAndMaybeExit(options);
7414
- if (exited) return;
7415
- }
7416
- console.log("Ensuring OpenClaw is running...\n");
7532
+ const packages = await pickInstalledPackages();
7533
+ console.log("Checking for newer @beeos-ai/cli on npm...");
7534
+ const reporter = new CliReporter();
7535
+ let outcome;
7536
+ try {
7537
+ outcome = await upgradeBeeosSuite({
7538
+ packages,
7539
+ progress: reporter
7540
+ });
7541
+ } catch (e) {
7542
+ reporter.stop();
7543
+ console.log(
7544
+ ` (upgrade skipped \u2014 ${e instanceof Error ? e.message : String(e)})
7545
+ Tip: re-run \`npm install -g @beeos-ai/cli\` manually. The device
7546
+ suite (device-agent + device-mcp-server) will be auto-installed
7547
+ on the next \`beeos device attach\`.`
7548
+ );
7549
+ return false;
7417
7550
  }
7418
- if (decision === "rebind") {
7419
- await removeBindingInfo();
7551
+ reporter.stop();
7552
+ if (outcome.anyFailed) {
7553
+ const failed = outcome.packages.find((p) => p.failed)?.failed ?? "unknown error";
7554
+ console.log(` \u26A0 npm install failed: ${failed}`);
7555
+ console.log(
7556
+ " Manual fix:\n npm i -g @beeos-ai/cli\n (the device suite is auto-installed on `beeos device attach`)\n"
7557
+ );
7558
+ return false;
7420
7559
  }
7421
- if (decision === "reset-all") {
7422
- await rotateIdentity();
7423
- await removeBindingInfo();
7560
+ if (!outcome.anyChanged) {
7561
+ console.log(" All BeeOS npm packages are up to date.\n");
7562
+ return false;
7424
7563
  }
7425
- if (decision === "fresh" && !options.framework && !options.yes && !options.json && process.stdin.isTTY) {
7426
- const sel = await pickFreshInstance(options);
7427
- if (sel.kind === "skip") {
7428
- console.log("Skipped. Re-run `beeos init` or `beeos device attach` whenever ready.");
7429
- return;
7430
- }
7431
- if (sel.kind === "device") {
7432
- await attach({ serial: sel.serial });
7433
- return;
7434
- }
7435
- const descriptor2 = frameworkById(sel.id);
7436
- if (!descriptor2 || descriptor2.status !== "available") {
7437
- throw new BeeosError({
7438
- code: "framework_unavailable",
7439
- message: `Agent framework '${sel.id}' is not available.`,
7440
- hint: "Pick a different framework or reinstall the CLI.",
7441
- details: { requested: sel.id, available: availableFrameworks().map((f) => f.id) }
7442
- });
7443
- }
7444
- await run(descriptor2.id, {
7445
- force: true,
7446
- json: options.json,
7447
- browser: options.browser !== false
7448
- });
7449
- if (!options.json && !options.skipServicePrompt) {
7450
- await maybePromptServiceInstall();
7451
- }
7452
- if (!options.json) {
7453
- printNextSteps();
7564
+ for (const pkg of outcome.packages) {
7565
+ if (pkg.changed) {
7566
+ console.log(` ${pkg.pkg}: ${pkg.before ?? "(none)"} \u2192 ${pkg.after}`);
7454
7567
  }
7455
- return;
7456
7568
  }
7457
- const frameworkId = await decideFramework(state, decision, options);
7458
- const descriptor = frameworkById(frameworkId);
7459
- if (!descriptor || descriptor.status !== "available") {
7460
- const avail = availableFrameworks().map((f) => f.id).join(", ");
7461
- throw new BeeosError({
7462
- code: "framework_unavailable",
7463
- message: `Agent framework '${frameworkId}' is not available. Available: ${avail}.`,
7464
- hint: avail.length > 0 ? `Re-run with --framework <one-of-the-above>` : "No frameworks are registered \u2014 reinstall the CLI.",
7465
- details: { requested: frameworkId, available: availableFrameworks().map((f) => f.id) }
7466
- });
7467
- }
7468
- await run(descriptor.id, {
7469
- force: true,
7470
- json: options.json,
7471
- browser: options.browser !== false
7472
- });
7473
- if (!options.json && !options.skipServicePrompt) {
7474
- await maybePromptServiceInstall();
7569
+ const cliEntry = outcome.packages.find((p) => p.pkg === NPM_PKGS.CLI);
7570
+ if (cliEntry?.changed) {
7571
+ console.log("");
7572
+ console.log(" CLI itself was upgraded \u2014 please re-run `beeos init` to continue");
7573
+ console.log(" with the new code. The current process is still running the old version.");
7574
+ console.log("");
7575
+ return true;
7475
7576
  }
7476
- if (!options.json) {
7477
- printNextSteps();
7577
+ console.log(" Agents updated. Continuing init...\n");
7578
+ return false;
7579
+ }
7580
+ async function pickInstalledPackages() {
7581
+ const packages = [NPM_PKGS.CLI];
7582
+ for (const pkg of [NPM_PKGS.DEVICE_AGENT, NPM_PKGS.DEVICE_MCP_SERVER]) {
7583
+ const installed = await npmGlobalVersion(pkg).catch(() => null);
7584
+ if (installed !== null) packages.push(pkg);
7478
7585
  }
7586
+ return packages;
7479
7587
  }
7480
- async function decideAction(state, options) {
7481
- const choice = inferInitChoices(state);
7482
- if (choice.options.length === 1) {
7483
- return choice.defaultDecision;
7588
+
7589
+ // src/commands/doctor.ts
7590
+ init_dist();
7591
+ init_json_envelope();
7592
+ import fs9 from "fs/promises";
7593
+ import path9 from "path";
7594
+ var LOG_SIZE_WARN_BYTES = 50 * 1024 * 1024;
7595
+ async function run8(options) {
7596
+ const state = await detectExistingInstall();
7597
+ const cfg = await loadOrCreateConfig();
7598
+ const p = getPlatformAdapter();
7599
+ const mgr = await getServiceManager();
7600
+ const fallbackReason2 = activeFallbackReason();
7601
+ const serviceCheck = await mgr.selfCheck();
7602
+ let services = [];
7603
+ try {
7604
+ services = await mgr.list();
7605
+ } catch {
7484
7606
  }
7485
- if (options.yes || options.json) {
7486
- return choice.defaultDecision;
7607
+ const resolvedAgentGatewayUrl = resolveAgentGatewayUrl(cfg);
7608
+ const agentGatewayHealth = await probeAgentGateway(resolvedAgentGatewayUrl);
7609
+ const tools = await collectToolStatus();
7610
+ const hasBoundDevices = state.devices.entries.length > 0;
7611
+ const shimReport = await collectShimReport(hasBoundDevices);
7612
+ const warnings = [];
7613
+ const hints = [];
7614
+ if (!state.hasIdentity) {
7615
+ warnings.push("identity keypair missing \u2014 will be generated on next bind");
7487
7616
  }
7488
- if (!process.stdin.isTTY) {
7489
- return choice.defaultDecision;
7617
+ if (state.openclaw.gatewayRunning && !state.openclaw.found) {
7618
+ warnings.push("port 18789 is in use but no OpenClaw install detected \u2014 another app may conflict");
7490
7619
  }
7491
- console.log("Detected existing install. Choose an action:");
7492
- choice.options.forEach((d, i) => {
7493
- const marker = d === choice.defaultDecision ? "*" : " ";
7494
- console.log(` ${marker} [${i + 1}] ${initDecisionLabel(d)}`);
7495
- });
7496
- console.log("");
7497
- const answer = await prompt(`Choose [1-${choice.options.length}] (default ${choice.options.indexOf(choice.defaultDecision) + 1}): `);
7498
- const trimmed = answer.trim();
7499
- if (!trimmed) return choice.defaultDecision;
7500
- const idx = parseInt(trimmed, 10);
7501
- if (!Number.isFinite(idx) || idx < 1 || idx > choice.options.length) {
7502
- return choice.defaultDecision;
7620
+ if (state.openclaw.found && state.openclaw.pluginInstalled === false) {
7621
+ warnings.push(
7622
+ `OpenClaw is installed at ${state.openclaw.home ?? "?"} but the beeos-claw plugin is not registered. BeeOS-specific tools may be unavailable to agents until this is fixed.`
7623
+ );
7624
+ hints.push(
7625
+ "re-run `beeos start openclaw --force` to re-install the plugin, or check ~/.beeos/logs/services/openclaw.log for the original failure"
7626
+ );
7503
7627
  }
7504
- return choice.options[idx - 1];
7505
- }
7506
- async function decideFramework(state, decision, options) {
7507
- if (options.framework && options.framework.trim()) {
7508
- return options.framework.trim();
7628
+ if (state.devices.entries.length > 0 && services.length === 0) {
7629
+ warnings.push(
7630
+ "devices tracked via legacy devices.json \u2014 no OS services registered. Re-run `beeos device attach` to migrate them to the OS service manager."
7631
+ );
7509
7632
  }
7510
- if (state.binding && decision === "upgrade") {
7511
- const persisted = state.binding.framework?.trim();
7512
- if (persisted) return persisted;
7513
- return defaultFrameworkId();
7633
+ if (state.supervisor.targets.length > 0 || state.supervisor.ipcReachable) {
7634
+ warnings.push(
7635
+ "legacy Node supervisor state detected \u2014 run `beeos migrate` to convert it to OS-native services."
7636
+ );
7514
7637
  }
7515
- if (options.yes || options.json || !process.stdin.isTTY) {
7516
- return defaultFrameworkId();
7638
+ if (agentGatewayHealth.status !== "ok") {
7639
+ warnings.push(
7640
+ `Agent Gateway unreachable at ${resolvedAgentGatewayUrl}: ${agentGatewayHealth.detail}. Binding and task delivery will fail until this resolves.`
7641
+ );
7642
+ hints.push(
7643
+ "check BEEOS_AGENT_GATEWAY_URL / platform.agent_gateway_url in ~/.beeos/config.toml"
7644
+ );
7517
7645
  }
7518
- const all = listFrameworks();
7519
- const avail = availableFrameworks();
7520
- if (avail.length <= 1) {
7521
- const only = avail[0];
7522
- if (only) {
7523
- console.log(`Installing ${only.displayName} (only available framework).`);
7524
- return only.id;
7646
+ if (fallbackReason2) {
7647
+ warnings.push(
7648
+ `No native OS service manager available (${fallbackReason2}) \u2014 using fallback, services WON'T persist across reboots.`
7649
+ );
7650
+ }
7651
+ for (const w of serviceCheck.warnings) warnings.push(w);
7652
+ for (const h of serviceCheck.hints) hints.push(h);
7653
+ for (const s of services) {
7654
+ if (!s.running && s.installed) {
7655
+ warnings.push(
7656
+ `service '${s.id}' is installed but not running \u2014 see log ${s.logFile}`
7657
+ );
7658
+ }
7659
+ if (s.lastExitCode !== null && s.lastExitCode !== 0) {
7660
+ warnings.push(
7661
+ `service '${s.id}' last exited with code ${s.lastExitCode} \u2014 see log ${s.logFile}`
7662
+ );
7663
+ if (s.lastExitCode === 127) {
7664
+ hints.push(
7665
+ `service '${s.id}' looks like its Node binary moved (exit 127). Re-run \`beeos start ${s.id} --force\` to refresh the service definition with the current node path.`
7666
+ );
7667
+ }
7668
+ if (s.id === `vnc-bridge-${OPENCLAW_VNC_BRIDGE_SERIAL}`) {
7669
+ if (process.platform === "darwin") {
7670
+ for (const line of MACOS_DESKTOP_DOCTOR_HINT_LINES) {
7671
+ hints.push(line);
7672
+ }
7673
+ } else {
7674
+ hints.push(
7675
+ `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.`
7676
+ );
7677
+ }
7678
+ }
7679
+ }
7680
+ }
7681
+ if (!tools.adb.path) {
7682
+ warnings.push(
7683
+ "adb not found \u2014 `beeos device attach` will download Android platform-tools on first use"
7684
+ );
7685
+ }
7686
+ for (const e of shimReport.entries) {
7687
+ if (e.outcome === "outdated") {
7688
+ warnings.push(
7689
+ `${e.pkg}: installed ${e.installed ?? "(missing)"}, npm latest ${e.latest}.`
7690
+ );
7691
+ } else if (e.outcome === "missing") {
7692
+ warnings.push(`${e.pkg}: not installed globally \u2014 run 'beeos init' to install.`);
7693
+ } else if (e.outcome === "error" || e.outcome === "not_installed_yet") {
7525
7694
  }
7526
- return defaultFrameworkId();
7695
+ }
7696
+ if (shimReport.entries.some((e) => e.outcome === "outdated" || e.outcome === "missing")) {
7697
+ hints.push(
7698
+ "run `beeos init` and pick option [1] to upgrade CLI + agents to the latest npm release"
7699
+ );
7700
+ }
7701
+ const bloatedLogs = await checkLogFileSizes();
7702
+ for (const l of bloatedLogs) {
7703
+ warnings.push(
7704
+ `Service log large: ${path9.basename(l.path)} = ${formatBytes(l.size)}`
7705
+ );
7706
+ hints.push(`truncate safely: : > ${l.path}`);
7707
+ }
7708
+ if (options.json) {
7709
+ emitJsonEnvelope(
7710
+ jsonOk({
7711
+ platform: p.platform(),
7712
+ arch: process.arch,
7713
+ node: process.version,
7714
+ beeosHome: state.beeosHome,
7715
+ config: cfg,
7716
+ state,
7717
+ serviceManager: {
7718
+ kind: mgr.kind,
7719
+ available: serviceCheck.available,
7720
+ fallbackReason: fallbackReason2,
7721
+ services
7722
+ },
7723
+ agentGateway: agentGatewayHealth,
7724
+ tools,
7725
+ npmShims: shimReport,
7726
+ warnings,
7727
+ hints
7728
+ })
7729
+ );
7730
+ return;
7527
7731
  }
7528
7732
  console.log("");
7529
- console.log("Choose agent framework:");
7530
- const def = defaultFrameworkId();
7531
- all.forEach((f, i) => {
7532
- const marker = f.id === def ? "*" : " ";
7533
- const tag = f.status === "coming-soon" ? " [coming soon]" : "";
7534
- const padName = f.displayName.padEnd(12);
7535
- console.log(` ${marker} [${i + 1}] ${padName} ${f.description}${tag}`);
7536
- });
7733
+ console.log(` OS/Arch : ${p.platform()}/${process.arch}`);
7734
+ console.log(` Node.js : ${process.version}`);
7735
+ console.log(` API URL : ${cfg.platform.api_url}`);
7736
+ console.log(
7737
+ ` AgentGw : ${resolvedAgentGatewayUrl} [${formatAgentGatewayStatus(agentGatewayHealth)}]`
7738
+ );
7537
7739
  console.log("");
7538
- const defaultIdx = all.findIndex((f) => f.id === def) + 1;
7539
- const answer = await prompt(`Choose [1-${all.length}] (default ${defaultIdx}): `);
7540
- const trimmed = answer.trim();
7541
- if (!trimmed) return def;
7542
- const idx = parseInt(trimmed, 10);
7543
- if (!Number.isFinite(idx) || idx < 1 || idx > all.length) {
7544
- return def;
7740
+ for (const line of summarizeExistingInstall(state)) {
7741
+ console.log(` ${line}`);
7545
7742
  }
7546
- const picked = all[idx - 1];
7547
- if (picked.status !== "available") {
7743
+ console.log(
7744
+ ` Service manager: ${mgr.kind}${fallbackReason2 ? " (fallback)" : ""} \u2014 ${services.length} service(s) registered`
7745
+ );
7746
+ for (const s of services) {
7747
+ const status2 = s.running ? "running" : s.installed ? "stopped" : "uninstalled";
7748
+ const pid = s.pid != null ? ` pid=${s.pid}` : "";
7749
+ console.log(` - ${s.id.padEnd(28)} ${status2}${pid}`);
7750
+ }
7751
+ console.log(` adb : ${tools.adb.path ?? "not found"}`);
7752
+ console.log(` scrcpy-bridge: ${tools.scrcpyBridge.path ?? "not installed (auto on attach)"}`);
7753
+ console.log(` vnc-bridge : ${tools.vncBridge.path ?? "not installed (auto on --vnc-host)"}`);
7754
+ console.log("");
7755
+ console.log(" npm shims:");
7756
+ for (const e of shimReport.entries) {
7757
+ const marker = shimMarker(e.outcome);
7758
+ const installed = e.installed ?? "(missing)";
7759
+ const latest = e.latest ?? "(unknown)";
7548
7760
  console.log(
7549
- ` ${picked.displayName} is not yet available. Falling back to ${def}.`
7761
+ ` ${marker} ${e.pkg.padEnd(34)} installed=${installed.padEnd(8)} npm=${latest}`
7550
7762
  );
7551
- return def;
7552
7763
  }
7553
- return picked.id;
7764
+ console.log("");
7765
+ if (warnings.length > 0) {
7766
+ console.log(" Warnings:");
7767
+ for (const w of warnings) {
7768
+ console.log(` - ${w}`);
7769
+ }
7770
+ console.log("");
7771
+ } else {
7772
+ console.log(" No warnings \u2014 machine looks good.\n");
7773
+ }
7774
+ if (hints.length > 0) {
7775
+ console.log(" Hints:");
7776
+ for (const h of hints) {
7777
+ console.log(` $ ${h}`);
7778
+ }
7779
+ console.log("");
7780
+ }
7554
7781
  }
7555
- async function safeListAdbDevices() {
7556
- const adbPath = await findAdb().catch(() => null);
7557
- if (!adbPath) return [];
7558
- try {
7559
- return await deviceRuntime.listAdbDevices();
7560
- } catch {
7561
- return [];
7562
- }
7782
+ async function collectToolStatus() {
7783
+ const [adbPath, scrcpyPath, vncPath] = await Promise.all([
7784
+ findAdb().catch(() => null),
7785
+ scrcpyBridgeRuntime.findBinary().catch(() => null),
7786
+ vncBridgeRuntime.findBinary().catch(() => null)
7787
+ ]);
7788
+ return {
7789
+ adb: { path: adbPath },
7790
+ scrcpyBridge: { path: scrcpyPath },
7791
+ vncBridge: { path: vncPath }
7792
+ };
7563
7793
  }
7564
- async function pickFreshInstance(_opts) {
7565
- const frameworks = availableFrameworks().map((f) => ({
7566
- kind: "framework",
7567
- id: f.id,
7568
- label: f.displayName,
7569
- description: f.description,
7570
- isDefault: f.id === defaultFrameworkId()
7571
- }));
7572
- const devices = (await safeListAdbDevices()).map((d) => ({
7573
- kind: "device",
7574
- serial: d.serial,
7575
- status: d.status
7576
- }));
7577
- return pickInstanceInteractive([...frameworks, ...devices], {
7578
- caller: "init"
7579
- });
7794
+ async function collectShimReport(hasBoundDevices) {
7795
+ const sources = readPinSourcesFromEnv();
7796
+ const deviceSuite = /* @__PURE__ */ new Set([
7797
+ NPM_PKGS.DEVICE_AGENT,
7798
+ NPM_PKGS.DEVICE_MCP_SERVER
7799
+ ]);
7800
+ const entries = await Promise.all(
7801
+ ALL_BEEOS_PKGS.map(async (pkg) => {
7802
+ const spec = resolveInstallSpec(pkg, sources);
7803
+ const [installed, latest] = await Promise.all([
7804
+ npmGlobalVersion(pkg).catch(() => null),
7805
+ npmRegistryVersion(spec).catch(() => null)
7806
+ ]);
7807
+ let outcome;
7808
+ if (latest === null) outcome = "error";
7809
+ else if (installed === null) {
7810
+ outcome = deviceSuite.has(pkg) && !hasBoundDevices ? "not_installed_yet" : "missing";
7811
+ } else if (installed === latest) outcome = "ok";
7812
+ else outcome = "outdated";
7813
+ if (pkg === NPM_PKGS.CLI && installed === null) {
7814
+ outcome = "missing";
7815
+ }
7816
+ return { pkg, installed, latest, spec, outcome };
7817
+ })
7818
+ );
7819
+ return { entries };
7580
7820
  }
7581
- async function maybePromptServiceInstall() {
7582
- try {
7583
- const mgr = await getServiceManager();
7584
- maybePrintFallbackBanner(mgr.kind);
7585
- const check = await mgr.selfCheck();
7586
- if (!check.available) {
7587
- console.log(` \u26A0 Service manager '${mgr.kind}' is not fully available \u2014 persistence limited.`);
7588
- }
7589
- for (const w of check.warnings) console.log(` ! ${w}`);
7590
- for (const h of check.hints) console.log(` $ ${h}`);
7591
- } catch (e) {
7592
- console.log(` (service-manager self-check skipped: ${e instanceof Error ? e.message : String(e)})`);
7821
+ function shimMarker(outcome) {
7822
+ switch (outcome) {
7823
+ case "ok":
7824
+ return "\u2713";
7825
+ case "outdated":
7826
+ return "\u2191";
7827
+ case "missing":
7828
+ return "\u2717";
7829
+ case "not_installed_yet":
7830
+ return "\xB7";
7831
+ case "error":
7832
+ return "?";
7593
7833
  }
7594
7834
  }
7595
- async function rotateIdentity() {
7596
- const p = getPlatformAdapter();
7597
- const home = beeoHome();
7598
- const identityDir = p.joinPath(home, "identity");
7599
- const keypair = p.joinPath(identityDir, "keypair.json");
7600
- const fp = p.joinPath(identityDir, "fingerprint");
7601
- if (!await p.exists(keypair)) return;
7602
- const ts = Math.floor(Date.now() / 1e3);
7603
- const backup = p.joinPath(identityDir, `keypair.json.backup-${ts}`);
7604
- try {
7605
- await p.copyFile(keypair, backup);
7606
- } catch {
7607
- }
7835
+ async function checkLogFileSizes() {
7836
+ const dir = path9.join(beeoHome(), "logs", "services");
7837
+ let entries;
7608
7838
  try {
7609
- await p.rm(keypair);
7839
+ entries = await fs9.readdir(dir);
7610
7840
  } catch {
7841
+ return [];
7611
7842
  }
7612
- try {
7613
- await p.rm(fp);
7614
- } catch {
7843
+ const bloated = [];
7844
+ for (const name of entries) {
7845
+ if (!name.endsWith(".log")) continue;
7846
+ const full = path9.join(dir, name);
7847
+ try {
7848
+ const st = await fs9.stat(full);
7849
+ if (st.isFile() && st.size >= LOG_SIZE_WARN_BYTES) {
7850
+ bloated.push({ path: full, size: st.size });
7851
+ }
7852
+ } catch {
7853
+ }
7615
7854
  }
7616
- console.log(`Rotated identity. Previous keypair saved at ${backup}`);
7617
- }
7618
- function printBanner() {
7619
- console.log("");
7620
- console.log(" BeeOS init");
7621
- console.log("");
7622
- }
7623
- function printNextSteps() {
7624
- console.log("");
7625
- console.log("Next steps:");
7626
- console.log(" beeos status # show running agents");
7627
- console.log(" beeos device attach # bind a physical Android device");
7628
- console.log(" beeos doctor # health check & diagnostics");
7629
- console.log("");
7630
- }
7631
- function prompt(question) {
7632
- const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
7633
- return new Promise((resolve) => {
7634
- rl.question(question, (answer) => {
7635
- rl.close();
7636
- resolve(answer);
7637
- });
7638
- });
7855
+ return bloated;
7639
7856
  }
7640
- async function shouldUpgradeBeeosSuite(options) {
7641
- if (options.headless) return false;
7642
- if (process.env.BEEOS_INIT_SKIP_NPM_UPGRADE === "1") return false;
7643
- return true;
7857
+ function formatBytes(n) {
7858
+ if (n >= 1024 * 1024 * 1024) return `${(n / (1024 * 1024 * 1024)).toFixed(1)}GB`;
7859
+ if (n >= 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(0)}MB`;
7860
+ if (n >= 1024) return `${(n / 1024).toFixed(0)}KB`;
7861
+ return `${n}B`;
7644
7862
  }
7645
- async function upgradeAndMaybeExit(options) {
7646
- if (options.json) {
7647
- return false;
7648
- }
7649
- const packages = await pickInstalledPackages();
7650
- console.log("Checking for newer @beeos-ai/cli on npm...");
7651
- const reporter = new CliReporter();
7652
- let outcome;
7863
+ async function probeAgentGateway(baseUrl) {
7864
+ const url = joinHealthzUrl(baseUrl);
7865
+ const started = Date.now();
7866
+ const ac = new AbortController();
7867
+ const timer = setTimeout(() => ac.abort(), 3e3);
7653
7868
  try {
7654
- outcome = await upgradeBeeosSuite({
7655
- packages,
7656
- progress: reporter
7869
+ const res = await fetch(url, {
7870
+ method: "GET",
7871
+ signal: ac.signal,
7872
+ headers: { accept: "application/json, text/plain;q=0.9, */*;q=0.1" }
7657
7873
  });
7658
- } catch (e) {
7659
- reporter.stop();
7660
- console.log(
7661
- ` (upgrade skipped \u2014 ${e instanceof Error ? e.message : String(e)})
7662
- Tip: re-run \`npm install -g @beeos-ai/cli\` manually. The device
7663
- suite (device-agent + device-mcp-server) will be auto-installed
7664
- on the next \`beeos device attach\`.`
7665
- );
7666
- return false;
7667
- }
7668
- reporter.stop();
7669
- if (outcome.anyFailed) {
7670
- const failed = outcome.packages.find((p) => p.failed)?.failed ?? "unknown error";
7671
- console.log(` \u26A0 npm install failed: ${failed}`);
7672
- console.log(
7673
- " Manual fix:\n npm i -g @beeos-ai/cli\n (the device suite is auto-installed on `beeos device attach`)\n"
7674
- );
7675
- return false;
7676
- }
7677
- if (!outcome.anyChanged) {
7678
- console.log(" All BeeOS npm packages are up to date.\n");
7679
- return false;
7680
- }
7681
- for (const pkg of outcome.packages) {
7682
- if (pkg.changed) {
7683
- console.log(` ${pkg.pkg}: ${pkg.before ?? "(none)"} \u2192 ${pkg.after}`);
7874
+ const latency = Date.now() - started;
7875
+ if (res.ok) {
7876
+ return {
7877
+ url,
7878
+ status: "ok",
7879
+ httpStatus: res.status,
7880
+ latencyMs: latency,
7881
+ detail: `HTTP ${res.status} in ${latency}ms`
7882
+ };
7684
7883
  }
7884
+ return {
7885
+ url,
7886
+ status: "unhealthy",
7887
+ httpStatus: res.status,
7888
+ latencyMs: latency,
7889
+ detail: `HTTP ${res.status} in ${latency}ms`
7890
+ };
7891
+ } catch (err) {
7892
+ const latency = Date.now() - started;
7893
+ const msg = err instanceof Error ? err.message : String(err);
7894
+ const timedOut = ac.signal.aborted;
7895
+ return {
7896
+ url,
7897
+ status: "unreachable",
7898
+ httpStatus: null,
7899
+ latencyMs: latency,
7900
+ detail: timedOut ? "timeout after 3s" : msg
7901
+ };
7902
+ } finally {
7903
+ clearTimeout(timer);
7685
7904
  }
7686
- const cliEntry = outcome.packages.find((p) => p.pkg === NPM_PKGS.CLI);
7687
- if (cliEntry?.changed) {
7688
- console.log("");
7689
- console.log(" CLI itself was upgraded \u2014 please re-run `beeos init` to continue");
7690
- console.log(" with the new code. The current process is still running the old version.");
7691
- console.log("");
7692
- return true;
7693
- }
7694
- console.log(" Agents updated. Continuing init...\n");
7695
- return false;
7696
7905
  }
7697
- async function pickInstalledPackages() {
7698
- const packages = [NPM_PKGS.CLI];
7699
- for (const pkg of [NPM_PKGS.DEVICE_AGENT, NPM_PKGS.DEVICE_MCP_SERVER]) {
7700
- const installed = await npmGlobalVersion(pkg).catch(() => null);
7701
- if (installed !== null) packages.push(pkg);
7702
- }
7703
- return packages;
7906
+ function joinHealthzUrl(base) {
7907
+ const trimmed = base.replace(/\/+$/, "");
7908
+ return `${trimmed}/healthz`;
7909
+ }
7910
+ function formatAgentGatewayStatus(h) {
7911
+ if (h.status === "ok") return `ok ${h.latencyMs}ms`;
7912
+ if (h.status === "unhealthy") return `HTTP ${h.httpStatus ?? "?"}`;
7913
+ return h.detail;
7704
7914
  }
7705
7915
 
7706
7916
  // src/commands/service.ts
@@ -7924,8 +8134,8 @@ setPlatformAdapter(new NodePlatformAdapter());
7924
8134
  var program = new Command();
7925
8135
  var cliVersion = getCliVersion(import.meta.url, resolveActiveDistTag());
7926
8136
  program.name("beeos").version(cliVersion.display).description("BeeOS \u2014 run AI agents from your desktop");
7927
- program.command("init").description("One-shot install + bind flow (default entry from the curl installer)").option("--framework <name>", "Agent framework to install (default: openclaw)").option("--yes", "Non-interactive \u2014 accept the default action", false).option("--json", "Output machine-readable JSON (implies --yes)", false).option("--no-browser", "Don't auto-open a browser for bind confirmation").option("--headless", "Never open a browser (use terminal QR only)", false).option("--skip-service-prompt", "Skip the system-service install prompt", false).option("--device", "Jump straight into `beeos device attach` instead", false).action((opts) => run8(opts));
7928
- program.command("doctor").description("Inspect local BeeOS state (install, binding, services, warnings)").option("--json", "Output machine-readable JSON", false).action((opts) => run7(opts));
8137
+ program.command("init").description("One-shot install + bind flow (default entry from the curl installer)").option("--framework <name>", "Agent framework to install (default: openclaw)").option("--yes", "Non-interactive \u2014 accept the default action", false).option("--json", "Output machine-readable JSON (implies --yes)", false).option("--no-browser", "Don't auto-open a browser for bind confirmation").option("--headless", "Never open a browser (use terminal QR only)", false).option("--skip-service-prompt", "Skip the system-service install prompt", false).option("--device", "Jump straight into `beeos device attach` instead", false).action((opts) => run7(opts));
8138
+ program.command("doctor").description("Inspect local BeeOS state (install, binding, services, warnings)").option("--json", "Output machine-readable JSON", false).action((opts) => run8(opts));
7929
8139
  program.command("migrate").description("Migrate legacy Node supervisor state to OS-native services (one-shot)").option("--json", "Output machine-readable JSON", false).action((opts) => run9(opts));
7930
8140
  program.command("logs").description(
7931
8141
  "Tail logs for a BeeOS service. Pass an ADB serial or service id; omit to list every registered service's log file path."