Add ASCII logo, spinner during install, all packages core, fix tests
- ASCII art logo on website hero and OAuth callback page - Clean spinner during postinstall instead of npm noise - pi-session-search and pi-memory moved to core (13 packages) - pi-generative-ui is the only optional package - Alpha Hub callback page branded with Feynman logo Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,9 @@
|
|||||||
"npm:pi-mermaid",
|
"npm:pi-mermaid",
|
||||||
"npm:@aliou/pi-processes",
|
"npm:@aliou/pi-processes",
|
||||||
"npm:pi-zotero",
|
"npm:pi-zotero",
|
||||||
|
"npm:@kaiserlich-dev/pi-session-search",
|
||||||
"npm:pi-schedule-prompt",
|
"npm:pi-schedule-prompt",
|
||||||
|
"npm:@samfp/pi-memory",
|
||||||
"npm:@tmustier/pi-ralph-wiggum"
|
"npm:@tmustier/pi-ralph-wiggum"
|
||||||
],
|
],
|
||||||
"quietStartup": true,
|
"quietStartup": true,
|
||||||
|
|||||||
@@ -14,14 +14,7 @@ export const FEYNMAN_VERSION = (() => {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
export const FEYNMAN_AGENT_LOGO = [
|
export { FEYNMAN_ASCII_LOGO as FEYNMAN_AGENT_LOGO } from "../../logo.mjs";
|
||||||
"███████╗███████╗██╗ ██╗███╗ ██╗███╗ ███╗ █████╗ ███╗ ██╗",
|
|
||||||
"██╔════╝██╔════╝╚██╗ ██╔╝████╗ ██║████╗ ████║██╔══██╗████╗ ██║",
|
|
||||||
"█████╗ █████╗ ╚████╔╝ ██╔██╗ ██║██╔████╔██║███████║██╔██╗ ██║",
|
|
||||||
"██╔══╝ ██╔══╝ ╚██╔╝ ██║╚██╗██║██║╚██╔╝██║██╔══██║██║╚██╗██║",
|
|
||||||
"██║ ███████╗ ██║ ██║ ╚████║██║ ╚═╝ ██║██║ ██║██║ ╚████║",
|
|
||||||
"╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝",
|
|
||||||
];
|
|
||||||
|
|
||||||
export const FEYNMAN_RESEARCH_TOOLS = [
|
export const FEYNMAN_RESEARCH_TOOLS = [
|
||||||
"alpha_search",
|
"alpha_search",
|
||||||
|
|||||||
3
logo.d.mts
Normal file
3
logo.d.mts
Normal file
@@ -0,0 +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;
|
||||||
12
logo.mjs
Normal file
12
logo.mjs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export const FEYNMAN_ASCII_LOGO = [
|
||||||
|
"███████╗███████╗██╗ ██╗███╗ ██╗███╗ ███╗ █████╗ ███╗ ██╗",
|
||||||
|
"██╔════╝██╔════╝╚██╗ ██╔╝████╗ ██║████╗ ████║██╔══██╗████╗ ██║",
|
||||||
|
"█████╗ █████╗ ╚████╔╝ ██╔██╗ ██║██╔████╔██║███████║██╔██╗ ██║",
|
||||||
|
"██╔══╝ ██╔══╝ ╚██╔╝ ██║╚██╗██║██║╚██╔╝██║██╔══██║██║╚██╗██║",
|
||||||
|
"██║ ███████╗ ██║ ██║ ╚████║██║ ╚═╝ ██║██║ ██║██║ ╚████║",
|
||||||
|
"╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const FEYNMAN_ASCII_LOGO_TEXT = FEYNMAN_ASCII_LOGO.join("\n");
|
||||||
|
|
||||||
|
export const FEYNMAN_ASCII_LOGO_HTML = `<pre style="font-size:8px;line-height:1.15;font-weight:700;color:#10b981;font-family:monospace;white-space:pre;margin:0">${FEYNMAN_ASCII_LOGO_TEXT}</pre>`;
|
||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@companion-ai/feynman",
|
"name": "@companion-ai/feynman",
|
||||||
"version": "0.2.6",
|
"version": "0.2.7",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@companion-ai/feynman",
|
"name": "@companion-ai/feynman",
|
||||||
"version": "0.2.6",
|
"version": "0.2.7",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@companion-ai/alpha-hub": "^0.1.2",
|
"@companion-ai/alpha-hub": "^0.1.2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@companion-ai/feynman",
|
"name": "@companion-ai/feynman",
|
||||||
"version": "0.2.6",
|
"version": "0.2.7",
|
||||||
"description": "Research-first CLI agent built on Pi and alphaXiv",
|
"description": "Research-first CLI agent built on Pi and alphaXiv",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { spawnSync } from "node:child_process";
|
|||||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||||
import { dirname, resolve } from "node:path";
|
import { dirname, resolve } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { FEYNMAN_ASCII_LOGO_HTML } from "../logo.mjs";
|
||||||
|
|
||||||
const here = dirname(fileURLToPath(import.meta.url));
|
const here = dirname(fileURLToPath(import.meta.url));
|
||||||
const appRoot = resolve(here, "..");
|
const appRoot = resolve(here, "..");
|
||||||
@@ -90,14 +91,27 @@ function ensurePackageWorkspace() {
|
|||||||
"utf8",
|
"utf8",
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("[feynman] installing research packages...");
|
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
||||||
const result = spawnSync("npm", ["install", "--prefer-offline", "--no-audit", "--no-fund", "--prefix", workspaceDir, ...packageSpecs], {
|
let frame = 0;
|
||||||
stdio: "inherit",
|
const start = Date.now();
|
||||||
|
const spinner = setInterval(() => {
|
||||||
|
const elapsed = Math.round((Date.now() - start) / 1000);
|
||||||
|
process.stderr.write(`\r${frames[frame++ % frames.length]} setting up feynman... ${elapsed}s`);
|
||||||
|
}, 80);
|
||||||
|
|
||||||
|
const result = spawnSync("npm", ["install", "--prefer-offline", "--no-audit", "--no-fund", "--loglevel", "error", "--prefix", workspaceDir, ...packageSpecs], {
|
||||||
|
stdio: ["ignore", "ignore", "pipe"],
|
||||||
timeout: 300000,
|
timeout: 300000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
clearInterval(spinner);
|
||||||
|
const elapsed = Math.round((Date.now() - start) / 1000);
|
||||||
|
|
||||||
if (result.status !== 0) {
|
if (result.status !== 0) {
|
||||||
console.warn("[feynman] warning: package install failed, Pi will retry on first launch");
|
process.stderr.write(`\r✗ setup failed (${elapsed}s)\n`);
|
||||||
|
if (result.stderr?.length) process.stderr.write(result.stderr);
|
||||||
|
} else {
|
||||||
|
process.stderr.write(`\r✓ feynman ready (${elapsed}s)\n`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,12 +365,33 @@ if (oauthPagePath && existsSync(oauthPagePath)) {
|
|||||||
let source = readFileSync(oauthPagePath, "utf8");
|
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>`;';
|
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)) {
|
if (source.includes(piLogo)) {
|
||||||
const feynmanLogo = 'const LOGO_SVG = `<span style="font-size:32px;font-weight:700;color:#10b981;font-family:system-ui,sans-serif;letter-spacing:-0.02em">feynman</span>`;';
|
const feynmanLogo = `const LOGO_SVG = \`${FEYNMAN_ASCII_LOGO_HTML}\`;`;
|
||||||
source = source.replace(piLogo, feynmanLogo);
|
source = source.replace(piLogo, feynmanLogo);
|
||||||
writeFileSync(oauthPagePath, source, "utf8");
|
writeFileSync(oauthPagePath, source, "utf8");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const alphaHubAuthPath = findPackageRoot("@companion-ai/alpha-hub")
|
||||||
|
? resolve(findPackageRoot("@companion-ai/alpha-hub"), "src", "lib", "auth.js")
|
||||||
|
: null;
|
||||||
|
|
||||||
|
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>'`;
|
||||||
|
if (source.includes(oldSuccess)) {
|
||||||
|
source = source.replace(oldSuccess, `'${successPage}'`);
|
||||||
|
}
|
||||||
|
if (source.includes(oldError)) {
|
||||||
|
source = source.replace(oldError, `'${errorPage}'`);
|
||||||
|
}
|
||||||
|
writeFileSync(alphaHubAuthPath, source, "utf8");
|
||||||
|
}
|
||||||
|
|
||||||
if (existsSync(piMemoryPath)) {
|
if (existsSync(piMemoryPath)) {
|
||||||
let source = readFileSync(piMemoryPath, "utf8");
|
let source = readFileSync(piMemoryPath, "utf8");
|
||||||
const memoryOriginal = 'const MEMORY_DIR = join(homedir(), ".pi", "memory");';
|
const memoryOriginal = 'const MEMORY_DIR = join(homedir(), ".pi", "memory");';
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ export const CORE_PACKAGE_SOURCES = [
|
|||||||
"npm:pi-mermaid",
|
"npm:pi-mermaid",
|
||||||
"npm:@aliou/pi-processes",
|
"npm:@aliou/pi-processes",
|
||||||
"npm:pi-zotero",
|
"npm:pi-zotero",
|
||||||
|
"npm:@kaiserlich-dev/pi-session-search",
|
||||||
"npm:pi-schedule-prompt",
|
"npm:pi-schedule-prompt",
|
||||||
|
"npm:@samfp/pi-memory",
|
||||||
"npm:@tmustier/pi-ralph-wiggum",
|
"npm:@tmustier/pi-ralph-wiggum",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
@@ -19,25 +21,11 @@ export const OPTIONAL_PACKAGE_PRESETS = {
|
|||||||
description: "Interactive Glimpse UI widgets.",
|
description: "Interactive Glimpse UI widgets.",
|
||||||
sources: ["npm:pi-generative-ui"],
|
sources: ["npm:pi-generative-ui"],
|
||||||
},
|
},
|
||||||
memory: {
|
|
||||||
description: "Cross-session memory and preference recall.",
|
|
||||||
sources: ["npm:@samfp/pi-memory"],
|
|
||||||
},
|
|
||||||
"session-search": {
|
|
||||||
description: "Indexed session recall with SQLite-backed search.",
|
|
||||||
sources: ["npm:@kaiserlich-dev/pi-session-search"],
|
|
||||||
},
|
|
||||||
"all-extras": {
|
|
||||||
description: "Install all optional packages.",
|
|
||||||
sources: ["npm:pi-generative-ui", "npm:@samfp/pi-memory", "npm:@kaiserlich-dev/pi-session-search"],
|
|
||||||
},
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const LEGACY_DEFAULT_PACKAGE_SOURCES = [
|
const LEGACY_DEFAULT_PACKAGE_SOURCES = [
|
||||||
...CORE_PACKAGE_SOURCES,
|
...CORE_PACKAGE_SOURCES,
|
||||||
"npm:pi-generative-ui",
|
"npm:pi-generative-ui",
|
||||||
"npm:@kaiserlich-dev/pi-session-search",
|
|
||||||
"npm:@samfp/pi-memory",
|
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type OptionalPackagePresetName = keyof typeof OPTIONAL_PACKAGE_PRESETS;
|
export type OptionalPackagePresetName = keyof typeof OPTIONAL_PACKAGE_PRESETS;
|
||||||
@@ -66,9 +54,6 @@ export function getOptionalPackagePresetSources(name: string): string[] | undefi
|
|||||||
if (normalized === "ui") {
|
if (normalized === "ui") {
|
||||||
return [...OPTIONAL_PACKAGE_PRESETS["generative-ui"].sources];
|
return [...OPTIONAL_PACKAGE_PRESETS["generative-ui"].sources];
|
||||||
}
|
}
|
||||||
if (normalized === "search") {
|
|
||||||
return [...OPTIONAL_PACKAGE_PRESETS["session-search"].sources];
|
|
||||||
}
|
|
||||||
|
|
||||||
const preset = OPTIONAL_PACKAGE_PRESETS[normalized as OptionalPackagePresetName];
|
const preset = OPTIONAL_PACKAGE_PRESETS[normalized as OptionalPackagePresetName];
|
||||||
return preset ? [...preset.sources] : undefined;
|
return preset ? [...preset.sources] : undefined;
|
||||||
|
|||||||
@@ -49,8 +49,6 @@ test("normalizeFeynmanSettings prunes the legacy slow default package set", () =
|
|||||||
packages: [
|
packages: [
|
||||||
...CORE_PACKAGE_SOURCES,
|
...CORE_PACKAGE_SOURCES,
|
||||||
"npm:pi-generative-ui",
|
"npm:pi-generative-ui",
|
||||||
"npm:@kaiserlich-dev/pi-session-search",
|
|
||||||
"npm:@samfp/pi-memory",
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
@@ -68,8 +66,8 @@ test("normalizeFeynmanSettings prunes the legacy slow default package set", () =
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("optional package presets map friendly aliases", () => {
|
test("optional package presets map friendly aliases", () => {
|
||||||
assert.deepEqual(getOptionalPackagePresetSources("memory"), ["npm:@samfp/pi-memory"]);
|
assert.deepEqual(getOptionalPackagePresetSources("memory"), undefined);
|
||||||
assert.deepEqual(getOptionalPackagePresetSources("ui"), ["npm:pi-generative-ui"]);
|
assert.deepEqual(getOptionalPackagePresetSources("ui"), ["npm:pi-generative-ui"]);
|
||||||
assert.deepEqual(getOptionalPackagePresetSources("search"), ["npm:@kaiserlich-dev/pi-session-search"]);
|
assert.deepEqual(getOptionalPackagePresetSources("search"), undefined);
|
||||||
assert.equal(shouldPruneLegacyDefaultPackages(["npm:custom"]), false);
|
assert.equal(shouldPruneLegacyDefaultPackages(["npm:custom"]), false);
|
||||||
});
|
});
|
||||||
|
|||||||
26
website/src/components/AsciiLogo.astro
Normal file
26
website/src/components/AsciiLogo.astro
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
interface Props {
|
||||||
|
class?: string;
|
||||||
|
size?: 'nav' | 'hero';
|
||||||
|
}
|
||||||
|
|
||||||
|
const { class: className = '', size = 'hero' } = Astro.props;
|
||||||
|
|
||||||
|
const sizeClasses = size === 'nav'
|
||||||
|
? 'text-[4px] sm:text-[5px]'
|
||||||
|
: 'text-[6px] sm:text-[8px] md:text-[10px]';
|
||||||
|
---
|
||||||
|
|
||||||
|
<pre
|
||||||
|
class:list={[
|
||||||
|
'font-mono text-accent font-bold leading-[1.15] m-0 p-0 inline-block',
|
||||||
|
sizeClasses,
|
||||||
|
className,
|
||||||
|
]}
|
||||||
|
aria-label="Feynman"
|
||||||
|
>███████╗███████╗██╗ ██╗███╗ ██╗███╗ ███╗ █████╗ ███╗ ██╗
|
||||||
|
██╔════╝██╔════╝╚██╗ ██╔╝████╗ ██║████╗ ████║██╔══██╗████╗ ██║
|
||||||
|
█████╗ █████╗ ╚████╔╝ ██╔██╗ ██║██╔████╔██║███████║██╔██╗ ██║
|
||||||
|
██╔══╝ ██╔══╝ ╚██╔╝ ██║╚██╗██║██║╚██╔╝██║██╔══██║██║╚██╗██║
|
||||||
|
██║ ███████╗ ██║ ██║ ╚████║██║ ╚═╝ ██║██║ ██║██║ ╚████║
|
||||||
|
╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝</pre>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
import ThemeToggle from './ThemeToggle.astro';
|
import ThemeToggle from './ThemeToggle.astro';
|
||||||
|
import AsciiLogo from './AsciiLogo.astro';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
active?: 'home' | 'docs';
|
active?: 'home' | 'docs';
|
||||||
@@ -10,7 +11,9 @@ const { active = 'home' } = Astro.props;
|
|||||||
|
|
||||||
<nav class="sticky top-0 z-50 bg-bg">
|
<nav class="sticky top-0 z-50 bg-bg">
|
||||||
<div class="max-w-6xl mx-auto px-6 h-14 flex items-center justify-between">
|
<div class="max-w-6xl mx-auto px-6 h-14 flex items-center justify-between">
|
||||||
<a href="/" class="text-xl font-bold text-accent tracking-tight">Feynman</a>
|
<a href="/" class="hover:opacity-80 transition-opacity" aria-label="Feynman">
|
||||||
|
<AsciiLogo size="nav" />
|
||||||
|
</a>
|
||||||
<div class="flex items-center gap-6">
|
<div class="flex items-center gap-6">
|
||||||
<a href="/docs/getting-started/installation"
|
<a href="/docs/getting-started/installation"
|
||||||
class:list={["text-sm transition-colors", active === 'docs' ? 'text-text-primary' : 'text-text-muted hover:text-text-primary']}>
|
class:list={["text-sm transition-colors", active === 'docs' ? 'text-text-primary' : 'text-text-muted hover:text-text-primary']}>
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
---
|
---
|
||||||
import Base from '../layouts/Base.astro';
|
import Base from '../layouts/Base.astro';
|
||||||
|
import AsciiLogo from '../components/AsciiLogo.astro';
|
||||||
---
|
---
|
||||||
|
|
||||||
<Base title="Feynman — The open source AI research agent" active="home">
|
<Base title="Feynman — The open source AI research agent" active="home">
|
||||||
<section class="text-center pt-24 pb-20 px-6">
|
<section class="text-center pt-24 pb-20 px-6">
|
||||||
<div class="max-w-2xl mx-auto">
|
<div class="max-w-2xl mx-auto">
|
||||||
|
<AsciiLogo size="hero" class="mb-8" />
|
||||||
<h1 class="text-5xl sm:text-6xl font-bold tracking-tight mb-6" style="text-wrap: balance">The open source AI research agent</h1>
|
<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>
|
<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">
|
<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