refactor(commands): split CLI commands
This commit is contained in:
190
src/commands/doctor-gateway-daemon-flow.ts
Normal file
190
src/commands/doctor-gateway-daemon-flow.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
import path from "node:path";
|
||||
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import { resolveGatewayPort } from "../config/config.js";
|
||||
import { resolveGatewayLaunchAgentLabel } from "../daemon/constants.js";
|
||||
import { readLastGatewayErrorLine } from "../daemon/diagnostics.js";
|
||||
import { resolveGatewayProgramArguments } from "../daemon/program-args.js";
|
||||
import {
|
||||
renderSystemNodeWarning,
|
||||
resolvePreferredNodePath,
|
||||
resolveSystemNodeInfo,
|
||||
} from "../daemon/runtime-paths.js";
|
||||
import { resolveGatewayService } from "../daemon/service.js";
|
||||
import { buildServiceEnvironment } from "../daemon/service-env.js";
|
||||
import { formatPortDiagnostics, inspectPortUsage } from "../infra/ports.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { note } from "../terminal/note.js";
|
||||
import { sleep } from "../utils.js";
|
||||
import {
|
||||
DEFAULT_GATEWAY_DAEMON_RUNTIME,
|
||||
GATEWAY_DAEMON_RUNTIME_OPTIONS,
|
||||
type GatewayDaemonRuntime,
|
||||
} from "./daemon-runtime.js";
|
||||
import {
|
||||
buildGatewayRuntimeHints,
|
||||
formatGatewayRuntimeSummary,
|
||||
} from "./doctor-format.js";
|
||||
import type { DoctorOptions, DoctorPrompter } from "./doctor-prompter.js";
|
||||
import { healthCommand } from "./health.js";
|
||||
import { formatHealthCheckFailure } from "./health-format.js";
|
||||
|
||||
export async function maybeRepairGatewayDaemon(params: {
|
||||
cfg: ClawdbotConfig;
|
||||
runtime: RuntimeEnv;
|
||||
prompter: DoctorPrompter;
|
||||
options: DoctorOptions;
|
||||
gatewayDetailsMessage: string;
|
||||
healthOk: boolean;
|
||||
}) {
|
||||
if (params.healthOk) return;
|
||||
|
||||
const service = resolveGatewayService();
|
||||
const loaded = await service.isLoaded({
|
||||
env: process.env,
|
||||
profile: process.env.CLAWDBOT_PROFILE,
|
||||
});
|
||||
let serviceRuntime:
|
||||
| Awaited<ReturnType<typeof service.readRuntime>>
|
||||
| undefined;
|
||||
if (loaded) {
|
||||
serviceRuntime = await service
|
||||
.readRuntime(process.env)
|
||||
.catch(() => undefined);
|
||||
}
|
||||
|
||||
if (params.cfg.gateway?.mode !== "remote") {
|
||||
const port = resolveGatewayPort(params.cfg, process.env);
|
||||
const diagnostics = await inspectPortUsage(port);
|
||||
if (diagnostics.status === "busy") {
|
||||
note(formatPortDiagnostics(diagnostics).join("\n"), "Gateway port");
|
||||
} else if (loaded && serviceRuntime?.status === "running") {
|
||||
const lastError = await readLastGatewayErrorLine(process.env);
|
||||
if (lastError) note(`Last gateway error: ${lastError}`, "Gateway");
|
||||
}
|
||||
}
|
||||
|
||||
if (!loaded) {
|
||||
note("Gateway daemon not installed.", "Gateway");
|
||||
if (params.cfg.gateway?.mode !== "remote") {
|
||||
const install = await params.prompter.confirmSkipInNonInteractive({
|
||||
message: "Install gateway daemon now?",
|
||||
initialValue: true,
|
||||
});
|
||||
if (install) {
|
||||
const daemonRuntime =
|
||||
await params.prompter.select<GatewayDaemonRuntime>(
|
||||
{
|
||||
message: "Gateway daemon runtime",
|
||||
options: GATEWAY_DAEMON_RUNTIME_OPTIONS,
|
||||
initialValue: DEFAULT_GATEWAY_DAEMON_RUNTIME,
|
||||
},
|
||||
DEFAULT_GATEWAY_DAEMON_RUNTIME,
|
||||
);
|
||||
const devMode =
|
||||
process.argv[1]?.includes(`${path.sep}src${path.sep}`) &&
|
||||
process.argv[1]?.endsWith(".ts");
|
||||
const port = resolveGatewayPort(params.cfg, process.env);
|
||||
const nodePath = await resolvePreferredNodePath({
|
||||
env: process.env,
|
||||
runtime: daemonRuntime,
|
||||
});
|
||||
const { programArguments, workingDirectory } =
|
||||
await resolveGatewayProgramArguments({
|
||||
port,
|
||||
dev: devMode,
|
||||
runtime: daemonRuntime,
|
||||
nodePath,
|
||||
});
|
||||
if (daemonRuntime === "node") {
|
||||
const systemNode = await resolveSystemNodeInfo({ env: process.env });
|
||||
const warning = renderSystemNodeWarning(
|
||||
systemNode,
|
||||
programArguments[0],
|
||||
);
|
||||
if (warning) note(warning, "Gateway runtime");
|
||||
}
|
||||
const environment = buildServiceEnvironment({
|
||||
env: process.env,
|
||||
port,
|
||||
token:
|
||||
params.cfg.gateway?.auth?.token ??
|
||||
process.env.CLAWDBOT_GATEWAY_TOKEN,
|
||||
launchdLabel:
|
||||
process.platform === "darwin"
|
||||
? resolveGatewayLaunchAgentLabel(process.env.CLAWDBOT_PROFILE)
|
||||
: undefined,
|
||||
});
|
||||
await service.install({
|
||||
env: process.env,
|
||||
stdout: process.stdout,
|
||||
programArguments,
|
||||
workingDirectory,
|
||||
environment,
|
||||
});
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const summary = formatGatewayRuntimeSummary(serviceRuntime);
|
||||
const hints = buildGatewayRuntimeHints(serviceRuntime, {
|
||||
platform: process.platform,
|
||||
env: process.env,
|
||||
});
|
||||
if (summary || hints.length > 0) {
|
||||
const lines: string[] = [];
|
||||
if (summary) lines.push(`Runtime: ${summary}`);
|
||||
lines.push(...hints);
|
||||
note(lines.join("\n"), "Gateway");
|
||||
}
|
||||
|
||||
if (serviceRuntime?.status !== "running") {
|
||||
const start = await params.prompter.confirmSkipInNonInteractive({
|
||||
message: "Start gateway daemon now?",
|
||||
initialValue: true,
|
||||
});
|
||||
if (start) {
|
||||
await service.restart({
|
||||
env: process.env,
|
||||
profile: process.env.CLAWDBOT_PROFILE,
|
||||
stdout: process.stdout,
|
||||
});
|
||||
await sleep(1500);
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
const label = resolveGatewayLaunchAgentLabel(process.env.CLAWDBOT_PROFILE);
|
||||
note(
|
||||
`LaunchAgent loaded; stopping requires "clawdbot daemon stop" or launchctl bootout gui/$UID/${label}.`,
|
||||
"Gateway",
|
||||
);
|
||||
}
|
||||
|
||||
if (serviceRuntime?.status === "running") {
|
||||
const restart = await params.prompter.confirmSkipInNonInteractive({
|
||||
message: "Restart gateway daemon now?",
|
||||
initialValue: true,
|
||||
});
|
||||
if (restart) {
|
||||
await service.restart({
|
||||
env: process.env,
|
||||
profile: process.env.CLAWDBOT_PROFILE,
|
||||
stdout: process.stdout,
|
||||
});
|
||||
await sleep(1500);
|
||||
try {
|
||||
await healthCommand({ json: false, timeoutMs: 10_000 }, params.runtime);
|
||||
} catch (err) {
|
||||
const message = String(err);
|
||||
if (message.includes("gateway closed")) {
|
||||
note("Gateway not running.", "Gateway");
|
||||
note(params.gatewayDetailsMessage, "Gateway connection");
|
||||
} else {
|
||||
params.runtime.error(formatHealthCheckFailure(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user