refactor: normalize cli command hints

This commit is contained in:
Peter Steinberger
2026-01-20 07:42:21 +00:00
parent 11b9b6dba5
commit 6d5195c890
106 changed files with 521 additions and 220 deletions

View File

@@ -11,6 +11,7 @@ import { defaultRuntime } from "../runtime.js";
import { movePathToTrash } from "../browser/trash.js";
import { formatDocsLink } from "../terminal/links.js";
import { theme } from "../terminal/theme.js";
import { formatCliCommand } from "./command-format.js";
function bundledExtensionRootDir() {
const here = path.dirname(fileURLToPath(import.meta.url));
@@ -103,7 +104,7 @@ export function registerBrowserExtensionCommands(
defaultRuntime.error(
danger(
[
'Chrome extension is not installed. Run: "clawdbot browser extension install"',
`Chrome extension is not installed. Run: "${formatCliCommand("clawdbot browser extension install")}"`,
`Docs: ${formatDocsLink("/tools/chrome-extension", "docs.clawd.bot/tools/chrome-extension")}`,
].join("\n"),
),

View File

@@ -4,6 +4,7 @@ import { danger } from "../globals.js";
import { defaultRuntime } from "../runtime.js";
import { formatDocsLink } from "../terminal/links.js";
import { theme } from "../terminal/theme.js";
import { formatCliCommand } from "./command-format.js";
import { registerBrowserActionInputCommands } from "./browser-cli-actions-input.js";
import { registerBrowserActionObserveCommands } from "./browser-cli-actions-observe.js";
import { registerBrowserDebugCommands } from "./browser-cli-debug.js";
@@ -32,7 +33,9 @@ export function registerBrowserCli(program: Command) {
)
.action(() => {
browser.outputHelp();
defaultRuntime.error(danger('Missing subcommand. Try: "clawdbot browser status"'));
defaultRuntime.error(
danger(`Missing subcommand. Try: "${formatCliCommand("clawdbot browser status")}"`),
);
defaultRuntime.exit(1);
});

16
src/cli/command-format.ts Normal file
View File

@@ -0,0 +1,16 @@
import { normalizeProfileName } from "./profile-utils.js";
const CLI_PREFIX_RE = /^(?:pnpm|npm|bunx|npx)\s+clawdbot\b|^clawdbot\b/;
const PROFILE_FLAG_RE = /\b--profile\b/;
const DEV_FLAG_RE = /\b--dev\b/;
export function formatCliCommand(
command: string,
env: Record<string, string | undefined> = process.env as Record<string, string | undefined>,
): string {
const profile = normalizeProfileName(env.CLAWDBOT_PROFILE);
if (!profile) return command;
if (!CLI_PREFIX_RE.test(command)) return command;
if (PROFILE_FLAG_RE.test(command) || DEV_FLAG_RE.test(command)) return command;
return command.replace(CLI_PREFIX_RE, (match) => `${match} --profile ${profile}`);
}

View File

@@ -5,6 +5,7 @@ import { readConfigFileSnapshot, writeConfigFile } from "../config/config.js";
import { danger, info } from "../globals.js";
import { defaultRuntime } from "../runtime.js";
import { formatDocsLink } from "../terminal/links.js";
import { formatCliCommand } from "./command-format.js";
import { theme } from "../terminal/theme.js";
type PathSegment = string;
@@ -171,7 +172,7 @@ async function loadValidConfig() {
for (const issue of snapshot.issues) {
defaultRuntime.error(`- ${issue.path || "<root>"}: ${issue.message}`);
}
defaultRuntime.error("Run `clawdbot doctor` to repair, then retry.");
defaultRuntime.error(`Run \`${formatCliCommand("clawdbot doctor")}\` to repair, then retry.`);
defaultRuntime.exit(1);
return snapshot;
}

View File

@@ -7,6 +7,7 @@ import { loadConfig, resolveGatewayPort } from "../../config/config.js";
import { resolveIsNixMode } from "../../config/paths.js";
import { resolveGatewayService } from "../../daemon/service.js";
import { defaultRuntime } from "../../runtime.js";
import { formatCliCommand } from "../command-format.js";
import { buildDaemonServiceSnapshot, createNullWriter, emitDaemonActionJson } from "./response.js";
import { parsePort } from "./shared.js";
import type { DaemonInstallOptions } from "./types.js";
@@ -82,7 +83,9 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) {
});
if (!json) {
defaultRuntime.log(`Gateway service already ${service.loadedText}.`);
defaultRuntime.log("Reinstall with: clawdbot daemon install --force");
defaultRuntime.log(
`Reinstall with: ${formatCliCommand("clawdbot daemon install --force")}`,
);
}
return;
}

View File

@@ -5,6 +5,7 @@ import {
} from "../../daemon/constants.js";
import { resolveGatewayLogPaths } from "../../daemon/launchd.js";
import { getResolvedLoggerSettings } from "../../logging.js";
import { formatCliCommand } from "../command-format.js";
export function parsePort(raw: unknown): number | null {
if (raw === undefined || raw === null) return null;
@@ -122,7 +123,7 @@ export function renderRuntimeHints(
}
})();
if (runtime.missingUnit) {
hints.push("Service not installed. Run: clawdbot daemon install");
hints.push(`Service not installed. Run: ${formatCliCommand("clawdbot daemon install", env)}`);
if (fileLog) hints.push(`File logs: ${fileLog}`);
return hints;
}
@@ -144,7 +145,10 @@ export function renderRuntimeHints(
}
export function renderGatewayServiceStartHints(env: NodeJS.ProcessEnv = process.env): string[] {
const base = ["clawdbot daemon install", "clawdbot gateway"];
const base = [
formatCliCommand("clawdbot daemon install", env),
formatCliCommand("clawdbot gateway", env),
];
const profile = env.CLAWDBOT_PROFILE;
switch (process.platform) {
case "darwin": {

View File

@@ -13,6 +13,7 @@ import { isWSLEnv } from "../../infra/wsl.js";
import { getResolvedLoggerSettings } from "../../logging.js";
import { defaultRuntime } from "../../runtime.js";
import { colorize, isRich, theme } from "../../terminal/theme.js";
import { formatCliCommand } from "../command-format.js";
import { formatRuntimeStatus, renderRuntimeHints, safeDaemonEnv } from "./shared.js";
import {
type DaemonStatus,
@@ -70,7 +71,9 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean })
defaultRuntime.error(`${warnText("Service config issue:")} ${issue.message}${detail}`);
}
defaultRuntime.error(
warnText('Recommendation: run "clawdbot doctor" (or "clawdbot doctor --repair").'),
warnText(
`Recommendation: run "${formatCliCommand("clawdbot doctor")}" (or "${formatCliCommand("clawdbot doctor --repair")}").`,
),
);
}
@@ -103,7 +106,7 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean })
);
defaultRuntime.error(
errorText(
"Fix: rerun `clawdbot daemon install --force` from the same --profile / CLAWDBOT_STATE_DIR you expect.",
`Fix: rerun \`${formatCliCommand("clawdbot daemon install --force")}\` from the same --profile / CLAWDBOT_STATE_DIR you expect.`,
),
);
}
@@ -205,7 +208,9 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean })
`LaunchAgent label cached but plist missing. Clear with: launchctl bootout gui/$UID/${labelValue}`,
),
);
defaultRuntime.error(errorText("Then reinstall: clawdbot daemon install"));
defaultRuntime.error(
errorText(`Then reinstall: ${formatCliCommand("clawdbot daemon install")}`),
);
spacer();
}
@@ -259,7 +264,7 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean })
for (const svc of legacyServices) {
defaultRuntime.error(`- ${errorText(svc.label)} (${svc.detail})`);
}
defaultRuntime.error(errorText("Cleanup: clawdbot doctor"));
defaultRuntime.error(errorText(`Cleanup: ${formatCliCommand("clawdbot doctor")}`));
spacer();
}
@@ -288,6 +293,6 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean })
spacer();
}
defaultRuntime.log(`${label("Troubles:")} run clawdbot status`);
defaultRuntime.log(`${label("Troubles:")} run ${formatCliCommand("clawdbot status")}`);
defaultRuntime.log(`${label("Troubleshooting:")} https://docs.clawd.bot/troubleshooting`);
}

View File

@@ -18,6 +18,7 @@ import { formatPortDiagnostics, inspectPortUsage } from "../../infra/ports.js";
import { setConsoleSubsystemFilter, setConsoleTimestampPrefix } from "../../logging/console.js";
import { createSubsystemLogger } from "../../logging/subsystem.js";
import { defaultRuntime } from "../../runtime.js";
import { formatCliCommand } from "../command-format.js";
import { forceFreePortAndWait } from "../ports.js";
import { ensureDevGatewayConfig } from "./dev.js";
import { runGatewayLoop } from "./run-loop.js";
@@ -161,7 +162,7 @@ async function runGatewayCommand(opts: GatewayRunOpts) {
if (!opts.allowUnconfigured && mode !== "local") {
if (!configExists) {
defaultRuntime.error(
"Missing config. Run `clawdbot setup` or set gateway.mode=local (or pass --allow-unconfigured).",
`Missing config. Run \`${formatCliCommand("clawdbot setup")}\` or set gateway.mode=local (or pass --allow-unconfigured).`,
);
} else {
defaultRuntime.error(
@@ -277,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: clawdbot daemon stop`,
`Gateway failed to start: ${errMessage}\nIf the gateway is supervised, stop it with: ${formatCliCommand("clawdbot daemon stop")}`,
);
try {
const diagnostics = await inspectPortUsage(port);

View File

@@ -5,6 +5,7 @@ import {
} from "../../daemon/constants.js";
import { resolveGatewayService } from "../../daemon/service.js";
import { defaultRuntime } from "../../runtime.js";
import { formatCliCommand } from "../command-format.js";
export function parsePort(raw: unknown): number | null {
if (raw === undefined || raw === null) return null;
@@ -67,21 +68,21 @@ export function renderGatewayServiceStopHints(env: NodeJS.ProcessEnv = process.e
switch (process.platform) {
case "darwin":
return [
"Tip: clawdbot daemon stop",
`Tip: ${formatCliCommand("clawdbot daemon stop")}`,
`Or: launchctl bootout gui/$UID/${resolveGatewayLaunchAgentLabel(profile)}`,
];
case "linux":
return [
"Tip: clawdbot daemon stop",
`Tip: ${formatCliCommand("clawdbot daemon stop")}`,
`Or: systemctl --user stop ${resolveGatewaySystemdServiceName(profile)}.service`,
];
case "win32":
return [
"Tip: clawdbot daemon stop",
`Tip: ${formatCliCommand("clawdbot daemon stop")}`,
`Or: schtasks /End /TN "${resolveGatewayWindowsTaskName(profile)}"`,
];
default:
return ["Tip: clawdbot daemon stop"];
return [`Tip: ${formatCliCommand("clawdbot daemon stop")}`];
}
}

View File

@@ -24,6 +24,7 @@ import { buildPluginStatusReport } from "../plugins/status.js";
import { defaultRuntime } from "../runtime.js";
import { formatDocsLink } from "../terminal/links.js";
import { theme } from "../terminal/theme.js";
import { formatCliCommand } from "./command-format.js";
import { resolveUserPath } from "../utils.js";
export type HooksListOptions = {
@@ -150,7 +151,7 @@ export function formatHooksList(report: HookStatusReport, opts: HooksListOptions
if (hooks.length === 0) {
const message = opts.eligible
? "No eligible hooks found. Run `clawdbot hooks list` to see all hooks."
? `No eligible hooks found. Run \`${formatCliCommand("clawdbot hooks list")}\` to see all hooks.`
: "No hooks found.";
return message;
}
@@ -194,7 +195,7 @@ export function formatHookInfo(
if (opts.json) {
return JSON.stringify({ error: "not found", hook: hookName }, null, 2);
}
return `Hook "${hookName}" not found. Run \`clawdbot hooks list\` to see available hooks.`;
return `Hook "${hookName}" not found. Run \`${formatCliCommand("clawdbot hooks list")}\` to see available hooks.`;
}
if (opts.json) {

View File

@@ -5,6 +5,7 @@ import { parseLogLine } from "../logging/parse-log-line.js";
import { defaultRuntime } from "../runtime.js";
import { formatDocsLink } from "../terminal/links.js";
import { colorize, isRich, theme } from "../terminal/theme.js";
import { formatCliCommand } from "./command-format.js";
import { addGatewayClientOptions, callGatewayFromCli } from "./gateway-rpc.js";
type LogsTailPayload = {
@@ -117,7 +118,7 @@ function emitGatewayError(
) {
const details = buildGatewayConnectionDetails({ url: opts.url });
const message = "Gateway not reachable. Is it running and accessible?";
const hint = "Hint: run `clawdbot doctor`.";
const hint = `Hint: run \`${formatCliCommand("clawdbot doctor")}\`.`;
const errorText = err instanceof Error ? err.message : String(err);
if (mode === "json") {

View File

@@ -18,6 +18,7 @@ import { isWSL } from "../../infra/wsl.js";
import { loadNodeHostConfig } from "../../node-host/config.js";
import { defaultRuntime } from "../../runtime.js";
import { colorize, isRich, theme } from "../../terminal/theme.js";
import { formatCliCommand } from "../command-format.js";
import {
buildDaemonServiceSnapshot,
createNullWriter,
@@ -46,7 +47,10 @@ type NodeDaemonStatusOptions = {
};
function renderNodeServiceStartHints(): string[] {
const base = ["clawdbot node service install", "clawdbot node start"];
const base = [
formatCliCommand("clawdbot node service install"),
formatCliCommand("clawdbot node start"),
];
switch (process.platform) {
case "darwin":
return [
@@ -168,7 +172,9 @@ export async function runNodeDaemonInstall(opts: NodeDaemonInstallOptions) {
});
if (!json) {
defaultRuntime.log(`Node service already ${service.loadedText}.`);
defaultRuntime.log("Reinstall with: clawdbot node service install --force");
defaultRuntime.log(
`Reinstall with: ${formatCliCommand("clawdbot node service install --force")}`,
);
}
return;
}

View File

@@ -10,6 +10,7 @@ import {
} from "../pairing/pairing-store.js";
import { formatDocsLink } from "../terminal/links.js";
import { theme } from "../terminal/theme.js";
import { formatCliCommand } from "./command-format.js";
/** Parse channel, allowing extension channels not in core registry. */
function parseChannel(raw: unknown, channels: PairingChannel[]): PairingChannel {
@@ -95,12 +96,12 @@ export function registerPairingCli(program: Command) {
const resolvedCode = opts.channel ? codeOrChannel : code;
if (!opts.channel && !code) {
throw new Error(
`Usage: clawdbot pairing approve <channel> <code> (or: clawdbot pairing approve --channel <channel> <code>)`,
`Usage: ${formatCliCommand("clawdbot pairing approve <channel> <code>")} (or: ${formatCliCommand("clawdbot pairing approve --channel <channel> <code>")})`,
);
}
if (opts.channel && code != null) {
throw new Error(
`Too many arguments. Use: clawdbot pairing approve --channel <channel> <code>`,
`Too many arguments. Use: ${formatCliCommand("clawdbot pairing approve --channel <channel> <code>")}`,
);
}
const channel = parseChannel(channelRaw, channels);

15
src/cli/profile-utils.ts Normal file
View File

@@ -0,0 +1,15 @@
const PROFILE_NAME_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
export function isValidProfileName(value: string): boolean {
if (!value) return false;
// Keep it path-safe + shell-friendly.
return PROFILE_NAME_RE.test(value);
}
export function normalizeProfileName(raw?: string | null): string | null {
const profile = raw?.trim();
if (!profile) return null;
if (profile.toLowerCase() === "default") return null;
if (!isValidProfileName(profile)) return null;
return profile;
}

View File

@@ -1,5 +1,6 @@
import path from "node:path";
import { describe, expect, it } from "vitest";
import { formatCliCommand } from "./command-format.js";
import { applyCliProfileEnv, parseCliProfileArgs } from "./profile.js";
describe("parseCliProfileArgs", () => {
@@ -76,3 +77,63 @@ describe("applyCliProfileEnv", () => {
expect(env.CLAWDBOT_CONFIG_PATH).toBe(path.join("/custom", "clawdbot.json"));
});
});
describe("formatCliCommand", () => {
it("returns command unchanged when no profile is set", () => {
expect(formatCliCommand("clawdbot doctor --fix", {})).toBe("clawdbot doctor --fix");
});
it("returns command unchanged when profile is default", () => {
expect(formatCliCommand("clawdbot doctor --fix", { CLAWDBOT_PROFILE: "default" })).toBe(
"clawdbot doctor --fix",
);
});
it("returns command unchanged when profile is Default (case-insensitive)", () => {
expect(formatCliCommand("clawdbot doctor --fix", { CLAWDBOT_PROFILE: "Default" })).toBe(
"clawdbot doctor --fix",
);
});
it("returns command unchanged when profile is invalid", () => {
expect(formatCliCommand("clawdbot doctor --fix", { CLAWDBOT_PROFILE: "bad profile" })).toBe(
"clawdbot doctor --fix",
);
});
it("returns command unchanged when --profile is already present", () => {
expect(
formatCliCommand("clawdbot --profile work doctor --fix", { CLAWDBOT_PROFILE: "work" }),
).toBe("clawdbot --profile work doctor --fix");
});
it("returns command unchanged when --dev is already present", () => {
expect(formatCliCommand("clawdbot --dev doctor", { CLAWDBOT_PROFILE: "dev" })).toBe(
"clawdbot --dev doctor",
);
});
it("inserts --profile flag when profile is set", () => {
expect(formatCliCommand("clawdbot doctor --fix", { CLAWDBOT_PROFILE: "work" })).toBe(
"clawdbot --profile work doctor --fix",
);
});
it("trims whitespace from profile", () => {
expect(formatCliCommand("clawdbot doctor --fix", { CLAWDBOT_PROFILE: " jbclawd " })).toBe(
"clawdbot --profile jbclawd doctor --fix",
);
});
it("handles command with no args after clawdbot", () => {
expect(formatCliCommand("clawdbot", { CLAWDBOT_PROFILE: "test" })).toBe(
"clawdbot --profile test",
);
});
it("handles pnpm wrapper", () => {
expect(formatCliCommand("pnpm clawdbot doctor", { CLAWDBOT_PROFILE: "work" })).toBe(
"pnpm clawdbot --profile work doctor",
);
});
});

View File

@@ -1,6 +1,8 @@
import os from "node:os";
import path from "node:path";
import { isValidProfileName } from "./profile-utils.js";
export type CliProfileParseResult =
| { ok: true; profile: string | null; argv: string[] }
| { ok: false; error: string };
@@ -21,12 +23,6 @@ function takeValue(
return { value: trimmed || null, consumedNext: Boolean(next) };
}
function isValidProfileName(value: string): boolean {
if (!value) return false;
// Keep it path-safe + shell-friendly.
return /^[a-z0-9][a-z0-9_-]{0,63}$/i.test(value);
}
export function parseCliProfileArgs(argv: string[]): CliProfileParseResult {
if (argv.length < 2) return { ok: true, profile: null, argv };

View File

@@ -4,6 +4,7 @@ import { loadAndMaybeMigrateDoctorConfig } from "../../commands/doctor-config-fl
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../agents/agent-scope.js";
import { loadClawdbotPlugins } from "../../plugins/loader.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"]);
@@ -72,7 +73,9 @@ export async function ensureConfigReady(params: {
params.runtime.error(pluginIssues.map((issue) => ` ${error(issue)}`).join("\n"));
}
params.runtime.error("");
params.runtime.error(`${muted("Run:")} ${commandText("clawdbot doctor --fix")}`);
params.runtime.error(
`${muted("Run:")} ${commandText(formatCliCommand("clawdbot doctor --fix"))}`,
);
if (!allowInvalid) {
params.runtime.exit(1);
}

View File

@@ -7,6 +7,7 @@ import { runSecurityAudit } from "../security/audit.js";
import { fixSecurityFootguns } from "../security/fix.js";
import { formatDocsLink } from "../terminal/links.js";
import { isRich, theme } from "../terminal/theme.js";
import { formatCliCommand } from "./command-format.js";
type SecurityAuditOptions = {
json?: boolean;
@@ -67,10 +68,10 @@ export function registerSecurityCli(program: Command) {
const lines: string[] = [];
lines.push(heading("Clawdbot security audit"));
lines.push(muted(`Summary: ${formatSummary(report.summary)}`));
lines.push(muted(`Run deeper: clawdbot security audit --deep`));
lines.push(muted(`Run deeper: ${formatCliCommand("clawdbot security audit --deep")}`));
if (opts.fix) {
lines.push(muted(`Fix: clawdbot security audit --fix`));
lines.push(muted(`Fix: ${formatCliCommand("clawdbot security audit --fix")}`));
if (!fixResult) {
lines.push(muted("Fixes: failed to apply (unexpected error)"));
} else if (

View File

@@ -10,6 +10,7 @@ import { loadConfig } from "../config/config.js";
import { defaultRuntime } from "../runtime.js";
import { formatDocsLink } from "../terminal/links.js";
import { theme } from "../terminal/theme.js";
import { formatCliCommand } from "./command-format.js";
export type SkillsListOptions = {
json?: boolean;
@@ -101,7 +102,7 @@ export function formatSkillsList(report: SkillStatusReport, opts: SkillsListOpti
if (skills.length === 0) {
const message = opts.eligible
? "No eligible skills found. Run `clawdbot skills list` to see all skills."
? `No eligible skills found. Run \`${formatCliCommand("clawdbot skills list")}\` to see all skills.`
: "No skills found.";
return appendClawdHubHint(message, opts.json);
}
@@ -148,7 +149,7 @@ export function formatSkillInfo(
return JSON.stringify({ error: "not found", skill: skillName }, null, 2);
}
return appendClawdHubHint(
`Skill "${skillName}" not found. Run \`clawdbot skills list\` to see available skills.`,
`Skill "${skillName}" not found. Run \`${formatCliCommand("clawdbot skills list")}\` to see available skills.`,
opts.json,
);
}

View File

@@ -15,6 +15,7 @@ import {
} from "../infra/update-runner.js";
import { defaultRuntime } from "../runtime.js";
import { formatDocsLink } from "../terminal/links.js";
import { formatCliCommand } from "./command-format.js";
import { stylePromptMessage } from "../terminal/prompt-style.js";
import { theme } from "../terminal/theme.js";
@@ -373,7 +374,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 `clawdbot doctor` and `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 daemon restart")}\`.`,
),
);
defaultRuntime.log(
@@ -410,7 +411,9 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
if (!opts.json) {
defaultRuntime.log(theme.warn(`Daemon restart failed: ${String(err)}`));
defaultRuntime.log(
theme.muted("You may need to restart the daemon manually: clawdbot daemon restart"),
theme.muted(
`You may need to restart the daemon manually: ${formatCliCommand("clawdbot daemon restart")}`,
),
);
}
}
@@ -419,12 +422,14 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
if (result.mode === "npm" || result.mode === "pnpm") {
defaultRuntime.log(
theme.muted(
"Tip: Run `clawdbot doctor`, then `clawdbot daemon restart` to apply updates to a running gateway.",
`Tip: Run \`${formatCliCommand("clawdbot doctor")}\`, then \`${formatCliCommand("clawdbot daemon restart")}\` to apply updates to a running gateway.`,
),
);
} else {
defaultRuntime.log(
theme.muted("Tip: Run `clawdbot daemon restart` to apply updates to a running gateway."),
theme.muted(
`Tip: Run \`${formatCliCommand("clawdbot daemon restart")}\` to apply updates to a running gateway.`,
),
);
}
}