From fa1bc589e4feaa18c0de6dfe187a2df42f26fe47 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 21 Jan 2026 16:48:26 +0000 Subject: [PATCH] feat: flatten node CLI commands --- src/cli/node-cli/daemon.ts | 9 +- src/cli/node-cli/register.ts | 112 +++++++---------- src/cli/program/register.subclis.ts | 8 -- src/cli/service-cli.coverage.test.ts | 59 --------- src/cli/service-cli.ts | 181 --------------------------- 5 files changed, 48 insertions(+), 321 deletions(-) delete mode 100644 src/cli/service-cli.coverage.test.ts delete mode 100644 src/cli/service-cli.ts diff --git a/src/cli/node-cli/daemon.ts b/src/cli/node-cli/daemon.ts index 111ac510e..4deaa49ca 100644 --- a/src/cli/node-cli/daemon.ts +++ b/src/cli/node-cli/daemon.ts @@ -47,10 +47,7 @@ type NodeDaemonStatusOptions = { }; function renderNodeServiceStartHints(): string[] { - const base = [ - formatCliCommand("clawdbot node service install"), - formatCliCommand("clawdbot node start"), - ]; + const base = [formatCliCommand("clawdbot node install"), formatCliCommand("clawdbot node start")]; switch (process.platform) { case "darwin": return [ @@ -172,9 +169,7 @@ export async function runNodeDaemonInstall(opts: NodeDaemonInstallOptions) { }); if (!json) { defaultRuntime.log(`Node service already ${service.loadedText}.`); - defaultRuntime.log( - `Reinstall with: ${formatCliCommand("clawdbot node service install --force")}`, - ); + defaultRuntime.log(`Reinstall with: ${formatCliCommand("clawdbot node install --force")}`); } return; } diff --git a/src/cli/node-cli/register.ts b/src/cli/node-cli/register.ts index 6717d001a..9a7412111 100644 --- a/src/cli/node-cli/register.ts +++ b/src/cli/node-cli/register.ts @@ -28,13 +28,13 @@ export function registerNodeCli(program: Command) { ); node - .command("start") - .description("Start the headless node host (foreground)") + .command("run") + .description("Run the headless node host (foreground)") .option("--host ", "Gateway host") .option("--port ", "Gateway port") .option("--tls", "Use TLS for the gateway connection", false) .option("--tls-fingerprint ", "Expected TLS certificate fingerprint (sha256)") - .option("--node-id ", "Override node id") + .option("--node-id ", "Override node id (clears pairing token)") .option("--display-name ", "Override node display name") .action(async (opts) => { const existing = await loadNodeHostConfig(); @@ -51,71 +51,51 @@ export function registerNodeCli(program: Command) { }); }); - const registerNodeServiceCommands = (cmd: Command) => { - cmd - .command("status") - .description("Show node service status") - .option("--json", "Output JSON", false) - .action(async (opts) => { - await runNodeDaemonStatus(opts); - }); + node + .command("status") + .description("Show node host status") + .option("--json", "Output JSON", false) + .action(async (opts) => { + await runNodeDaemonStatus(opts); + }); - cmd - .command("install") - .description("Install the node service (launchd/systemd/schtasks)") - .option("--host ", "Gateway host") - .option("--port ", "Gateway port") - .option("--tls", "Use TLS for the gateway connection", false) - .option("--tls-fingerprint ", "Expected TLS certificate fingerprint (sha256)") - .option("--node-id ", "Override node id") - .option("--display-name ", "Override node display name") - .option("--runtime ", "Service runtime (node|bun). Default: node") - .option("--force", "Reinstall/overwrite if already installed", false) - .option("--json", "Output JSON", false) - .action(async (opts) => { - await runNodeDaemonInstall(opts); - }); + node + .command("install") + .description("Install the node host service (launchd/systemd/schtasks)") + .option("--host ", "Gateway host") + .option("--port ", "Gateway port") + .option("--tls", "Use TLS for the gateway connection", false) + .option("--tls-fingerprint ", "Expected TLS certificate fingerprint (sha256)") + .option("--node-id ", "Override node id (clears pairing token)") + .option("--display-name ", "Override node display name") + .option("--runtime ", "Service runtime (node|bun). Default: node") + .option("--force", "Reinstall/overwrite if already installed", false) + .option("--json", "Output JSON", false) + .action(async (opts) => { + await runNodeDaemonInstall(opts); + }); - cmd - .command("uninstall") - .description("Uninstall the node service (launchd/systemd/schtasks)") - .option("--json", "Output JSON", false) - .action(async (opts) => { - await runNodeDaemonUninstall(opts); - }); + node + .command("uninstall") + .description("Uninstall the node host service (launchd/systemd/schtasks)") + .option("--json", "Output JSON", false) + .action(async (opts) => { + await runNodeDaemonUninstall(opts); + }); - cmd - .command("start") - .description("Start the node service (launchd/systemd/schtasks)") - .option("--json", "Output JSON", false) - .action(async (opts) => { - await runNodeDaemonStart(opts); - }); + node + .command("stop") + .description("Stop the node host service (launchd/systemd/schtasks)") + .option("--json", "Output JSON", false) + .action(async (opts) => { + await runNodeDaemonStop(opts); + }); - cmd - .command("stop") - .description("Stop the node service (launchd/systemd/schtasks)") - .option("--json", "Output JSON", false) - .action(async (opts) => { - await runNodeDaemonStop(opts); - }); - - cmd - .command("restart") - .description("Restart the node service (launchd/systemd/schtasks)") - .option("--json", "Output JSON", false) - .action(async (opts) => { - await runNodeDaemonRestart(opts); - }); - }; - - const service = node - .command("service") - .description("Manage the headless node service (launchd/systemd/schtasks)"); - registerNodeServiceCommands(service); - - const daemon = node - .command("daemon", { hidden: true }) - .description("Legacy alias for node service commands"); - registerNodeServiceCommands(daemon); + node + .command("restart") + .description("Restart the node host service (launchd/systemd/schtasks)") + .option("--json", "Output JSON", false) + .action(async (opts) => { + await runNodeDaemonRestart(opts); + }); } diff --git a/src/cli/program/register.subclis.ts b/src/cli/program/register.subclis.ts index 74b075af0..b13a4c76f 100644 --- a/src/cli/program/register.subclis.ts +++ b/src/cli/program/register.subclis.ts @@ -52,14 +52,6 @@ const entries: SubCliEntry[] = [ mod.registerGatewayCli(program); }, }, - { - name: "service", - description: "Service helpers", - register: async (program) => { - const mod = await import("../service-cli.js"); - mod.registerServiceCli(program); - }, - }, { name: "logs", description: "Gateway logs", diff --git a/src/cli/service-cli.coverage.test.ts b/src/cli/service-cli.coverage.test.ts deleted file mode 100644 index e0bb6604a..000000000 --- a/src/cli/service-cli.coverage.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Command } from "commander"; -import { describe, expect, it, vi } from "vitest"; - -const runDaemonStatus = vi.fn(async () => {}); -const runNodeDaemonStatus = vi.fn(async () => {}); - -vi.mock("./daemon-cli/runners.js", () => ({ - runDaemonInstall: vi.fn(async () => {}), - runDaemonRestart: vi.fn(async () => {}), - runDaemonStart: vi.fn(async () => {}), - runDaemonStatus: (opts: unknown) => runDaemonStatus(opts), - runDaemonStop: vi.fn(async () => {}), - runDaemonUninstall: vi.fn(async () => {}), -})); - -vi.mock("./node-cli/daemon.js", () => ({ - runNodeDaemonInstall: vi.fn(async () => {}), - runNodeDaemonRestart: vi.fn(async () => {}), - runNodeDaemonStart: vi.fn(async () => {}), - runNodeDaemonStatus: (opts: unknown) => runNodeDaemonStatus(opts), - runNodeDaemonStop: vi.fn(async () => {}), - runNodeDaemonUninstall: vi.fn(async () => {}), -})); - -vi.mock("./deps.js", () => ({ - createDefaultDeps: vi.fn(), -})); - -describe("service CLI coverage", () => { - it("routes service gateway status to daemon status", async () => { - runDaemonStatus.mockClear(); - runNodeDaemonStatus.mockClear(); - - const { registerServiceCli } = await import("./service-cli.js"); - const program = new Command(); - program.exitOverride(); - registerServiceCli(program); - - await program.parseAsync(["service", "gateway", "status"], { from: "user" }); - - expect(runDaemonStatus).toHaveBeenCalledTimes(1); - expect(runNodeDaemonStatus).toHaveBeenCalledTimes(0); - }); - - it("routes service node status to node daemon status", async () => { - runDaemonStatus.mockClear(); - runNodeDaemonStatus.mockClear(); - - const { registerServiceCli } = await import("./service-cli.js"); - const program = new Command(); - program.exitOverride(); - registerServiceCli(program); - - await program.parseAsync(["service", "node", "status"], { from: "user" }); - - expect(runNodeDaemonStatus).toHaveBeenCalledTimes(1); - expect(runDaemonStatus).toHaveBeenCalledTimes(0); - }); -}); diff --git a/src/cli/service-cli.ts b/src/cli/service-cli.ts deleted file mode 100644 index fa2ac3ffb..000000000 --- a/src/cli/service-cli.ts +++ /dev/null @@ -1,181 +0,0 @@ -import type { Command } from "commander"; -import { formatDocsLink } from "../terminal/links.js"; -import { theme } from "../terminal/theme.js"; -import { formatHelpExampleGroup } from "./help-format.js"; -import { createDefaultDeps } from "./deps.js"; -import { - runDaemonInstall, - runDaemonRestart, - runDaemonStart, - runDaemonStatus, - runDaemonStop, - runDaemonUninstall, -} from "./daemon-cli/runners.js"; -import { - runNodeDaemonInstall, - runNodeDaemonRestart, - runNodeDaemonStart, - runNodeDaemonStatus, - runNodeDaemonStop, - runNodeDaemonUninstall, -} from "./node-cli/daemon.js"; - -export function registerServiceCli(program: Command) { - const gatewayExamples: Array<[string, string]> = [ - ["clawdbot service gateway status", "Show gateway service status + probe."], - [ - "clawdbot service gateway install --port 18789 --token ", - "Install the Gateway service on port 18789.", - ], - ["clawdbot service gateway restart", "Restart the Gateway service."], - ]; - - const nodeExamples: Array<[string, string]> = [ - ["clawdbot service node status", "Show node host service status."], - [ - "clawdbot service node install --host gateway.local --port 18789 --tls", - "Install the node host service with TLS.", - ], - ["clawdbot service node restart", "Restart the node host service."], - ]; - - const service = program - .command("service") - .description("Manage Gateway and node host services (launchd/systemd/schtasks)") - .addHelpText( - "after", - () => - `\n${theme.heading("Examples:")}\n${formatHelpExampleGroup( - "Gateway:", - gatewayExamples, - )}\n\n${formatHelpExampleGroup("Node:", nodeExamples)}\n\n${theme.muted( - "Docs:", - )} ${formatDocsLink("/cli/service", "docs.clawd.bot/cli/service")}\n`, - ); - - const gateway = service.command("gateway").description("Manage the Gateway service"); - - gateway - .command("status") - .description("Show gateway service status + probe the Gateway") - .option("--url ", "Gateway WebSocket URL (defaults to config/remote/local)") - .option("--token ", "Gateway token (if required)") - .option("--password ", "Gateway password (password auth)") - .option("--timeout ", "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 ", "Gateway port") - .option("--runtime ", "Service runtime (node|bun). Default: node") - .option("--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); - }); - - const node = service.command("node").description("Manage the node host service"); - - node - .command("status") - .description("Show node host service status") - .option("--json", "Output JSON", false) - .action(async (opts) => { - await runNodeDaemonStatus(opts); - }); - - node - .command("install") - .description("Install the node host service (launchd/systemd/schtasks)") - .option("--host ", "Gateway host") - .option("--port ", "Gateway port") - .option("--tls", "Use TLS for the Gateway connection", false) - .option("--tls-fingerprint ", "Expected TLS certificate fingerprint (sha256)") - .option("--node-id ", "Override node id (clears pairing token)") - .option("--display-name ", "Override node display name") - .option("--runtime ", "Service runtime (node|bun). Default: node") - .option("--force", "Reinstall/overwrite if already installed", false) - .option("--json", "Output JSON", false) - .action(async (opts) => { - await runNodeDaemonInstall(opts); - }); - - node - .command("uninstall") - .description("Uninstall the node host service (launchd/systemd/schtasks)") - .option("--json", "Output JSON", false) - .action(async (opts) => { - await runNodeDaemonUninstall(opts); - }); - - node - .command("start") - .description("Start the node host service (launchd/systemd/schtasks)") - .option("--json", "Output JSON", false) - .action(async (opts) => { - await runNodeDaemonStart(opts); - }); - - node - .command("stop") - .description("Stop the node host service (launchd/systemd/schtasks)") - .option("--json", "Output JSON", false) - .action(async (opts) => { - await runNodeDaemonStop(opts); - }); - - node - .command("restart") - .description("Restart the node host service (launchd/systemd/schtasks)") - .option("--json", "Output JSON", false) - .action(async (opts) => { - await runNodeDaemonRestart(opts); - }); - - // Build default deps (parity with daemon CLI). - void createDefaultDeps(); -}