From 336d28f112212efb970543b9995a853753e7d988 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 1 Apr 2026 23:21:07 -0400 Subject: [PATCH] fix(cli): restore colored help logo (#20592) --- packages/opencode/src/cli/ui.ts | 59 ++++++++++++++++++++++++++++++--- packages/opencode/src/index.ts | 28 +++++++++++++--- 2 files changed, 78 insertions(+), 9 deletions(-) diff --git a/packages/opencode/src/cli/ui.ts b/packages/opencode/src/cli/ui.ts index c592d4206c..b24a2a2f44 100644 --- a/packages/opencode/src/cli/ui.ts +++ b/packages/opencode/src/cli/ui.ts @@ -1,6 +1,7 @@ import z from "zod" import { EOL } from "os" import { NamedError } from "@opencode-ai/util/error" +import { logo as glyphs } from "./logo" export namespace UI { const wordmark = [ @@ -47,12 +48,60 @@ export namespace UI { } export function logo(pad?: string) { - const result = [] - for (const row of wordmark) { - if (pad) result.push(pad) - result.push(row) - result.push(EOL) + if (!process.stdout.isTTY && !process.stderr.isTTY) { + const result = [] + for (const row of wordmark) { + if (pad) result.push(pad) + result.push(row) + result.push(EOL) + } + return result.join("").trimEnd() } + + const result: string[] = [] + const reset = "\x1b[0m" + const left = { + fg: "\x1b[90m", + shadow: "\x1b[38;5;235m", + bg: "\x1b[48;5;235m", + } + const right = { + fg: reset, + shadow: "\x1b[38;5;238m", + bg: "\x1b[48;5;238m", + } + const gap = " " + const draw = (line: string, fg: string, shadow: string, bg: string) => { + const parts: string[] = [] + for (const char of line) { + if (char === "_") { + parts.push(bg, " ", reset) + continue + } + if (char === "^") { + parts.push(fg, bg, "▀", reset) + continue + } + if (char === "~") { + parts.push(shadow, "▀", reset) + continue + } + if (char === " ") { + parts.push(" ") + continue + } + parts.push(fg, char, reset) + } + return parts.join("") + } + glyphs.left.forEach((row, index) => { + if (pad) result.push(pad) + result.push(draw(row, left.fg, left.shadow, left.bg)) + result.push(gap) + const other = glyphs.right[index] ?? "" + result.push(draw(other, right.fg, right.shadow, right.bg)) + result.push(EOL) + }) return result.join("").trimEnd() } diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index 2da35ace1d..bb14e0588a 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -48,7 +48,19 @@ process.on("uncaughtException", (e) => { }) }) -const cli = yargs(hideBin(process.argv)) +const args = hideBin(process.argv) + +function show(out: string) { + const text = out.trimStart() + if (!text.startsWith("opencode ")) { + process.stderr.write(UI.logo() + EOL + EOL) + process.stderr.write(text) + return + } + process.stderr.write(out) +} + +const cli = yargs(args) .parserConfiguration({ "populate--": true }) .scriptName("opencode") .wrap(100) @@ -130,7 +142,7 @@ const cli = yargs(hideBin(process.argv)) process.stderr.write("Database migration complete." + EOL) } }) - .usage("\n" + UI.logo()) + .usage("") .completion("completion", "generate shell completion script") .command(AcpCommand) .command(McpCommand) @@ -162,7 +174,7 @@ const cli = yargs(hideBin(process.argv)) msg?.startsWith("Invalid values:") ) { if (err) throw err - cli.showHelp("log") + cli.showHelp(show) } if (err) throw err process.exit(1) @@ -170,7 +182,15 @@ const cli = yargs(hideBin(process.argv)) .strict() try { - await cli.parse() + if (args.includes("-h") || args.includes("--help")) { + await cli.parse(args, (err: Error | undefined, _argv: unknown, out: string) => { + if (err) throw err + if (!out) return + show(out) + }) + } else { + await cli.parse() + } } catch (e) { let data: Record = {} if (e instanceof NamedError) {