openclaw / src /cli /node-cli /register.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
f778c12 verified
Raw
History Blame Contribute Delete
7.06 kB
// Commander registration for foreground node host and node service lifecycle commands.
import { Option, type Command } from "commander";
import { formatDocsLink } from "../../../packages/terminal-core/src/links.js";
import { theme } from "../../../packages/terminal-core/src/theme.js";
import { loadNodeHostConfig } from "../../node-host/config.js";
import { runNodeHost } from "../../node-host/runner.js";
import { runNodeHostWorker } from "../../node-host/worker.js";
import { defaultRuntime } from "../../runtime.js";
import { inheritOptionFromParent } from "../command-options.js";
import { formatInvalidPortOption } from "../error-format.js";
import { formatHelpExamples } from "../help-format.js";
import { addNodeCommandOptions } from "./command-options.js";
import {
runNodeDaemonInstall,
runNodeDaemonRestart,
runNodeDaemonStart,
runNodeDaemonStatus,
runNodeDaemonStop,
runNodeDaemonUninstall,
} from "./daemon.js";
import { resolveNodeGatewayOptions, resolveNodePairGatewayOptions } from "./gateway-options.js";
import { runNodeIdentityShow } from "./identity.js";
export function registerNodeCli(program: Command) {
const node = addNodeCommandOptions(
program.command("node").description("Run and manage the headless node host service"),
).addHelpText(
"after",
() =>
`\n${theme.heading("Examples:")}\n${formatHelpExamples([
["openclaw node run --host 127.0.0.1 --port 18789", "Run the node host in the foreground."],
["openclaw node status", "Check node host service status."],
["openclaw node install", "Install the node host service."],
["openclaw node start", "Start the installed node host service."],
["openclaw node restart", "Restart the installed node host service."],
])}\n\n${theme.muted("Docs:")} ${formatDocsLink("/cli/node", "docs.openclaw.ai/cli/node")}\n`,
);
node
.command("worker", { hidden: true })
.description("Run the private macOS app node-host worker")
.action(async () => {
await runNodeHostWorker();
});
addNodeCommandOptions(node.command("run").description("Run the headless node host (foreground)"))
.option(
"--pair <code-or-url>",
"Pair with a setup code or oc-pair URL; explicit gateway flags take precedence",
)
.option("--host <host>", "Gateway host")
.option("--port <port>", "Gateway port")
.option("--context-path <path>", "Gateway WebSocket context path (e.g. /openclaw-gw)")
.option("--tls", "Use TLS for the gateway connection")
.option("--no-tls", "Disable TLS for the gateway connection")
.option("--tls-fingerprint <sha256>", "Expected TLS certificate fingerprint (sha256)")
.option("--node-id <id>", "Override the generated node instance id")
.option("--display-name <name>", "Override node display name")
.addOption(new Option("--ephemeral").hideHelp())
.option("--share-installed-apps", "Share installed macOS applications with the Gateway")
.option("--no-share-installed-apps", "Disable installed application sharing")
.action(async (opts, command: Command) => {
let pair;
let gatewayOptions;
try {
pair = opts.pair ? resolveNodePairGatewayOptions(opts.pair) : undefined;
const existing = await loadNodeHostConfig();
gatewayOptions = resolveNodeGatewayOptions(opts, existing, pair);
} catch (error) {
defaultRuntime.error(error instanceof Error ? error.message : String(error));
defaultRuntime.exit(1);
return;
}
const { host, port, contextPath, tls, tlsFingerprint, cloudflareAccess, gatewayCandidates } =
gatewayOptions;
if (port === null) {
defaultRuntime.error(formatInvalidPortOption("--port"));
defaultRuntime.exit(1);
return;
}
if (opts.tls === false && opts.tlsFingerprint !== undefined) {
defaultRuntime.error("--no-tls cannot be combined with --tls-fingerprint");
defaultRuntime.exit(1);
return;
}
await runNodeHost({
gatewayHost: host,
gatewayPort: port,
gatewayTls: tls,
gatewayTlsFingerprint: tlsFingerprint,
gatewayContextPath: contextPath,
gatewayCloudflareAccess: cloudflareAccess,
gatewayCandidates,
gatewayBootstrapToken: pair?.bootstrapToken,
preferGatewayBootstrapToken: pair !== undefined,
...(opts.ephemeral === true ? { forceWorkerRuns: true, ephemeral: true } : {}),
nodeId: opts.nodeId,
displayName: opts.displayName,
installedAppsSharing: opts.shareInstalledApps,
commands: opts.commands ?? inheritOptionFromParent<string[]>(command, "commands"),
allCommands: opts.allCommands ?? inheritOptionFromParent<boolean>(command, "allCommands"),
});
});
node
.command("status")
.description("Show node host status")
.option("--json", "Output JSON", false)
.action(async (opts) => {
await runNodeDaemonStatus(opts);
});
node
.command("identity")
.description("Print the node host device identity (device id + public key)")
.option("--json", "Output JSON", false)
.action((opts) => {
runNodeIdentityShow(opts);
});
addNodeCommandOptions(
node.command("install").description("Install the node host service (launchd/systemd/schtasks)"),
)
.option("--host <host>", "Gateway host")
.option("--port <port>", "Gateway port")
.option("--context-path <path>", "Gateway WebSocket context path (e.g. /openclaw-gw)")
.option("--tls", "Use TLS for the gateway connection")
.option("--no-tls", "Disable TLS for the gateway connection")
.option("--tls-fingerprint <sha256>", "Expected TLS certificate fingerprint (sha256)")
.option("--node-id <id>", "Override the generated node instance id")
.option("--display-name <name>", "Override node display name")
.option("--share-installed-apps", "Share installed macOS applications with the Gateway")
.option("--no-share-installed-apps", "Disable installed application sharing")
.option("--runtime <runtime>", "Service runtime (node|bun). Default: node")
.option("--force", "Reinstall/overwrite if already installed", false)
.option("--json", "Output JSON", false)
.action(async (opts, command: Command) => {
await runNodeDaemonInstall({
...opts,
commands: opts.commands ?? inheritOptionFromParent<string[]>(command, "commands"),
allCommands: opts.allCommands ?? inheritOptionFromParent<boolean>(command, "allCommands"),
});
});
for (const [name, action] of [
["uninstall", runNodeDaemonUninstall],
["stop", runNodeDaemonStop],
["start", runNodeDaemonStart],
["restart", runNodeDaemonRestart],
] as const) {
node
.command(name)
.description(
`${name.charAt(0).toUpperCase()}${name.slice(1)} the node host service (launchd/systemd/schtasks)`,
)
.option("--json", "Output JSON", false)
.action(async (opts) => {
await action(opts);
});
}
}