mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 11:25:50 -06:00
126 lines
4.2 KiB
TypeScript
126 lines
4.2 KiB
TypeScript
// CLI banner formatter and one-shot emitter.
|
|
import { visibleWidth } from "../../packages/terminal-core/src/ansi.js";
|
|
import {
|
|
decorativeEmoji,
|
|
decorativePrefix,
|
|
stripDecorativeEmojiForTerminal,
|
|
type DecorativeEmojiOptions,
|
|
} from "../../packages/terminal-core/src/decorative-emoji.js";
|
|
import { isRich, theme } from "../../packages/terminal-core/src/theme.js";
|
|
import { resolveCommitHash } from "../infra/git-commit.js";
|
|
import { hasRootVersionAlias } from "./argv.js";
|
|
import { parseTaglineMode, readCliBannerTaglineMode } from "./banner-config-lite.js";
|
|
import { pickTagline, type TaglineMode, type TaglineOptions } from "./tagline.js";
|
|
|
|
type BannerOptions = TaglineOptions & {
|
|
argv?: string[];
|
|
commit?: string | null;
|
|
columns?: number;
|
|
isTty?: boolean;
|
|
platform?: NodeJS.Platform;
|
|
richTty?: boolean;
|
|
};
|
|
|
|
let bannerEmitted = false;
|
|
|
|
const hasJsonFlag = (argv: string[]) =>
|
|
argv.some((arg) => arg === "--json" || arg.startsWith("--json="));
|
|
|
|
const hasVersionFlag = (argv: string[]) =>
|
|
argv.some((arg) => arg === "--version" || arg === "-V") || hasRootVersionAlias(argv);
|
|
|
|
function resolveTaglineMode(options: BannerOptions): TaglineMode | undefined {
|
|
const explicit = parseTaglineMode(options.mode);
|
|
if (explicit) {
|
|
return explicit;
|
|
}
|
|
return readCliBannerTaglineMode(options.env);
|
|
}
|
|
|
|
function resolveEmojiOptions(options: BannerOptions): DecorativeEmojiOptions {
|
|
return {
|
|
...(options.env ? { env: options.env } : {}),
|
|
...(options.isTty === undefined ? {} : { isTty: options.isTty }),
|
|
...(options.platform ? { platform: options.platform } : {}),
|
|
};
|
|
}
|
|
|
|
/** Format the compact one-line CLI banner, wrapping tagline when terminal width requires it. */
|
|
export function formatCliBannerLine(version: string, options: BannerOptions = {}): string {
|
|
const commit =
|
|
options.commit ?? resolveCommitHash({ env: options.env, moduleUrl: import.meta.url });
|
|
const commitLabel = commit ?? "unknown";
|
|
const emojiOptions = resolveEmojiOptions(options);
|
|
const tagline = stripDecorativeEmojiForTerminal(
|
|
pickTagline({ ...options, mode: resolveTaglineMode(options) }),
|
|
emojiOptions,
|
|
);
|
|
const rich = options.richTty ?? isRich();
|
|
const title = decorativePrefix("🦞", "OpenClaw", emojiOptions);
|
|
const prefix = decorativeEmoji("🦞", emojiOptions);
|
|
const indent = prefix ? `${prefix} ` : "";
|
|
const columns = options.columns ?? process.stdout.columns ?? 120;
|
|
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)}`;
|
|
}
|
|
const line1 = `${theme.heading(title)} ${theme.info(version)} ${theme.muted(
|
|
`(${commitLabel})`,
|
|
)}`;
|
|
if (!tagline) {
|
|
return line1;
|
|
}
|
|
const line2 = `${" ".repeat(indent.length)}${theme.accentDim(tagline)}`;
|
|
return `${line1}\n${line2}`;
|
|
}
|
|
if (fitsOnOneLine) {
|
|
return plainFullLine;
|
|
}
|
|
const line1 = plainBaseLine;
|
|
if (!tagline) {
|
|
return line1;
|
|
}
|
|
const line2 = `${" ".repeat(indent.length)}${tagline}`;
|
|
return `${line1}\n${line2}`;
|
|
}
|
|
|
|
/** Emit the CLI banner once for interactive, non-JSON, non-version invocations. */
|
|
export function emitCliBanner(version: string, options: BannerOptions = {}) {
|
|
if (bannerEmitted) {
|
|
return;
|
|
}
|
|
const argv = options.argv ?? process.argv;
|
|
const isTty = options.isTty ?? process.stdout.isTTY;
|
|
if (!isTty) {
|
|
return;
|
|
}
|
|
if (hasJsonFlag(argv)) {
|
|
return;
|
|
}
|
|
if (hasVersionFlag(argv)) {
|
|
return;
|
|
}
|
|
const line = formatCliBannerLine(version, options);
|
|
process.stdout.write(`\n${line}\n\n`);
|
|
bannerEmitted = true;
|
|
}
|
|
|
|
/** Return whether the current process already emitted the CLI banner. */
|
|
export function hasEmittedCliBanner(): boolean {
|
|
return bannerEmitted;
|
|
}
|
|
|
|
export const testing = {
|
|
resetBannerEmittedForTests(): void {
|
|
bannerEmitted = false;
|
|
},
|
|
};
|