mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 14:38:38 +00:00
feat(cli): add configurable banner tagline mode
This commit is contained in:
@@ -17,6 +17,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Hooks/message lifecycle: add internal hook events `message:transcribed` and `message:preprocessed`, plus richer outbound `message:sent` context (`isGroup`, `groupId`) for group-conversation correlation and post-transcription automations. (#9859) Thanks @Drickon.
|
||||
- Telegram/Streaming defaults: default `channels.telegram.streaming` to `partial` (from `off`) so new Telegram setups get live preview streaming out of the box, with runtime fallback to message-edit preview when native drafts are unavailable.
|
||||
- CLI/Config validation: add `openclaw config validate` (with `--json`) to validate config files before gateway startup, and include detailed invalid-key paths in startup invalid-config errors. (#31220) thanks @Sid-Qin.
|
||||
- CLI/Banner taglines: add `cli.banner.taglineMode` (`random` | `default` | `off`) to control funny tagline behavior in startup output, with docs + FAQ guidance and regression tests for config override behavior.
|
||||
- Tools/Diffs: add PDF file output support and rendering quality customization controls (`fileQuality`, `fileScale`, `fileMaxWidth`) for generated diff artifacts, and document PDF as the preferred option when messaging channels compress images. (#31342) Thanks @gumadeiras.
|
||||
- README/Contributors: rank contributor avatars by composite score (commits + merged PRs + code LOC), excluding docs-only LOC to prevent bulk-generated files from inflating rankings. (#23970) Thanks @tyler6204.
|
||||
|
||||
|
||||
@@ -2731,6 +2731,26 @@ Notes:
|
||||
|
||||
---
|
||||
|
||||
## CLI
|
||||
|
||||
```json5
|
||||
{
|
||||
cli: {
|
||||
banner: {
|
||||
taglineMode: "off", // random | default | off
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- `cli.banner.taglineMode` controls banner tagline style:
|
||||
- `"random"` (default): rotating funny/seasonal taglines.
|
||||
- `"default"`: fixed neutral tagline (`All your chats, one OpenClaw.`).
|
||||
- `"off"`: no tagline text (banner title/version still shown).
|
||||
- To hide the entire banner (not just taglines), set env `OPENCLAW_HIDE_BANNER=1`.
|
||||
|
||||
---
|
||||
|
||||
## Wizard
|
||||
|
||||
Metadata written by CLI wizards (`onboard`, `configure`, `doctor`):
|
||||
|
||||
@@ -101,6 +101,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
|
||||
- [I set `gateway.bind: "lan"` (or `"tailnet"`) and now nothing listens / the UI says unauthorized](#i-set-gatewaybind-lan-or-tailnet-and-now-nothing-listens-the-ui-says-unauthorized)
|
||||
- [Why do I need a token on localhost now?](#why-do-i-need-a-token-on-localhost-now)
|
||||
- [Do I have to restart after changing config?](#do-i-have-to-restart-after-changing-config)
|
||||
- [How do I disable funny CLI taglines?](#how-do-i-disable-funny-cli-taglines)
|
||||
- [How do I enable web search (and web fetch)?](#how-do-i-enable-web-search-and-web-fetch)
|
||||
- [config.apply wiped my config. How do I recover and avoid this?](#configapply-wiped-my-config-how-do-i-recover-and-avoid-this)
|
||||
- [How do I run a central Gateway with specialized workers across devices?](#how-do-i-run-a-central-gateway-with-specialized-workers-across-devices)
|
||||
@@ -1466,6 +1467,25 @@ The Gateway watches the config and supports hot-reload:
|
||||
- `gateway.reload.mode: "hybrid"` (default): hot-apply safe changes, restart for critical ones
|
||||
- `hot`, `restart`, `off` are also supported
|
||||
|
||||
### How do I disable funny CLI taglines
|
||||
|
||||
Set `cli.banner.taglineMode` in config:
|
||||
|
||||
```json5
|
||||
{
|
||||
cli: {
|
||||
banner: {
|
||||
taglineMode: "off", // random | default | off
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- `off`: hides tagline text but keeps the banner title/version line.
|
||||
- `default`: uses `All your chats, one OpenClaw.` every time.
|
||||
- `random`: rotating funny/seasonal taglines (default behavior).
|
||||
- If you want no banner at all, set env `OPENCLAW_HIDE_BANNER=1`.
|
||||
|
||||
### How do I enable web search and web fetch
|
||||
|
||||
`web_fetch` works without an API key. `web_search` requires a Brave Search API
|
||||
|
||||
60
src/cli/banner.test.ts
Normal file
60
src/cli/banner.test.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const loadConfigMock = vi.fn();
|
||||
|
||||
vi.mock("../config/config.js", () => ({
|
||||
loadConfig: loadConfigMock,
|
||||
}));
|
||||
|
||||
let formatCliBannerLine: typeof import("./banner.js").formatCliBannerLine;
|
||||
|
||||
beforeAll(async () => {
|
||||
({ formatCliBannerLine } = await import("./banner.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
loadConfigMock.mockReset();
|
||||
loadConfigMock.mockReturnValue({});
|
||||
});
|
||||
|
||||
describe("formatCliBannerLine", () => {
|
||||
it("hides tagline text when cli.banner.taglineMode is off", () => {
|
||||
loadConfigMock.mockReturnValue({
|
||||
cli: { banner: { taglineMode: "off" } },
|
||||
});
|
||||
|
||||
const line = formatCliBannerLine("2026.3.3", {
|
||||
commit: "abc1234",
|
||||
richTty: false,
|
||||
});
|
||||
|
||||
expect(line).toBe("🦞 OpenClaw 2026.3.3 (abc1234)");
|
||||
});
|
||||
|
||||
it("uses default tagline when cli.banner.taglineMode is default", () => {
|
||||
loadConfigMock.mockReturnValue({
|
||||
cli: { banner: { taglineMode: "default" } },
|
||||
});
|
||||
|
||||
const line = formatCliBannerLine("2026.3.3", {
|
||||
commit: "abc1234",
|
||||
richTty: false,
|
||||
});
|
||||
|
||||
expect(line).toBe("🦞 OpenClaw 2026.3.3 (abc1234) — All your chats, one OpenClaw.");
|
||||
});
|
||||
|
||||
it("prefers explicit tagline mode over config", () => {
|
||||
loadConfigMock.mockReturnValue({
|
||||
cli: { banner: { taglineMode: "off" } },
|
||||
});
|
||||
|
||||
const line = formatCliBannerLine("2026.3.3", {
|
||||
commit: "abc1234",
|
||||
richTty: false,
|
||||
mode: "default",
|
||||
});
|
||||
|
||||
expect(line).toBe("🦞 OpenClaw 2026.3.3 (abc1234) — All your chats, one OpenClaw.");
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,9 @@
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { resolveCommitHash } from "../infra/git-commit.js";
|
||||
import { visibleWidth } from "../terminal/ansi.js";
|
||||
import { isRich, theme } from "../terminal/theme.js";
|
||||
import { hasRootVersionAlias } from "./argv.js";
|
||||
import { pickTagline, type TaglineOptions } from "./tagline.js";
|
||||
import { pickTagline, type TaglineMode, type TaglineOptions } from "./tagline.js";
|
||||
|
||||
type BannerOptions = TaglineOptions & {
|
||||
argv?: string[];
|
||||
@@ -35,18 +36,42 @@ const hasJsonFlag = (argv: string[]) =>
|
||||
const hasVersionFlag = (argv: string[]) =>
|
||||
argv.some((arg) => arg === "--version" || arg === "-V") || hasRootVersionAlias(argv);
|
||||
|
||||
function parseTaglineMode(value: unknown): TaglineMode | undefined {
|
||||
if (value === "random" || value === "default" || value === "off") {
|
||||
return value;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolveTaglineMode(options: BannerOptions): TaglineMode | undefined {
|
||||
const explicit = parseTaglineMode(options.mode);
|
||||
if (explicit) {
|
||||
return explicit;
|
||||
}
|
||||
try {
|
||||
return parseTaglineMode(loadConfig().cli?.banner?.taglineMode);
|
||||
} catch {
|
||||
// Fall back to default random behavior when config is missing/invalid.
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function formatCliBannerLine(version: string, options: BannerOptions = {}): string {
|
||||
const commit = options.commit ?? resolveCommitHash({ env: options.env });
|
||||
const commitLabel = commit ?? "unknown";
|
||||
const tagline = pickTagline(options);
|
||||
const tagline = pickTagline({ ...options, mode: resolveTaglineMode(options) });
|
||||
const rich = options.richTty ?? isRich();
|
||||
const title = "🦞 OpenClaw";
|
||||
const prefix = "🦞 ";
|
||||
const columns = options.columns ?? process.stdout.columns ?? 120;
|
||||
const plainFullLine = `${title} ${version} (${commitLabel}) — ${tagline}`;
|
||||
const plainBaseLine = `${title} ${version} (${commitLabel})`;
|
||||
const plainFullLine = tagline ? `${plainBaseLine} — ${tagline}` : plainBaseLine;
|
||||
const fitsOnOneLine = visibleWidth(plainFullLine) <= columns;
|
||||
if (rich) {
|
||||
if (fitsOnOneLine) {
|
||||
if (!tagline) {
|
||||
return `${theme.heading(title)} ${theme.info(version)} ${theme.muted(`(${commitLabel})`)}`;
|
||||
}
|
||||
return `${theme.heading(title)} ${theme.info(version)} ${theme.muted(
|
||||
`(${commitLabel})`,
|
||||
)} ${theme.muted("—")} ${theme.accentDim(tagline)}`;
|
||||
@@ -54,13 +79,19 @@ export function formatCliBannerLine(version: string, options: BannerOptions = {}
|
||||
const line1 = `${theme.heading(title)} ${theme.info(version)} ${theme.muted(
|
||||
`(${commitLabel})`,
|
||||
)}`;
|
||||
if (!tagline) {
|
||||
return line1;
|
||||
}
|
||||
const line2 = `${" ".repeat(prefix.length)}${theme.accentDim(tagline)}`;
|
||||
return `${line1}\n${line2}`;
|
||||
}
|
||||
if (fitsOnOneLine) {
|
||||
return plainFullLine;
|
||||
}
|
||||
const line1 = `${title} ${version} (${commitLabel})`;
|
||||
const line1 = plainBaseLine;
|
||||
if (!tagline) {
|
||||
return line1;
|
||||
}
|
||||
const line2 = `${" ".repeat(prefix.length)}${tagline}`;
|
||||
return `${line1}\n${line2}`;
|
||||
}
|
||||
|
||||
21
src/cli/tagline.test.ts
Normal file
21
src/cli/tagline.test.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { DEFAULT_TAGLINE, pickTagline } from "./tagline.js";
|
||||
|
||||
describe("pickTagline", () => {
|
||||
it("returns empty string when mode is off", () => {
|
||||
expect(pickTagline({ mode: "off" })).toBe("");
|
||||
});
|
||||
|
||||
it("returns default tagline when mode is default", () => {
|
||||
expect(pickTagline({ mode: "default" })).toBe(DEFAULT_TAGLINE);
|
||||
});
|
||||
|
||||
it("keeps OPENCLAW_TAGLINE_INDEX behavior in random mode", () => {
|
||||
const value = pickTagline({
|
||||
mode: "random",
|
||||
env: { OPENCLAW_TAGLINE_INDEX: "0" } as NodeJS.ProcessEnv,
|
||||
});
|
||||
expect(value.length).toBeGreaterThan(0);
|
||||
expect(value).not.toBe(DEFAULT_TAGLINE);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
const DEFAULT_TAGLINE = "All your chats, one OpenClaw.";
|
||||
export type TaglineMode = "random" | "default" | "off";
|
||||
|
||||
const HOLIDAY_TAGLINES = {
|
||||
newYear:
|
||||
@@ -248,6 +249,7 @@ export interface TaglineOptions {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
random?: () => number;
|
||||
now?: () => Date;
|
||||
mode?: TaglineMode;
|
||||
}
|
||||
|
||||
export function activeTaglines(options: TaglineOptions = {}): string[] {
|
||||
@@ -260,6 +262,12 @@ export function activeTaglines(options: TaglineOptions = {}): string[] {
|
||||
}
|
||||
|
||||
export function pickTagline(options: TaglineOptions = {}): string {
|
||||
if (options.mode === "off") {
|
||||
return "";
|
||||
}
|
||||
if (options.mode === "default") {
|
||||
return DEFAULT_TAGLINE;
|
||||
}
|
||||
const env = options.env ?? process.env;
|
||||
const override = env?.OPENCLAW_TAGLINE_INDEX;
|
||||
if (override !== undefined) {
|
||||
|
||||
@@ -9,6 +9,7 @@ const ROOT_SECTIONS = [
|
||||
"wizard",
|
||||
"diagnostics",
|
||||
"logging",
|
||||
"cli",
|
||||
"update",
|
||||
"browser",
|
||||
"ui",
|
||||
@@ -421,6 +422,7 @@ const ENUM_EXPECTATIONS: Record<string, string[]> = {
|
||||
],
|
||||
"logging.consoleStyle": ['"pretty"', '"compact"', '"json"'],
|
||||
"logging.redactSensitive": ['"off"', '"tools"'],
|
||||
"cli.banner.taglineMode": ['"random"', '"default"', '"off"'],
|
||||
"update.channel": ['"stable"', '"beta"', '"dev"'],
|
||||
"agents.defaults.compaction.mode": ['"default"', '"safeguard"'],
|
||||
"agents.defaults.compaction.identifierPolicy": ['"strict"', '"off"', '"custom"'],
|
||||
|
||||
@@ -46,6 +46,11 @@ export const FIELD_HELP: Record<string, string> = {
|
||||
'Sensitive redaction mode: "off" disables built-in masking, while "tools" redacts sensitive tool/config payload fields. Keep "tools" in shared logs unless you have isolated secure log sinks.',
|
||||
"logging.redactPatterns":
|
||||
"Additional custom redact regex patterns applied to log output before emission/storage. Use this to mask org-specific tokens and identifiers not covered by built-in redaction rules.",
|
||||
cli: "CLI presentation controls for local command output behavior such as banner and tagline style. Use this section to keep startup output aligned with operator preference without changing runtime behavior.",
|
||||
"cli.banner":
|
||||
"CLI startup banner controls for title/version line and tagline style behavior. Keep banner enabled for fast version/context checks, then tune tagline mode to your preferred noise level.",
|
||||
"cli.banner.taglineMode":
|
||||
'Controls tagline style in the CLI startup banner: "random" (default) picks from the rotating tagline pool, "default" always shows the neutral default tagline, and "off" hides tagline text while keeping the banner version line.',
|
||||
update:
|
||||
"Update-channel and startup-check behavior for keeping OpenClaw runtime versions current. Use conservative channels in production and more experimental channels only in controlled environments.",
|
||||
"update.channel": 'Update channel for git + npm installs ("stable", "beta", or "dev").',
|
||||
|
||||
@@ -13,6 +13,7 @@ export type { ConfigUiHint, ConfigUiHints } from "../shared/config-ui-hints-type
|
||||
const GROUP_LABELS: Record<string, string> = {
|
||||
wizard: "Wizard",
|
||||
update: "Update",
|
||||
cli: "CLI",
|
||||
diagnostics: "Diagnostics",
|
||||
logging: "Logging",
|
||||
gateway: "Gateway",
|
||||
@@ -41,6 +42,7 @@ const GROUP_LABELS: Record<string, string> = {
|
||||
const GROUP_ORDER: Record<string, number> = {
|
||||
wizard: 20,
|
||||
update: 25,
|
||||
cli: 26,
|
||||
diagnostics: 27,
|
||||
gateway: 30,
|
||||
nodeHost: 35,
|
||||
|
||||
@@ -26,6 +26,9 @@ export const FIELD_LABELS: Record<string, string> = {
|
||||
"logging.consoleStyle": "Console Log Style",
|
||||
"logging.redactSensitive": "Sensitive Data Redaction Mode",
|
||||
"logging.redactPatterns": "Custom Redaction Patterns",
|
||||
cli: "CLI",
|
||||
"cli.banner": "CLI Banner",
|
||||
"cli.banner.taglineMode": "CLI Banner Tagline Mode",
|
||||
update: "Updates",
|
||||
"update.channel": "Update Channel",
|
||||
"update.checkOnStart": "Update Check on Start",
|
||||
|
||||
13
src/config/types.cli.ts
Normal file
13
src/config/types.cli.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export type CliBannerTaglineMode = "random" | "default" | "off";
|
||||
|
||||
export type CliConfig = {
|
||||
banner?: {
|
||||
/**
|
||||
* Controls CLI banner tagline behavior.
|
||||
* - "random": pick from tagline pool (default)
|
||||
* - "default": always use DEFAULT_TAGLINE
|
||||
* - "off": hide tagline text
|
||||
*/
|
||||
taglineMode?: CliBannerTaglineMode;
|
||||
};
|
||||
};
|
||||
@@ -5,6 +5,7 @@ import type { AuthConfig } from "./types.auth.js";
|
||||
import type { DiagnosticsConfig, LoggingConfig, SessionConfig, WebConfig } from "./types.base.js";
|
||||
import type { BrowserConfig } from "./types.browser.js";
|
||||
import type { ChannelsConfig } from "./types.channels.js";
|
||||
import type { CliConfig } from "./types.cli.js";
|
||||
import type { CronConfig } from "./types.cron.js";
|
||||
import type {
|
||||
CanvasHostConfig,
|
||||
@@ -61,6 +62,7 @@ export type OpenClawConfig = {
|
||||
};
|
||||
diagnostics?: DiagnosticsConfig;
|
||||
logging?: LoggingConfig;
|
||||
cli?: CliConfig;
|
||||
update?: {
|
||||
/** Update channel for git + npm installs ("stable", "beta", or "dev"). */
|
||||
channel?: "stable" | "beta" | "dev";
|
||||
|
||||
@@ -8,6 +8,7 @@ export * from "./types.auth.js";
|
||||
export * from "./types.base.js";
|
||||
export * from "./types.browser.js";
|
||||
export * from "./types.channels.js";
|
||||
export * from "./types.cli.js";
|
||||
export * from "./types.openclaw.js";
|
||||
export * from "./types.cron.js";
|
||||
export * from "./types.discord.js";
|
||||
|
||||
@@ -222,6 +222,19 @@ export const OpenClawSchema = z
|
||||
})
|
||||
.strict()
|
||||
.optional(),
|
||||
cli: z
|
||||
.object({
|
||||
banner: z
|
||||
.object({
|
||||
taglineMode: z
|
||||
.union([z.literal("random"), z.literal("default"), z.literal("off")])
|
||||
.optional(),
|
||||
})
|
||||
.strict()
|
||||
.optional(),
|
||||
})
|
||||
.strict()
|
||||
.optional(),
|
||||
update: z
|
||||
.object({
|
||||
channel: z.union([z.literal("stable"), z.literal("beta"), z.literal("dev")]).optional(),
|
||||
|
||||
Reference in New Issue
Block a user