feat: fold gateway service commands into gateway
This commit is contained in:
@@ -66,7 +66,7 @@ describe("buildAgentSystemPrompt", () => {
|
||||
});
|
||||
|
||||
expect(prompt).toContain("## Clawdbot CLI Quick Reference");
|
||||
expect(prompt).toContain("clawdbot daemon restart");
|
||||
expect(prompt).toContain("clawdbot gateway restart");
|
||||
expect(prompt).toContain("Do not invent commands");
|
||||
});
|
||||
|
||||
|
||||
@@ -365,11 +365,11 @@ export function buildAgentSystemPrompt(params: {
|
||||
"## Clawdbot CLI Quick Reference",
|
||||
"Clawdbot is controlled via subcommands. Do not invent commands.",
|
||||
"To manage the Gateway daemon service (start/stop/restart):",
|
||||
"- clawdbot daemon status",
|
||||
"- clawdbot daemon start",
|
||||
"- clawdbot daemon stop",
|
||||
"- clawdbot daemon restart",
|
||||
"If unsure, ask the user to run `clawdbot help` (or `clawdbot daemon --help`) and paste the output.",
|
||||
"- clawdbot gateway status",
|
||||
"- clawdbot gateway start",
|
||||
"- clawdbot gateway stop",
|
||||
"- clawdbot gateway restart",
|
||||
"If unsure, ask the user to run `clawdbot help` (or `clawdbot gateway --help`) and paste the output.",
|
||||
"",
|
||||
...skillsSection,
|
||||
...memorySection,
|
||||
|
||||
@@ -43,7 +43,7 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) {
|
||||
};
|
||||
|
||||
if (resolveIsNixMode(process.env)) {
|
||||
fail("Nix mode detected; daemon install is disabled.");
|
||||
fail("Nix mode detected; service install is disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) {
|
||||
if (!json) {
|
||||
defaultRuntime.log(`Gateway service already ${service.loadedText}.`);
|
||||
defaultRuntime.log(
|
||||
`Reinstall with: ${formatCliCommand("clawdbot daemon install --force")}`,
|
||||
`Reinstall with: ${formatCliCommand("clawdbot gateway install --force")}`,
|
||||
);
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -33,7 +33,7 @@ export async function runDaemonUninstall(opts: DaemonLifecycleOptions = {}) {
|
||||
};
|
||||
|
||||
if (resolveIsNixMode(process.env)) {
|
||||
fail("Nix mode detected; daemon uninstall is disabled.");
|
||||
fail("Nix mode detected; service uninstall is disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ export async function runDaemonStop(opts: DaemonLifecycleOptions = {}) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart the gateway daemon service.
|
||||
* Restart the gateway service service.
|
||||
* @returns `true` if restart succeeded, `false` if the service was not loaded.
|
||||
* Throws/exits on check or restart failures.
|
||||
*/
|
||||
|
||||
@@ -14,16 +14,16 @@ import {
|
||||
export function registerDaemonCli(program: Command) {
|
||||
const daemon = program
|
||||
.command("daemon")
|
||||
.description("Manage the Gateway daemon service (launchd/systemd/schtasks)")
|
||||
.description("Manage the Gateway service (launchd/systemd/schtasks)")
|
||||
.addHelpText(
|
||||
"after",
|
||||
() =>
|
||||
`\n${theme.muted("Docs:")} ${formatDocsLink("/cli/daemon", "docs.clawd.bot/cli/daemon")}\n`,
|
||||
`\n${theme.muted("Docs:")} ${formatDocsLink("/cli/gateway", "docs.clawd.bot/cli/gateway")}\n`,
|
||||
);
|
||||
|
||||
daemon
|
||||
.command("status")
|
||||
.description("Show daemon install status + probe the Gateway")
|
||||
.description("Show service install status + probe the Gateway")
|
||||
.option("--url <url>", "Gateway WebSocket URL (defaults to config/remote/local)")
|
||||
.option("--token <token>", "Gateway token (if required)")
|
||||
.option("--password <password>", "Gateway password (password auth)")
|
||||
|
||||
@@ -123,7 +123,7 @@ export function renderRuntimeHints(
|
||||
}
|
||||
})();
|
||||
if (runtime.missingUnit) {
|
||||
hints.push(`Service not installed. Run: ${formatCliCommand("clawdbot daemon install", env)}`);
|
||||
hints.push(`Service not installed. Run: ${formatCliCommand("clawdbot gateway install", env)}`);
|
||||
if (fileLog) hints.push(`File logs: ${fileLog}`);
|
||||
return hints;
|
||||
}
|
||||
@@ -146,7 +146,7 @@ export function renderRuntimeHints(
|
||||
|
||||
export function renderGatewayServiceStartHints(env: NodeJS.ProcessEnv = process.env): string[] {
|
||||
const base = [
|
||||
formatCliCommand("clawdbot daemon install", env),
|
||||
formatCliCommand("clawdbot gateway install", env),
|
||||
formatCliCommand("clawdbot gateway", env),
|
||||
];
|
||||
const profile = env.CLAWDBOT_PROFILE;
|
||||
|
||||
@@ -60,7 +60,7 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean })
|
||||
}
|
||||
const daemonEnvLines = safeDaemonEnv(service.command?.environment);
|
||||
if (daemonEnvLines.length > 0) {
|
||||
defaultRuntime.log(`${label("Daemon env:")} ${daemonEnvLines.join(" ")}`);
|
||||
defaultRuntime.log(`${label("Service env:")} ${daemonEnvLines.join(" ")}`);
|
||||
}
|
||||
spacer();
|
||||
|
||||
@@ -89,11 +89,11 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean })
|
||||
}
|
||||
if (status.config.daemon) {
|
||||
const daemonCfg = `${status.config.daemon.path}${status.config.daemon.exists ? "" : " (missing)"}${status.config.daemon.valid ? "" : " (invalid)"}`;
|
||||
defaultRuntime.log(`${label("Config (daemon):")} ${infoText(daemonCfg)}`);
|
||||
defaultRuntime.log(`${label("Config (service):")} ${infoText(daemonCfg)}`);
|
||||
if (!status.config.daemon.valid && status.config.daemon.issues?.length) {
|
||||
for (const issue of status.config.daemon.issues.slice(0, 5)) {
|
||||
defaultRuntime.error(
|
||||
`${errorText("Daemon config issue:")} ${issue.path || "<root>"}: ${issue.message}`,
|
||||
`${errorText("Service config issue:")} ${issue.path || "<root>"}: ${issue.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -101,12 +101,12 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean })
|
||||
if (status.config.mismatch) {
|
||||
defaultRuntime.error(
|
||||
errorText(
|
||||
"Root cause: CLI and daemon are using different config paths (likely a profile/state-dir mismatch).",
|
||||
"Root cause: CLI and service are using different config paths (likely a profile/state-dir mismatch).",
|
||||
),
|
||||
);
|
||||
defaultRuntime.error(
|
||||
errorText(
|
||||
`Fix: rerun \`${formatCliCommand("clawdbot daemon install --force")}\` from the same --profile / CLAWDBOT_STATE_DIR you expect.`,
|
||||
`Fix: rerun \`${formatCliCommand("clawdbot gateway install --force")}\` from the same --profile / CLAWDBOT_STATE_DIR you expect.`,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -209,7 +209,7 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean })
|
||||
),
|
||||
);
|
||||
defaultRuntime.error(
|
||||
errorText(`Then reinstall: ${formatCliCommand("clawdbot daemon install")}`),
|
||||
errorText(`Then reinstall: ${formatCliCommand("clawdbot gateway install")}`),
|
||||
);
|
||||
spacer();
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export async function runDaemonStatus(opts: DaemonStatusOptions) {
|
||||
printDaemonStatus(status, { json: Boolean(opts.json) });
|
||||
} catch (err) {
|
||||
const rich = isRich();
|
||||
defaultRuntime.error(colorize(rich, theme.error, `Daemon status failed: ${String(err)}`));
|
||||
defaultRuntime.error(colorize(rich, theme.error, `Gateway status failed: ${String(err)}`));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ describe("gateway-cli coverage", () => {
|
||||
expect(runtimeLogs.join("\n")).toContain('"ok": true');
|
||||
}, 30_000);
|
||||
|
||||
it("registers gateway status and routes to gatewayStatusCommand", async () => {
|
||||
it("registers gateway probe and routes to gatewayStatusCommand", async () => {
|
||||
runtimeLogs.length = 0;
|
||||
runtimeErrors.length = 0;
|
||||
gatewayStatusCommand.mockClear();
|
||||
@@ -128,7 +128,7 @@ describe("gateway-cli coverage", () => {
|
||||
program.exitOverride();
|
||||
registerGatewayCli(program);
|
||||
|
||||
await program.parseAsync(["gateway", "status", "--json"], { from: "user" });
|
||||
await program.parseAsync(["gateway", "probe", "--json"], { from: "user" });
|
||||
|
||||
expect(gatewayStatusCommand).toHaveBeenCalledTimes(1);
|
||||
}, 30_000);
|
||||
@@ -311,7 +311,7 @@ describe("gateway-cli coverage", () => {
|
||||
|
||||
expect(startGatewayServer).toHaveBeenCalled();
|
||||
expect(runtimeErrors.join("\n")).toContain("Gateway failed to start:");
|
||||
expect(runtimeErrors.join("\n")).toContain("clawdbot daemon stop");
|
||||
expect(runtimeErrors.join("\n")).toContain("clawdbot gateway stop");
|
||||
});
|
||||
|
||||
it("uses env/config port when --port is omitted", async () => {
|
||||
|
||||
@@ -8,6 +8,14 @@ import { formatDocsLink } from "../../terminal/links.js";
|
||||
import { colorize, isRich, theme } from "../../terminal/theme.js";
|
||||
import { withProgress } from "../progress.js";
|
||||
import { runCommandWithRuntime } from "../cli-utils.js";
|
||||
import {
|
||||
runDaemonInstall,
|
||||
runDaemonRestart,
|
||||
runDaemonStart,
|
||||
runDaemonStatus,
|
||||
runDaemonStop,
|
||||
runDaemonUninstall,
|
||||
} from "../daemon-cli.js";
|
||||
import { callGatewayCli, gatewayCallOpts } from "./call.js";
|
||||
import type { GatewayDiscoverOpts } from "./discover.js";
|
||||
import {
|
||||
@@ -62,13 +70,73 @@ export function registerGatewayCli(program: Command) {
|
||||
),
|
||||
);
|
||||
|
||||
// Back-compat: legacy launchd plists used gateway-daemon; keep hidden alias.
|
||||
addGatewayRunCommand(
|
||||
program
|
||||
.command("gateway-daemon", { hidden: true })
|
||||
.description("Run the WebSocket Gateway as a long-lived daemon"),
|
||||
gateway.command("run").description("Run the WebSocket Gateway (foreground)"),
|
||||
);
|
||||
|
||||
gateway
|
||||
.command("status")
|
||||
.description("Show gateway service status + probe the Gateway")
|
||||
.option("--url <url>", "Gateway WebSocket URL (defaults to config/remote/local)")
|
||||
.option("--token <token>", "Gateway token (if required)")
|
||||
.option("--password <password>", "Gateway password (password auth)")
|
||||
.option("--timeout <ms>", "Timeout in ms", "10000")
|
||||
.option("--no-probe", "Skip RPC probe")
|
||||
.option("--deep", "Scan system-level services", false)
|
||||
.option("--json", "Output JSON", false)
|
||||
.action(async (opts) => {
|
||||
await runDaemonStatus({
|
||||
rpc: opts,
|
||||
probe: Boolean(opts.probe),
|
||||
deep: Boolean(opts.deep),
|
||||
json: Boolean(opts.json),
|
||||
});
|
||||
});
|
||||
|
||||
gateway
|
||||
.command("install")
|
||||
.description("Install the Gateway service (launchd/systemd/schtasks)")
|
||||
.option("--port <port>", "Gateway port")
|
||||
.option("--runtime <runtime>", "Daemon runtime (node|bun). Default: node")
|
||||
.option("--token <token>", "Gateway token (token auth)")
|
||||
.option("--force", "Reinstall/overwrite if already installed", false)
|
||||
.option("--json", "Output JSON", false)
|
||||
.action(async (opts) => {
|
||||
await runDaemonInstall(opts);
|
||||
});
|
||||
|
||||
gateway
|
||||
.command("uninstall")
|
||||
.description("Uninstall the Gateway service (launchd/systemd/schtasks)")
|
||||
.option("--json", "Output JSON", false)
|
||||
.action(async (opts) => {
|
||||
await runDaemonUninstall(opts);
|
||||
});
|
||||
|
||||
gateway
|
||||
.command("start")
|
||||
.description("Start the Gateway service (launchd/systemd/schtasks)")
|
||||
.option("--json", "Output JSON", false)
|
||||
.action(async (opts) => {
|
||||
await runDaemonStart(opts);
|
||||
});
|
||||
|
||||
gateway
|
||||
.command("stop")
|
||||
.description("Stop the Gateway service (launchd/systemd/schtasks)")
|
||||
.option("--json", "Output JSON", false)
|
||||
.action(async (opts) => {
|
||||
await runDaemonStop(opts);
|
||||
});
|
||||
|
||||
gateway
|
||||
.command("restart")
|
||||
.description("Restart the Gateway service (launchd/systemd/schtasks)")
|
||||
.option("--json", "Output JSON", false)
|
||||
.action(async (opts) => {
|
||||
await runDaemonRestart(opts);
|
||||
});
|
||||
|
||||
gatewayCallOpts(
|
||||
gateway
|
||||
.command("call")
|
||||
@@ -121,7 +189,7 @@ export function registerGatewayCli(program: Command) {
|
||||
);
|
||||
|
||||
gateway
|
||||
.command("status")
|
||||
.command("probe")
|
||||
.description("Show gateway reachability + discovery + health + status summary (local + remote)")
|
||||
.option("--url <url>", "Explicit Gateway WebSocket URL (still probes localhost)")
|
||||
.option("--ssh <target>", "SSH target for remote gateway tunnel (user@host or user@host:port)")
|
||||
|
||||
@@ -278,7 +278,7 @@ async function runGatewayCommand(opts: GatewayRunOpts) {
|
||||
) {
|
||||
const errMessage = describeUnknownError(err);
|
||||
defaultRuntime.error(
|
||||
`Gateway failed to start: ${errMessage}\nIf the gateway is supervised, stop it with: ${formatCliCommand("clawdbot daemon stop")}`,
|
||||
`Gateway failed to start: ${errMessage}\nIf the gateway is supervised, stop it with: ${formatCliCommand("clawdbot gateway stop")}`,
|
||||
);
|
||||
try {
|
||||
const diagnostics = await inspectPortUsage(port);
|
||||
|
||||
@@ -68,21 +68,21 @@ export function renderGatewayServiceStopHints(env: NodeJS.ProcessEnv = process.e
|
||||
switch (process.platform) {
|
||||
case "darwin":
|
||||
return [
|
||||
`Tip: ${formatCliCommand("clawdbot daemon stop")}`,
|
||||
`Tip: ${formatCliCommand("clawdbot gateway stop")}`,
|
||||
`Or: launchctl bootout gui/$UID/${resolveGatewayLaunchAgentLabel(profile)}`,
|
||||
];
|
||||
case "linux":
|
||||
return [
|
||||
`Tip: ${formatCliCommand("clawdbot daemon stop")}`,
|
||||
`Tip: ${formatCliCommand("clawdbot gateway stop")}`,
|
||||
`Or: systemctl --user stop ${resolveGatewaySystemdServiceName(profile)}.service`,
|
||||
];
|
||||
case "win32":
|
||||
return [
|
||||
`Tip: ${formatCliCommand("clawdbot daemon stop")}`,
|
||||
`Tip: ${formatCliCommand("clawdbot gateway stop")}`,
|
||||
`Or: schtasks /End /TN "${resolveGatewayWindowsTaskName(profile)}"`,
|
||||
];
|
||||
default:
|
||||
return [`Tip: ${formatCliCommand("clawdbot daemon stop")}`];
|
||||
return [`Tip: ${formatCliCommand("clawdbot gateway stop")}`];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,19 @@ import { colorize, isRich, theme } from "../../terminal/theme.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
import { formatCliCommand } from "../command-format.js";
|
||||
|
||||
const ALLOWED_INVALID_COMMANDS = new Set(["doctor", "logs", "health", "help", "status", "service"]);
|
||||
const ALLOWED_INVALID_COMMANDS = new Set(["doctor", "logs", "health", "help", "status"]);
|
||||
const ALLOWED_INVALID_GATEWAY_SUBCOMMANDS = new Set([
|
||||
"status",
|
||||
"probe",
|
||||
"health",
|
||||
"discover",
|
||||
"call",
|
||||
"install",
|
||||
"uninstall",
|
||||
"start",
|
||||
"stop",
|
||||
"restart",
|
||||
]);
|
||||
let didRunDoctorConfigFlow = false;
|
||||
|
||||
function formatConfigIssues(issues: Array<{ path: string; message: string }>): string[] {
|
||||
@@ -25,7 +37,13 @@ export async function ensureConfigReady(params: {
|
||||
|
||||
const snapshot = await readConfigFileSnapshot();
|
||||
const commandName = params.commandPath?.[0];
|
||||
const allowInvalid = commandName ? ALLOWED_INVALID_COMMANDS.has(commandName) : false;
|
||||
const subcommandName = params.commandPath?.[1];
|
||||
const allowInvalid = commandName
|
||||
? ALLOWED_INVALID_COMMANDS.has(commandName) ||
|
||||
(commandName === "gateway" &&
|
||||
subcommandName &&
|
||||
ALLOWED_INVALID_GATEWAY_SUBCOMMANDS.has(subcommandName))
|
||||
: false;
|
||||
const issues = snapshot.exists && !snapshot.valid ? formatConfigIssues(snapshot.issues) : [];
|
||||
const legacyIssues =
|
||||
snapshot.legacyIssues.length > 0
|
||||
|
||||
@@ -36,14 +36,6 @@ const entries: SubCliEntry[] = [
|
||||
mod.registerAcpCli(program);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "daemon",
|
||||
description: "Manage the gateway daemon",
|
||||
register: async (program) => {
|
||||
const mod = await import("../daemon-cli.js");
|
||||
mod.registerDaemonCli(program);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "gateway",
|
||||
description: "Gateway control",
|
||||
|
||||
@@ -785,7 +785,7 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
|
||||
if (result.reason === "not-git-install") {
|
||||
defaultRuntime.log(
|
||||
theme.warn(
|
||||
`Skipped: this Clawdbot install isn't a git checkout, and the package manager couldn't be detected. Update via your package manager, then run \`${formatCliCommand("clawdbot doctor")}\` and \`${formatCliCommand("clawdbot daemon restart")}\`.`,
|
||||
`Skipped: this Clawdbot install isn't a git checkout, and the package manager couldn't be detected. Update via your package manager, then run \`${formatCliCommand("clawdbot doctor")}\` and \`${formatCliCommand("clawdbot gateway restart")}\`.`,
|
||||
),
|
||||
);
|
||||
defaultRuntime.log(
|
||||
@@ -877,11 +877,11 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
|
||||
defaultRuntime.log(theme.warn("Skipping plugin updates: config is invalid."));
|
||||
}
|
||||
|
||||
// Restart daemon if requested
|
||||
// Restart service if requested
|
||||
if (opts.restart) {
|
||||
if (!opts.json) {
|
||||
defaultRuntime.log("");
|
||||
defaultRuntime.log(theme.heading("Restarting daemon..."));
|
||||
defaultRuntime.log(theme.heading("Restarting service..."));
|
||||
}
|
||||
try {
|
||||
const { runDaemonRestart } = await import("./daemon-cli.js");
|
||||
@@ -905,7 +905,7 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
|
||||
defaultRuntime.log(theme.warn(`Daemon restart failed: ${String(err)}`));
|
||||
defaultRuntime.log(
|
||||
theme.muted(
|
||||
`You may need to restart the daemon manually: ${formatCliCommand("clawdbot daemon restart")}`,
|
||||
`You may need to restart the service manually: ${formatCliCommand("clawdbot gateway restart")}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -915,13 +915,13 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
|
||||
if (result.mode === "npm" || result.mode === "pnpm") {
|
||||
defaultRuntime.log(
|
||||
theme.muted(
|
||||
`Tip: Run \`${formatCliCommand("clawdbot doctor")}\`, then \`${formatCliCommand("clawdbot daemon restart")}\` to apply updates to a running gateway.`,
|
||||
`Tip: Run \`${formatCliCommand("clawdbot doctor")}\`, then \`${formatCliCommand("clawdbot gateway restart")}\` to apply updates to a running gateway.`,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
defaultRuntime.log(
|
||||
theme.muted(
|
||||
`Tip: Run \`${formatCliCommand("clawdbot daemon restart")}\` to apply updates to a running gateway.`,
|
||||
`Tip: Run \`${formatCliCommand("clawdbot gateway restart")}\` to apply updates to a running gateway.`,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -937,7 +937,7 @@ export function registerUpdateCli(program: Command) {
|
||||
.command("update")
|
||||
.description("Update Clawdbot to the latest version")
|
||||
.option("--json", "Output result as JSON", false)
|
||||
.option("--restart", "Restart the gateway daemon after a successful update", false)
|
||||
.option("--restart", "Restart the gateway service after a successful update", false)
|
||||
.option("--channel <stable|beta|dev>", "Persist update channel (git + npm)")
|
||||
.option("--tag <dist-tag|version>", "Override npm dist-tag or version for this update")
|
||||
.option("--timeout <seconds>", "Timeout for each update step in seconds (default: 1200)")
|
||||
@@ -948,7 +948,7 @@ export function registerUpdateCli(program: Command) {
|
||||
["clawdbot update --channel beta", "Switch to beta channel (git + npm)"],
|
||||
["clawdbot update --channel dev", "Switch to dev channel (git + npm)"],
|
||||
["clawdbot update --tag beta", "One-off update to a dist-tag or version"],
|
||||
["clawdbot update --restart", "Update and restart the daemon"],
|
||||
["clawdbot update --restart", "Update and restart the service"],
|
||||
["clawdbot update --json", "Output result as JSON"],
|
||||
["clawdbot update --yes", "Non-interactive (accept downgrade prompts)"],
|
||||
["clawdbot --update", "Shorthand for clawdbot update"],
|
||||
|
||||
@@ -37,14 +37,14 @@ export async function maybeInstallDaemon(params: {
|
||||
);
|
||||
if (action === "restart") {
|
||||
await withProgress(
|
||||
{ label: "Gateway daemon", indeterminate: true, delayMs: 0 },
|
||||
{ label: "Gateway service", indeterminate: true, delayMs: 0 },
|
||||
async (progress) => {
|
||||
progress.setLabel("Restarting Gateway daemon…");
|
||||
progress.setLabel("Restarting Gateway service…");
|
||||
await service.restart({
|
||||
env: process.env,
|
||||
stdout: process.stdout,
|
||||
});
|
||||
progress.setLabel("Gateway daemon restarted.");
|
||||
progress.setLabel("Gateway service restarted.");
|
||||
},
|
||||
);
|
||||
shouldCheckLinger = true;
|
||||
@@ -53,11 +53,11 @@ export async function maybeInstallDaemon(params: {
|
||||
if (action === "skip") return;
|
||||
if (action === "reinstall") {
|
||||
await withProgress(
|
||||
{ label: "Gateway daemon", indeterminate: true, delayMs: 0 },
|
||||
{ label: "Gateway service", indeterminate: true, delayMs: 0 },
|
||||
async (progress) => {
|
||||
progress.setLabel("Uninstalling Gateway daemon…");
|
||||
progress.setLabel("Uninstalling Gateway service…");
|
||||
await service.uninstall({ env: process.env, stdout: process.stdout });
|
||||
progress.setLabel("Gateway daemon uninstalled.");
|
||||
progress.setLabel("Gateway service uninstalled.");
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -66,12 +66,12 @@ export async function maybeInstallDaemon(params: {
|
||||
if (shouldInstall) {
|
||||
let installError: string | null = null;
|
||||
await withProgress(
|
||||
{ label: "Gateway daemon", indeterminate: true, delayMs: 0 },
|
||||
{ label: "Gateway service", indeterminate: true, delayMs: 0 },
|
||||
async (progress) => {
|
||||
if (!params.daemonRuntime) {
|
||||
daemonRuntime = guardCancel(
|
||||
await select({
|
||||
message: "Gateway daemon runtime",
|
||||
message: "Gateway service runtime",
|
||||
options: GATEWAY_DAEMON_RUNTIME_OPTIONS,
|
||||
initialValue: DEFAULT_GATEWAY_DAEMON_RUNTIME,
|
||||
}),
|
||||
@@ -79,7 +79,7 @@ export async function maybeInstallDaemon(params: {
|
||||
) as GatewayDaemonRuntime;
|
||||
}
|
||||
|
||||
progress.setLabel("Preparing Gateway daemon…");
|
||||
progress.setLabel("Preparing Gateway service…");
|
||||
|
||||
const { programArguments, workingDirectory, environment } = await buildGatewayInstallPlan({
|
||||
env: process.env,
|
||||
@@ -89,7 +89,7 @@ export async function maybeInstallDaemon(params: {
|
||||
warn: (message, title) => note(message, title),
|
||||
});
|
||||
|
||||
progress.setLabel("Installing Gateway daemon…");
|
||||
progress.setLabel("Installing Gateway service…");
|
||||
try {
|
||||
await service.install({
|
||||
env: process.env,
|
||||
@@ -98,15 +98,15 @@ export async function maybeInstallDaemon(params: {
|
||||
workingDirectory,
|
||||
environment,
|
||||
});
|
||||
progress.setLabel("Gateway daemon installed.");
|
||||
progress.setLabel("Gateway service installed.");
|
||||
} catch (err) {
|
||||
installError = err instanceof Error ? err.message : String(err);
|
||||
progress.setLabel("Gateway daemon install failed.");
|
||||
progress.setLabel("Gateway service install failed.");
|
||||
}
|
||||
},
|
||||
);
|
||||
if (installError) {
|
||||
note("Gateway daemon install failed: " + installError, "Gateway");
|
||||
note("Gateway service install failed: " + installError, "Gateway");
|
||||
note(gatewayInstallErrorHint(), "Gateway");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -359,7 +359,7 @@ export async function runConfigureWizard(
|
||||
if (!selected.includes("gateway")) {
|
||||
const portInput = guardCancel(
|
||||
await text({
|
||||
message: "Gateway port for daemon install",
|
||||
message: "Gateway port for service install",
|
||||
initialValue: String(gatewayPort),
|
||||
validate: (value) => (Number.isFinite(Number(value)) ? undefined : "Invalid port"),
|
||||
}),
|
||||
@@ -481,7 +481,7 @@ export async function runConfigureWizard(
|
||||
if (!didConfigureGateway) {
|
||||
const portInput = guardCancel(
|
||||
await text({
|
||||
message: "Gateway port for daemon install",
|
||||
message: "Gateway port for service install",
|
||||
initialValue: String(gatewayPort),
|
||||
validate: (value) => (Number.isFinite(Number(value)) ? undefined : "Invalid port"),
|
||||
}),
|
||||
|
||||
@@ -100,6 +100,6 @@ describe("buildGatewayInstallPlan", () => {
|
||||
describe("gatewayInstallErrorHint", () => {
|
||||
it("returns platform-specific hints", () => {
|
||||
expect(gatewayInstallErrorHint("win32")).toContain("Run as administrator");
|
||||
expect(gatewayInstallErrorHint("linux")).toContain("clawdbot daemon install");
|
||||
expect(gatewayInstallErrorHint("linux")).toContain("clawdbot gateway install");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -65,6 +65,6 @@ export async function buildGatewayInstallPlan(params: {
|
||||
|
||||
export function gatewayInstallErrorHint(platform = process.platform): string {
|
||||
return platform === "win32"
|
||||
? "Tip: rerun from an elevated PowerShell (Start → type PowerShell → right-click → Run as administrator) or skip daemon install."
|
||||
: `Tip: rerun \`${formatCliCommand("clawdbot daemon install")}\` after fixing the error.`;
|
||||
? "Tip: rerun from an elevated PowerShell (Start → type PowerShell → right-click → Run as administrator) or skip service install."
|
||||
: `Tip: rerun \`${formatCliCommand("clawdbot gateway install")}\` after fixing the error.`;
|
||||
}
|
||||
|
||||
@@ -70,10 +70,10 @@ export function buildGatewayRuntimeHints(
|
||||
hints.push(
|
||||
`LaunchAgent label cached but plist missing. Clear with: launchctl bootout gui/$UID/${label}`,
|
||||
);
|
||||
hints.push(`Then reinstall: ${formatCliCommand("clawdbot daemon install", env)}`);
|
||||
hints.push(`Then reinstall: ${formatCliCommand("clawdbot gateway install", env)}`);
|
||||
}
|
||||
if (runtime.missingUnit) {
|
||||
hints.push(`Service not installed. Run: ${formatCliCommand("clawdbot daemon install", env)}`);
|
||||
hints.push(`Service not installed. Run: ${formatCliCommand("clawdbot gateway install", env)}`);
|
||||
if (fileLog) hints.push(`File logs: ${fileLog}`);
|
||||
return hints;
|
||||
}
|
||||
|
||||
@@ -139,16 +139,16 @@ export async function maybeRepairGatewayDaemon(params: {
|
||||
return;
|
||||
}
|
||||
}
|
||||
note("Gateway daemon not installed.", "Gateway");
|
||||
note("Gateway service not installed.", "Gateway");
|
||||
if (params.cfg.gateway?.mode !== "remote") {
|
||||
const install = await params.prompter.confirmSkipInNonInteractive({
|
||||
message: "Install gateway daemon now?",
|
||||
message: "Install gateway service now?",
|
||||
initialValue: true,
|
||||
});
|
||||
if (install) {
|
||||
const daemonRuntime = await params.prompter.select<GatewayDaemonRuntime>(
|
||||
{
|
||||
message: "Gateway daemon runtime",
|
||||
message: "Gateway service runtime",
|
||||
options: GATEWAY_DAEMON_RUNTIME_OPTIONS,
|
||||
initialValue: DEFAULT_GATEWAY_DAEMON_RUNTIME,
|
||||
},
|
||||
@@ -171,7 +171,7 @@ export async function maybeRepairGatewayDaemon(params: {
|
||||
environment,
|
||||
});
|
||||
} catch (err) {
|
||||
note(`Gateway daemon install failed: ${String(err)}`, "Gateway");
|
||||
note(`Gateway service install failed: ${String(err)}`, "Gateway");
|
||||
note(gatewayInstallErrorHint(), "Gateway");
|
||||
}
|
||||
}
|
||||
@@ -193,7 +193,7 @@ export async function maybeRepairGatewayDaemon(params: {
|
||||
|
||||
if (serviceRuntime?.status !== "running") {
|
||||
const start = await params.prompter.confirmSkipInNonInteractive({
|
||||
message: "Start gateway daemon now?",
|
||||
message: "Start gateway service now?",
|
||||
initialValue: true,
|
||||
});
|
||||
if (start) {
|
||||
@@ -208,14 +208,14 @@ export async function maybeRepairGatewayDaemon(params: {
|
||||
if (process.platform === "darwin") {
|
||||
const label = resolveGatewayLaunchAgentLabel(process.env.CLAWDBOT_PROFILE);
|
||||
note(
|
||||
`LaunchAgent loaded; stopping requires "${formatCliCommand("clawdbot daemon stop")}" or launchctl bootout gui/$UID/${label}.`,
|
||||
`LaunchAgent loaded; stopping requires "${formatCliCommand("clawdbot gateway stop")}" or launchctl bootout gui/$UID/${label}.`,
|
||||
"Gateway",
|
||||
);
|
||||
}
|
||||
|
||||
if (serviceRuntime?.status === "running") {
|
||||
const restart = await params.prompter.confirmSkipInNonInteractive({
|
||||
message: "Restart gateway daemon now?",
|
||||
message: "Restart gateway service now?",
|
||||
initialValue: true,
|
||||
});
|
||||
if (restart) {
|
||||
|
||||
@@ -97,7 +97,7 @@ export async function maybeMigrateLegacyGatewayService(
|
||||
|
||||
const daemonRuntime = await prompter.select<GatewayDaemonRuntime>(
|
||||
{
|
||||
message: "Gateway daemon runtime",
|
||||
message: "Gateway service runtime",
|
||||
options: GATEWAY_DAEMON_RUNTIME_OPTIONS,
|
||||
initialValue: DEFAULT_GATEWAY_DAEMON_RUNTIME,
|
||||
},
|
||||
@@ -120,7 +120,7 @@ export async function maybeMigrateLegacyGatewayService(
|
||||
environment,
|
||||
});
|
||||
} catch (err) {
|
||||
runtime.error(`Gateway daemon install failed: ${String(err)}`);
|
||||
runtime.error(`Gateway service install failed: ${String(err)}`);
|
||||
note(gatewayInstallErrorHint(), "Gateway");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export async function installGatewayDaemonNonInteractive(params: {
|
||||
const systemdAvailable =
|
||||
process.platform === "linux" ? await isSystemdUserServiceAvailable() : true;
|
||||
if (process.platform === "linux" && !systemdAvailable) {
|
||||
runtime.log("Systemd user services are unavailable; skipping daemon install.");
|
||||
runtime.log("Systemd user services are unavailable; skipping service install.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ export async function installGatewayDaemonNonInteractive(params: {
|
||||
environment,
|
||||
});
|
||||
} catch (err) {
|
||||
runtime.error(`Gateway daemon install failed: ${String(err)}`);
|
||||
runtime.error(`Gateway service install failed: ${String(err)}`);
|
||||
runtime.log(gatewayInstallErrorHint());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -574,6 +574,6 @@ export async function statusCommand(
|
||||
if (gatewayReachable) {
|
||||
runtime.log(` Need to test channels? ${formatCliCommand("clawdbot status --deep")}`);
|
||||
} else {
|
||||
runtime.log(` Fix reachability first: ${formatCliCommand("clawdbot gateway status")}`);
|
||||
runtime.log(` Fix reachability first: ${formatCliCommand("clawdbot gateway probe")}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ function buildScopeSelection(opts: UninstallOptions): {
|
||||
|
||||
async function stopAndUninstallService(runtime: RuntimeEnv): Promise<boolean> {
|
||||
if (isNixMode) {
|
||||
runtime.error("Nix mode detected; daemon uninstall is disabled.");
|
||||
runtime.error("Nix mode detected; service uninstall is disabled.");
|
||||
return false;
|
||||
}
|
||||
const service = resolveGatewayService();
|
||||
|
||||
@@ -22,6 +22,6 @@ export function renderSystemdUnavailableHints(options: { wsl?: boolean } = {}):
|
||||
}
|
||||
return [
|
||||
"systemd user services are unavailable; install/enable systemd or run the gateway under your supervisor.",
|
||||
`If you're in a container, run the gateway in the foreground instead of \`${formatCliCommand("clawdbot daemon")}\`.`,
|
||||
`If you're in a container, run the gateway in the foreground instead of \`${formatCliCommand("clawdbot gateway")}\`.`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export function buildPortHints(listeners: PortListener[], port: number): string[
|
||||
const hints: string[] = [];
|
||||
if (kinds.has("gateway")) {
|
||||
hints.push(
|
||||
`Gateway already running locally. Stop it (${formatCliCommand("clawdbot daemon stop")}) or use a different port.`,
|
||||
`Gateway already running locally. Stop it (${formatCliCommand("clawdbot gateway stop")}) or use a different port.`,
|
||||
);
|
||||
}
|
||||
if (kinds.has("ssh")) {
|
||||
|
||||
Reference in New Issue
Block a user