@beeos-ai/cli 1.0.18 → 1.0.19

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,6 +746,17 @@ 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}`;
@@ -764,8 +775,7 @@ async function agentBind(apiUrl, publicKey, fingerprint2, agentFramework, hostna
764
775
  })
765
776
  });
766
777
  if (!resp.ok) {
767
- const text = await resp.text().catch(() => "");
768
- throw new Error(`agent bind failed (${resp.status}): ${text}`);
778
+ throw await bindHttpError(resp, "bind");
769
779
  }
770
780
  return await resp.json();
771
781
  }
@@ -776,7 +786,7 @@ async function pollBind(apiUrl, bindId) {
776
786
  signal: AbortSignal.timeout(35e3)
777
787
  });
778
788
  if (!resp.ok) {
779
- throw new Error(`bind poll failed: ${resp.status}`);
789
+ throw await bindHttpError(resp, "poll");
780
790
  }
781
791
  return await resp.json();
782
792
  }
@@ -811,7 +821,7 @@ async function getBindStatus(apiUrl, bindId) {
811
821
  signal: AbortSignal.timeout(5e3)
812
822
  });
813
823
  if (!resp.ok) {
814
- throw new Error(`bind status check failed: ${resp.status}`);
824
+ throw await bindHttpError(resp, "status");
815
825
  }
816
826
  return await resp.json();
817
827
  }
@@ -820,6 +830,7 @@ var init_client = __esm({
820
830
  "use strict";
821
831
  init_platform_adapter();
822
832
  init_keypair();
833
+ init_errors();
823
834
  }
824
835
  });
825
836
 
@@ -925,9 +936,20 @@ async function bindAgent(opts) {
925
936
  throw e;
926
937
  }
927
938
  if (resp.status === "bound") {
939
+ if (!resp.instance_id) {
940
+ throw new BeeosError({
941
+ code: "platform_protocol_violation",
942
+ message: "Platform returned status='bound' but no instance_id.",
943
+ hint: "This indicates a platform bug. Re-run `beeos init`; if it persists, open a support ticket with the fingerprint below.",
944
+ details: {
945
+ fingerprint: opts.fingerprint,
946
+ response: resp
947
+ }
948
+ });
949
+ }
928
950
  return {
929
951
  status: "bound",
930
- instanceId: resp.instance_id ?? "",
952
+ instanceId: resp.instance_id,
931
953
  source: "already_bound"
932
954
  };
933
955
  }
@@ -939,8 +961,10 @@ async function bindAgent(opts) {
939
961
  return { status: "bound", instanceId, source: "fresh" };
940
962
  }
941
963
  await presentBindUrl(bindUrl, opts.headless ?? false, log);
964
+ const expiryMinutes = Math.round(pollTimeoutMs / 6e4);
965
+ log(` Press Ctrl+C to cancel; the bind session expires in ${expiryMinutes} minutes if not approved.`);
942
966
  try {
943
- const instanceId = await pollUntilBound(opts.apiUrl, bindId, pollTimeoutMs);
967
+ const instanceId = await pollUntilBound(opts.apiUrl, bindId, pollTimeoutMs, log);
944
968
  return { status: "bound", instanceId, source: "fresh" };
945
969
  } catch (e) {
946
970
  if (e instanceof BeeosError && e.code === "bind_pending_expired") {
@@ -980,8 +1004,10 @@ async function presentBindUrl(bindUrl, forceHeadless, log = console.log) {
980
1004
  log(" -----------------------------------------------------");
981
1005
  }
982
1006
  }
983
- async function pollUntilBound(apiUrl, bindId, timeoutMs) {
1007
+ async function pollUntilBound(apiUrl, bindId, timeoutMs, log = () => void 0) {
984
1008
  const deadline = Date.now() + timeoutMs;
1009
+ let lastTick = Date.now();
1010
+ const TICK_INTERVAL_MS = 3e4;
985
1011
  while (true) {
986
1012
  if (Date.now() > deadline) {
987
1013
  throw new BeeosError({
@@ -991,10 +1017,23 @@ async function pollUntilBound(apiUrl, bindId, timeoutMs) {
991
1017
  details: { source: "client_timeout", bindId }
992
1018
  });
993
1019
  }
1020
+ if (Date.now() - lastTick >= TICK_INTERVAL_MS) {
1021
+ const remainingSec = Math.max(0, Math.round((deadline - Date.now()) / 1e3));
1022
+ log(` Still waiting for confirmation... (${remainingSec}s remaining)`);
1023
+ lastTick = Date.now();
1024
+ }
994
1025
  try {
995
1026
  const resp = await pollBind(apiUrl, bindId);
996
1027
  if (resp.status === "bound") {
997
- return resp.instance_id ?? "";
1028
+ if (!resp.instance_id) {
1029
+ throw new BeeosError({
1030
+ code: "platform_protocol_violation",
1031
+ message: "Platform poll returned status='bound' but no instance_id.",
1032
+ hint: "This indicates a platform bug. Re-run `beeos init`; if it persists, open a support ticket with the bind_id below.",
1033
+ details: { bindId, response: resp }
1034
+ });
1035
+ }
1036
+ return resp.instance_id;
998
1037
  }
999
1038
  if (resp.status === "expired") {
1000
1039
  throw new BeeosError({
@@ -1005,7 +1044,7 @@ async function pollUntilBound(apiUrl, bindId, timeoutMs) {
1005
1044
  });
1006
1045
  }
1007
1046
  } catch (e) {
1008
- if (e instanceof BeeosError && e.code === "bind_pending_expired") {
1047
+ if (e instanceof BeeosError && (e.code === "bind_pending_expired" || e.code === "platform_protocol_violation" || e.code === "platform_unauthorized")) {
1009
1048
  throw e;
1010
1049
  }
1011
1050
  await sleep(2e3);
@@ -3381,7 +3420,7 @@ function initDecisionLabel(decision) {
3381
3420
  case "rebind":
3382
3421
  return "Re-bind (keep key, refresh binding \u2014 same instance)";
3383
3422
  case "reset-all":
3384
- return "Reset everything (NEW key, NEW instance \u2014 for compromised key)";
3423
+ 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
3424
  case "skip":
3386
3425
  return "Skip (do nothing)";
3387
3426
  }
@@ -6813,11 +6852,28 @@ async function run(agentFramework, options) {
6813
6852
  const isRebind = !isOffline && cachedBinding !== null && cachedBinding.instance_id !== boundInstanceId;
6814
6853
  const mgr = await getServiceManager();
6815
6854
  maybePrintFallbackBanner(mgr.kind);
6816
- let status2 = await mgr.install(spec);
6855
+ let status2;
6856
+ try {
6857
+ status2 = await mgr.install(spec);
6858
+ } catch (err) {
6859
+ throw wrapServiceInstallFailure(err, {
6860
+ stage: "install",
6861
+ instanceId: boundInstanceId,
6862
+ driverId: driver.id
6863
+ });
6864
+ }
6817
6865
  if (isRebind) {
6818
- await mgr.restart(spec.id);
6819
- const refreshed = await mgr.status(spec.id);
6820
- if (refreshed) status2 = refreshed;
6866
+ try {
6867
+ await mgr.restart(spec.id);
6868
+ const refreshed = await mgr.status(spec.id);
6869
+ if (refreshed) status2 = refreshed;
6870
+ } catch (err) {
6871
+ throw wrapServiceInstallFailure(err, {
6872
+ stage: "restart",
6873
+ instanceId: boundInstanceId,
6874
+ driverId: driver.id
6875
+ });
6876
+ }
6821
6877
  }
6822
6878
  if (spec.healthcheck) {
6823
6879
  try {
@@ -6829,13 +6885,17 @@ async function run(agentFramework, options) {
6829
6885
  throw new BeeosError({
6830
6886
  code: "service_install_failed",
6831
6887
  message: diag.reason,
6832
- hint: diag.hints.length > 0 ? diag.hints.map((h) => `\u2022 ${h}`).join("\n ") : void 0,
6888
+ 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
6889
  cause: err,
6834
- details: { driver: driver.id }
6890
+ details: { driver: driver.id, instance_id: boundInstanceId }
6835
6891
  });
6836
6892
  }
6837
6893
  }
6838
- throw err;
6894
+ throw wrapServiceInstallFailure(err, {
6895
+ stage: "healthcheck",
6896
+ instanceId: boundInstanceId,
6897
+ driverId: driver.id
6898
+ });
6839
6899
  }
6840
6900
  }
6841
6901
  const gatewayPid = status2.pid ?? void 0;
@@ -6855,6 +6915,17 @@ function buildHostname() {
6855
6915
  const user = process.env.USER || process.env.USERNAME || "";
6856
6916
  return user ? `${user}@${machine}` : machine;
6857
6917
  }
6918
+ function wrapServiceInstallFailure(err, ctx) {
6919
+ if (err instanceof BeeosError) return err;
6920
+ const msg = err instanceof Error ? err.message : String(err);
6921
+ return new BeeosError({
6922
+ code: "service_install_failed",
6923
+ message: `Bind succeeded (instance ${ctx.instanceId}) but service ${ctx.stage} failed: ${msg}`,
6924
+ 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.`,
6925
+ cause: err,
6926
+ details: { stage: ctx.stage, instance_id: ctx.instanceId, driver: ctx.driverId }
6927
+ });
6928
+ }
6858
6929
  function emit(json, payload, human) {
6859
6930
  if (json) {
6860
6931
  emitJsonEnvelope(jsonOk(payload));
@@ -7063,562 +7134,247 @@ init_device2();
7063
7134
  // src/commands/init.ts
7064
7135
  init_dist();
7065
7136
  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;
7137
+ init_attach();
7138
+ init_progress2();
7139
+ init_fallback_banner();
7140
+ init_instance_picker();
7073
7141
  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");
7142
+ await ensureDirs();
7143
+ if (options.device) {
7144
+ const { attach: attach2 } = await Promise.resolve().then(() => (init_device2(), device_exports));
7145
+ await attach2({});
7146
+ return;
7097
7147
  }
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
- );
7148
+ const state = await detectExistingInstall();
7149
+ if (!options.json) {
7150
+ printBanner();
7151
+ for (const line of summarizeExistingInstall(state)) {
7152
+ console.log(` ${line}`);
7153
+ }
7154
+ if (state.supervisor.targets.length > 0 || state.supervisor.ipcReachable) {
7155
+ console.log("");
7156
+ console.log(
7157
+ " Legacy Node supervisor state detected. Run `beeos migrate` to convert it to OS-native services."
7158
+ );
7159
+ }
7160
+ console.log("");
7105
7161
  }
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
- );
7162
+ const decision = await decideAction(state, options);
7163
+ if (decision === "skip") {
7164
+ console.log("Nothing to do. Run `beeos init` again or `beeos device attach` when ready.");
7165
+ return;
7110
7166
  }
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
- );
7167
+ if (decision === "upgrade") {
7168
+ if (await shouldUpgradeBeeosSuite(options)) {
7169
+ const exited = await upgradeAndMaybeExit(options);
7170
+ if (exited) return;
7171
+ }
7172
+ console.log("Ensuring OpenClaw is running...\n");
7115
7173
  }
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
- );
7174
+ if (decision === "rebind") {
7175
+ await removeBindingInfo();
7123
7176
  }
7124
- if (fallbackReason2) {
7125
- warnings.push(
7126
- `No native OS service manager available (${fallbackReason2}) \u2014 using fallback, services WON'T persist across reboots.`
7127
- );
7177
+ if (decision === "reset-all") {
7178
+ await rotateIdentity();
7179
+ await removeBindingInfo();
7128
7180
  }
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
- );
7181
+ if (decision === "fresh" && !options.framework && !options.yes && !options.json && process.stdin.isTTY) {
7182
+ const sel = await pickFreshInstance(options);
7183
+ if (sel.kind === "skip") {
7184
+ console.log("Skipped. Re-run `beeos init` or `beeos device attach` whenever ready.");
7185
+ return;
7136
7186
  }
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
- );
7187
+ if (sel.kind === "device") {
7188
+ await attach({ serial: sel.serial });
7189
+ return;
7141
7190
  }
7142
- }
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
- );
7147
- }
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") {
7191
+ const descriptor2 = frameworkById(sel.id);
7192
+ if (!descriptor2 || descriptor2.status !== "available") {
7193
+ throw new BeeosError({
7194
+ code: "framework_unavailable",
7195
+ message: `Agent framework '${sel.id}' is not available.`,
7196
+ hint: "Pick a different framework or reinstall the CLI.",
7197
+ details: { requested: sel.id, available: availableFrameworks().map((f) => f.id) }
7198
+ });
7199
+ }
7200
+ await run(descriptor2.id, {
7201
+ force: true,
7202
+ json: options.json,
7203
+ browser: options.browser !== false
7204
+ });
7205
+ if (!options.json && !options.skipServicePrompt) {
7206
+ await maybePromptServiceInstall();
7207
+ }
7208
+ if (!options.json) {
7209
+ printNextSteps();
7156
7210
  }
7211
+ return;
7157
7212
  }
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
- );
7213
+ const frameworkId = await decideFramework(state, decision, options);
7214
+ const descriptor = frameworkById(frameworkId);
7215
+ if (!descriptor || descriptor.status !== "available") {
7216
+ const avail = availableFrameworks().map((f) => f.id).join(", ");
7217
+ throw new BeeosError({
7218
+ code: "framework_unavailable",
7219
+ message: `Agent framework '${frameworkId}' is not available. Available: ${avail}.`,
7220
+ hint: avail.length > 0 ? `Re-run with --framework <one-of-the-above>` : "No frameworks are registered \u2014 reinstall the CLI.",
7221
+ details: { requested: frameworkId, available: availableFrameworks().map((f) => f.id) }
7222
+ });
7162
7223
  }
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}`);
7224
+ await run(descriptor.id, {
7225
+ force: true,
7226
+ json: options.json,
7227
+ browser: options.browser !== false
7228
+ });
7229
+ if (!options.json && !options.skipServicePrompt) {
7230
+ await maybePromptServiceInstall();
7169
7231
  }
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;
7232
+ if (!options.json) {
7233
+ printNextSteps();
7193
7234
  }
7194
- 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}`);
7235
+ }
7236
+ async function decideAction(state, options) {
7237
+ const choice = inferInitChoices(state);
7238
+ if (choice.options.length === 1) {
7239
+ return choice.defaultDecision;
7204
7240
  }
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}`);
7241
+ if (options.yes || options.json) {
7242
+ return choice.defaultDecision;
7212
7243
  }
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)"}`);
7244
+ if (!process.stdin.isTTY) {
7245
+ return choice.defaultDecision;
7246
+ }
7247
+ console.log("Detected existing install. Choose an action:");
7248
+ choice.options.forEach((d, i) => {
7249
+ const marker = d === choice.defaultDecision ? "*" : " ";
7250
+ console.log(` ${marker} [${i + 1}] ${initDecisionLabel(d)}`);
7251
+ });
7216
7252
  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
- );
7253
+ const answer = await prompt(`Choose [1-${choice.options.length}] (default ${choice.options.indexOf(choice.defaultDecision) + 1}): `);
7254
+ const trimmed = answer.trim();
7255
+ if (!trimmed) return choice.defaultDecision;
7256
+ const idx = parseInt(trimmed, 10);
7257
+ if (!Number.isFinite(idx) || idx < 1 || idx > choice.options.length) {
7258
+ return choice.defaultDecision;
7225
7259
  }
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");
7260
+ return choice.options[idx - 1];
7261
+ }
7262
+ async function decideFramework(state, decision, options) {
7263
+ if (options.framework && options.framework.trim()) {
7264
+ return options.framework.trim();
7235
7265
  }
7236
- if (hints.length > 0) {
7237
- console.log(" Hints:");
7238
- for (const h of hints) {
7239
- console.log(` $ ${h}`);
7266
+ if (state.binding && decision === "upgrade") {
7267
+ const persisted = state.binding.framework?.trim();
7268
+ if (persisted) return persisted;
7269
+ return defaultFrameworkId();
7270
+ }
7271
+ if (options.yes || options.json || !process.stdin.isTTY) {
7272
+ return defaultFrameworkId();
7273
+ }
7274
+ const all = listFrameworks();
7275
+ const avail = availableFrameworks();
7276
+ if (avail.length <= 1) {
7277
+ const only = avail[0];
7278
+ if (only) {
7279
+ console.log(`Installing ${only.displayName} (only available framework).`);
7280
+ return only.id;
7240
7281
  }
7241
- console.log("");
7282
+ return defaultFrameworkId();
7242
7283
  }
7243
- }
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
- };
7255
- }
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 };
7282
- }
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 "?";
7284
+ console.log("");
7285
+ console.log("Choose agent framework:");
7286
+ const def = defaultFrameworkId();
7287
+ all.forEach((f, i) => {
7288
+ const marker = f.id === def ? "*" : " ";
7289
+ const tag = f.status === "coming-soon" ? " [coming soon]" : "";
7290
+ const padName = f.displayName.padEnd(12);
7291
+ console.log(` ${marker} [${i + 1}] ${padName} ${f.description}${tag}`);
7292
+ });
7293
+ console.log("");
7294
+ const defaultIdx = all.findIndex((f) => f.id === def) + 1;
7295
+ const answer = await prompt(`Choose [1-${all.length}] (default ${defaultIdx}): `);
7296
+ const trimmed = answer.trim();
7297
+ if (!trimmed) return def;
7298
+ const idx = parseInt(trimmed, 10);
7299
+ if (!Number.isFinite(idx) || idx < 1 || idx > all.length) {
7300
+ return def;
7301
+ }
7302
+ const picked = all[idx - 1];
7303
+ if (picked.status !== "available") {
7304
+ console.log(
7305
+ ` ${picked.displayName} is not yet available. Falling back to ${def}.`
7306
+ );
7307
+ return def;
7295
7308
  }
7309
+ return picked.id;
7296
7310
  }
7297
- async function checkLogFileSizes() {
7298
- const dir = path9.join(beeoHome(), "logs", "services");
7299
- let entries;
7311
+ async function safeListAdbDevices() {
7312
+ const adbPath = await findAdb().catch(() => null);
7313
+ if (!adbPath) return [];
7300
7314
  try {
7301
- entries = await fs9.readdir(dir);
7315
+ return await deviceRuntime.listAdbDevices();
7302
7316
  } catch {
7303
7317
  return [];
7304
7318
  }
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
- }
7316
- }
7317
- return bloated;
7318
7319
  }
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`;
7320
+ async function pickFreshInstance(_opts) {
7321
+ const frameworks = availableFrameworks().map((f) => ({
7322
+ kind: "framework",
7323
+ id: f.id,
7324
+ label: f.displayName,
7325
+ description: f.description,
7326
+ isDefault: f.id === defaultFrameworkId()
7327
+ }));
7328
+ const devices = (await safeListAdbDevices()).map((d) => ({
7329
+ kind: "device",
7330
+ serial: d.serial,
7331
+ status: d.status
7332
+ }));
7333
+ return pickInstanceInteractive([...frameworks, ...devices], {
7334
+ caller: "init"
7335
+ });
7324
7336
  }
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);
7337
+ async function maybePromptServiceInstall() {
7330
7338
  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
- };
7339
+ const mgr = await getServiceManager();
7340
+ maybePrintFallbackBanner(mgr.kind);
7341
+ const check = await mgr.selfCheck();
7342
+ if (!check.available) {
7343
+ console.log(` \u26A0 Service manager '${mgr.kind}' is not fully available \u2014 persistence limited.`);
7345
7344
  }
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);
7345
+ for (const w of check.warnings) console.log(` ! ${w}`);
7346
+ for (const h of check.hints) console.log(` $ ${h}`);
7347
+ } catch (e) {
7348
+ console.log(` (service-manager self-check skipped: ${e instanceof Error ? e.message : String(e)})`);
7366
7349
  }
7367
7350
  }
7368
- function joinHealthzUrl(base) {
7369
- const trimmed = base.replace(/\/+$/, "");
7370
- return `${trimmed}/healthz`;
7351
+ async function rotateIdentity() {
7352
+ const p = getPlatformAdapter();
7353
+ const home = beeoHome();
7354
+ const identityDir = p.joinPath(home, "identity");
7355
+ const keypair = p.joinPath(identityDir, "keypair.json");
7356
+ const fp = p.joinPath(identityDir, "fingerprint");
7357
+ if (!await p.exists(keypair)) return;
7358
+ const ts = Math.floor(Date.now() / 1e3);
7359
+ const backup = p.joinPath(identityDir, `keypair.json.backup-${ts}`);
7360
+ try {
7361
+ await p.copyFile(keypair, backup);
7362
+ } catch {
7363
+ }
7364
+ try {
7365
+ await p.rm(keypair);
7366
+ } catch {
7367
+ }
7368
+ try {
7369
+ await p.rm(fp);
7370
+ } catch {
7371
+ }
7372
+ console.log(`Rotated identity. Previous keypair saved at ${backup}`);
7371
7373
  }
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;
7376
- }
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();
7391
- 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;
7410
- }
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");
7417
- }
7418
- if (decision === "rebind") {
7419
- await removeBindingInfo();
7420
- }
7421
- if (decision === "reset-all") {
7422
- await rotateIdentity();
7423
- await removeBindingInfo();
7424
- }
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();
7454
- }
7455
- return;
7456
- }
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();
7475
- }
7476
- if (!options.json) {
7477
- printNextSteps();
7478
- }
7479
- }
7480
- async function decideAction(state, options) {
7481
- const choice = inferInitChoices(state);
7482
- if (choice.options.length === 1) {
7483
- return choice.defaultDecision;
7484
- }
7485
- if (options.yes || options.json) {
7486
- return choice.defaultDecision;
7487
- }
7488
- if (!process.stdin.isTTY) {
7489
- return choice.defaultDecision;
7490
- }
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;
7503
- }
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();
7509
- }
7510
- if (state.binding && decision === "upgrade") {
7511
- const persisted = state.binding.framework?.trim();
7512
- if (persisted) return persisted;
7513
- return defaultFrameworkId();
7514
- }
7515
- if (options.yes || options.json || !process.stdin.isTTY) {
7516
- return defaultFrameworkId();
7517
- }
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;
7525
- }
7526
- return defaultFrameworkId();
7527
- }
7528
- 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
- });
7537
- 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;
7545
- }
7546
- const picked = all[idx - 1];
7547
- if (picked.status !== "available") {
7548
- console.log(
7549
- ` ${picked.displayName} is not yet available. Falling back to ${def}.`
7550
- );
7551
- return def;
7552
- }
7553
- return picked.id;
7554
- }
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
- }
7563
- }
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
- });
7580
- }
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)})`);
7593
- }
7594
- }
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
- }
7608
- try {
7609
- await p.rm(keypair);
7610
- } catch {
7611
- }
7612
- try {
7613
- await p.rm(fp);
7614
- } catch {
7615
- }
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("");
7374
+ function printBanner() {
7375
+ console.log("");
7376
+ console.log(" BeeOS init");
7377
+ console.log("");
7622
7378
  }
7623
7379
  function printNextSteps() {
7624
7380
  console.log("");
@@ -7703,6 +7459,322 @@ async function pickInstalledPackages() {
7703
7459
  return packages;
7704
7460
  }
7705
7461
 
7462
+ // src/commands/doctor.ts
7463
+ init_dist();
7464
+ init_json_envelope();
7465
+ import fs9 from "fs/promises";
7466
+ import path9 from "path";
7467
+ var LOG_SIZE_WARN_BYTES = 50 * 1024 * 1024;
7468
+ async function run8(options) {
7469
+ const state = await detectExistingInstall();
7470
+ const cfg = await loadOrCreateConfig();
7471
+ const p = getPlatformAdapter();
7472
+ const mgr = await getServiceManager();
7473
+ const fallbackReason2 = activeFallbackReason();
7474
+ const serviceCheck = await mgr.selfCheck();
7475
+ let services = [];
7476
+ try {
7477
+ services = await mgr.list();
7478
+ } catch {
7479
+ }
7480
+ const resolvedAgentGatewayUrl = resolveAgentGatewayUrl(cfg);
7481
+ const agentGatewayHealth = await probeAgentGateway(resolvedAgentGatewayUrl);
7482
+ const tools = await collectToolStatus();
7483
+ const hasBoundDevices = state.devices.entries.length > 0;
7484
+ const shimReport = await collectShimReport(hasBoundDevices);
7485
+ const warnings = [];
7486
+ const hints = [];
7487
+ if (!state.hasIdentity) {
7488
+ warnings.push("identity keypair missing \u2014 will be generated on next bind");
7489
+ }
7490
+ if (state.openclaw.gatewayRunning && !state.openclaw.found) {
7491
+ warnings.push("port 18789 is in use but no OpenClaw install detected \u2014 another app may conflict");
7492
+ }
7493
+ if (state.openclaw.found && state.openclaw.pluginInstalled === false) {
7494
+ warnings.push(
7495
+ `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.`
7496
+ );
7497
+ hints.push(
7498
+ "re-run `beeos start openclaw --force` to re-install the plugin, or check ~/.beeos/logs/services/openclaw.log for the original failure"
7499
+ );
7500
+ }
7501
+ if (state.devices.entries.length > 0 && services.length === 0) {
7502
+ warnings.push(
7503
+ "devices tracked via legacy devices.json \u2014 no OS services registered. Re-run `beeos device attach` to migrate them to the OS service manager."
7504
+ );
7505
+ }
7506
+ if (state.supervisor.targets.length > 0 || state.supervisor.ipcReachable) {
7507
+ warnings.push(
7508
+ "legacy Node supervisor state detected \u2014 run `beeos migrate` to convert it to OS-native services."
7509
+ );
7510
+ }
7511
+ if (agentGatewayHealth.status !== "ok") {
7512
+ warnings.push(
7513
+ `Agent Gateway unreachable at ${resolvedAgentGatewayUrl}: ${agentGatewayHealth.detail}. Binding and task delivery will fail until this resolves.`
7514
+ );
7515
+ hints.push(
7516
+ "check BEEOS_AGENT_GATEWAY_URL / platform.agent_gateway_url in ~/.beeos/config.toml"
7517
+ );
7518
+ }
7519
+ if (fallbackReason2) {
7520
+ warnings.push(
7521
+ `No native OS service manager available (${fallbackReason2}) \u2014 using fallback, services WON'T persist across reboots.`
7522
+ );
7523
+ }
7524
+ for (const w of serviceCheck.warnings) warnings.push(w);
7525
+ for (const h of serviceCheck.hints) hints.push(h);
7526
+ for (const s of services) {
7527
+ if (!s.running && s.installed) {
7528
+ warnings.push(
7529
+ `service '${s.id}' is installed but not running \u2014 see log ${s.logFile}`
7530
+ );
7531
+ }
7532
+ if (s.lastExitCode !== null && s.lastExitCode !== 0) {
7533
+ warnings.push(
7534
+ `service '${s.id}' last exited with code ${s.lastExitCode} \u2014 see log ${s.logFile}`
7535
+ );
7536
+ if (s.lastExitCode === 127) {
7537
+ hints.push(
7538
+ `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.`
7539
+ );
7540
+ }
7541
+ }
7542
+ }
7543
+ if (!tools.adb.path) {
7544
+ warnings.push(
7545
+ "adb not found \u2014 `beeos device attach` will download Android platform-tools on first use"
7546
+ );
7547
+ }
7548
+ for (const e of shimReport.entries) {
7549
+ if (e.outcome === "outdated") {
7550
+ warnings.push(
7551
+ `${e.pkg}: installed ${e.installed ?? "(missing)"}, npm latest ${e.latest}.`
7552
+ );
7553
+ } else if (e.outcome === "missing") {
7554
+ warnings.push(`${e.pkg}: not installed globally \u2014 run 'beeos init' to install.`);
7555
+ } else if (e.outcome === "error" || e.outcome === "not_installed_yet") {
7556
+ }
7557
+ }
7558
+ if (shimReport.entries.some((e) => e.outcome === "outdated" || e.outcome === "missing")) {
7559
+ hints.push(
7560
+ "run `beeos init` and pick option [1] to upgrade CLI + agents to the latest npm release"
7561
+ );
7562
+ }
7563
+ const bloatedLogs = await checkLogFileSizes();
7564
+ for (const l of bloatedLogs) {
7565
+ warnings.push(
7566
+ `Service log large: ${path9.basename(l.path)} = ${formatBytes(l.size)}`
7567
+ );
7568
+ hints.push(`truncate safely: : > ${l.path}`);
7569
+ }
7570
+ if (options.json) {
7571
+ emitJsonEnvelope(
7572
+ jsonOk({
7573
+ platform: p.platform(),
7574
+ arch: process.arch,
7575
+ node: process.version,
7576
+ beeosHome: state.beeosHome,
7577
+ config: cfg,
7578
+ state,
7579
+ serviceManager: {
7580
+ kind: mgr.kind,
7581
+ available: serviceCheck.available,
7582
+ fallbackReason: fallbackReason2,
7583
+ services
7584
+ },
7585
+ agentGateway: agentGatewayHealth,
7586
+ tools,
7587
+ npmShims: shimReport,
7588
+ warnings,
7589
+ hints
7590
+ })
7591
+ );
7592
+ return;
7593
+ }
7594
+ console.log("");
7595
+ console.log(` OS/Arch : ${p.platform()}/${process.arch}`);
7596
+ console.log(` Node.js : ${process.version}`);
7597
+ console.log(` API URL : ${cfg.platform.api_url}`);
7598
+ console.log(
7599
+ ` AgentGw : ${resolvedAgentGatewayUrl} [${formatAgentGatewayStatus(agentGatewayHealth)}]`
7600
+ );
7601
+ console.log("");
7602
+ for (const line of summarizeExistingInstall(state)) {
7603
+ console.log(` ${line}`);
7604
+ }
7605
+ console.log(
7606
+ ` Service manager: ${mgr.kind}${fallbackReason2 ? " (fallback)" : ""} \u2014 ${services.length} service(s) registered`
7607
+ );
7608
+ for (const s of services) {
7609
+ const status2 = s.running ? "running" : s.installed ? "stopped" : "uninstalled";
7610
+ const pid = s.pid != null ? ` pid=${s.pid}` : "";
7611
+ console.log(` - ${s.id.padEnd(28)} ${status2}${pid}`);
7612
+ }
7613
+ console.log(` adb : ${tools.adb.path ?? "not found"}`);
7614
+ console.log(` scrcpy-bridge: ${tools.scrcpyBridge.path ?? "not installed (auto on attach)"}`);
7615
+ console.log(` vnc-bridge : ${tools.vncBridge.path ?? "not installed (auto on --vnc-host)"}`);
7616
+ console.log("");
7617
+ console.log(" npm shims:");
7618
+ for (const e of shimReport.entries) {
7619
+ const marker = shimMarker(e.outcome);
7620
+ const installed = e.installed ?? "(missing)";
7621
+ const latest = e.latest ?? "(unknown)";
7622
+ console.log(
7623
+ ` ${marker} ${e.pkg.padEnd(34)} installed=${installed.padEnd(8)} npm=${latest}`
7624
+ );
7625
+ }
7626
+ console.log("");
7627
+ if (warnings.length > 0) {
7628
+ console.log(" Warnings:");
7629
+ for (const w of warnings) {
7630
+ console.log(` - ${w}`);
7631
+ }
7632
+ console.log("");
7633
+ } else {
7634
+ console.log(" No warnings \u2014 machine looks good.\n");
7635
+ }
7636
+ if (hints.length > 0) {
7637
+ console.log(" Hints:");
7638
+ for (const h of hints) {
7639
+ console.log(` $ ${h}`);
7640
+ }
7641
+ console.log("");
7642
+ }
7643
+ }
7644
+ async function collectToolStatus() {
7645
+ const [adbPath, scrcpyPath, vncPath] = await Promise.all([
7646
+ findAdb().catch(() => null),
7647
+ scrcpyBridgeRuntime.findBinary().catch(() => null),
7648
+ vncBridgeRuntime.findBinary().catch(() => null)
7649
+ ]);
7650
+ return {
7651
+ adb: { path: adbPath },
7652
+ scrcpyBridge: { path: scrcpyPath },
7653
+ vncBridge: { path: vncPath }
7654
+ };
7655
+ }
7656
+ async function collectShimReport(hasBoundDevices) {
7657
+ const sources = readPinSourcesFromEnv();
7658
+ const deviceSuite = /* @__PURE__ */ new Set([
7659
+ NPM_PKGS.DEVICE_AGENT,
7660
+ NPM_PKGS.DEVICE_MCP_SERVER
7661
+ ]);
7662
+ const entries = await Promise.all(
7663
+ ALL_BEEOS_PKGS.map(async (pkg) => {
7664
+ const spec = resolveInstallSpec(pkg, sources);
7665
+ const [installed, latest] = await Promise.all([
7666
+ npmGlobalVersion(pkg).catch(() => null),
7667
+ npmRegistryVersion(spec).catch(() => null)
7668
+ ]);
7669
+ let outcome;
7670
+ if (latest === null) outcome = "error";
7671
+ else if (installed === null) {
7672
+ outcome = deviceSuite.has(pkg) && !hasBoundDevices ? "not_installed_yet" : "missing";
7673
+ } else if (installed === latest) outcome = "ok";
7674
+ else outcome = "outdated";
7675
+ if (pkg === NPM_PKGS.CLI && installed === null) {
7676
+ outcome = "missing";
7677
+ }
7678
+ return { pkg, installed, latest, spec, outcome };
7679
+ })
7680
+ );
7681
+ return { entries };
7682
+ }
7683
+ function shimMarker(outcome) {
7684
+ switch (outcome) {
7685
+ case "ok":
7686
+ return "\u2713";
7687
+ case "outdated":
7688
+ return "\u2191";
7689
+ case "missing":
7690
+ return "\u2717";
7691
+ case "not_installed_yet":
7692
+ return "\xB7";
7693
+ case "error":
7694
+ return "?";
7695
+ }
7696
+ }
7697
+ async function checkLogFileSizes() {
7698
+ const dir = path9.join(beeoHome(), "logs", "services");
7699
+ let entries;
7700
+ try {
7701
+ entries = await fs9.readdir(dir);
7702
+ } catch {
7703
+ return [];
7704
+ }
7705
+ const bloated = [];
7706
+ for (const name of entries) {
7707
+ if (!name.endsWith(".log")) continue;
7708
+ const full = path9.join(dir, name);
7709
+ try {
7710
+ const st = await fs9.stat(full);
7711
+ if (st.isFile() && st.size >= LOG_SIZE_WARN_BYTES) {
7712
+ bloated.push({ path: full, size: st.size });
7713
+ }
7714
+ } catch {
7715
+ }
7716
+ }
7717
+ return bloated;
7718
+ }
7719
+ function formatBytes(n) {
7720
+ if (n >= 1024 * 1024 * 1024) return `${(n / (1024 * 1024 * 1024)).toFixed(1)}GB`;
7721
+ if (n >= 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(0)}MB`;
7722
+ if (n >= 1024) return `${(n / 1024).toFixed(0)}KB`;
7723
+ return `${n}B`;
7724
+ }
7725
+ async function probeAgentGateway(baseUrl) {
7726
+ const url = joinHealthzUrl(baseUrl);
7727
+ const started = Date.now();
7728
+ const ac = new AbortController();
7729
+ const timer = setTimeout(() => ac.abort(), 3e3);
7730
+ try {
7731
+ const res = await fetch(url, {
7732
+ method: "GET",
7733
+ signal: ac.signal,
7734
+ headers: { accept: "application/json, text/plain;q=0.9, */*;q=0.1" }
7735
+ });
7736
+ const latency = Date.now() - started;
7737
+ if (res.ok) {
7738
+ return {
7739
+ url,
7740
+ status: "ok",
7741
+ httpStatus: res.status,
7742
+ latencyMs: latency,
7743
+ detail: `HTTP ${res.status} in ${latency}ms`
7744
+ };
7745
+ }
7746
+ return {
7747
+ url,
7748
+ status: "unhealthy",
7749
+ httpStatus: res.status,
7750
+ latencyMs: latency,
7751
+ detail: `HTTP ${res.status} in ${latency}ms`
7752
+ };
7753
+ } catch (err) {
7754
+ const latency = Date.now() - started;
7755
+ const msg = err instanceof Error ? err.message : String(err);
7756
+ const timedOut = ac.signal.aborted;
7757
+ return {
7758
+ url,
7759
+ status: "unreachable",
7760
+ httpStatus: null,
7761
+ latencyMs: latency,
7762
+ detail: timedOut ? "timeout after 3s" : msg
7763
+ };
7764
+ } finally {
7765
+ clearTimeout(timer);
7766
+ }
7767
+ }
7768
+ function joinHealthzUrl(base) {
7769
+ const trimmed = base.replace(/\/+$/, "");
7770
+ return `${trimmed}/healthz`;
7771
+ }
7772
+ function formatAgentGatewayStatus(h) {
7773
+ if (h.status === "ok") return `ok ${h.latencyMs}ms`;
7774
+ if (h.status === "unhealthy") return `HTTP ${h.httpStatus ?? "?"}`;
7775
+ return h.detail;
7776
+ }
7777
+
7706
7778
  // src/commands/service.ts
7707
7779
  init_dist();
7708
7780
  init_json_envelope();
@@ -7924,8 +7996,8 @@ setPlatformAdapter(new NodePlatformAdapter());
7924
7996
  var program = new Command();
7925
7997
  var cliVersion = getCliVersion(import.meta.url, resolveActiveDistTag());
7926
7998
  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));
7999
+ 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));
8000
+ program.command("doctor").description("Inspect local BeeOS state (install, binding, services, warnings)").option("--json", "Output machine-readable JSON", false).action((opts) => run8(opts));
7929
8001
  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
8002
  program.command("logs").description(
7931
8003
  "Tail logs for a BeeOS service. Pass an ADB serial or service id; omit to list every registered service's log file path."