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) <noreply@anthropic.com>
This commit is contained in:
@@ -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("");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
17
logo.mjs
17
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 = `<link href="https://fonts.googleapis.com/css2?family=Silkscreen:wght@700&display=swap" rel="stylesheet"><span style="font-family:'Silkscreen',cursive;font-size:48px;font-weight:700;color:#10b981">feynman</span>`;
|
||||
export const FEYNMAN_LOGO_HTML = `<link href="https://fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet"><span style="font-family:'VT323',monospace;font-size:64px;letter-spacing:-0.05em;color:#10b981">feynman</span>`;
|
||||
|
||||
@@ -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 = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800" aria-hidden="true"><path fill="#fff" fill-rule="evenodd" d="M165.29 165.29 H517.36 V400 H400 V517.36 H282.65 V634.72 H165.29 Z M282.65 282.65 V400 H400 V282.65 Z"/><path fill="#fff" d="M517.36 400 H634.72 V634.72 H517.36 Z"/></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 = `<html><body ${callbackStyle}>${logoHtml}<h2 style="color:#34d399;margin-top:24px">Logged in</h2><p style="color:#8aaa9a">You can close this tab.</p></body></html>`;
|
||||
const errorPage = `<html><body ${callbackStyle}>${logoHtml}<h2 style="color:#ef4444;margin-top:24px">Login failed</h2><p style="color:#8aaa9a">You can close this tab.</p></body></html>`;
|
||||
const oldSuccess = `'<html><body><h2>Logged in to Alpha Hub</h2><p>You can close this tab.</p></body></html>'`;
|
||||
const oldError = `'<html><body><h2>Login failed</h2><p>You can close this tab.</p></body></html>'`;
|
||||
const oldSuccess = "'<html><body><h2>Logged in to Alpha Hub</h2><p>You can close this tab.</p></body></html>'";
|
||||
const oldError = "'<html><body><h2>Login failed</h2><p>You can close this tab.</p></body></html>'";
|
||||
const bodyAttr = `style="font-family:system-ui,sans-serif;text-align:center;padding-top:20vh;background:#050a08;color:#f0f5f2"`;
|
||||
const logo = `<h1 style="font-family:monospace;font-size:48px;color:#34d399;margin:0">feynman</h1>`;
|
||||
const newSuccess = `'<html><body ${bodyAttr}>${logo}<h2 style="color:#34d399;margin-top:16px">Logged in</h2><p style="color:#8aaa9a">You can close this tab.</p></body></html>'`;
|
||||
const newError = `'<html><body ${bodyAttr}>${logo}<h2 style="color:#ef4444;margin-top:16px">Login failed</h2><p style="color:#8aaa9a">You can close this tab.</p></body></html>'`;
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<void> {
|
||||
export async function loginModelProvider(authPath: string, providerId?: string, settingsPath?: string): Promise<void> {
|
||||
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<void> {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
---
|
||||
|
||||
<span
|
||||
class:list={[
|
||||
"font-['Silkscreen'] text-accent font-bold tracking-tight inline-block",
|
||||
"font-['VT323'] text-accent inline-block tracking-tighter",
|
||||
sizeClasses,
|
||||
className,
|
||||
]}
|
||||
|
||||
@@ -22,7 +22,7 @@ const { title, description = 'Research-first AI agent', active = 'home' } = Astr
|
||||
<title>{title}</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Silkscreen:wght@400;700&display=swap" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet" />
|
||||
<ViewTransitions fallback="none" />
|
||||
<script is:inline>
|
||||
(function() {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
---
|
||||
import Base from '../layouts/Base.astro';
|
||||
import AsciiLogo from '../components/AsciiLogo.astro';
|
||||
---
|
||||
|
||||
<Base title="Feynman — The open source AI research agent" active="home">
|
||||
<section class="text-center pt-24 pb-20 px-6">
|
||||
<div class="max-w-2xl mx-auto">
|
||||
<AsciiLogo size="hero" class="mb-4" />
|
||||
<h1 class="text-5xl sm:text-6xl font-bold tracking-tight mb-6" style="text-wrap: balance">The open source AI research agent</h1>
|
||||
<p class="text-lg text-text-muted mb-10 leading-relaxed" style="text-wrap: pretty">Investigate topics, write papers, run experiments, review research, audit codebases — every output cited and source-grounded</p>
|
||||
<div class="inline-flex items-center gap-3 bg-surface rounded-lg px-5 py-3 mb-8 font-mono text-sm">
|
||||
|
||||
Reference in New Issue
Block a user