@beeos-ai/cli 1.0.17 → 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 +613 -541
- package/package.json +1 -1
- package/scripts/_existing_install_actions.json +30 -34
- package/scripts/install.Tests.ps1 +3 -1
- package/scripts/install.ps1 +58 -130
- package/scripts/install.sh +106 -157
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
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("
|
|
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
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
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
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
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
|
-
|
|
7099
|
-
|
|
7100
|
-
|
|
7101
|
-
)
|
|
7102
|
-
|
|
7103
|
-
|
|
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
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
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 (
|
|
7112
|
-
|
|
7113
|
-
|
|
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 (
|
|
7117
|
-
|
|
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 (
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
);
|
|
7177
|
+
if (decision === "reset-all") {
|
|
7178
|
+
await rotateIdentity();
|
|
7179
|
+
await removeBindingInfo();
|
|
7128
7180
|
}
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
|
|
7132
|
-
|
|
7133
|
-
|
|
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 (
|
|
7138
|
-
|
|
7139
|
-
|
|
7140
|
-
);
|
|
7187
|
+
if (sel.kind === "device") {
|
|
7188
|
+
await attach({ serial: sel.serial });
|
|
7189
|
+
return;
|
|
7141
7190
|
}
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
|
|
7146
|
-
|
|
7147
|
-
|
|
7148
|
-
|
|
7149
|
-
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
}
|
|
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
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
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
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
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
|
-
|
|
7206
|
-
|
|
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
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
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
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
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
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
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 (
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
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
|
-
|
|
7282
|
+
return defaultFrameworkId();
|
|
7242
7283
|
}
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
const
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
};
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
const
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
]
|
|
7262
|
-
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
|
|
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
|
|
7298
|
-
const
|
|
7299
|
-
|
|
7311
|
+
async function safeListAdbDevices() {
|
|
7312
|
+
const adbPath = await findAdb().catch(() => null);
|
|
7313
|
+
if (!adbPath) return [];
|
|
7300
7314
|
try {
|
|
7301
|
-
|
|
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
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
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
|
|
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
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
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
|
-
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
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
|
|
7369
|
-
const
|
|
7370
|
-
|
|
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
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
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) =>
|
|
7928
|
-
program.command("doctor").description("Inspect local BeeOS state (install, binding, services, warnings)").option("--json", "Output machine-readable JSON", false).action((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."
|