From 779dd2441c1384f0029c55e94943b600a8e70667 Mon Sep 17 00:00:00 2001 From: Advait Paliwal Date: Mon, 23 Mar 2026 23:28:54 -0700 Subject: [PATCH] Unify branding: VT323 logotype across website, TUI, and OAuth pages - Add VT323 ASCII art logo to logo.mjs as single source of truth - Website nav and hero use VT323 font via AsciiLogo.astro component - TUI header and CLI help render the ASCII logo with block-centered alignment - OAuth callback pages (Pi and alphaXiv) show branded feynman logotype - Auto-set recommended model after provider login Co-Authored-By: Claude Opus 4.6 (1M context) --- extensions/research-tools/header.ts | 4 +++- logo.d.mts | 2 +- logo.mjs | 17 ++++++++++------- scripts/patch-embedded-pi.mjs | 20 ++++++++++---------- src/cli.ts | 6 +++--- src/model/commands.ts | 18 +++++++++++++++++- src/ui/terminal.ts | 13 +++++++++++++ website/src/components/AsciiLogo.astro | 6 +++--- website/src/layouts/Base.astro | 2 +- website/src/pages/index.astro | 2 ++ 10 files changed, 63 insertions(+), 27 deletions(-) diff --git a/extensions/research-tools/header.ts b/extensions/research-tools/header.ts index d0baf31..4871c9c 100644 --- a/extensions/research-tools/header.ts +++ b/extensions/research-tools/header.ts @@ -230,8 +230,10 @@ export function installFeynmanHeader( push(""); if (cardW >= 70) { + const maxLogoW = Math.max(...FEYNMAN_AGENT_LOGO.map((l) => l.length)); + const logoOffset = " ".repeat(Math.max(0, Math.floor((cardW - maxLogoW) / 2))); for (const logoLine of FEYNMAN_AGENT_LOGO) { - push(theme.fg("accent", theme.bold(centerText(truncateVisible(logoLine, cardW), cardW)))); + push(theme.fg("accent", theme.bold(`${logoOffset}${truncateVisible(logoLine, cardW)}`))); } push(""); } diff --git a/logo.d.mts b/logo.d.mts index 1552567..49ba517 100644 --- a/logo.d.mts +++ b/logo.d.mts @@ -1,3 +1,3 @@ export declare const FEYNMAN_ASCII_LOGO: string[]; export declare const FEYNMAN_ASCII_LOGO_TEXT: string; -export declare const FEYNMAN_ASCII_LOGO_HTML: string; +export declare const FEYNMAN_LOGO_HTML: string; diff --git a/logo.mjs b/logo.mjs index dd55f47..268d2ea 100644 --- a/logo.mjs +++ b/logo.mjs @@ -1,12 +1,15 @@ export const FEYNMAN_ASCII_LOGO = [ - "███████╗███████╗██╗ ██╗███╗ ██╗███╗ ███╗ █████╗ ███╗ ██╗", - "██╔════╝██╔════╝╚██╗ ██╔╝████╗ ██║████╗ ████║██╔══██╗████╗ ██║", - "█████╗ █████╗ ╚████╔╝ ██╔██╗ ██║██╔████╔██║███████║██╔██╗ ██║", - "██╔══╝ ██╔══╝ ╚██╔╝ ██║╚██╗██║██║╚██╔╝██║██╔══██║██║╚██╗██║", - "██║ ███████╗ ██║ ██║ ╚████║██║ ╚═╝ ██║██║ ██║██║ ╚████║", - "╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝", + " ██████", + " ███", + "█████████ ████████ ███ ███ ███ ██████ ██ ███ ████ ███████ ███ ██████", + " ███ ███ ███ ███ ███ ████ ███ ███ ██ ███ ███ ████ ███", + " ███ ████████████ ███ ███ ███ ███ ███ ██ ███ █████████ ███ ███", + " ███ ███ ██ ███ ███ ███ ███ ██ ███ ███ ███ ███ ███", + "███████ ████████ ████ ███ ███ ███ ██ ███ ██████ ███ ███ ███", + " ███", + " █████", ]; export const FEYNMAN_ASCII_LOGO_TEXT = FEYNMAN_ASCII_LOGO.join("\n"); -export const FEYNMAN_LOGO_HTML = `feynman`; +export const FEYNMAN_LOGO_HTML = `feynman`; diff --git a/scripts/patch-embedded-pi.mjs b/scripts/patch-embedded-pi.mjs index cc0c657..b459f13 100644 --- a/scripts/patch-embedded-pi.mjs +++ b/scripts/patch-embedded-pi.mjs @@ -2,7 +2,7 @@ import { spawnSync } from "node:child_process"; import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; -import { FEYNMAN_ASCII_LOGO_HTML } from "../logo.mjs"; +import { FEYNMAN_LOGO_HTML } from "../logo.mjs"; const here = dirname(fileURLToPath(import.meta.url)); const appRoot = resolve(here, ".."); @@ -365,7 +365,7 @@ if (oauthPagePath && existsSync(oauthPagePath)) { let source = readFileSync(oauthPagePath, "utf8"); const piLogo = 'const LOGO_SVG = ``;'; if (source.includes(piLogo)) { - const feynmanLogo = `const LOGO_SVG = \`${FEYNMAN_ASCII_LOGO_HTML}\`;`; + const feynmanLogo = `const LOGO_SVG = \`${FEYNMAN_LOGO_HTML}\`;`; source = source.replace(piLogo, feynmanLogo); writeFileSync(oauthPagePath, source, "utf8"); } @@ -377,17 +377,17 @@ const alphaHubAuthPath = findPackageRoot("@companion-ai/alpha-hub") if (alphaHubAuthPath && existsSync(alphaHubAuthPath)) { let source = readFileSync(alphaHubAuthPath, "utf8"); - const callbackStyle = `style="font-family:system-ui,sans-serif;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:80vh;background:#050a08;color:#f0f5f2"`; - const logoHtml = FEYNMAN_ASCII_LOGO_HTML.replace('color:#10b981', 'color:#34d399'); - const successPage = `${logoHtml}

Logged in

You can close this tab.

`; - const errorPage = `${logoHtml}

Login failed

You can close this tab.

`; - const oldSuccess = `'

Logged in to Alpha Hub

You can close this tab.

'`; - const oldError = `'

Login failed

You can close this tab.

'`; + const oldSuccess = "'

Logged in to Alpha Hub

You can close this tab.

'"; + const oldError = "'

Login failed

You can close this tab.

'"; + const bodyAttr = `style="font-family:system-ui,sans-serif;text-align:center;padding-top:20vh;background:#050a08;color:#f0f5f2"`; + const logo = `

feynman

`; + const newSuccess = `'${logo}

Logged in

You can close this tab.

'`; + const newError = `'${logo}

Login failed

You can close this tab.

'`; if (source.includes(oldSuccess)) { - source = source.replace(oldSuccess, `'${successPage}'`); + source = source.replace(oldSuccess, newSuccess); } if (source.includes(oldError)) { - source = source.replace(oldError, `'${errorPage}'`); + source = source.replace(oldError, newError); } writeFileSync(alphaHubAuthPath, source, "utf8"); } diff --git a/src/cli.ts b/src/cli.ts index bd16cc5..443b516 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -28,7 +28,7 @@ import { printSearchStatus } from "./search/commands.js"; import { runDoctor, runStatus } from "./setup/doctor.js"; import { setupPreviewDependencies } from "./setup/preview.js"; import { runSetup } from "./setup/setup.js"; -import { printInfo, printPanel, printSection } from "./ui/terminal.js"; +import { printAsciiHeader, printInfo, printPanel, printSection } from "./ui/terminal.js"; import { cliCommandSections, formatCliWorkflowUsage, @@ -50,7 +50,7 @@ function printHelp(appRoot: string): void { (command) => command.section === "Research Workflows" && command.topLevelCli, ); - printPanel("Feynman", [ + printAsciiHeader([ "Research-first agent shell built on Pi.", "Use `feynman setup` first if this is a new machine.", ]); @@ -123,7 +123,7 @@ async function handleModelCommand(subcommand: string | undefined, args: string[] } if (subcommand === "login") { - await loginModelProvider(feynmanAuthPath, args[0]); + await loginModelProvider(feynmanAuthPath, args[0], feynmanSettingsPath); return; } diff --git a/src/model/commands.ts b/src/model/commands.ts index 40a602f..6d8df32 100644 --- a/src/model/commands.ts +++ b/src/model/commands.ts @@ -6,6 +6,7 @@ import { promptChoice, promptText } from "../setup/prompts.js"; import { printInfo, printSection, printSuccess, printWarning } from "../ui/terminal.js"; import { buildModelStatusSnapshotFromRecords, + chooseRecommendedModel, getAvailableModelRecords, getSupportedModelRecords, type ModelStatusSnapshot, @@ -109,7 +110,7 @@ export function printModelList(settingsPath: string, authPath: string): void { } } -export async function loginModelProvider(authPath: string, providerId?: string): Promise { +export async function loginModelProvider(authPath: string, providerId?: string, settingsPath?: string): Promise { const provider = providerId ? resolveOAuthProvider(authPath, providerId) : await selectOAuthProvider(authPath, "login"); if (!provider) { if (providerId) { @@ -143,6 +144,21 @@ export async function loginModelProvider(authPath: string, providerId?: string): }); printSuccess(`Model provider login complete: ${provider.id}`); + + if (settingsPath) { + const currentSpec = getCurrentModelSpec(settingsPath); + const available = getAvailableModelRecords(authPath); + const currentValid = currentSpec + ? available.some((m) => `${m.provider}/${m.id}` === currentSpec) + : false; + + if ((!currentSpec || !currentValid) && available.length > 0) { + const recommended = chooseRecommendedModel(authPath); + if (recommended) { + setDefaultModelSpec(settingsPath, authPath, recommended.spec); + } + } + } } export async function logoutModelProvider(authPath: string, providerId?: string): Promise { diff --git a/src/ui/terminal.ts b/src/ui/terminal.ts index b7f6078..9916e35 100644 --- a/src/ui/terminal.ts +++ b/src/ui/terminal.ts @@ -1,3 +1,5 @@ +import { FEYNMAN_ASCII_LOGO } from "../../logo.mjs"; + const RESET = "\x1b[0m"; const BOLD = "\x1b[1m"; const DIM = "\x1b[2m"; @@ -40,6 +42,17 @@ export function printSection(title: string): void { console.log(paint(`◆ ${title}`, TEAL, BOLD)); } +export function printAsciiHeader(subtitleLines: string[] = []): void { + console.log(""); + for (const line of FEYNMAN_ASCII_LOGO) { + console.log(paint(` ${line}`, TEAL, BOLD)); + } + for (const line of subtitleLines) { + console.log(paint(` ${line}`, ASH)); + } + console.log(""); +} + export function printPanel(title: string, subtitleLines: string[] = []): void { const inner = 53; const border = "─".repeat(inner + 2); diff --git a/website/src/components/AsciiLogo.astro b/website/src/components/AsciiLogo.astro index 6f0ebd1..a2d57af 100644 --- a/website/src/components/AsciiLogo.astro +++ b/website/src/components/AsciiLogo.astro @@ -7,13 +7,13 @@ interface Props { const { class: className = '', size = 'hero' } = Astro.props; const sizeClasses = size === 'nav' - ? 'text-lg' - : 'text-4xl sm:text-5xl md:text-6xl'; + ? 'text-2xl' + : 'text-6xl sm:text-7xl md:text-8xl'; --- {title} - +