From af1f6fab292eca9b7e3e8996797ee746d0db33cd Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 9 Jan 2026 07:51:32 +0100 Subject: [PATCH] chore: add lobster CLI banner art --- src/cli/banner.ts | 35 +++++++++++++++++++++++++++++++++++ src/cli/program.ts | 15 +++++++++++---- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/cli/banner.ts b/src/cli/banner.ts index 3153d5a81..670a1cddc 100644 --- a/src/cli/banner.ts +++ b/src/cli/banner.ts @@ -33,6 +33,41 @@ export function formatCliBannerLine( return `${title} ${version} (${commitLabel}) — ${tagline}`; } +const LOBSTER_ASCII = [ + "░████░█░░░░░█████░█░░░█░███░░████░░████░░▀█▀", + "█░░░░░█░░░░░█░░░█░█░█░█░█░░█░█░░░█░█░░░█░░█░", + "█░░░░░█░░░░░█████░█░█░█░█░░█░████░░█░░░█░░█░", + "█░░░░░█░░░░░█░░░█░█░█░█░█░░█░█░░█░░█░░░█░░█░", + "░████░█████░█░░░█░░█░█░░███░░████░░░███░░░█░", + " 🦞 FRESH DAILY 🦞", +]; + +export function formatCliBannerArt(options: BannerOptions = {}): string { + const rich = options.richTty ?? isRich(); + if (!rich) return LOBSTER_ASCII.join("\n"); + + const colorChar = (ch: string) => { + if (ch === "█") return theme.accentBright(ch); + if (ch === "░") return theme.accentDim(ch); + if (ch === "▀") return theme.accent(ch); + return theme.muted(ch); + }; + + const colored = LOBSTER_ASCII.map((line) => { + if (line.includes("FRESH DAILY")) { + return ( + theme.muted(" ") + + theme.accent("🦞") + + theme.info(" FRESH DAILY ") + + theme.accent("🦞") + ); + } + return [...line].map(colorChar).join(""); + }); + + return colored.join("\n"); +} + export function emitCliBanner(version: string, options: BannerOptions = {}) { if (bannerEmitted) return; const argv = options.argv ?? process.argv; diff --git a/src/cli/program.ts b/src/cli/program.ts index 1ab75732f..1b8f20675 100644 --- a/src/cli/program.ts +++ b/src/cli/program.ts @@ -25,7 +25,11 @@ import { autoMigrateLegacyState } from "../infra/state-migrations.js"; import { defaultRuntime } from "../runtime.js"; import { isRich, theme } from "../terminal/theme.js"; import { VERSION } from "../version.js"; -import { emitCliBanner, formatCliBannerLine } from "./banner.js"; +import { + emitCliBanner, + formatCliBannerArt, + formatCliBannerLine, +} from "./banner.js"; import { registerBrowserCli } from "./browser-cli.js"; import { hasExplicitOptions } from "./command-options.js"; import { registerCronCli } from "./cron-cli.js"; @@ -95,8 +99,10 @@ export function buildProgram() { } program.addHelpText("beforeAll", () => { - const line = formatCliBannerLine(PROGRAM_VERSION, { richTty: isRich() }); - return `\n${line}\n`; + const rich = isRich(); + const art = formatCliBannerArt({ richTty: rich }); + const line = formatCliBannerLine(PROGRAM_VERSION, { richTty: rich }); + return `\n${art}\n${line}\n`; }); program.hook("preAction", async (_thisCommand, actionCommand) => { @@ -231,7 +237,7 @@ export function buildProgram() { .option("--mode ", "Wizard mode: local|remote") .option( "--auth-choice ", - "Auth: oauth|claude-cli|openai-codex|codex-cli|antigravity|gemini-api-key|apiKey|minimax|skip", + "Auth: oauth|claude-cli|token|openai-codex|codex-cli|antigravity|gemini-api-key|apiKey|minimax|skip", ) .option("--anthropic-api-key ", "Anthropic API key") .option("--gemini-api-key ", "Gemini API key") @@ -260,6 +266,7 @@ export function buildProgram() { authChoice: opts.authChoice as | "oauth" | "claude-cli" + | "token" | "openai-codex" | "codex-cli" | "antigravity"